]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
kernel/generic: restore files for v6.12
authorMieczyslaw Nalewaj <namiltd@yahoo.com>
Fri, 27 Mar 2026 16:47:48 +0000 (17:47 +0100)
committerRobert Marko <robimarko@gmail.com>
Sat, 28 Mar 2026 10:51:09 +0000 (11:51 +0100)
This is an automatically generated commit which aids following Kernel patch
history, as git will see the move and copy as a rename thus defeating the
purpose.

For the original discussion see:
https://lists.openwrt.org/pipermail/openwrt-devel/2023-October/041673.html

Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Link: https://github.com/openwrt/openwrt/pull/21078
Signed-off-by: Robert Marko <robimarko@gmail.com>
575 files changed:
target/linux/generic/backport-6.12/010-v6.19-nvmem-layouts-u-boot-env-add-optional-env-size-property.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/152-v6.16-net-wireguard-add-nonstring-annotation-to-fix-build-with-GCC15.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/200-01-v6.13-jiffies-Define-secs_to_jiffies.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/200-02-v6.14-jiffies-Cast-to-unsigned-long-in-secs_to_jiffies-con.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/201-v6.13-of-property-add-of_graph_get_next_port.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/202-v6.13-clk-Provide-devm_clk_bulk_get_all_enabled-helper.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/203-01-v6.13-of-address-Rework-bus-matching-to-avoid-warnings.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/203-02-v6.13-of-address-Fix-WARN-when-attempting-translating-non-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/205-v6.16-of-reserved_mem-Add-functions-to-parse-memory-region.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/210-v6.13-fortify-Hide-run-time-copy-size-from-value-range-tracking.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/211-01-v6.19-bitfield-Add-less-checking-__FIELD_-GET-PREP.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/211-02-v6.19-bitfield-Add-non-constant-field_-prep-get-helpers.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/212-v6.17-of-reserved_mem-Add-missing-IORESOURCE_MEM-flag-on-r.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/400-v6.14-mtd-spinand-add-support-for-FORESEE-F35SQA001G.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/401-v6.17-mtd-spinand-add-support-for-FudanMicro-FM25S01A.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/402-v6.18-mtd-nand-move-chunk-to-core.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/404-v6.19-mtd-spinand-fmsh-remove-QE-bit-for-FM25S01A-flash.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-01-v6.14-mtd-rawnand-qcom-cleanup-qcom_nandc-driver.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-02-v6.14-mtd-rawnand-qcom-Add-qcom-prefix-to-common-api.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-03-v6.14-mtd-nand-Add-qpic_common-API-file.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-04-v6.14-mtd-rawnand-qcom-use-FIELD_PREP-and-GENMASK.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-05-v6.14-mtd-rawnand-qcom-fix-broken-config-in-qcom_param_pag.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-06-v6.14-mtd-rawnand-qcom-Fix-build-issue-on-x86-architecture.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-07-v6.15-spi-spi-qpic-add-driver-for-QCOM-SPI-NAND-flash-Inte.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-08-v6.15-spi-spi-qpic-snand-Fix-ECC_CFG_ECC_DISABLE-shift-in-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-09-v6.15-spi-spi-qpic-snand-avoid-memleak-in-qcom_spi_ecc_ini.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-10-v6.15-spi-SPI_QPIC_SNAND-should-be-tristate-and-depend-on-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-11-v6.15-spi-spi-qpic-snand-propagate-errors-from-qcom_spi_block_erase.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-12-v6.15-spi-spi-qpic-snand-fix-NAND_READ_LOCATION_2-register-handling.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-13-v6.15-spi-spi-qpic-snand-use-kmalloc-for-OOB-buffer-alloca.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/410-14-v6.16-spi-spi-qpic-snand-remove-unused-wlen-member-of-struct-qpic_spi_nand.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/411-v6.15-mtd-nand-Drop-explicit-test-for-built-in-CONFIG_SPI_.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/412-v6.16-next-spi-spi-qpic-snand-validate-user-chip-specific-ECC-properties.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/413-v6.16-spi-spi-qpic-snand-use-CW_PER_PAGE_MASK-bitmask.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/414-v6.16-spi-spi-qpic-snand-reallocate-BAM-transactions.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/415-v6.16-spi-spi-qpic-snand-don-t-hardcode-ECC-steps.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/416-v6.17-mtd-nand-qpic-common-add-defines-for-ECC_MODE-values.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/417-v6.17-spi-spi-qpic-snand-add-support-for-8-bits-ECC-strength.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/418-v6.17-spi-spi-qpic-snand-use-correct-CW_PER_PAGE-value-for.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/419-v6.17-spi-spi-qpic-snand-fix-calculating-of-ECC-OOB-region.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/420-01-v6.16-mtd-rawnand-brcmnand-remove-unused-parameters.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/420-02-v6.16-mtd-nand-brcmnand-fix-NAND-timeout-when-accessing-eM.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/420-03-v6.16-mtd-rawnand-brcmnand-legacy-exec_op-implementation.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/422-v6.17-spi-spi-qpic-snand-unregister-ECC-engine-on-probe-er.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/429-01-v6.18-mtd-spinand-fix-direct-mapping-creation-sizes.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/429-02-v6.18-mtd-spinand-try-a-regular-dirmap-if-creating-a-di.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/429-03-v6.18-mtd-spinand-repeat-reading-in-regular-mode-if-con.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/430-v6.14-mtd-spinand-Introduce-a-way-to-avoid-raw-access.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/431-v6.14-mtd-spinand-Add-support-for-SkyHigh-S35ML-3-family.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/434-v6.19-mtd-spinand-esmt-add-support-for-F50L1G41LC.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/435-v6.19-mtd-spinand-add-support-for-FudanMicro-FM25S01BI3.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/500-01-v6.13-block-add-support-for-defining-read-only-partitions.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/500-02-v6.13-block-introduce-add_disk_fwnode.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/500-03-v6.13-mmc-block-attach-partitions-fwnode-if-found-in-mmc-c.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/500-04-v6.13-block-add-support-for-partition-table-defined-in-OF.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/600-01-v6.13-net-phylink-move-manual-flow-control-setting.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/600-02-v6.13-net-phylink-move-MLO_AN_FIXED-resolve-handling-to-if.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/600-03-v6.13-net-phylink-move-MLO_AN_PHY-resolve-handling-to-if-s.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/600-04-v6.13-net-phylink-remove-switch-statement-in-resolve-handl.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/600-05-v6.13-net-phylink-clean-up-phylink_resolve.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/601-01-v6.14-net-phylink-pass-phylink-and-pcs-into-phylink_pcs_ne.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/601-02-v6.14-net-phylink-split-cur_link_an_mode-into-requested-an.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/601-03-v6.14-net-phylink-add-debug-for-phylink_major_config.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/601-04-v6.14-net-phy-add-phy_inband_caps.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/601-05-v6.14-net-phy-bcm84881-implement-phy_inband_caps-method.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/601-06-v6.14-net-phy-marvell-implement-phy_inband_caps-method.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/601-07-v6.14-net-phy-add-phy_config_inband.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/601-08-v6.14-net-phy-marvell-implement-config_inband-method.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/601-09-v6.14-net-phylink-add-pcs_inband_caps-method.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/601-10-v6.14-net-mvneta-implement-pcs_inband_caps-method.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/601-11-v6.14-net-mvpp2-implement-pcs_inband_caps-method.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/601-12-v6.14-net-phylink-add-negotiation-of-in-band-capabilities.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/601-13-v6.14-net-phylink-remove-phylink_phy_no_inband.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/602-01-v6.14-net-pcs-pcs-lynx-implement-pcs_inband_caps-method.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/602-02-v6.14-net-pcs-pcs-mtk-lynxi-implement-pcs_inband_caps-meth.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/602-03-v6.14-net-pcs-xpcs-implement-pcs_inband_caps-method.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/605-02-v6.17-net-phylink-provide-phylink_get_inband_type.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/605-03-v6.17-net-phylink-disable-autoneg-for-interfaces-that-have.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/610-01-v6.13-net-dsa-use-ethtool-string-helpers.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/610-02-v6.15-net-dsa-b53-mdio-add-support-for-BCM53101.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/610-03-v6.16-net-dsa-b53-implement-setting-ageing-time.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/610-04-v6.16-net-dsa-b53-do-not-configure-bcm63xx-s-IMP-port-inte.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-01-v6.17-net-dsa-tag_brcm-add-support-for-legacy-FCS-tags.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-02-v6.17-net-dsa-b53-support-legacy-FCS-tags.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-03-v6.17-net-dsa-b53-detect-BCM5325-variants.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-04-v6.17-net-dsa-b53-add-support-for-FDB-operations-on-5325-5365.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-05-v6.17-net-dsa-b53-prevent-FAST_AGE-access-on-BCM5325.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-06-v6.17-net-dsa-b53-prevent-BRCM_HDR-access-on-older-devices.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-07-v6.17-net-dsa-b53-fix-unicast-multicast-flooding-on-BCM5325.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-08-v6.18-net-dsa-b53-Add-phy_enable-phy_disable-methods.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-09-v6.18-net-dsa-b53-Define-chip-IDs-for-more-bcm63xx-SoCs.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-10-v6.18-net-dsa-b53-mmap-Add-syscon-reference-and-register-layout-for-bcm63268.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-11-v6.18-net-dsa-b53-mmap-Add-register-layout-for-bcm6318.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-12-v6.18-net-dsa-b53-mmap-Add-register-layout-for-bcm6368.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-13-v6.18-net-dsa-b53-mmap-Implement-bcm63xx-ephy-power-control.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-14-v6.18-net-dsa-b53-mmap-Add-gphy-port-to-phy-info-for-bcm63268.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-15-v6.18-net-dsa-b53-mmap-Implement-bcm63268-gphy-power-control.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-16-v6.18-net-dsa-b53-fix-reserved-register-access-in-b53_fdb_dump.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-17-v6.18-net-dsa-b53-fix-ageing-time-for-BCM53101.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-18-v6.18-net-dsa-b53-properly-bound-ARL-searches-for-4-ARL-bin-chips.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/611-19-v6.18-net-dsa-tag_brcm-do-not-mark-link-local-traffic-as-offloaded.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/612-01-v6.19-net-dsa-b53-b53_arl_read-25-use-the-entry-for-comparision.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/612-02-v6.19-net-dsa-b53-move-reading-ARL-entries-into-their-own-function.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/612-03-v6.19-net-dsa-b53-move-writing-ARL-entries-into-their-own-functions.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/612-04-v6.19-net-dsa-b53-provide-accessors-for-accessing-ARL_SRCH_CTL.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/612-05-v6.19-net-dsa-b53-split-reading-search-entry-into-their-own-functions.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/612-06-v6.19-net-dsa-b53-move-ARL-entry-functions-into-ops-struct.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/612-07-v6.19-net-dsa-b53-add-support-for-5389-5397-5398-ARL-entry-format.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/612-08-v6.19-net-dsa-b53-add-support-for-bcm63xx-ARL-entry-format.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/613-01-v6.19-net-dsa-b53-fix-VLAN_ID_IDX-write-size-for-BCM5325-65.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/613-02-v6.19-net-dsa-b53-fix-extracting-VID-from-entry-for-BCM5325-65.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/613-03-v6.19-net-dsa-b53-use-same-ARL-search-result-offset-for-BCM5325-65.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/613-04-v6.19-net-dsa-b53-fix-CPU-port-unicast-ARL-entries-for-BCM5325-65.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/613-05-v6.19-net-dsa-b53-fix-BCM5325-65-ARL-entry-multicast-port-masks.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/613-06-v6.19-net-dsa-b53-fix-BCM5325-65-ARL-entry-VIDs.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/613-07-v6.19-net-dsa-b53-allow-VID-0-for-BCM5325-65.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/620-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/621-v6.17-ppp-convert-to-percpu-netstats.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/622-v6.18-ppp-remove-rwlock-usage.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/623-v6.18-pppoe-remove-rwlock-usage.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/624-v6.18-pppoe-drop-sock-reference-counting-on-fast-path.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/625-v7.0-ppp-enable-TX-scatter-gather.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-01-v6.13-net-pse-pd-Add-power-limit-check.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-02-v6.13-net-pse-pd-tps23881-Simplify-function-returns.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-03-v6.13-net-pse-pd-tps23881-Use-helpers-to-calculate-bit-offset.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-04-v6.13-net-pse-pd-tps23881-Add-missing-configuration-register.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-05-v6.13-net-pse-pd-Split-ethtool_get_status-into-multiple-callbacks.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-06-v6.13-net-pse-pd-Remove-is_enabled-callback-from-drivers.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-07-v6.13-net-pse-pd-tps23881-Add-power-limit-and-measurement-features.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-08-v6.13-net-pse-pd-Fix-missing-PI-of_node-description.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-09-v6.13-net-pse-pd-Clean-ethtool-header-of-PSE-structures.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-10-v6.17-net-pse-pd-Introduce-attached_phydev-to-pse-control.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-11-v6.17-net-pse-pd-Add-support-for-reporting-events.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-12-v6.17-net-pse-pd-tps23881-Add-support-for-PSE-events.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-13-v6.17-net-pse-pd-Add-support-for-PSE-power-domains.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-14-v6.17-net-pse-pd-ethtool-Add-support-for-power-domains-index.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-15-v6.17-net-pse-pd-Add-helper-to-report-hw-enable-status.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-16-v6.17-net-pse-pd-Add-support-for-budget-evaluation-strategies.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-16b-v6.17-regulator-Add-support-for-power-budget.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-17-v6.17-net-pse-pd-pd692x0-Add-PSE-PI-priority-feature.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-18-v6.17-net-pse-pd-pd692x0-Add-controller-and-manager-power.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-19-v6.17-net-pse-pd-tps23881-Add-static-port-priority-feature.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-20-v6.17-net-pse-pd-pd692x0-reduce-stack-usage.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-21-v6.18-net-pse-pd-pd692x0-Fix-power-budget-leak.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-22-v6.18-net-pse-pd-pd692x0-Skip-power-budget-when-undefined.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-23-v6.18-net-pse-pd-Add-Si3474-PSE-controller-driver.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-24-v6.19-net-pse-pd-tps23881-Fix-current-measurement-scaling.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-25-v6.19-net-pse-pd-pd692x0-Replace-__free-macro.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-26-v6.19-net-pse-pd-pd692x0-Separate-configuration-parsing.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-27-v6.19-net-pse-pd-pd692x0-Preserve-PSE-configuration.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-28-v6.19-net-pse-pd-tps23881-Add-support-for-TPS23881B.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-v6.16-net-bridge-locally-receive-all-multicast-packets-if-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/627-01-v6.17-net-pse-pd-Add-ethtool_netlink_generated-header.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/627-02-v6.17-net-ethtool-Add-PSE-port-priority-support.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/630-v6.13-net-usb-qmi_wwan-add-Quectel-RG255C.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/630-v6.14-PM-sleep-wakeirq-Introduce-device-managed-variant-of.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/700-01-v7.0-net-sched-Export-mq-functions-for-reuse.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/700-02-v7.0-net-sched-sch_cake-Factor-out-config-variables-into-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/700-03-v7.0-net-sched-sch_cake-Add-cake_mq-qdisc-for-using-cake-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/700-04-v7.0-net-sched-sch_cake-Share-config-across-cake_mq-sub-q.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/700-05-v7.0-net-sched-sch_cake-share-shaper-state-across-sub-ins.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/700-06-v7.0-selftests-tc-testing-add-selftests-for-cake_mq-qdisc.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/700-07-v7.0-net-sched-cake-avoid-separate-allocation-of-struct-c.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/701-01-v7.0-net-sched-sch_cake-avoid-sync-overhead-when-unlimite.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/701-02-v7.0-net-sched-sch_cake-fixup-cake_mq-rate-adjustment-for.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/710-v6.16-igc-enable-HW-vlan-tag-insertion-stripping-by-defaul.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-01-v6.13-net-phy-mediatek-ge-soc-Fix-coding-style.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-02-v6.13-net-phy-mediatek-ge-soc-Shrink-line-wrapping-to-80-c.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-03-v6.13-net-phy-mediatek-ge-soc-Propagate-error-code-correct.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-04-v6.13-net-phy-mediatek-Re-organize-MediaTek-ethernet-phy-d.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-05-v6.13-net-phy-mediatek-Move-LED-helper-functions-into-mtk-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-06-v6.13-net-phy-mediatek-Improve-readability-of-mtk-phy-lib..patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-07-v6.13-net-phy-mediatek-Integrate-read-write-page-helper-fu.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-08-v6.13-net-phy-mediatek-add-MT7530-MT7531-s-PHY-ID-macros.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-09-v6.14-net-phy-Constify-struct-mdio_device_id.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-10-v6.15-net-phy-mediatek-Change-to-more-meaningful-macros.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-11-v6.15-net-phy-mediatek-Add-token-ring-access-helper-functi.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-12-v6.15-net-phy-mediatek-Add-token-ring-set-bit-operation-su.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-13-v6.15-net-phy-mediatek-Add-token-ring-clear-bit-operation-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-14-v6.15-net-phy-mediatek-Move-some-macros-to-phy-lib-for-lat.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-15-v6.16-net-phy-mediatek-permit-to-compile-test-GE-SOC-PHY-d.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-16-v6.16-net-phy-mediatek-add-Airoha-PHY-ID-to-SoC-driver.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/720-17-v6.16-net-phy-mediatek-init-val-in-.phy_led_polarity_set-f.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/721-01-v6.15-net-ethernet-mediatek-add-EEE-support.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/730-01-v6.13-net-phy-aquantia-allow-forcing-order-of-MDI-pairs.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/730-02-v6.13-net-phy-aquantia-fix-return-value-check-in-aqr107_co.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/730-03-v6.13-net-phy-support-active-high-property-for-PHY-LEDs.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/730-04-v6.13-net-phy-aquantia-correctly-describe-LED-polarity-ove.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/730-05-v6.13-net-phy-mxl-gpy-add-basic-LED-support.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/730-06-v6.13-net-phy-mxl-gpy-add-missing-support-for-TRIGGER_NETD.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/730-07-v6.13-net-phy-mxl-gpy-correctly-describe-LED-polarity.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/730-08-v6.13-net-phy-intel-xway-add-support-for-PHY-LEDs.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/730-09-v6.18-net-phy-mxl-gpy-fix-link-properties-on-USXGMII-and-i.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/730-10-v6.19-net-phy-mxl-gpy-add-support-for-MxL86211C.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/730-11-v7.0-net-phy-mxl-gpy-implement-SGMII-in-band-configuratio.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/731-v6.18-net-mediatek-wed-Introduce-MT7992-WED-support-to-MT7.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/732-v6.18-wifi-mt76-wed-use-proper-wed-reference-in-mt76-wed-d.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/733-v6.18-net-mtk-wed-add-dma-mask-limitation-and-GFP_DMA32-fo.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/735-v6.13-net-phy-avoid-undefined-behavior-in-_led_polarity_se.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/740-v6.13-net-dsa-mv88e6xxx-Support-LED-control.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/741-v6.13-net-dsa-mv88e6xxx-fix-unreleased-fwnode_handle-in-se.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/742-v6.17-net-dsa-mv88e6xxx-Fix-fwnode-reference-leaks-in-mv88.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/750-v7.0-net-phy-move-mmd_phy_read-and-mmd_phy_write-to-phyli.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/760-v6.18-net-phy-introduce-phy_id_compare_model-PHY-ID-helper.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/761-v6.19-net-phy-mxl-gpy-add-support-for-mxl86252-and-mxl86282.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/762-v7.0-net-dsa-add-tag-format-for-MxL862xx-switches.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/763-v7.0-net-mdio-add-unlocked-mdiodev-C45-bus-accessors.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/764-v7.0-net-dsa-add-basic-initial-driver-for-MxL862xx-switch.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/765-v7.0-net-dsa-mxl862xx-rename-MDIO-op-arguments.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/766-v7.0-net-dsa-mxl862xx-don-t-set-user_mii_bus.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/767-v7.0-net-dsa-MxL862xx-don-t-force-enable-MAXLINEAR_GPHY.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/768-v7.1-net-dsa-MxL862xx-don-t-force-enable-MAXLINEAR_GPHY.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/769-v7.1-net-dsa-mxl862xx-add-CRC-for-MDIO-communication.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/770-v7.1-net-dsa-mxl862xx-use-RST_DATA-to-skip-writing-zero-w.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-01-v6.13-r8169-remove-original-workaround-for-RTL8125-broken-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-02-v6.13-r8169-enable-SG-TSO-on-selected-chip-versions-per-de.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-03-v6.13-r8169-implement-additional-ethtool-stats-ops.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-04-v6.13-r8169-don-t-take-RTNL-lock-in-rtl_task.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-05-v6.13-replace-custom-flag-with-disable_work-et-al.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-06-v6.13-r8169-avoid-duplicated-messages-if-loading-firmware-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-07-v6.13-r8169-remove-rtl_dash_loop_wait_high-low.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-08-v6.13-r8169-enable-EEE-at-2.5G-per-default-on-RTL8125B.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-10-v6.13-r8169-fix-inconsistent-indenting-in-rtl8169_get_eth_.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-11-v6.13-r8169-align-RTL8125-EEE-config-with-vendor-driver.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-12-v6.13-r8169-align-RTL8125-RTL8126-PHY-config-with-vendor-d.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-13-v6.13-r8169-align-RTL8126-EEE-config-with-vendor-driver.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-14-v6.13-r8169-improve-initialization-of-RSS-registers-on-RTL.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-15-v6.13-r8169-remove-leftover-locks-after-reverted-change.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-16-v6.13-r8169-improve-__rtl8169_set_wol.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-17-v6.13-r8169-improve-rtl_set_d3_pll_down.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-18-v6.13-r8169-align-WAKE_PHY-handling-with-r8125-r8126-vendo.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-19-v6.13-r8169-use-helper-r8169_mod_reg8_cond-to-simplify-rtl.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-20-v6.13-r8169-copy-vendor-driver-2.5G-5G-EEE-advertisement-c.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-21-v6.14-r8169-remove-unused-flag-RTL_FLAG_TASK_RESET_NO_QUEU.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-22-v6.14-r8169-remove-support-for-chip-version-11.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-23-v6.14-r8169-adjust-version-numbering-for-RTL8126.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-24-v6.14-r8169-add-support-for-RTL8125D-rev.b.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-25-v6.14-r8169-add-support-for-RTL8125BP-rev.b.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-26-v6.15-r8169-make-Kconfig-option-for-LED-support-user-visib.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-28-v6.15-r8169-add-support-for-Intel-Killer-E5000.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-29-v6.15-r8169-add-PHY-c45-ops-for-MDIO_MMD_VENDOR2-registers.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-30-v6.15-r8169-increase-max-jumbo-packet-size-on-RTL8125-RTL8.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-31-v6.15-r8169-switch-away-from-deprecated-pcim_iomap_table.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-32-v6.15-r8169-enable-RTL8168H-RTL8168EP-RTL8168FP-ASPM-suppo.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-34-v6.16-r8169-add-helper-rtl_csi_mod-for-accessing-extended.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-35-v6.16-r8169-add-helper-rtl8125_phy_param.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-36-v6.16-r8169-refactor-chip-version-detection.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-37-v6.16-r8169-add-RTL_GIGA_MAC_VER_LAST-to-facilitate-adding.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-38-v6.16-r8169-use-pci_prepare_to_sleep-in-rtl_shutdown.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-39-v6.16-r8169-merge-chip-versions-70-and-71-RTL8126A.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-40-v6.16-r8169-merge-chip-versions-64-and-65-RTL8125D.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-41-v6.16-r8169-merge-chip-versions-52-and-53-RTL8117.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/780-42-v6.16-r8169-add-support-for-RTL8127A.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-01-v6.13-net-phy-realtek-read-duplex-and-gbit-master-from-PHY.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-02-v6.13-net-phy-realtek-change-order-of-calls-in-C22-read_st.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-03-v6.13-net-phy-realtek-clear-1000Base-T-link-partner-advert.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-06-v6.14-net-phy-realtek-add-support-for-reading-MDIO_MMD_VEN.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-07-v6.14-net-phy-realtek-clear-1000Base-T-lpa-if-link-is-down.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-08-v6.14-net-phy-realtek-clear-master_slave_state-if-link-is-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-09-v6.14-net-phy-realtek-always-clear-NBase-T-lpa.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-10-v6.14-net-phy-move-realtek-PHY-driver-to-its-own-subdirect.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-11-v6.14-net-phy-realtek-add-hwmon-support-for-temp-sensor-on.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-12-v6.14-net-phy-realtek-HWMON-support-for-standalone-version.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-13-v6.15-net-phy-realtek-make-HWMON-support-a-user-visible-Kc.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-14-v6.15-net-phy-realtek-use-string-choices-helpers.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-15-v6.15-net-phy-realtek-improve-mmd-register-access-for-inte.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-16-v6.15-net-phy-realtek-switch-from-paged-to-MMD-ops-in-rtl8.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-17-v6.15-net-phy-realtek-add-helper-RTL822X_VND2_C22_REG.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-18-v6.15-net-phy-realtek-add-defines-for-shadowed-c45-standar.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-19-v6.15-net-phy-realtek-disable-PHY-mode-EEE.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-20-v6.16-net-phy-realtek-Add-support-for-WOL-magic-packet-on.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-21-v6.16-net-phy-realtek-remove-unsed-RTL821x_PHYSR-macros.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-22-v6.16-net-phy-realtek-Clean-up-RTL821x-ExtPage-access.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-23-v6.16-net-phy-realtek-add-RTL8211F-register-defines.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-24-v6.16-net-phy-realtek-Group-RTL82-macro-definitions.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-25-v6.16-net-phy-realtek-use-__set_bit-in-rtl8211f_led_hw_con.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-26-v6.16-net-phy-realtek-Add-support-for-PHY-LEDs-on-RTL8211E.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-27-v6.16-net-phy-realtek-add-RTL8127-internal-PHY.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-28-v6.18-net-phy-realtek-convert-RTL8226-CG-to-c45-only.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/781-29-v6.18-net-phy-realtek-enable-serdes-option-mode-for-RTL8226-CG.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/782-01-v6.16-net-phy-pass-PHY-driver-to-.match_phy_device-OP.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/782-04-v6.16-net-phy-introduce-genphy_match_phy_device.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/782-05-v6.16-net-phy-Add-support-for-Aeonsemi-AS21xxx-PHYs.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/783-01-v6.18-net-dsa-Move-KS8995-to-the-DSA-subsystem.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/783-02-v6.18-net-dsa-ks8995-Add-proper-RESET-delay.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/783-03-v6.18-net-dsa-ks8995-Delete-sysfs-register-access.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/783-04-v6.18-net-dsa-ks8995-Add-basic-switch-set-up.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/784-01-v6.17-net-phy-realtek-add-error-handling-to-rtl8211f_get_w.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/784-02-v6.18-net-phy-realtek-Avoid-PHYCR2-access-if-PHYCR2-not-pr.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/784-02-v6.18-net-phy-realtek-fix-RTL8211F-wake-on-lan-support.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/784-03-v6.18-net-phy-realtek-fix-rtl8221b-vm-cg-name.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/784-03-v6.18-net-phy-realtek-support-for-TRIGGER_NETDEV_LINK-on-R.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/784-06-v6.19-net-phy-realtek-Add-RTL8224-cable-testing-support.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/784-07-v6.19-net-phy-realtek-add-interrupt-support-for-RTL8221B.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/784-08-v6.19-net-phy-realtek-create-rtl8211f_config_rgmii_delay.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/784-09-v6.19-net-phy-realtek-eliminate-priv-phycr2-variable.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/784-10-v6.19-net-phy-realtek-eliminate-has_phycr2-variable.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/784-11-v6.19-net-phy-realtek-allow-CLKOUT-to-be-disabled-on-RTL82.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/784-12-v6.19-net-phy-realtek-eliminate-priv-phycr1-variable.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/784-13-v6.19-net-phy-realtek-create-rtl8211f_config_phy_eee-helpe.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/785-v6.18-r8169-fix-RTL8127-hang-on-suspend-shutdown.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/786-01-v6.18-net-phy-introduce-phy_id_compare_vendor-PHY-ID-helpe.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/786-02-v6.18-net-phy-as21xxx-better-handle-PHY-HW-reset-on-soft-r.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/787-v6.19-net-phy-RTL8211FVD-Restore-disabling-of-PHY-mode-EEE.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/788-v7.0-net-phy-realtek-fix-whitespace-in-struct-phy_driver-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/789-v7.0-net-phy-realtek-implement-configuring-in-band-an.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/790-v7.0-net-phy-realtek-use-paged-access-for-MDIO_MMD_VEND2-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/791-v7.0-net-phy-realtek-get-rid-of-magic-number-in-rtlgen_re.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/792-v7.0-net-phy-realtek-add-dummy-PHY-driver-for-RTL8127ATF.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/793-v7.0-net-phy-realtek-fix-in-band-capabilities-for-2.5G-PH.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/794-v7.0-net-phy-realtek-support-interrupt-also-for-C22-varia.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/795-v7.0-net-phy-realtek-simplify-C22-reg-access-via-MDIO_MMD.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/796-v7.0-net-phy-realtek-reunify-C22-and-C45-drivers.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/797-v7.0-net-phy-realtek-demystify-PHYSR-register-location.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/798-v7.0-net-phy-realtek-simplify-bogus-paged-operations.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/799-v7.0-mtd-physmap-core-Prioritize-ofparts-for-OF-probe.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/800-v6.13-hwmon-Add-static-visibility-member-to-struct-hwmon_o.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/801-01-v6.13-dt-bindings-clocks-add-binding-for-gated-fixed-clock.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/801-02-v6.13-clk-clk-gpio-update-documentation-for-gpio-gate-cloc.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/801-03-v6.13-clk-clk-gpio-use-dev_err_probe-for-gpio-get-failure.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/801-04-v6.13-clk-clk-gpio-add-driver-for-gated-fixed-clocks.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/810-v6.14-gpio-regmap-Use-generic-request-free-ops.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/813-v6.16-pinctrl-aw9523-fix-can_sleep-flag-for-GPIO-chip.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/820-v6.15-power-supply-sysfs-remove-duplicate-nul-termination-to-fix-build-with-GCC15.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/821-01-v7.0-dt-bindings-gpio-add-gpio-line-mux-controller.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/821-02-v7.0-gpio-add-gpio-line-mux-driver.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/821-03-v7.0-gpio-line-mux-remove-bits-already-handled-by-GPIO-co.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/822-v6.19-ALSA-usb-audio-Convert-to-common-field_-get-prep-hel.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/823-v6.19-clk-at91-Convert-to-common-field_-get-prep-helpers.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/824-v6.19-iio-mlx90614-Convert-to-common-field_-get-prep-helpe.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/825-v6.19-pinctrl-ma35-Convert-to-common-field_-get-prep-helpe.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/891-v6.14-dt-bindings-leds-Add-LED1202-LED-Controller.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/892-v6.14-leds-Add-LED1202-I2C-driver.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/893-v6.14-leds_st1202-Fix-NULL-pointer-access-error.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/900-v6.14-driver-core-add-a-faux-bus-for-use-when-a-simple-dev.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/910-v7.0-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/911-v7.0-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/912-v7.0-crypto-testmgr-Add-test-vectors-for-authenc-hmac-md5.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/913-v7.0-crypto-testmgr-allow-authenc-sha224-rfc3686-variant-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/914-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-md5.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/915-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/916-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/917-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/918-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/919-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/930-v7.1-crypto-safexcel-Group-authenc-ciphersuites.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/931-v7.1-crypto-safexcel-Add-support-for-authenc-hmac-md5-sui.patch [new file with mode: 0644]
target/linux/generic/config-6.12 [new file with mode: 0644]
target/linux/generic/hack-6.12/200-tools_portability.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/204-module_strip.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/205-kconfig-abort-configuration-on-unset-symbol.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/210-darwin_scripts_include.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/211-darwin-uuid-typedef-clash.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/214-spidev_h_portability.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/230-openwrt_lzma_options.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/249-udp-tunnel-selection.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/250-netfilter_depends.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/251-kconfig.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/253-ksmbd-config.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/259-regmap_dynamic.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/260-crypto_test_dependencies.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/261-lib-arc4-unhide.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/280-rfkill-stubs.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/300-MIPS-r4k_cache-use-more-efficient-cache-blast.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/301-01-mm-permit-to-declare-custom-execmem-alloc-free-funct.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/301-02-mips-replace-mlong-calls-with-mno-long-calls-if-poss.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/402-mtd-blktrans-call-add-disks-after-mtd-device.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/420-mtd-support-OpenWrt-s-MTD_ROOTFS_ROOT_DEV.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/421-drivers-mtd-parsers-add-nvmem-support-to-cmdlinepart.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/430-mtk-bmt-support.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/499-LEGACY-block-partitions-populate-fwnode.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/600-net-enable-fraglist-GRO-by-default.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/610-net-page_pool-try-to-free-deferred-skbs-while-waitin.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/645-netfilter-connmark-introduce-set-dscpmark.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/650-netfilter-add-xt_FLOWOFFLOAD-target.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/651-wireless_mesh_header.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/660-fq_codel_defaults.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/661-kernel-ct-size-the-hashtable-more-adequately.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/700-swconfig_switch_drivers.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/721-net-add-packet-mangeling.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/722-net-phy-aquantia-enable-AQR112-and-AQR412.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/723-net-phy-aquantia-fix-system-side-protocol-mi.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/725-net-phy-aquantia-add-PHY_IDs-for-AQR112-variants.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/730-net-ethernet-mtk_eth_soc-add-hw-dump-for-forced-rese.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/735-net-phy-realtek-rtl8261n.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/750-net-pcs-mtk-lynxi-workaround-2500BaseX-no-an.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/755-net-phy-motorcomm-yt8821-bus-collision-workaround.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/760-net-usb-r8152-add-LED-configuration-from-OF.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/761-dt-bindings-net-add-RTL8152-binding-documentation.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/766-net-phy-mediatek-ge-add-LED-configuration-interface.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/770-r8169-LED-uses-original-network-port-name.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/773-bgmac-add-srab-switch.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/780-usb-net-MeigLink_modem_support.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/781-usb-net-rndis-support-asr.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/800-GPIO-add-named-gpio-exports.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/810-bcma-ssb-fallback-sprom.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/901-debloat_sock_diag.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/902-debloat_proc.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/904-debloat_dma_buf.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/910-kobject_uevent.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/911-kobject_add_broadcast_uevent.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/920-device_tree_cmdline.patch [new file with mode: 0644]
target/linux/generic/hack-6.12/930-Revert-Revert-Revert-driver-core-Set-fw_devlink-on-b.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/100-compiler.h-only-include-asm-rwonce.h-for-kernel-code.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/102-MIPS-only-process-negative-stack-offsets-on-stack-tr.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/103-kbuild-export-SUBARCH.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/111-watchdog-max63xx_wdt-Add-support-for-specifying-WDI-.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/140-jffs2-use-.rename2-and-add-RENAME_WHITEOUT-support.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/141-jffs2-add-RENAME_EXCHANGE-support.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/142-jffs2-add-splice-ops.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/150-bridge_allow_receiption_on_disabled_port.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/151-net-bridge-do-not-send-arp-replies-if-src-and-target.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/190-rtc-rs5c372-support_alarms_up_to_1_week.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/191-rtc-rs5c372-let_the_alarm_to_be_used_as_wakeup_source.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/200-ARM-9404-1-arm32-fix-boot-hang-with-HAVE_LD_DEAD_COD.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/203-kallsyms_uncompressed.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/205-backtrace_module_info.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/240-remove-unsane-filenames-from-deps_initramfs-list.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/250-kernel-fork-Increase-minimum-number-of-allowed-threa.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/270-platform-mikrotik-build-bits.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/300-mips_expose_boot_raw.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/301-MIPS-Add-barriers-between-dcache-icache-flushes.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/302-mips_no_branch_likely.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/303-Revert-powerpc-dts-mpc85xx-remove-simple-bus-compatible-from-ifc-node.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/308-mips32r2_tune.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/310-arm_module_unresolved_weak_sym.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/330-MIPS-kexec-Accept-command-line-parameters-from-users.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/332-arc-add-OWRTDTB-section.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/333-arc-enable-unaligned-access-in-kernel-mode.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/342-powerpc-Enable-kernel-XZ-compression-option-on-PPC_8.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/350-mips-kernel-fix-detect_memory_region-function.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/360-Revert-MIPS-mm-kmalloc-tlb_vpn-array-to-avoid-stack-.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/361-Revert-MIPS-mm-Prevent-a-TLB-shutdown-on-initial-uni.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/400-mtd-mtdsplit-support.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/402-mtd-spi-nor-write-support-for-minor-aligned-partitions.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/417-mtd-spi-nand-macronix-disable-continuous-read-for-MX.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/420-mtd-redboot_space.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/430-mtd-add-myloader-partition-parser.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/431-mtd-bcm47xxpart-check-for-bad-blocks-when-calculatin.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/432-mtd-bcm47xxpart-detect-T_Meter-partition.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/435-mtd-add-routerbootpart-parser-config.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/450-block-allow-setting-partition-of_node.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/451-block-partitions-of-assign-Device-Tree-node-to-parti.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/452-partitions-efi-apply-Linux-code-style.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/453-partitions-efi-allow-assigning-partition-Device-Tree.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/454-block-add-support-for-notifications.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/455-block-add-new-genhd-flag-GENHD_FL_NVMEM.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/456-nvmem-implement-block-NVMEM-provider.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/457-mmc-block-set-GENHD_FL_NVMEM.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/460-mtd-cfi_cmdset_0002-no-erase_suspend.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/461-mtd-cfi_cmdset_0002-add-buffer-write-cmd-timeout.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/476-mtd-spi-nor-add-eon-en25q128.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/477-mtd-spi-nor-add-eon-en25qx128a.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/479-mtd-spi-nor-add-xtx-xt25f128b.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/481-mtd-spi-nor-add-support-for-Gigadevice-GD25D05.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/482-mtd-spi-nor-add-gd25q512.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/484-mtd-spi-nor-add-esmt-f25l16pa.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/485-mtd-spi-nor-add-xmc-xm25qh128c.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/487-mtd-spinand-Add-support-for-Etron-EM73D044VCx.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/488-mtd-spi-nor-add-xmc-xm25qh64c.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/490-ubi-auto-attach-mtd-device-named-ubi-or-data-on-boot.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/491-ubi-auto-create-ubiblock-device-for-rootfs.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/492-try-auto-mounting-ubi0-rootfs-in-init-do_mounts.c.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/493-ubi-set-ROOT_DEV-to-ubiblock-rootfs-if-unset.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/494-mtd-ubi-add-EOF-marker-support.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/496-dt-bindings-add-bindings-for-mtd-concat-devices.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/497-mtd-mtdconcat-add-dt-driver-for-concat-devices.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/500-fs_cdrom_dependencies.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/510-block-add-uImage.FIT-subimage-block-driver.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/511-init-bypass-device-lookup-for-dev-fit-rootfs.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/530-jffs2_make_lzma_available.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/532-jffs2_eofdetect.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/600-netfilter_conntrack_flush.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/610-netfilter_match_bypass_default_checks.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/611-netfilter_match_bypass_default_table.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/612-netfilter_match_reduce_memory_access.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/620-net-sfp-improve-Huawei-MA5671a-fixup.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/630-packet_socket_type.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/640-net-bridge-fix-switchdev-host-mdb-entry-updates.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/641-net-bridge-switchdev-Don-t-drop-packets-between-port.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/650-net-pppoe-implement-GRO-support.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/655-increase_skb_pad.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/666-Add-support-for-MAP-E-FMRs-mesh-mode.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/670-ipv6-allow-rejecting-with-source-address-failed-policy.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/671-net-provide-defines-for-_POLICY_FAILED-until-all-cod.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/681-net-remove-NETIF_F_GSO_FRAGLIST-from-NETIF_F_GSO_SOF.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/683-of_net-add-mac-address-to-of-tree.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/690-net-add-missing-check-for-TCP-fraglist-GRO.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/700-netfilter-nft_flow_offload-handle-netdevice-events-f.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/701-netfilter-nf_tables-ignore-EOPNOTSUPP-on-flowtable-d.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/702-net-ethernet-mtk_eth_soc-enable-threaded-NAPI.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/703-phy-add-detach-callback-to-struct-phy_driver.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/704-net-phy-register-phy-led_triggers-during-probe-to-av.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/705-net-dsa-tag_mtk-add-padding-for-tx-packets.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/706-net-phy-populate-host_interfaces-when-attaching-PHY.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/710-bridge-add-knob-for-filtering-rx-tx-BPDU-pack.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/711-01-net-dsa-qca8k-implement-lag_fdb_add-del-ops.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/711-02-net-dsa-qca8k-enable-flooding-to-both-CPU-port.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/711-03-net-dsa-qca8k-add-support-for-port_change_master.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/712-net-dsa-qca8k-enable-assisted-learning-on-CPU-port.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/713-net-phy-c45-check-validity-of-10GbE-link-partner.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/720-01-net-phy-realtek-use-genphy_soft_reset-for-2.5G-PHYs.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/720-03-net-phy-realtek-make-sure-paged-read-is-protected-by.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/720-04-net-phy-realtek-setup-aldps.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/720-05-net-phy-realtek-detect-early-version-of-RTL8221B.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/720-06-net-phy-realtek-mark-existing-MMDs-as-present.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/720-07-net-phy-realtek-disable-MDIO-broadcast.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/720-08-net-phy-realtek-rate-adapter-in-C22-mode.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/731-net-permit-ieee80211_ptr-even-with-no-CFG82111-suppo.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/732-00-net-ethernet-mtk_eth_soc-compile-out-netsys-v2-code-.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/732-01-net-ethernet-mtk_eth_soc-work-around-issue-with-send.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/732-03-net-ethernet-mtk_eth_soc-optimize-dma-ring-address-i.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/732-04-net-ethernet-mtk_eth_soc-shrink-struct-mtk_tx_buf.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/732-05-net-ethernet-mtk_eth_soc-add-support-for-sending-fra.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/733-01-net-ethernet-mtk_eth_soc-use-napi_build_skb.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/734-net-ethernet-mediatek-enlarge-DMA-reserve-buffer.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/736-03-net-ethernet-mtk_eth_soc-improve-keeping-track-of-of.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/736-04-net-ethernet-mediatek-fix-ppe-flow-accounting-for-L2.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/736-05-net-ethernet-mtk_eth_soc-zero-initialize-PPE-flow-ta.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/737-net-ethernet-mtk_eth_soc-add-paths-and-SerDes-modes-.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/738-01-net-ethernet-mtk_eth_soc-reduce-rx-ring-size-for-older.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/738-02-net-ethernet-mtk_eth_soc-do-not-enable-page-pool-sta.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/739-01-dt-bindings-phy-mediatek-xfi-tphy-add-new-bindings.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/739-03-net-pcs-pcs-mtk-lynxi-add-platform-driver-for-MT7988.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/739-04-dt-bindings-net-pcs-add-bindings-for-MediaTek-USXGMI.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/739-05-net-pcs-add-driver-for-MediaTek-USXGMII-PCS.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/740-net-phy-motorcomm-Add-missing-include.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/741-net-phy-broadcom-update-dependency-condition.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/750-net-sfp-add-quirk-for-QINIYEK-BJ-SFP-10G-T-copper-SF.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-01-net-dsa-move-dsa_bridge_ports-helper-to-dsa.h.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-02-net-dsa-add-bridge-member-iteration-macro.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-03-dsa-tag_mxl862xx-set-dsa_default_offload_fwd_mark.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-04-net-dsa-mxl862xx-implement-bridge-offloading.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-05-net-dsa-mxl862xx-implement-VLAN-functionality.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-06-net-dsa-mxl862xx-add-ethtool-statistics-support.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-07-net-dsa-mxl862xx-implement-.get_stats64.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-08-net-dsa-mxl862xx-store-firmware-version-for-feature-.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-09-net-dsa-mxl862xx-move-phylink-stubs-to-mxl862xx-phyl.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-10-net-dsa-mxl862xx-move-API-macros-to-mxl862xx-host.h.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-11-net-dsa-mxl862xx-add-support-for-SerDes-ports.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-12-net-dsa-mxl862xx-add-SerDes-ethtool-statistics.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-13-net-dsa-mxl862xx-add-SerDes-self-test-via-PRBS-and-B.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-14-net-dsa-mxl862xx-trap-link-local-frames-to-the-CPU-p.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-15-net-dsa-mxl862xx-warn-about-old-firmware-default-PCE.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-16-net-dsa-add-802.1Q-VLAN-based-tag-driver-for-MxL862x.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-17-net-dsa-mxl862xx-add-link-aggregation-support.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-18-net-dsa-mxl862xx-add-support-for-mirror-port.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-19-net-dsa-wire-flash_update-devlink-callback-to-driver.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-20-net-dsa-mxl862xx-add-SMDIO-clause-22-register-access.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-21-net-dsa-mxl862xx-add-devlink-flash_update-and-info_g.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-22-net-dsa-mxl862xx-implement-port-MTU-configuration.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-23-net-dsa-mxl862xx-support-BR_HAIRPIN_MODE-bridge-flag.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-24-net-dsa-mxl862xx-support-BR_ISOLATED-bridge-flag.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-25-DO-NOT-SUBMIT-net-dsa-mxl862xx-re-introduce-PCE-work.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/760-26-DO-NOT-SUBMIT-net-dsa-mxl862xx-legacy-SFP-API-fallba.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/780-ARM-kirkwood-add-missing-linux-if_ether.h-for-ETH_AL.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/790-bus-mhi-core-add-SBL-state-callback.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/791-tg3-Fix-DMA-allocations-on-57766-devices.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/800-bcma-get-SoC-device-struct-copy-its-DMA-params-to-th.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/802-OPP-Provide-old-opp-to-config_clks-on-_set_opp.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/804-nvmem-core-support-mac-base-fixed-layout-cells.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/809-01-nvmem-core-generalize-mac-base-cells-handling.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/809-02-nvmem-layouts-add-support-for-ascii-env-driver.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/809-03-nvmem-layouts-ascii-env-handle-CRLF-while-parsing.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/810-pci_disable_common_quirks.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/811-pci_disable_usb_common_quirks.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/812-PCI-sysfs-enforce-single-creation-of-sysfs-entry-for.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/834-ledtrig-libata.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/840-hwrng-bcm2835-set-quality-to-1000.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/850-0023-PCI-aardvark-Make-main-irq_chip-structure-a-static-d.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/890-usb-serial-add-support-for-CH348.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/900-net-ag71xx-fix-qca9530-and-qca9550-mdio-probe.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/910-crypto-testmgr-Add-test-vectors-for-authenc-hmac-md5.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/920-mangle_bootargs.patch [new file with mode: 0644]

diff --git a/target/linux/generic/backport-6.12/010-v6.19-nvmem-layouts-u-boot-env-add-optional-env-size-property.patch b/target/linux/generic/backport-6.12/010-v6.19-nvmem-layouts-u-boot-env-add-optional-env-size-property.patch
new file mode 100644 (file)
index 0000000..207161a
--- /dev/null
@@ -0,0 +1,62 @@
+From 06e92afca89075628b12c9b4085b4cc7320081ac Mon Sep 17 00:00:00 2001
+From: Jascha Sundaresan <flizarthanon@gmail.com>
+Date: Thu, 23 Oct 2025 03:07:41 +0400
+Subject: nvmem: layouts: u-boot-env: add optional "env-size" property
+
+Some devices reserve a larger NVMEM region for the U-Boot environment
+than the actual environment data length used by U-Boot itself. The CRC32
+in the U-Boot header is calculated over the smaller data length, causing
+CRC validation to fail when Linux reads the full partition.
+
+Allow an optional device tree property "env-size" to specify the
+environment data size to use for CRC computation.
+
+v2: add missing $ref line to DT binding
+
+Signed-off-by: Jascha Sundaresan <flizarthanon@gmail.com>
+Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
+Signed-off-by: Srinivas Kandagatla <srini@kernel.org>
+---
+ Documentation/devicetree/bindings/nvmem/layouts/u-boot,env.yaml | 7 +++++++
+ drivers/nvmem/layouts/u-boot-env.c                              | 4 +++-
+ 2 files changed, 10 insertions(+), 1 deletion(-)
+
+--- a/Documentation/devicetree/bindings/nvmem/layouts/u-boot,env.yaml
++++ b/Documentation/devicetree/bindings/nvmem/layouts/u-boot,env.yaml
+@@ -46,6 +46,12 @@ properties:
+     type: object
+     description: Command to use for automatic booting
++  env-size:
++    description:
++      Size in bytes of the environment data used by U-Boot for CRC
++      calculation. If omitted, the full NVMEM region size is used.
++    $ref: /schemas/types.yaml#/definitions/uint32
++
+   ethaddr:
+     type: object
+     description: Ethernet interfaces base MAC address.
+@@ -104,6 +110,7 @@ examples:
+             partition-u-boot-env {
+                 compatible = "brcm,env";
++                env-size = <0x20000>;
+                 ethaddr {
+                 };
+--- a/drivers/nvmem/layouts/u-boot-env.c
++++ b/drivers/nvmem/layouts/u-boot-env.c
+@@ -99,10 +99,12 @@ int u_boot_env_parse(struct device *dev,
+       uint32_t crc32;
+       uint32_t calc;
+       uint8_t *buf;
++      u32 env_size;
+       int bytes;
+       int err;
+-      dev_size = nvmem_dev_size(nvmem);
++      dev_size = device_property_read_u32(dev, "env-size", &env_size) ?
++              nvmem_dev_size(nvmem) : (size_t)env_size;
+       buf = kzalloc(dev_size, GFP_KERNEL);
+       if (!buf) {
diff --git a/target/linux/generic/backport-6.12/152-v6.16-net-wireguard-add-nonstring-annotation-to-fix-build-with-GCC15.patch b/target/linux/generic/backport-6.12/152-v6.16-net-wireguard-add-nonstring-annotation-to-fix-build-with-GCC15.patch
new file mode 100644 (file)
index 0000000..1fdf2f1
--- /dev/null
@@ -0,0 +1,63 @@
+From 71e5da46e78c1cd24e2feed251a2845327447ad8 Mon Sep 17 00:00:00 2001
+From: Kees Cook <kees@kernel.org>
+Date: Wed, 21 May 2025 23:27:04 +0200
+Subject: wireguard: global: add __nonstring annotations for unterminated
+ strings
+
+When a character array without a terminating NUL character has a static
+initializer, GCC 15's -Wunterminated-string-initialization will only
+warn if the array lacks the "nonstring" attribute[1]. Mark the arrays
+with __nonstring to correctly identify the char array as "not a C string"
+and thereby eliminate the warning:
+
+../drivers/net/wireguard/cookie.c:29:56: warning: initializer-string for array of 'unsigned char' truncates NUL terminator but destination lacks 'nonstring' attribute (9 chars into 8 available) [-Wunterminated-string-initialization]
+   29 | static const u8 mac1_key_label[COOKIE_KEY_LABEL_LEN] = "mac1----";
+      |                                                        ^~~~~~~~~~
+../drivers/net/wireguard/cookie.c:30:58: warning: initializer-string for array of 'unsigned char' truncates NUL terminator but destination lacks 'nonstring' attribute (9 chars into 8 available) [-Wunterminated-string-initialization]
+   30 | static const u8 cookie_key_label[COOKIE_KEY_LABEL_LEN] = "cookie--";
+      |                                                          ^~~~~~~~~~
+../drivers/net/wireguard/noise.c:28:38: warning: initializer-string for array of 'unsigned char' truncates NUL terminator but destination lacks 'nonstring' attribute (38 chars into 37 available) [-Wunterminated-string-initialization]
+   28 | static const u8 handshake_name[37] = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s";
+      |                                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+../drivers/net/wireguard/noise.c:29:39: warning: initializer-string for array of 'unsigned char' truncates NUL terminator but destination lacks 'nonstring' attribute (35 chars into 34 available) [-Wunterminated-string-initialization]
+   29 | static const u8 identifier_name[34] = "WireGuard v1 zx2c4 Jason@zx2c4.com";
+      |                                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The arrays are always used with their fixed size, so use __nonstring.
+
+Link: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117178 [1]
+Signed-off-by: Kees Cook <kees@kernel.org>
+Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
+Link: https://patch.msgid.link/20250521212707.1767879-3-Jason@zx2c4.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/wireguard/cookie.c | 4 ++--
+ drivers/net/wireguard/noise.c  | 4 ++--
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/wireguard/cookie.c
++++ b/drivers/net/wireguard/cookie.c
+@@ -26,8 +26,8 @@ void wg_cookie_checker_init(struct cooki
+ }
+ enum { COOKIE_KEY_LABEL_LEN = 8 };
+-static const u8 mac1_key_label[COOKIE_KEY_LABEL_LEN] = "mac1----";
+-static const u8 cookie_key_label[COOKIE_KEY_LABEL_LEN] = "cookie--";
++static const u8 mac1_key_label[COOKIE_KEY_LABEL_LEN] __nonstring = "mac1----";
++static const u8 cookie_key_label[COOKIE_KEY_LABEL_LEN] __nonstring = "cookie--";
+ static void precompute_key(u8 key[NOISE_SYMMETRIC_KEY_LEN],
+                          const u8 pubkey[NOISE_PUBLIC_KEY_LEN],
+--- a/drivers/net/wireguard/noise.c
++++ b/drivers/net/wireguard/noise.c
+@@ -25,8 +25,8 @@
+  * <- e, ee, se, psk, {}
+  */
+-static const u8 handshake_name[37] = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s";
+-static const u8 identifier_name[34] = "WireGuard v1 zx2c4 Jason@zx2c4.com";
++static const u8 handshake_name[37] __nonstring = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s";
++static const u8 identifier_name[34] __nonstring = "WireGuard v1 zx2c4 Jason@zx2c4.com";
+ static u8 handshake_init_hash[NOISE_HASH_LEN] __ro_after_init;
+ static u8 handshake_init_chaining_key[NOISE_HASH_LEN] __ro_after_init;
+ static atomic64_t keypair_counter = ATOMIC64_INIT(0);
diff --git a/target/linux/generic/backport-6.12/200-01-v6.13-jiffies-Define-secs_to_jiffies.patch b/target/linux/generic/backport-6.12/200-01-v6.13-jiffies-Define-secs_to_jiffies.patch
new file mode 100644 (file)
index 0000000..ad9af1d
--- /dev/null
@@ -0,0 +1,60 @@
+From b35108a51cf7bab58d7eace1267d7965978bcdb8 Mon Sep 17 00:00:00 2001
+From: Easwar Hariharan <eahariha@linux.microsoft.com>
+Date: Wed, 30 Oct 2024 17:47:35 +0000
+Subject: [PATCH] jiffies: Define secs_to_jiffies()
+
+secs_to_jiffies() is defined in hci_event.c and cannot be reused by
+other call sites. Hoist it into the core code to allow conversion of the
+~1150 usages of msecs_to_jiffies() that either:
+
+ - use a multiplier value of 1000 or equivalently MSEC_PER_SEC, or
+ - have timeouts that are denominated in seconds (i.e. end in 000)
+
+It's implemented as a macro to allow usage in static initializers.
+
+This will also allow conversion of yet more sites that use (sec * HZ)
+directly, and improve their readability.
+
+Suggested-by: Michael Kelley <mhklinux@outlook.com>
+Signed-off-by: Easwar Hariharan <eahariha@linux.microsoft.com>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Reviewed-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Link: https://lore.kernel.org/all/20241030-open-coded-timeouts-v3-1-9ba123facf88@linux.microsoft.com
+---
+ include/linux/jiffies.h   | 13 +++++++++++++
+ net/bluetooth/hci_event.c |  2 --
+ 2 files changed, 13 insertions(+), 2 deletions(-)
+
+--- a/include/linux/jiffies.h
++++ b/include/linux/jiffies.h
+@@ -526,6 +526,19 @@ static __always_inline unsigned long mse
+       }
+ }
++/**
++ * secs_to_jiffies: - convert seconds to jiffies
++ * @_secs: time in seconds
++ *
++ * Conversion is done by simple multiplication with HZ
++ *
++ * secs_to_jiffies() is defined as a macro rather than a static inline
++ * function so it can be used in static initializers.
++ *
++ * Return: jiffies value
++ */
++#define secs_to_jiffies(_secs) ((_secs) * HZ)
++
+ extern unsigned long __usecs_to_jiffies(const unsigned int u);
+ #if !(USEC_PER_SEC % HZ)
+ static inline unsigned long _usecs_to_jiffies(const unsigned int u)
+--- a/net/bluetooth/hci_event.c
++++ b/net/bluetooth/hci_event.c
+@@ -42,8 +42,6 @@
+ #define ZERO_KEY "\x00\x00\x00\x00\x00\x00\x00\x00" \
+                "\x00\x00\x00\x00\x00\x00\x00\x00"
+-#define secs_to_jiffies(_secs) msecs_to_jiffies((_secs) * 1000)
+-
+ /* Handle HCI Event packets */
+ static void *hci_ev_skb_pull(struct hci_dev *hdev, struct sk_buff *skb,
diff --git a/target/linux/generic/backport-6.12/200-02-v6.14-jiffies-Cast-to-unsigned-long-in-secs_to_jiffies-con.patch b/target/linux/generic/backport-6.12/200-02-v6.14-jiffies-Cast-to-unsigned-long-in-secs_to_jiffies-con.patch
new file mode 100644 (file)
index 0000000..cddd558
--- /dev/null
@@ -0,0 +1,35 @@
+From bb2784d9ab49587ba4fbff37a319fff2924db289 Mon Sep 17 00:00:00 2001
+From: Easwar Hariharan <eahariha@linux.microsoft.com>
+Date: Thu, 30 Jan 2025 19:26:58 +0000
+Subject: [PATCH] jiffies: Cast to unsigned long in secs_to_jiffies()
+ conversion
+
+While converting users of msecs_to_jiffies(), lkp reported that some range
+checks would always be true because of the mismatch between the implied int
+value of secs_to_jiffies() vs the unsigned long return value of the
+msecs_to_jiffies() calls it was replacing.
+
+Fix this by casting the secs_to_jiffies() input value to unsigned long.
+
+Fixes: b35108a51cf7ba ("jiffies: Define secs_to_jiffies()")
+Reported-by: kernel test robot <lkp@intel.com>
+Signed-off-by: Easwar Hariharan <eahariha@linux.microsoft.com>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Cc: stable@vger.kernel.org
+Link: https://lore.kernel.org/all/20250130192701.99626-1-eahariha@linux.microsoft.com
+Closes: https://lore.kernel.org/oe-kbuild-all/202501301334.NB6NszQR-lkp@intel.com/
+---
+ include/linux/jiffies.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/include/linux/jiffies.h
++++ b/include/linux/jiffies.h
+@@ -537,7 +537,7 @@ static __always_inline unsigned long mse
+  *
+  * Return: jiffies value
+  */
+-#define secs_to_jiffies(_secs) ((_secs) * HZ)
++#define secs_to_jiffies(_secs) (unsigned long)((_secs) * HZ)
+ extern unsigned long __usecs_to_jiffies(const unsigned int u);
+ #if !(USEC_PER_SEC % HZ)
diff --git a/target/linux/generic/backport-6.12/201-v6.13-of-property-add-of_graph_get_next_port.patch b/target/linux/generic/backport-6.12/201-v6.13-of-property-add-of_graph_get_next_port.patch
new file mode 100644 (file)
index 0000000..5178dc8
--- /dev/null
@@ -0,0 +1,199 @@
+From 02ac5f9d6caec96071103f7c62b5117526e47b64 Mon Sep 17 00:00:00 2001
+From: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+Date: Thu, 24 Oct 2024 02:20:02 +0000
+Subject: [PATCH] of: property: add of_graph_get_next_port()
+
+We have endpoint base functions
+       - of_graph_get_next_endpoint()
+       - of_graph_get_endpoint_count()
+       - for_each_endpoint_of_node()
+
+Here, for_each_endpoint_of_node() loop finds each endpoints
+
+       ports {
+               port@0 {
+(1)                    endpoint {...};
+               };
+               port@1 {
+(2)                    endpoint {...};
+               };
+               ...
+       };
+
+In above case, it finds endpoint as (1) -> (2) -> ...
+
+Basically, user/driver knows which port is used for what, but not in
+all cases. For example on flexible/generic driver case, how many ports
+are used is not fixed.
+
+For example Sound Generic Card driver which is very flexible/generic and
+used from many venders can't know how many ports are used, and used for
+what, because it depends on each vender SoC and/or its used board.
+
+And more, the port can have multi endpoints. For example Generic Sound
+Card case, it supports many type of connection between CPU / Codec, and
+some of them uses multi endpoint in one port. see below.
+
+       ports {
+(A)            port@0 {
+(1)                    endpoint@0 {...};
+(2)                    endpoint@1 {...};
+               };
+(B)            port@1 {
+(3)                    endpoint {...};
+               };
+               ...
+       };
+
+Generic Sound Card want to handle each connection via "port" base instead
+of "endpoint" base. But, it is very difficult to handle each "port" via
+existing for_each_endpoint_of_node(). Because getting each "port" via
+of_get_parent() from each "endpoint" doesn't work. For example in above
+case, both (1) (2) endpoint has same "port" (= A).
+
+Add "port" base functions.
+
+Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+Link: https://lore.kernel.org/r/87ldyeb5t9.wl-kuninori.morimoto.gx@renesas.com
+Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
+---
+ drivers/of/property.c    | 54 ++++++++++++++++++++++++++++++++++++++++
+ include/linux/of_graph.h | 28 +++++++++++++++++++++
+ 2 files changed, 82 insertions(+)
+
+--- a/drivers/of/property.c
++++ b/drivers/of/property.c
+@@ -631,6 +631,43 @@ struct device_node *of_graph_get_port_by
+ EXPORT_SYMBOL(of_graph_get_port_by_id);
+ /**
++ * of_graph_get_next_port() - get next port node.
++ * @parent: pointer to the parent device node, or parent ports node
++ * @prev: previous port node, or NULL to get first
++ *
++ * Parent device node can be used as @parent whether device node has ports node
++ * or not. It will work same as ports@0 node.
++ *
++ * Return: A 'port' node pointer with refcount incremented. Refcount
++ * of the passed @prev node is decremented.
++ */
++struct device_node *of_graph_get_next_port(const struct device_node *parent,
++                                         struct device_node *prev)
++{
++      if (!parent)
++              return NULL;
++
++      if (!prev) {
++              struct device_node *node __free(device_node) =
++                      of_get_child_by_name(parent, "ports");
++
++              if (node)
++                      parent = node;
++
++              return of_get_child_by_name(parent, "port");
++      }
++
++      do {
++              prev = of_get_next_child(parent, prev);
++              if (!prev)
++                      break;
++      } while (!of_node_name_eq(prev, "port"));
++
++      return prev;
++}
++EXPORT_SYMBOL(of_graph_get_next_port);
++
++/**
+  * of_graph_get_next_endpoint() - get next endpoint node
+  * @parent: pointer to the parent device node
+  * @prev: previous endpoint node, or NULL to get first
+@@ -824,6 +861,23 @@ unsigned int of_graph_get_endpoint_count
+ EXPORT_SYMBOL(of_graph_get_endpoint_count);
+ /**
++ * of_graph_get_port_count() - get the number of port in a device or ports node
++ * @np: pointer to the device or ports node
++ *
++ * Return: count of port of this device or ports node
++ */
++unsigned int of_graph_get_port_count(struct device_node *np)
++{
++      unsigned int num = 0;
++
++      for_each_of_graph_port(np, port)
++              num++;
++
++      return num;
++}
++EXPORT_SYMBOL(of_graph_get_port_count);
++
++/**
+  * of_graph_get_remote_node() - get remote parent device_node for given port/endpoint
+  * @node: pointer to parent device_node containing graph port/endpoint
+  * @port: identifier (value of reg property) of the parent port node
+--- a/include/linux/of_graph.h
++++ b/include/linux/of_graph.h
+@@ -11,6 +11,7 @@
+ #ifndef __LINUX_OF_GRAPH_H
+ #define __LINUX_OF_GRAPH_H
++#include <linux/cleanup.h>
+ #include <linux/types.h>
+ #include <linux/errno.h>
+@@ -37,14 +38,29 @@ struct of_endpoint {
+       for (child = of_graph_get_next_endpoint(parent, NULL); child != NULL; \
+            child = of_graph_get_next_endpoint(parent, child))
++/**
++ * for_each_of_graph_port - iterate over every port in a device or ports node
++ * @parent: parent device or ports node containing port
++ * @child: loop variable pointing to the current port node
++ *
++ * When breaking out of the loop, and continue to use the @child, you need to
++ * use return_ptr(@child) or no_free_ptr(@child) not to call __free() for it.
++ */
++#define for_each_of_graph_port(parent, child)                 \
++      for (struct device_node *child __free(device_node) = of_graph_get_next_port(parent, NULL);\
++           child != NULL; child = of_graph_get_next_port(parent, child))
++
+ #ifdef CONFIG_OF
+ bool of_graph_is_present(const struct device_node *node);
+ int of_graph_parse_endpoint(const struct device_node *node,
+                               struct of_endpoint *endpoint);
+ unsigned int of_graph_get_endpoint_count(const struct device_node *np);
++unsigned int of_graph_get_port_count(struct device_node *np);
+ struct device_node *of_graph_get_port_by_id(struct device_node *node, u32 id);
+ struct device_node *of_graph_get_next_endpoint(const struct device_node *parent,
+                                       struct device_node *previous);
++struct device_node *of_graph_get_next_port(const struct device_node *parent,
++                                         struct device_node *port);
+ struct device_node *of_graph_get_endpoint_by_regs(
+               const struct device_node *parent, int port_reg, int reg);
+ struct device_node *of_graph_get_remote_endpoint(
+@@ -73,6 +89,11 @@ static inline unsigned int of_graph_get_
+       return 0;
+ }
++static inline unsigned int of_graph_get_port_count(struct device_node *np)
++{
++      return 0;
++}
++
+ static inline struct device_node *of_graph_get_port_by_id(
+                                       struct device_node *node, u32 id)
+ {
+@@ -83,6 +104,13 @@ static inline struct device_node *of_gra
+                                       const struct device_node *parent,
+                                       struct device_node *previous)
+ {
++      return NULL;
++}
++
++static inline struct device_node *of_graph_get_next_port(
++                                      const struct device_node *parent,
++                                      struct device_node *previous)
++{
+       return NULL;
+ }
diff --git a/target/linux/generic/backport-6.12/202-v6.13-clk-Provide-devm_clk_bulk_get_all_enabled-helper.patch b/target/linux/generic/backport-6.12/202-v6.13-clk-Provide-devm_clk_bulk_get_all_enabled-helper.patch
new file mode 100644 (file)
index 0000000..c02e4ed
--- /dev/null
@@ -0,0 +1,118 @@
+From 51e32e897539663957f7a0950f66b48f8896efee Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+Date: Sat, 19 Oct 2024 14:16:00 +0300
+Subject: [PATCH] clk: Provide devm_clk_bulk_get_all_enabled() helper
+
+Commit 265b07df758a ("clk: Provide managed helper to get and enable bulk
+clocks") added devm_clk_bulk_get_all_enable() function, but missed to
+return the number of clocks stored in the clk_bulk_data table referenced
+by the clks argument.  Without knowing the number, it's not possible to
+iterate these clocks when needed, hence the argument is useless and
+could have been simply removed.
+
+Introduce devm_clk_bulk_get_all_enabled() variant, which is consistent
+with devm_clk_bulk_get_all() in terms of the returned value:
+
+ > 0 if one or more clocks have been stored
+ = 0 if there are no clocks
+ < 0 if an error occurred
+
+Moreover, the naming is consistent with devm_clk_get_enabled(), i.e. use
+the past form of 'enable'.
+
+To reduce code duplication and improve patch readability, make
+devm_clk_bulk_get_all_enable() use the new helper, as suggested by
+Stephen Boyd.
+
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+Link: https://lore.kernel.org/r/20241019-clk_bulk_ena_fix-v4-1-57f108f64e70@collabora.com
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+---
+ drivers/clk/clk-devres.c |  9 +++++----
+ include/linux/clk.h      | 21 ++++++++++++++++-----
+ 2 files changed, 21 insertions(+), 9 deletions(-)
+
+--- a/drivers/clk/clk-devres.c
++++ b/drivers/clk/clk-devres.c
+@@ -218,8 +218,8 @@ static void devm_clk_bulk_release_all_en
+       clk_bulk_put_all(devres->num_clks, devres->clks);
+ }
+-int __must_check devm_clk_bulk_get_all_enable(struct device *dev,
+-                                            struct clk_bulk_data **clks)
++int __must_check devm_clk_bulk_get_all_enabled(struct device *dev,
++                                             struct clk_bulk_data **clks)
+ {
+       struct clk_bulk_devres *devres;
+       int ret;
+@@ -244,11 +244,12 @@ int __must_check devm_clk_bulk_get_all_e
+       } else {
+               clk_bulk_put_all(devres->num_clks, devres->clks);
+               devres_free(devres);
++              return ret;
+       }
+-      return ret;
++      return devres->num_clks;
+ }
+-EXPORT_SYMBOL_GPL(devm_clk_bulk_get_all_enable);
++EXPORT_SYMBOL_GPL(devm_clk_bulk_get_all_enabled);
+ static int devm_clk_match(struct device *dev, void *res, void *data)
+ {
+--- a/include/linux/clk.h
++++ b/include/linux/clk.h
+@@ -520,11 +520,13 @@ int __must_check devm_clk_bulk_get_all(s
+                                      struct clk_bulk_data **clks);
+ /**
+- * devm_clk_bulk_get_all_enable - Get and enable all clocks of the consumer (managed)
++ * devm_clk_bulk_get_all_enabled - Get and enable all clocks of the consumer (managed)
+  * @dev: device for clock "consumer"
+  * @clks: pointer to the clk_bulk_data table of consumer
+  *
+- * Returns success (0) or negative errno.
++ * Returns a positive value for the number of clocks obtained while the
++ * clock references are stored in the clk_bulk_data table in @clks field.
++ * Returns 0 if there're none and a negative value if something failed.
+  *
+  * This helper function allows drivers to get all clocks of the
+  * consumer and enables them in one operation with management.
+@@ -532,8 +534,8 @@ int __must_check devm_clk_bulk_get_all(s
+  * is unbound.
+  */
+-int __must_check devm_clk_bulk_get_all_enable(struct device *dev,
+-                                            struct clk_bulk_data **clks);
++int __must_check devm_clk_bulk_get_all_enabled(struct device *dev,
++                                             struct clk_bulk_data **clks);
+ /**
+  * devm_clk_get - lookup and obtain a managed reference to a clock producer.
+@@ -1041,7 +1043,7 @@ static inline int __must_check devm_clk_
+       return 0;
+ }
+-static inline int __must_check devm_clk_bulk_get_all_enable(struct device *dev,
++static inline int __must_check devm_clk_bulk_get_all_enabled(struct device *dev,
+                                               struct clk_bulk_data **clks)
+ {
+       return 0;
+@@ -1136,6 +1138,15 @@ static inline struct clk *clk_get_sys(co
+ #endif
++/* Deprecated. Use devm_clk_bulk_get_all_enabled() */
++static inline int __must_check
++devm_clk_bulk_get_all_enable(struct device *dev, struct clk_bulk_data **clks)
++{
++      int ret = devm_clk_bulk_get_all_enabled(dev, clks);
++
++      return ret > 0 ? 0 : ret;
++}
++
+ /* clk_prepare_enable helps cases using clk_enable in non-atomic context. */
+ static inline int clk_prepare_enable(struct clk *clk)
+ {
diff --git a/target/linux/generic/backport-6.12/203-01-v6.13-of-address-Rework-bus-matching-to-avoid-warnings.patch b/target/linux/generic/backport-6.12/203-01-v6.13-of-address-Rework-bus-matching-to-avoid-warnings.patch
new file mode 100644 (file)
index 0000000..d7a6751
--- /dev/null
@@ -0,0 +1,61 @@
+From 64ee3cf096ac590e7da2ceac1c390546bff5e240 Mon Sep 17 00:00:00 2001
+From: "Rob Herring (Arm)" <robh@kernel.org>
+Date: Fri, 8 Nov 2024 13:35:48 -0600
+Subject: [PATCH] of/address: Rework bus matching to avoid warnings
+
+With warnings added for deprecated #address-cells/#size-cells handling,
+the DT address handling code causes warnings when used on nodes with no
+address. This happens frequently with calls to of_platform_populate() as
+it is perfectly acceptable to have devices without a 'reg' property. The
+desired behavior is to just silently return an error when retrieving an
+address.
+
+The warnings can be avoided by checking for "#address-cells" presence
+first and checking for an address property before fetching
+"#address-cells" and "#size-cells".
+
+Reported-by: Marek Szyprowski <m.szyprowski@samsung.com>
+Reported-by: Steven Price <steven.price@arm.com>
+Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
+Link: https://lore.kernel.org/r/20241108193547.2647986-2-robh@kernel.org
+Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
+---
+ drivers/of/address.c | 14 +++++++++-----
+ 1 file changed, 9 insertions(+), 5 deletions(-)
+
+--- a/drivers/of/address.c
++++ b/drivers/of/address.c
+@@ -331,7 +331,11 @@ static unsigned int of_bus_isa_get_flags
+ static int of_bus_default_flags_match(struct device_node *np)
+ {
+-      return of_bus_n_addr_cells(np) == 3;
++      /*
++       * Check for presence first since of_bus_n_addr_cells() will warn when
++       * walking parent nodes.
++       */
++      return of_property_present(np, "#address-cells") && (of_bus_n_addr_cells(np) == 3);
+ }
+ /*
+@@ -700,16 +704,16 @@ const __be32 *__of_get_address(struct de
+       if (strcmp(bus->name, "pci") && (bar_no >= 0))
+               return NULL;
+-      bus->count_cells(dev, &na, &ns);
+-      if (!OF_CHECK_ADDR_COUNT(na))
+-              return NULL;
+-
+       /* Get "reg" or "assigned-addresses" property */
+       prop = of_get_property(dev, bus->addresses, &psize);
+       if (prop == NULL)
+               return NULL;
+       psize /= 4;
++      bus->count_cells(dev, &na, &ns);
++      if (!OF_CHECK_ADDR_COUNT(na))
++              return NULL;
++
+       onesize = na + ns;
+       for (i = 0; psize >= onesize; psize -= onesize, prop += onesize, i++) {
+               u32 val = be32_to_cpu(prop[0]);
diff --git a/target/linux/generic/backport-6.12/203-02-v6.13-of-address-Fix-WARN-when-attempting-translating-non-.patch b/target/linux/generic/backport-6.12/203-02-v6.13-of-address-Fix-WARN-when-attempting-translating-non-.patch
new file mode 100644 (file)
index 0000000..2ad67cb
--- /dev/null
@@ -0,0 +1,85 @@
+From 6e5773d52f4a2d9c80692245f295069260cff6fc Mon Sep 17 00:00:00 2001
+From: "Rob Herring (Arm)" <robh@kernel.org>
+Date: Fri, 10 Jan 2025 15:50:29 -0600
+Subject: [PATCH] of/address: Fix WARN when attempting translating
+ non-translatable addresses
+
+The recently added WARN() for deprecated #address-cells and #size-cells
+triggered a WARN when of_platform_populate() (which calls
+of_address_to_resource()) is used on nodes with non-translatable
+addresses. This case is expected to return an error.
+
+Rework the bus matching to allow no match and make the default require
+an #address-cells property. That should be safe to do as any platform
+missing #address-cells would have a warning already.
+
+Fixes: 045b14ca5c36 ("of: WARN on deprecated #address-cells/#size-cells handling")
+Tested-by: Sean Anderson <sean.anderson@linux.dev>
+Link: https://lore.kernel.org/r/20250110215030.3637845-2-robh@kernel.org
+Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
+---
+ drivers/of/address.c | 18 +++++++++++++++---
+ 1 file changed, 15 insertions(+), 3 deletions(-)
+
+--- a/drivers/of/address.c
++++ b/drivers/of/address.c
+@@ -338,6 +338,15 @@ static int of_bus_default_flags_match(st
+       return of_property_present(np, "#address-cells") && (of_bus_n_addr_cells(np) == 3);
+ }
++static int of_bus_default_match(struct device_node *np)
++{
++      /*
++       * Check for presence first since of_bus_n_addr_cells() will warn when
++       * walking parent nodes.
++       */
++      return of_property_present(np, "#address-cells");
++}
++
+ /*
+  * Array of bus specific translators
+  */
+@@ -382,7 +391,7 @@ static struct of_bus of_busses[] = {
+       {
+               .name = "default",
+               .addresses = "reg",
+-              .match = NULL,
++              .match = of_bus_default_match,
+               .count_cells = of_bus_default_count_cells,
+               .map = of_bus_default_map,
+               .translate = of_bus_default_translate,
+@@ -397,7 +406,6 @@ static struct of_bus *of_match_bus(struc
+       for (i = 0; i < ARRAY_SIZE(of_busses); i++)
+               if (!of_busses[i].match || of_busses[i].match(np))
+                       return &of_busses[i];
+-      BUG();
+       return NULL;
+ }
+@@ -519,6 +527,8 @@ static u64 __of_translate_address(struct
+       if (parent == NULL)
+               return OF_BAD_ADDR;
+       bus = of_match_bus(parent);
++      if (!bus)
++              return OF_BAD_ADDR;
+       /* Count address cells & copy address locally */
+       bus->count_cells(dev, &na, &ns);
+@@ -562,6 +572,8 @@ static u64 __of_translate_address(struct
+               /* Get new parent bus and counts */
+               pbus = of_match_bus(parent);
++              if (!pbus)
++                      return OF_BAD_ADDR;
+               pbus->count_cells(dev, &pna, &pns);
+               if (!OF_CHECK_COUNTS(pna, pns)) {
+                       pr_err("Bad cell count for %pOF\n", dev);
+@@ -701,7 +713,7 @@ const __be32 *__of_get_address(struct de
+       /* match the parent's bus type */
+       bus = of_match_bus(parent);
+-      if (strcmp(bus->name, "pci") && (bar_no >= 0))
++      if (!bus || (strcmp(bus->name, "pci") && (bar_no >= 0)))
+               return NULL;
+       /* Get "reg" or "assigned-addresses" property */
diff --git a/target/linux/generic/backport-6.12/205-v6.16-of-reserved_mem-Add-functions-to-parse-memory-region.patch b/target/linux/generic/backport-6.12/205-v6.16-of-reserved_mem-Add-functions-to-parse-memory-region.patch
new file mode 100644 (file)
index 0000000..b5bbb6e
--- /dev/null
@@ -0,0 +1,163 @@
+From f4fcfdda2fd8834c62dcb9bfddcf1f89d190b70e Mon Sep 17 00:00:00 2001
+From: "Rob Herring (Arm)" <robh@kernel.org>
+Date: Wed, 23 Apr 2025 14:42:13 -0500
+Subject: [PATCH] of: reserved_mem: Add functions to parse "memory-region"
+
+Drivers with "memory-region" properties currently have to do their own
+parsing of "memory-region" properties. The result is all the drivers
+have similar patterns of a call to parse "memory-region" and then get
+the region's address and size. As this is a standard property, it should
+have common functions for drivers to use. Add new functions to count the
+number of regions and retrieve the region's address as a resource.
+
+Reviewed-by: Daniel Baluta <daniel.baluta@nxp.com>
+Acked-by: Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>
+Link: https://lore.kernel.org/r/20250423-dt-memory-region-v2-v2-1-2fbd6ebd3c88@kernel.org
+Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
+---
+ drivers/of/of_reserved_mem.c    | 80 +++++++++++++++++++++++++++++++++
+ include/linux/of_reserved_mem.h | 26 +++++++++++
+ 2 files changed, 106 insertions(+)
+
+--- a/drivers/of/of_reserved_mem.c
++++ b/drivers/of/of_reserved_mem.c
+@@ -12,6 +12,7 @@
+ #define pr_fmt(fmt)   "OF: reserved mem: " fmt
+ #include <linux/err.h>
++#include <linux/ioport.h>
+ #include <linux/libfdt.h>
+ #include <linux/of.h>
+ #include <linux/of_fdt.h>
+@@ -694,3 +695,82 @@ struct reserved_mem *of_reserved_mem_loo
+       return NULL;
+ }
+ EXPORT_SYMBOL_GPL(of_reserved_mem_lookup);
++
++/**
++ * of_reserved_mem_region_to_resource() - Get a reserved memory region as a resource
++ * @np:               node containing 'memory-region' property
++ * @idx:      index of 'memory-region' property to lookup
++ * @res:      Pointer to a struct resource to fill in with reserved region
++ *
++ * This function allows drivers to lookup a node's 'memory-region' property
++ * entries by index and return a struct resource for the entry.
++ *
++ * Returns 0 on success with @res filled in. Returns -ENODEV if 'memory-region'
++ * is missing or unavailable, -EINVAL for any other error.
++ */
++int of_reserved_mem_region_to_resource(const struct device_node *np,
++                                     unsigned int idx, struct resource *res)
++{
++      struct reserved_mem *rmem;
++
++      if (!np)
++              return -EINVAL;
++
++      struct device_node __free(device_node) *target = of_parse_phandle(np, "memory-region", idx);
++      if (!target || !of_device_is_available(target))
++              return -ENODEV;
++
++      rmem = of_reserved_mem_lookup(target);
++      if (!rmem)
++              return -EINVAL;
++
++      resource_set_range(res, rmem->base, rmem->size);
++      res->name = rmem->name;
++      return 0;
++}
++EXPORT_SYMBOL_GPL(of_reserved_mem_region_to_resource);
++
++/**
++ * of_reserved_mem_region_to_resource_byname() - Get a reserved memory region as a resource
++ * @np:               node containing 'memory-region' property
++ * @name:     name of 'memory-region' property entry to lookup
++ * @res:      Pointer to a struct resource to fill in with reserved region
++ *
++ * This function allows drivers to lookup a node's 'memory-region' property
++ * entries by name and return a struct resource for the entry.
++ *
++ * Returns 0 on success with @res filled in, or a negative error-code on
++ * failure.
++ */
++int of_reserved_mem_region_to_resource_byname(const struct device_node *np,
++                                            const char *name,
++                                            struct resource *res)
++{
++      int idx;
++
++      if (!name)
++              return -EINVAL;
++
++      idx = of_property_match_string(np, "memory-region-names", name);
++      if (idx < 0)
++              return idx;
++
++      return of_reserved_mem_region_to_resource(np, idx, res);
++}
++EXPORT_SYMBOL_GPL(of_reserved_mem_region_to_resource_byname);
++
++/**
++ * of_reserved_mem_region_count() - Return the number of 'memory-region' entries
++ * @np:               node containing 'memory-region' property
++ *
++ * This function allows drivers to retrieve the number of entries for a node's
++ * 'memory-region' property.
++ *
++ * Returns the number of entries on success, or negative error code on a
++ * malformed property.
++ */
++int of_reserved_mem_region_count(const struct device_node *np)
++{
++      return of_count_phandle_with_args(np, "memory-region", NULL);
++}
++EXPORT_SYMBOL_GPL(of_reserved_mem_region_count);
+--- a/include/linux/of_reserved_mem.h
++++ b/include/linux/of_reserved_mem.h
+@@ -7,6 +7,7 @@
+ struct of_phandle_args;
+ struct reserved_mem_ops;
++struct resource;
+ struct reserved_mem {
+       const char                      *name;
+@@ -39,6 +40,12 @@ int of_reserved_mem_device_init_by_name(
+ void of_reserved_mem_device_release(struct device *dev);
+ struct reserved_mem *of_reserved_mem_lookup(struct device_node *np);
++int of_reserved_mem_region_to_resource(const struct device_node *np,
++                                     unsigned int idx, struct resource *res);
++int of_reserved_mem_region_to_resource_byname(const struct device_node *np,
++                                            const char *name, struct resource *res);
++int of_reserved_mem_region_count(const struct device_node *np);
++
+ #else
+ #define RESERVEDMEM_OF_DECLARE(name, compat, init)                    \
+@@ -63,6 +70,25 @@ static inline struct reserved_mem *of_re
+ {
+       return NULL;
+ }
++
++static inline int of_reserved_mem_region_to_resource(const struct device_node *np,
++                                                   unsigned int idx,
++                                                   struct resource *res)
++{
++      return -ENOSYS;
++}
++
++static inline int of_reserved_mem_region_to_resource_byname(const struct device_node *np,
++                                                          const char *name,
++                                                          struct resource *res)
++{
++      return -ENOSYS;
++}
++
++static inline int of_reserved_mem_region_count(const struct device_node *np)
++{
++      return 0;
++}
+ #endif
+ /**
diff --git a/target/linux/generic/backport-6.12/210-v6.13-fortify-Hide-run-time-copy-size-from-value-range-tracking.patch b/target/linux/generic/backport-6.12/210-v6.13-fortify-Hide-run-time-copy-size-from-value-range-tracking.patch
new file mode 100644 (file)
index 0000000..8dca649
--- /dev/null
@@ -0,0 +1,155 @@
+From 239d87327dcd361b0098038995f8908f3296864f Mon Sep 17 00:00:00 2001
+From: Kees Cook <kees@kernel.org>
+Date: Thu, 12 Dec 2024 17:28:06 -0800
+Subject: fortify: Hide run-time copy size from value range tracking
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+GCC performs value range tracking for variables as a way to provide better
+diagnostics. One place this is regularly seen is with warnings associated
+with bounds-checking, e.g. -Wstringop-overflow, -Wstringop-overread,
+-Warray-bounds, etc. In order to keep the signal-to-noise ratio high,
+warnings aren't emitted when a value range spans the entire value range
+representable by a given variable. For example:
+
+       unsigned int len;
+       char dst[8];
+       ...
+       memcpy(dst, src, len);
+
+If len's value is unknown, it has the full "unsigned int" range of [0,
+UINT_MAX], and GCC's compile-time bounds checks against memcpy() will
+be ignored. However, when a code path has been able to narrow the range:
+
+       if (len > 16)
+               return;
+       memcpy(dst, src, len);
+
+Then the range will be updated for the execution path. Above, len is
+now [0, 16] when reading memcpy(), so depending on other optimizations,
+we might see a -Wstringop-overflow warning like:
+
+       error: '__builtin_memcpy' writing between 9 and 16 bytes into region of size 8 [-Werror=stringop-overflow]
+
+When building with CONFIG_FORTIFY_SOURCE, the fortified run-time bounds
+checking can appear to narrow value ranges of lengths for memcpy(),
+depending on how the compiler constructs the execution paths during
+optimization passes, due to the checks against the field sizes. For
+example:
+
+       if (p_size_field != SIZE_MAX &&
+           p_size != p_size_field && p_size_field < size)
+
+As intentionally designed, these checks only affect the kernel warnings
+emitted at run-time and do not block the potentially overflowing memcpy(),
+so GCC thinks it needs to produce a warning about the resulting value
+range that might be reaching the memcpy().
+
+We have seen this manifest a few times now, with the most recent being
+with cpumasks:
+
+In function ‘bitmap_copy’,
+    inlined from ‘cpumask_copy’ at ./include/linux/cpumask.h:839:2,
+    inlined from ‘__padata_set_cpumasks’ at kernel/padata.c:730:2:
+./include/linux/fortify-string.h:114:33: error: ‘__builtin_memcpy’ reading between 257 and 536870904 bytes from a region of size 256 [-Werror=stringop-overread]
+  114 | #define __underlying_memcpy     __builtin_memcpy
+      |                                 ^
+./include/linux/fortify-string.h:633:9: note: in expansion of macro ‘__underlying_memcpy’
+  633 |         __underlying_##op(p, q, __fortify_size);                        \
+      |         ^~~~~~~~~~~~~
+./include/linux/fortify-string.h:678:26: note: in expansion of macro ‘__fortify_memcpy_chk’
+  678 | #define memcpy(p, q, s)  __fortify_memcpy_chk(p, q, s,                  \
+      |                          ^~~~~~~~~~~~~~~~~~~~
+./include/linux/bitmap.h:259:17: note: in expansion of macro ‘memcpy’
+  259 |                 memcpy(dst, src, len);
+      |                 ^~~~~~
+kernel/padata.c: In function ‘__padata_set_cpumasks’:
+kernel/padata.c:713:48: note: source object ‘pcpumask’ of size [0, 256]
+  713 |                                  cpumask_var_t pcpumask,
+      |                                  ~~~~~~~~~~~~~~^~~~~~~~
+
+This warning is _not_ emitted when CONFIG_FORTIFY_SOURCE is disabled,
+and with the recent -fdiagnostics-details we can confirm the origin of
+the warning is due to FORTIFY's bounds checking:
+
+../include/linux/bitmap.h:259:17: note: in expansion of macro 'memcpy'
+  259 |                 memcpy(dst, src, len);
+      |                 ^~~~~~
+  '__padata_set_cpumasks': events 1-2
+../include/linux/fortify-string.h:613:36:
+  612 |         if (p_size_field != SIZE_MAX &&
+      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  613 |             p_size != p_size_field && p_size_field < size)
+      |             ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
+      |                                    |
+      |                                    (1) when the condition is evaluated to false
+      |                                    (2) when the condition is evaluated to true
+  '__padata_set_cpumasks': event 3
+  114 | #define __underlying_memcpy     __builtin_memcpy
+      |                                 ^
+      |                                 |
+      |                                 (3) out of array bounds here
+
+Note that the cpumask warning started appearing since bitmap functions
+were recently marked __always_inline in commit ed8cd2b3bd9f ("bitmap:
+Switch from inline to __always_inline"), which allowed GCC to gain
+visibility into the variables as they passed through the FORTIFY
+implementation.
+
+In order to silence these false positives but keep otherwise deterministic
+compile-time warnings intact, hide the length variable from GCC with
+OPTIMIZE_HIDE_VAR() before calling the builtin memcpy.
+
+Additionally add a comment about why all the macro args have copies with
+const storage.
+
+Reported-by: "Thomas Weißschuh" <linux@weissschuh.net>
+Closes: https://lore.kernel.org/all/db7190c8-d17f-4a0d-bc2f-5903c79f36c2@t-8ch.de/
+Reported-by: Nilay Shroff <nilay@linux.ibm.com>
+Closes: https://lore.kernel.org/all/20241112124127.1666300-1-nilay@linux.ibm.com/
+Tested-by: Nilay Shroff <nilay@linux.ibm.com>
+Acked-by: Yury Norov <yury.norov@gmail.com>
+Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Kees Cook <kees@kernel.org>
+---
+ include/linux/fortify-string.h | 14 +++++++++++++-
+ 1 file changed, 13 insertions(+), 1 deletion(-)
+
+--- a/include/linux/fortify-string.h
++++ b/include/linux/fortify-string.h
+@@ -616,6 +616,12 @@ __FORTIFY_INLINE bool fortify_memcpy_chk
+       return false;
+ }
++/*
++ * To work around what seems to be an optimizer bug, the macro arguments
++ * need to have const copies or the values end up changed by the time they
++ * reach fortify_warn_once(). See commit 6f7630b1b5bc ("fortify: Capture
++ * __bos() results in const temp vars") for more details.
++ */
+ #define __fortify_memcpy_chk(p, q, size, p_size, q_size,              \
+                            p_size_field, q_size_field, op) ({         \
+       const size_t __fortify_size = (size_t)(size);                   \
+@@ -623,6 +629,8 @@ __FORTIFY_INLINE bool fortify_memcpy_chk
+       const size_t __q_size = (q_size);                               \
+       const size_t __p_size_field = (p_size_field);                   \
+       const size_t __q_size_field = (q_size_field);                   \
++      /* Keep a mutable version of the size for the final copy. */    \
++      size_t __copy_size = __fortify_size;                            \
+       fortify_warn_once(fortify_memcpy_chk(__fortify_size, __p_size,  \
+                                    __q_size, __p_size_field,          \
+                                    __q_size_field, FORTIFY_FUNC_ ##op), \
+@@ -630,7 +638,11 @@ __FORTIFY_INLINE bool fortify_memcpy_chk
+                 __fortify_size,                                       \
+                 "field \"" #p "\" at " FILE_LINE,                     \
+                 __p_size_field);                                      \
+-      __underlying_##op(p, q, __fortify_size);                        \
++      /* Hide only the run-time size from value range tracking to */  \
++      /* silence compile-time false positive bounds warnings. */      \
++      if (!__builtin_constant_p(__copy_size))                         \
++              OPTIMIZER_HIDE_VAR(__copy_size);                        \
++      __underlying_##op(p, q, __copy_size);                           \
+ })
+ /*
diff --git a/target/linux/generic/backport-6.12/211-01-v6.19-bitfield-Add-less-checking-__FIELD_-GET-PREP.patch b/target/linux/generic/backport-6.12/211-01-v6.19-bitfield-Add-less-checking-__FIELD_-GET-PREP.patch
new file mode 100644 (file)
index 0000000..1c56542
--- /dev/null
@@ -0,0 +1,103 @@
+From 2a6c045640c38a407a39cd40c3c4d8dd2fd89aa8 Mon Sep 17 00:00:00 2001
+From: Geert Uytterhoeven <geert+renesas@glider.be>
+Date: Thu, 6 Nov 2025 14:34:00 +0100
+Subject: [PATCH 1/2] bitfield: Add less-checking __FIELD_{GET,PREP}()
+
+The BUILD_BUG_ON_MSG() check against "~0ull" works only with "unsigned
+(long) long" _mask types.  For constant masks, that condition is usually
+met, as GENMASK() yields an UL value.  The few places where the
+constant mask is stored in an intermediate variable were fixed by
+changing the variable type to u64 (see e.g. [1] and [2]).
+
+However, for non-constant masks, smaller unsigned types should be valid,
+too, but currently lead to "result of comparison of constant
+18446744073709551615 with expression of type ... is always
+false"-warnings with clang and W=1.
+
+Hence refactor the __BF_FIELD_CHECK() helper, and factor out
+__FIELD_{GET,PREP}().  The later lack the single problematic check, but
+are otherwise identical to FIELD_{GET,PREP}(), and are intended to be
+used in the fully non-const variants later.
+
+[1] commit 5c667d5a5a3ec166 ("clk: sp7021: Adjust width of _m in
+    HWM_FIELD_PREP()")
+[2] commit cfd6fb45cfaf46fa ("crypto: ccree - avoid out-of-range
+    warnings from clang")
+
+Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Link: https://git.kernel.org/torvalds/c/5c667d5a5a3ec166 [1]
+Signed-off-by: Yury Norov (NVIDIA) <yury.norov@gmail.com>
+---
+ include/linux/bitfield.h | 36 ++++++++++++++++++++++++++++--------
+ 1 file changed, 28 insertions(+), 8 deletions(-)
+
+--- a/include/linux/bitfield.h
++++ b/include/linux/bitfield.h
+@@ -60,7 +60,7 @@
+ #define __bf_cast_unsigned(type, x)   ((__unsigned_scalar_typeof(type))(x))
+-#define __BF_FIELD_CHECK(_mask, _reg, _val, _pfx)                     \
++#define __BF_FIELD_CHECK_MASK(_mask, _val, _pfx)                      \
+       ({                                                              \
+               BUILD_BUG_ON_MSG(!__builtin_constant_p(_mask),          \
+                                _pfx "mask is not constant");          \
+@@ -69,13 +69,33 @@
+                                ~((_mask) >> __bf_shf(_mask)) &        \
+                                       (0 + (_val)) : 0,               \
+                                _pfx "value too large for the field"); \
+-              BUILD_BUG_ON_MSG(__bf_cast_unsigned(_mask, _mask) >     \
+-                               __bf_cast_unsigned(_reg, ~0ull),       \
+-                               _pfx "type of reg too small for mask"); \
+               __BUILD_BUG_ON_NOT_POWER_OF_2((_mask) +                 \
+                                             (1ULL << __bf_shf(_mask))); \
+       })
++#define __BF_FIELD_CHECK_REG(mask, reg, pfx)                          \
++      BUILD_BUG_ON_MSG(__bf_cast_unsigned(mask, mask) >               \
++                       __bf_cast_unsigned(reg, ~0ull),                \
++                       pfx "type of reg too small for mask")
++
++#define __BF_FIELD_CHECK(mask, reg, val, pfx)                         \
++      ({                                                              \
++              __BF_FIELD_CHECK_MASK(mask, val, pfx);                  \
++              __BF_FIELD_CHECK_REG(mask, reg, pfx);                   \
++      })
++
++#define __FIELD_PREP(mask, val, pfx)                                  \
++      ({                                                              \
++              __BF_FIELD_CHECK_MASK(mask, val, pfx);                  \
++              ((typeof(mask))(val) << __bf_shf(mask)) & (mask);       \
++      })
++
++#define __FIELD_GET(mask, reg, pfx)                                   \
++      ({                                                              \
++              __BF_FIELD_CHECK_MASK(mask, 0U, pfx);                   \
++              (typeof(mask))(((reg) & (mask)) >> __bf_shf(mask));     \
++      })
++
+ /**
+  * FIELD_MAX() - produce the maximum value representable by a field
+  * @_mask: shifted mask defining the field's length and position
+@@ -112,8 +132,8 @@
+  */
+ #define FIELD_PREP(_mask, _val)                                               \
+       ({                                                              \
+-              __BF_FIELD_CHECK(_mask, 0ULL, _val, "FIELD_PREP: ");    \
+-              ((typeof(_mask))(_val) << __bf_shf(_mask)) & (_mask);   \
++              __BF_FIELD_CHECK_REG(_mask, 0ULL, "FIELD_PREP: ");      \
++              __FIELD_PREP(_mask, _val, "FIELD_PREP: ");              \
+       })
+ #define __BF_CHECK_POW2(n)    BUILD_BUG_ON_ZERO(((n) & ((n) - 1)) != 0)
+@@ -152,8 +172,8 @@
+  */
+ #define FIELD_GET(_mask, _reg)                                                \
+       ({                                                              \
+-              __BF_FIELD_CHECK(_mask, _reg, 0U, "FIELD_GET: ");       \
+-              (typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask)); \
++              __BF_FIELD_CHECK_REG(_mask, _reg, "FIELD_GET: ");       \
++              __FIELD_GET(_mask, _reg, "FIELD_GET: ");                \
+       })
+ extern void __compiletime_error("value doesn't fit into mask")
diff --git a/target/linux/generic/backport-6.12/211-02-v6.19-bitfield-Add-non-constant-field_-prep-get-helpers.patch b/target/linux/generic/backport-6.12/211-02-v6.19-bitfield-Add-non-constant-field_-prep-get-helpers.patch
new file mode 100644 (file)
index 0000000..4257ec8
--- /dev/null
@@ -0,0 +1,114 @@
+From c1c6ab80b25c8db1e2ef5ae3ac8075d2c242ae13 Mon Sep 17 00:00:00 2001
+From: Geert Uytterhoeven <geert+renesas@glider.be>
+Date: Thu, 6 Nov 2025 14:34:01 +0100
+Subject: [PATCH 2/2] bitfield: Add non-constant field_{prep,get}() helpers
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The existing FIELD_{GET,PREP}() macros are limited to compile-time
+constants.  However, it is very common to prepare or extract bitfield
+elements where the bitfield mask is not a compile-time constant.
+
+To avoid this limitation, the AT91 clock driver and several other
+drivers already have their own non-const field_{prep,get}() macros.
+Make them available for general use by adding them to
+<linux/bitfield.h>, and improve them slightly:
+  1. Avoid evaluating macro parameters more than once,
+  2. Replace "ffs() - 1" by "__ffs()",
+  3. Support 64-bit use on 32-bit architectures,
+  4. Wire field_{get,prep}() to FIELD_{GET,PREP}() when mask is
+     actually constant.
+
+This is deliberately not merged into the existing FIELD_{GET,PREP}()
+macros, as people expressed the desire to keep stricter variants for
+increased safety, or for performance critical paths.
+
+Yury: use __mask withing new macros.
+
+Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Acked-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
+Acked-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Acked-by: Crt Mori <cmo@melexis.com>
+Acked-by: Nuno Sá <nuno.sa@analog.com>
+Acked-by: Richard Genoud <richard.genoud@bootlin.com>
+Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Reviewed-by: Yury Norov (NVIDIA) <yury.norov@gmail.com>
+Signed-off-by: Yury Norov (NVIDIA) <yury.norov@gmail.com>
+---
+ include/linux/bitfield.h | 59 ++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 59 insertions(+)
+
+--- a/include/linux/bitfield.h
++++ b/include/linux/bitfield.h
+@@ -16,6 +16,7 @@
+  * FIELD_{GET,PREP} macros take as first parameter shifted mask
+  * from which they extract the base mask and shift amount.
+  * Mask must be a compilation time constant.
++ * field_{get,prep} are variants that take a non-const mask.
+  *
+  * Example:
+  *
+@@ -223,4 +224,62 @@ __MAKE_OP(64)
+ #undef __MAKE_OP
+ #undef ____MAKE_OP
++#define __field_prep(mask, val)                                               \
++      ({                                                              \
++              __auto_type __mask = (mask);                            \
++              typeof(__mask) __val = (val);                           \
++              unsigned int __shift = BITS_PER_TYPE(__mask) <= 32 ?    \
++                                     __ffs(__mask) : __ffs64(__mask); \
++              (__val << __shift) & __mask;                            \
++      })
++
++#define __field_get(mask, reg)                                                \
++      ({                                                              \
++              __auto_type __mask = (mask);                            \
++              typeof(__mask) __reg =  (reg);                          \
++              unsigned int __shift = BITS_PER_TYPE(__mask) <= 32 ?    \
++                                     __ffs(__mask) : __ffs64(__mask); \
++              (__reg & __mask) >> __shift;                            \
++      })
++
++/**
++ * field_prep() - prepare a bitfield element
++ * @mask: shifted mask defining the field's length and position, must be
++ *        non-zero
++ * @val:  value to put in the field
++ *
++ * Return: field value masked and shifted to its final destination
++ *
++ * field_prep() masks and shifts up the value.  The result should be
++ * combined with other fields of the bitfield using logical OR.
++ * Unlike FIELD_PREP(), @mask is not limited to a compile-time constant.
++ * Typical usage patterns are a value stored in a table, or calculated by
++ * shifting a constant by a variable number of bits.
++ * If you want to ensure that @mask is a compile-time constant, please use
++ * FIELD_PREP() directly instead.
++ */
++#define field_prep(mask, val)                                         \
++      (__builtin_constant_p(mask) ? __FIELD_PREP(mask, val, "field_prep: ") \
++                                  : __field_prep(mask, val))
++
++/**
++ * field_get() - extract a bitfield element
++ * @mask: shifted mask defining the field's length and position, must be
++ *        non-zero
++ * @reg:  value of entire bitfield
++ *
++ * Return: extracted field value
++ *
++ * field_get() extracts the field specified by @mask from the
++ * bitfield passed in as @reg by masking and shifting it down.
++ * Unlike FIELD_GET(), @mask is not limited to a compile-time constant.
++ * Typical usage patterns are a value stored in a table, or calculated by
++ * shifting a constant by a variable number of bits.
++ * If you want to ensure that @mask is a compile-time constant, please use
++ * FIELD_GET() directly instead.
++ */
++#define field_get(mask, reg)                                          \
++      (__builtin_constant_p(mask) ? __FIELD_GET(mask, reg, "field_get: ") \
++                                  : __field_get(mask, reg))
++
+ #endif
diff --git a/target/linux/generic/backport-6.12/212-v6.17-of-reserved_mem-Add-missing-IORESOURCE_MEM-flag-on-r.patch b/target/linux/generic/backport-6.12/212-v6.17-of-reserved_mem-Add-missing-IORESOURCE_MEM-flag-on-r.patch
new file mode 100644 (file)
index 0000000..1f2d52a
--- /dev/null
@@ -0,0 +1,32 @@
+From aea70964b5a7ca491a3701f2dde6c9d05d51878d Mon Sep 17 00:00:00 2001
+From: "Rob Herring (Arm)" <robh@kernel.org>
+Date: Wed, 20 Aug 2025 14:28:04 -0500
+Subject: of: reserved_mem: Add missing IORESOURCE_MEM flag on resources
+
+Commit f4fcfdda2fd8 ('of: reserved_mem: Add functions to parse
+"memory-region"') failed to set IORESOURCE_MEM flag on the resources.
+The result is functions such as devm_ioremap_resource_wc() will fail.
+Add the missing flag.
+
+Fixes: f4fcfdda2fd8 ('of: reserved_mem: Add functions to parse "memory-region"')
+Reported-by: Iuliana Prodan <iuliana.prodan@nxp.com>
+Reported-by: Daniel Baluta <daniel.baluta@gmail.com>
+Tested-by: Iuliana Prodan <iuliana.prodan@nxp.com>
+Reviewed-by: Iuliana Prodan <iuliana.prodan@nxp.com>
+Reviewed-by: Saravana Kannan <saravanak@google.com>
+Link: https://lore.kernel.org/r/20250820192805.565568-1-robh@kernel.org
+Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
+---
+ drivers/of/of_reserved_mem.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/of/of_reserved_mem.c
++++ b/drivers/of/of_reserved_mem.c
+@@ -725,6 +725,7 @@ int of_reserved_mem_region_to_resource(c
+               return -EINVAL;
+       resource_set_range(res, rmem->base, rmem->size);
++      res->flags = IORESOURCE_MEM;
+       res->name = rmem->name;
+       return 0;
+ }
diff --git a/target/linux/generic/backport-6.12/400-v6.14-mtd-spinand-add-support-for-FORESEE-F35SQA001G.patch b/target/linux/generic/backport-6.12/400-v6.14-mtd-spinand-add-support-for-FORESEE-F35SQA001G.patch
new file mode 100644 (file)
index 0000000..84b3b2a
--- /dev/null
@@ -0,0 +1,38 @@
+From ae461cde5c559675fc4c0ba351c7c31ace705f56 Mon Sep 17 00:00:00 2001
+From: Bohdan Chubuk <chbgdn@gmail.com>
+Date: Sun, 10 Nov 2024 22:50:47 +0200
+Subject: [PATCH] mtd: spinand: add support for FORESEE F35SQA001G
+
+Add support for FORESEE F35SQA001G SPI NAND.
+
+Similar to F35SQA002G, but differs in capacity.
+Datasheet:
+  -  https://cdn.ozdisan.com/ETicaret_Dosya/704795_871495.pdf
+
+Tested on Xiaomi AX3000T flashed with OpenWRT.
+
+Signed-off-by: Bohdan Chubuk <chbgdn@gmail.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/spi/foresee.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+--- a/drivers/mtd/nand/spi/foresee.c
++++ b/drivers/mtd/nand/spi/foresee.c
+@@ -81,6 +81,16 @@ static const struct spinand_info foresee
+                    SPINAND_HAS_QE_BIT,
+                    SPINAND_ECCINFO(&f35sqa002g_ooblayout,
+                                    f35sqa002g_ecc_get_status)),
++      SPINAND_INFO("F35SQA001G",
++                   SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x71, 0x71),
++                   NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
++                   NAND_ECCREQ(1, 512),
++                   SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++                                            &write_cache_variants,
++                                            &update_cache_variants),
++                   SPINAND_HAS_QE_BIT,
++                   SPINAND_ECCINFO(&f35sqa002g_ooblayout,
++                                   f35sqa002g_ecc_get_status)),
+ };
+ static const struct spinand_manufacturer_ops foresee_spinand_manuf_ops = {
diff --git a/target/linux/generic/backport-6.12/401-v6.17-mtd-spinand-add-support-for-FudanMicro-FM25S01A.patch b/target/linux/generic/backport-6.12/401-v6.17-mtd-spinand-add-support-for-FudanMicro-FM25S01A.patch
new file mode 100644 (file)
index 0000000..0199fcb
--- /dev/null
@@ -0,0 +1,124 @@
+From 5f284dc15ca8695d0394414045ac64616a3b0e69 Mon Sep 17 00:00:00 2001
+From: Tianling Shen <cnsztl@gmail.com>
+Date: Mon, 25 Aug 2025 01:00:13 +0800
+Subject: [PATCH] mtd: spinand: add support for FudanMicro FM25S01A
+
+Add support for FudanMicro FM25S01A SPI NAND.
+Datasheet: http://eng.fmsh.com/nvm/FM25S01A_ds_eng.pdf
+
+Signed-off-by: Tianling Shen <cnsztl@gmail.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://lore.kernel.org/linux-mtd/20250824170013.3328777-1-cnsztl@gmail.com
+---
+ drivers/mtd/nand/spi/Makefile |  2 +-
+ drivers/mtd/nand/spi/core.c   |  1 +
+ drivers/mtd/nand/spi/fmsh.c   | 74 +++++++++++++++++++++++++++++++++++
+ include/linux/mtd/spinand.h   |  1 +
+ 4 files changed, 77 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/mtd/nand/spi/fmsh.c
+
+--- a/drivers/mtd/nand/spi/Makefile
++++ b/drivers/mtd/nand/spi/Makefile
+@@ -1,4 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0
+-spinand-objs := core.o alliancememory.o ato.o esmt.o foresee.o gigadevice.o macronix.o
++spinand-objs := core.o alliancememory.o ato.o esmt.o fmsh.o foresee.o gigadevice.o macronix.o
+ spinand-objs += micron.o paragon.o toshiba.o winbond.o xtx.o
+ obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -1123,6 +1123,7 @@ static const struct spinand_manufacturer
+       &alliancememory_spinand_manufacturer,
+       &ato_spinand_manufacturer,
+       &esmt_c8_spinand_manufacturer,
++      &fmsh_spinand_manufacturer,
+       &foresee_spinand_manufacturer,
+       &gigadevice_spinand_manufacturer,
+       &macronix_spinand_manufacturer,
+--- /dev/null
++++ b/drivers/mtd/nand/spi/fmsh.c
+@@ -0,0 +1,74 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (c) 2020-2021 Rockchip Electronics Co., Ltd.
++ *
++ * Author: Dingqiang Lin <jon.lin@rock-chips.com>
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include <linux/mtd/spinand.h>
++
++#define SPINAND_MFR_FMSH              0xA1
++
++static SPINAND_OP_VARIANTS(read_cache_variants,
++              SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
++
++static SPINAND_OP_VARIANTS(write_cache_variants,
++              SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
++              SPINAND_PROG_LOAD(true, 0, NULL, 0));
++
++static SPINAND_OP_VARIANTS(update_cache_variants,
++              SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
++              SPINAND_PROG_LOAD(false, 0, NULL, 0));
++
++static int fm25s01a_ooblayout_ecc(struct mtd_info *mtd, int section,
++                                struct mtd_oob_region *region)
++{
++      return -ERANGE;
++}
++
++static int fm25s01a_ooblayout_free(struct mtd_info *mtd, int section,
++                                 struct mtd_oob_region *region)
++{
++      if (section)
++              return -ERANGE;
++
++      region->offset = 2;
++      region->length = 62;
++
++      return 0;
++}
++
++static const struct mtd_ooblayout_ops fm25s01a_ooblayout = {
++      .ecc = fm25s01a_ooblayout_ecc,
++      .free = fm25s01a_ooblayout_free,
++};
++
++static const struct spinand_info fmsh_spinand_table[] = {
++      SPINAND_INFO("FM25S01A",
++                   SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE4),
++                   NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
++                   NAND_ECCREQ(1, 512),
++                   SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++                                            &write_cache_variants,
++                                            &update_cache_variants),
++                   SPINAND_HAS_QE_BIT,
++                   SPINAND_ECCINFO(&fm25s01a_ooblayout, NULL)),
++};
++
++static const struct spinand_manufacturer_ops fmsh_spinand_manuf_ops = {
++};
++
++const struct spinand_manufacturer fmsh_spinand_manufacturer = {
++      .id = SPINAND_MFR_FMSH,
++      .name = "Fudan Micro",
++      .chips = fmsh_spinand_table,
++      .nchips = ARRAY_SIZE(fmsh_spinand_table),
++      .ops = &fmsh_spinand_manuf_ops,
++};
+--- a/include/linux/mtd/spinand.h
++++ b/include/linux/mtd/spinand.h
+@@ -263,6 +263,7 @@ struct spinand_manufacturer {
+ extern const struct spinand_manufacturer alliancememory_spinand_manufacturer;
+ extern const struct spinand_manufacturer ato_spinand_manufacturer;
+ extern const struct spinand_manufacturer esmt_c8_spinand_manufacturer;
++extern const struct spinand_manufacturer fmsh_spinand_manufacturer;
+ extern const struct spinand_manufacturer foresee_spinand_manufacturer;
+ extern const struct spinand_manufacturer gigadevice_spinand_manufacturer;
+ extern const struct spinand_manufacturer macronix_spinand_manufacturer;
diff --git a/target/linux/generic/backport-6.12/402-v6.18-mtd-nand-move-chunk-to-core.patch b/target/linux/generic/backport-6.12/402-v6.18-mtd-nand-move-chunk-to-core.patch
new file mode 100644 (file)
index 0000000..7933874
--- /dev/null
@@ -0,0 +1,329 @@
+From 6b88293aae7fb78872e5cc1ec36e2f750ae12e38 Mon Sep 17 00:00:00 2001
+From: Markus Stockhausen <markus.stockhausen@gmx.de>
+Date: Wed, 10 Sep 2025 14:32:58 -0400
+Subject: mtd: nand: move nand_check_erased_ecc_chunk() to nand/core
+
+The check function for bitflips in erased blocks will be needed
+by the Realtek ECC engine driver (which is currently under
+development). Right now it is located in raw/nand_base.c.
+While this is sufficient for the current usecases, there is
+no real dependency for an ECC engine on the raw nand library.
+
+Move the function over to a more generic place in core library.
+
+Suggested-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/core.c          | 131 +++++++++++++++++++++++++++++++++++++++
+ drivers/mtd/nand/raw/nand_base.c | 131 ---------------------------------------
+ include/linux/mtd/nand.h         |   5 ++
+ include/linux/mtd/rawnand.h      |   5 --
+ 4 files changed, 136 insertions(+), 136 deletions(-)
+
+--- a/drivers/mtd/nand/core.c
++++ b/drivers/mtd/nand/core.c
+@@ -13,6 +13,137 @@
+ #include <linux/mtd/nand.h>
+ /**
++ * nand_check_erased_buf - check if a buffer contains (almost) only 0xff data
++ * @buf: buffer to test
++ * @len: buffer length
++ * @bitflips_threshold: maximum number of bitflips
++ *
++ * Check if a buffer contains only 0xff, which means the underlying region
++ * has been erased and is ready to be programmed.
++ * The bitflips_threshold specify the maximum number of bitflips before
++ * considering the region is not erased.
++ * Note: The logic of this function has been extracted from the memweight
++ * implementation, except that nand_check_erased_buf function exit before
++ * testing the whole buffer if the number of bitflips exceed the
++ * bitflips_threshold value.
++ *
++ * Returns a positive number of bitflips less than or equal to
++ * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
++ * threshold.
++ */
++static int nand_check_erased_buf(void *buf, int len, int bitflips_threshold)
++{
++      const unsigned char *bitmap = buf;
++      int bitflips = 0;
++      int weight;
++
++      for (; len && ((uintptr_t)bitmap) % sizeof(long);
++           len--, bitmap++) {
++              weight = hweight8(*bitmap);
++              bitflips += BITS_PER_BYTE - weight;
++              if (unlikely(bitflips > bitflips_threshold))
++                      return -EBADMSG;
++      }
++
++      for (; len >= sizeof(long);
++           len -= sizeof(long), bitmap += sizeof(long)) {
++              unsigned long d = *((unsigned long *)bitmap);
++              if (d == ~0UL)
++                      continue;
++              weight = hweight_long(d);
++              bitflips += BITS_PER_LONG - weight;
++              if (unlikely(bitflips > bitflips_threshold))
++                      return -EBADMSG;
++      }
++
++      for (; len > 0; len--, bitmap++) {
++              weight = hweight8(*bitmap);
++              bitflips += BITS_PER_BYTE - weight;
++              if (unlikely(bitflips > bitflips_threshold))
++                      return -EBADMSG;
++      }
++
++      return bitflips;
++}
++
++/**
++ * nand_check_erased_ecc_chunk - check if an ECC chunk contains (almost) only
++ *                             0xff data
++ * @data: data buffer to test
++ * @datalen: data length
++ * @ecc: ECC buffer
++ * @ecclen: ECC length
++ * @extraoob: extra OOB buffer
++ * @extraooblen: extra OOB length
++ * @bitflips_threshold: maximum number of bitflips
++ *
++ * Check if a data buffer and its associated ECC and OOB data contains only
++ * 0xff pattern, which means the underlying region has been erased and is
++ * ready to be programmed.
++ * The bitflips_threshold specify the maximum number of bitflips before
++ * considering the region as not erased.
++ *
++ * Note:
++ * 1/ ECC algorithms are working on pre-defined block sizes which are usually
++ *    different from the NAND page size. When fixing bitflips, ECC engines will
++ *    report the number of errors per chunk, and the NAND core infrastructure
++ *    expect you to return the maximum number of bitflips for the whole page.
++ *    This is why you should always use this function on a single chunk and
++ *    not on the whole page. After checking each chunk you should update your
++ *    max_bitflips value accordingly.
++ * 2/ When checking for bitflips in erased pages you should not only check
++ *    the payload data but also their associated ECC data, because a user might
++ *    have programmed almost all bits to 1 but a few. In this case, we
++ *    shouldn't consider the chunk as erased, and checking ECC bytes prevent
++ *    this case.
++ * 3/ The extraoob argument is optional, and should be used if some of your OOB
++ *    data are protected by the ECC engine.
++ *    It could also be used if you support subpages and want to attach some
++ *    extra OOB data to an ECC chunk.
++ *
++ * Returns a positive number of bitflips less than or equal to
++ * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
++ * threshold. In case of success, the passed buffers are filled with 0xff.
++ */
++int nand_check_erased_ecc_chunk(void *data, int datalen,
++                              void *ecc, int ecclen,
++                              void *extraoob, int extraooblen,
++                              int bitflips_threshold)
++{
++      int data_bitflips = 0, ecc_bitflips = 0, extraoob_bitflips = 0;
++
++      data_bitflips = nand_check_erased_buf(data, datalen,
++                                            bitflips_threshold);
++      if (data_bitflips < 0)
++              return data_bitflips;
++
++      bitflips_threshold -= data_bitflips;
++
++      ecc_bitflips = nand_check_erased_buf(ecc, ecclen, bitflips_threshold);
++      if (ecc_bitflips < 0)
++              return ecc_bitflips;
++
++      bitflips_threshold -= ecc_bitflips;
++
++      extraoob_bitflips = nand_check_erased_buf(extraoob, extraooblen,
++                                                bitflips_threshold);
++      if (extraoob_bitflips < 0)
++              return extraoob_bitflips;
++
++      if (data_bitflips)
++              memset(data, 0xff, datalen);
++
++      if (ecc_bitflips)
++              memset(ecc, 0xff, ecclen);
++
++      if (extraoob_bitflips)
++              memset(extraoob, 0xff, extraooblen);
++
++      return data_bitflips + ecc_bitflips + extraoob_bitflips;
++}
++EXPORT_SYMBOL(nand_check_erased_ecc_chunk);
++
++/**
+  * nanddev_isbad() - Check if a block is bad
+  * @nand: NAND device
+  * @pos: position pointing to the block we want to check
+--- a/drivers/mtd/nand/raw/nand_base.c
++++ b/drivers/mtd/nand/raw/nand_base.c
+@@ -2784,137 +2784,6 @@ int nand_set_features(struct nand_chip *
+ }
+ /**
+- * nand_check_erased_buf - check if a buffer contains (almost) only 0xff data
+- * @buf: buffer to test
+- * @len: buffer length
+- * @bitflips_threshold: maximum number of bitflips
+- *
+- * Check if a buffer contains only 0xff, which means the underlying region
+- * has been erased and is ready to be programmed.
+- * The bitflips_threshold specify the maximum number of bitflips before
+- * considering the region is not erased.
+- * Note: The logic of this function has been extracted from the memweight
+- * implementation, except that nand_check_erased_buf function exit before
+- * testing the whole buffer if the number of bitflips exceed the
+- * bitflips_threshold value.
+- *
+- * Returns a positive number of bitflips less than or equal to
+- * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
+- * threshold.
+- */
+-static int nand_check_erased_buf(void *buf, int len, int bitflips_threshold)
+-{
+-      const unsigned char *bitmap = buf;
+-      int bitflips = 0;
+-      int weight;
+-
+-      for (; len && ((uintptr_t)bitmap) % sizeof(long);
+-           len--, bitmap++) {
+-              weight = hweight8(*bitmap);
+-              bitflips += BITS_PER_BYTE - weight;
+-              if (unlikely(bitflips > bitflips_threshold))
+-                      return -EBADMSG;
+-      }
+-
+-      for (; len >= sizeof(long);
+-           len -= sizeof(long), bitmap += sizeof(long)) {
+-              unsigned long d = *((unsigned long *)bitmap);
+-              if (d == ~0UL)
+-                      continue;
+-              weight = hweight_long(d);
+-              bitflips += BITS_PER_LONG - weight;
+-              if (unlikely(bitflips > bitflips_threshold))
+-                      return -EBADMSG;
+-      }
+-
+-      for (; len > 0; len--, bitmap++) {
+-              weight = hweight8(*bitmap);
+-              bitflips += BITS_PER_BYTE - weight;
+-              if (unlikely(bitflips > bitflips_threshold))
+-                      return -EBADMSG;
+-      }
+-
+-      return bitflips;
+-}
+-
+-/**
+- * nand_check_erased_ecc_chunk - check if an ECC chunk contains (almost) only
+- *                             0xff data
+- * @data: data buffer to test
+- * @datalen: data length
+- * @ecc: ECC buffer
+- * @ecclen: ECC length
+- * @extraoob: extra OOB buffer
+- * @extraooblen: extra OOB length
+- * @bitflips_threshold: maximum number of bitflips
+- *
+- * Check if a data buffer and its associated ECC and OOB data contains only
+- * 0xff pattern, which means the underlying region has been erased and is
+- * ready to be programmed.
+- * The bitflips_threshold specify the maximum number of bitflips before
+- * considering the region as not erased.
+- *
+- * Note:
+- * 1/ ECC algorithms are working on pre-defined block sizes which are usually
+- *    different from the NAND page size. When fixing bitflips, ECC engines will
+- *    report the number of errors per chunk, and the NAND core infrastructure
+- *    expect you to return the maximum number of bitflips for the whole page.
+- *    This is why you should always use this function on a single chunk and
+- *    not on the whole page. After checking each chunk you should update your
+- *    max_bitflips value accordingly.
+- * 2/ When checking for bitflips in erased pages you should not only check
+- *    the payload data but also their associated ECC data, because a user might
+- *    have programmed almost all bits to 1 but a few. In this case, we
+- *    shouldn't consider the chunk as erased, and checking ECC bytes prevent
+- *    this case.
+- * 3/ The extraoob argument is optional, and should be used if some of your OOB
+- *    data are protected by the ECC engine.
+- *    It could also be used if you support subpages and want to attach some
+- *    extra OOB data to an ECC chunk.
+- *
+- * Returns a positive number of bitflips less than or equal to
+- * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
+- * threshold. In case of success, the passed buffers are filled with 0xff.
+- */
+-int nand_check_erased_ecc_chunk(void *data, int datalen,
+-                              void *ecc, int ecclen,
+-                              void *extraoob, int extraooblen,
+-                              int bitflips_threshold)
+-{
+-      int data_bitflips = 0, ecc_bitflips = 0, extraoob_bitflips = 0;
+-
+-      data_bitflips = nand_check_erased_buf(data, datalen,
+-                                            bitflips_threshold);
+-      if (data_bitflips < 0)
+-              return data_bitflips;
+-
+-      bitflips_threshold -= data_bitflips;
+-
+-      ecc_bitflips = nand_check_erased_buf(ecc, ecclen, bitflips_threshold);
+-      if (ecc_bitflips < 0)
+-              return ecc_bitflips;
+-
+-      bitflips_threshold -= ecc_bitflips;
+-
+-      extraoob_bitflips = nand_check_erased_buf(extraoob, extraooblen,
+-                                                bitflips_threshold);
+-      if (extraoob_bitflips < 0)
+-              return extraoob_bitflips;
+-
+-      if (data_bitflips)
+-              memset(data, 0xff, datalen);
+-
+-      if (ecc_bitflips)
+-              memset(ecc, 0xff, ecclen);
+-
+-      if (extraoob_bitflips)
+-              memset(extraoob, 0xff, extraooblen);
+-
+-      return data_bitflips + ecc_bitflips + extraoob_bitflips;
+-}
+-EXPORT_SYMBOL(nand_check_erased_ecc_chunk);
+-
+-/**
+  * nand_read_page_raw_notsupp - dummy read raw page function
+  * @chip: nand chip info structure
+  * @buf: buffer to store read data
+--- a/include/linux/mtd/nand.h
++++ b/include/linux/mtd/nand.h
+@@ -1136,4 +1136,9 @@ static inline bool nanddev_bbt_is_initia
+ int nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo);
+ int nanddev_mtd_max_bad_blocks(struct mtd_info *mtd, loff_t offs, size_t len);
++int nand_check_erased_ecc_chunk(void *data, int datalen,
++                              void *ecc, int ecclen,
++                              void *extraoob, int extraooblen,
++                              int threshold);
++
+ #endif /* __LINUX_MTD_NAND_H */
+--- a/include/linux/mtd/rawnand.h
++++ b/include/linux/mtd/rawnand.h
+@@ -1519,11 +1519,6 @@ int rawnand_sw_bch_correct(struct nand_c
+                          unsigned char *read_ecc, unsigned char *calc_ecc);
+ void rawnand_sw_bch_cleanup(struct nand_chip *chip);
+-int nand_check_erased_ecc_chunk(void *data, int datalen,
+-                              void *ecc, int ecclen,
+-                              void *extraoob, int extraooblen,
+-                              int threshold);
+-
+ int nand_ecc_choose_conf(struct nand_chip *chip,
+                        const struct nand_ecc_caps *caps, int oobavail);
diff --git a/target/linux/generic/backport-6.12/404-v6.19-mtd-spinand-fmsh-remove-QE-bit-for-FM25S01A-flash.patch b/target/linux/generic/backport-6.12/404-v6.19-mtd-spinand-fmsh-remove-QE-bit-for-FM25S01A-flash.patch
new file mode 100644 (file)
index 0000000..7f9ec6a
--- /dev/null
@@ -0,0 +1,28 @@
+From a1d3bc606bf5c3b3ea811cc2019df6285d75b00f Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Mon, 3 Nov 2025 04:01:48 +0300
+Subject: [PATCH] mtd: spinand: fmsh: remove QE bit for FM25S01A flash
+
+According to datasheet (http://eng.fmsh.com/nvm/FM25S01A_ds_eng.pdf)
+there is no QE (Quad Enable) bit for FM25S01A flash, so remove it.
+
+Fixes: 5f284dc15ca86 ("mtd: spinand: add support for FudanMicro FM25S01A")
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Tested-by: Tianling Shen <cnsztl@gmail.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: Link: https://lore.kernel.org/linux-mtd/176216634975.908445.2776312239779833518.b4-ty@bootlin.com
+---
+ drivers/mtd/nand/spi/fmsh.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/mtd/nand/spi/fmsh.c
++++ b/drivers/mtd/nand/spi/fmsh.c
+@@ -58,7 +58,7 @@ static const struct spinand_info fmsh_sp
+                    SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+                                             &write_cache_variants,
+                                             &update_cache_variants),
+-                   SPINAND_HAS_QE_BIT,
++                   0,
+                    SPINAND_ECCINFO(&fm25s01a_ooblayout, NULL)),
+ };
diff --git a/target/linux/generic/backport-6.12/410-01-v6.14-mtd-rawnand-qcom-cleanup-qcom_nandc-driver.patch b/target/linux/generic/backport-6.12/410-01-v6.14-mtd-rawnand-qcom-cleanup-qcom_nandc-driver.patch
new file mode 100644 (file)
index 0000000..1d1288e
--- /dev/null
@@ -0,0 +1,1026 @@
+From 8c52932da5e6756fa66f52f0720da283fba13aa6 Mon Sep 17 00:00:00 2001
+From: Md Sadre Alam <quic_mdalam@quicinc.com>
+Date: Wed, 20 Nov 2024 14:45:00 +0530
+Subject: [PATCH 1/4] mtd: rawnand: qcom: cleanup qcom_nandc driver
+
+Perform a global cleanup of the Qualcomm NAND
+controller driver with the following improvements:
+
+- Remove register value indirection API
+
+- Remove set_reg() API
+
+- Convert read_loc_first & read_loc_last macro to functions
+
+- Rename multiple variables
+
+Signed-off-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/raw/qcom_nandc.c | 516 ++++++++++++++----------------
+ 1 file changed, 234 insertions(+), 282 deletions(-)
+
+--- a/drivers/mtd/nand/raw/qcom_nandc.c
++++ b/drivers/mtd/nand/raw/qcom_nandc.c
+@@ -189,17 +189,6 @@
+ #define       ECC_BCH_4BIT    BIT(2)
+ #define       ECC_BCH_8BIT    BIT(3)
+-#define nandc_set_read_loc_first(chip, reg, cw_offset, read_size, is_last_read_loc)   \
+-nandc_set_reg(chip, reg,                      \
+-            ((cw_offset) << READ_LOCATION_OFFSET) |           \
+-            ((read_size) << READ_LOCATION_SIZE) |                     \
+-            ((is_last_read_loc) << READ_LOCATION_LAST))
+-
+-#define nandc_set_read_loc_last(chip, reg, cw_offset, read_size, is_last_read_loc)    \
+-nandc_set_reg(chip, reg,                      \
+-            ((cw_offset) << READ_LOCATION_OFFSET) |           \
+-            ((read_size) << READ_LOCATION_SIZE) |                     \
+-            ((is_last_read_loc) << READ_LOCATION_LAST))
+ /*
+  * Returns the actual register address for all NAND_DEV_ registers
+  * (i.e. NAND_DEV_CMD0, NAND_DEV_CMD1, NAND_DEV_CMD2 and NAND_DEV_CMD_VLD)
+@@ -257,8 +246,6 @@ nandc_set_reg(chip, reg,                   \
+  * @tx_sgl_start - start index in data sgl for tx.
+  * @rx_sgl_pos - current index in data sgl for rx.
+  * @rx_sgl_start - start index in data sgl for rx.
+- * @wait_second_completion - wait for second DMA desc completion before making
+- *                         the NAND transfer completion.
+  */
+ struct bam_transaction {
+       struct bam_cmd_element *bam_ce;
+@@ -275,7 +262,6 @@ struct bam_transaction {
+       u32 tx_sgl_start;
+       u32 rx_sgl_pos;
+       u32 rx_sgl_start;
+-      bool wait_second_completion;
+ };
+ /*
+@@ -471,9 +457,9 @@ struct qcom_op {
+       unsigned int data_instr_idx;
+       unsigned int rdy_timeout_ms;
+       unsigned int rdy_delay_ns;
+-      u32 addr1_reg;
+-      u32 addr2_reg;
+-      u32 cmd_reg;
++      __le32 addr1_reg;
++      __le32 addr2_reg;
++      __le32 cmd_reg;
+       u8 flag;
+ };
+@@ -549,17 +535,17 @@ struct qcom_nand_host {
+  * among different NAND controllers.
+  * @ecc_modes - ecc mode for NAND
+  * @dev_cmd_reg_start - NAND_DEV_CMD_* registers starting offset
+- * @is_bam - whether NAND controller is using BAM
+- * @is_qpic - whether NAND CTRL is part of qpic IP
+- * @qpic_v2 - flag to indicate QPIC IP version 2
++ * @supports_bam - whether NAND controller is using Bus Access Manager (BAM)
++ * @nandc_part_of_qpic - whether NAND controller is part of qpic IP
++ * @qpic_version2 - flag to indicate QPIC IP version 2
+  * @use_codeword_fixup - whether NAND has different layout for boot partitions
+  */
+ struct qcom_nandc_props {
+       u32 ecc_modes;
+       u32 dev_cmd_reg_start;
+-      bool is_bam;
+-      bool is_qpic;
+-      bool qpic_v2;
++      bool supports_bam;
++      bool nandc_part_of_qpic;
++      bool qpic_version2;
+       bool use_codeword_fixup;
+ };
+@@ -613,19 +599,11 @@ static void clear_bam_transaction(struct
+ {
+       struct bam_transaction *bam_txn = nandc->bam_txn;
+-      if (!nandc->props->is_bam)
++      if (!nandc->props->supports_bam)
+               return;
+-      bam_txn->bam_ce_pos = 0;
+-      bam_txn->bam_ce_start = 0;
+-      bam_txn->cmd_sgl_pos = 0;
+-      bam_txn->cmd_sgl_start = 0;
+-      bam_txn->tx_sgl_pos = 0;
+-      bam_txn->tx_sgl_start = 0;
+-      bam_txn->rx_sgl_pos = 0;
+-      bam_txn->rx_sgl_start = 0;
++      memset(&bam_txn->bam_ce_pos, 0, sizeof(u32) * 8);
+       bam_txn->last_data_desc = NULL;
+-      bam_txn->wait_second_completion = false;
+       sg_init_table(bam_txn->cmd_sgl, nandc->max_cwperpage *
+                     QPIC_PER_CW_CMD_SGL);
+@@ -640,46 +618,35 @@ static void qpic_bam_dma_done(void *data
+ {
+       struct bam_transaction *bam_txn = data;
+-      /*
+-       * In case of data transfer with NAND, 2 callbacks will be generated.
+-       * One for command channel and another one for data channel.
+-       * If current transaction has data descriptors
+-       * (i.e. wait_second_completion is true), then set this to false
+-       * and wait for second DMA descriptor completion.
+-       */
+-      if (bam_txn->wait_second_completion)
+-              bam_txn->wait_second_completion = false;
+-      else
+-              complete(&bam_txn->txn_done);
++      complete(&bam_txn->txn_done);
+ }
+-static inline struct qcom_nand_host *to_qcom_nand_host(struct nand_chip *chip)
++static struct qcom_nand_host *to_qcom_nand_host(struct nand_chip *chip)
+ {
+       return container_of(chip, struct qcom_nand_host, chip);
+ }
+-static inline struct qcom_nand_controller *
++static struct qcom_nand_controller *
+ get_qcom_nand_controller(struct nand_chip *chip)
+ {
+       return container_of(chip->controller, struct qcom_nand_controller,
+                           controller);
+ }
+-static inline u32 nandc_read(struct qcom_nand_controller *nandc, int offset)
++static u32 nandc_read(struct qcom_nand_controller *nandc, int offset)
+ {
+       return ioread32(nandc->base + offset);
+ }
+-static inline void nandc_write(struct qcom_nand_controller *nandc, int offset,
+-                             u32 val)
++static void nandc_write(struct qcom_nand_controller *nandc, int offset,
++                      u32 val)
+ {
+       iowrite32(val, nandc->base + offset);
+ }
+-static inline void nandc_read_buffer_sync(struct qcom_nand_controller *nandc,
+-                                        bool is_cpu)
++static void nandc_dev_to_mem(struct qcom_nand_controller *nandc, bool is_cpu)
+ {
+-      if (!nandc->props->is_bam)
++      if (!nandc->props->supports_bam)
+               return;
+       if (is_cpu)
+@@ -694,93 +661,90 @@ static inline void nandc_read_buffer_syn
+                                          DMA_FROM_DEVICE);
+ }
+-static __le32 *offset_to_nandc_reg(struct nandc_regs *regs, int offset)
+-{
+-      switch (offset) {
+-      case NAND_FLASH_CMD:
+-              return &regs->cmd;
+-      case NAND_ADDR0:
+-              return &regs->addr0;
+-      case NAND_ADDR1:
+-              return &regs->addr1;
+-      case NAND_FLASH_CHIP_SELECT:
+-              return &regs->chip_sel;
+-      case NAND_EXEC_CMD:
+-              return &regs->exec;
+-      case NAND_FLASH_STATUS:
+-              return &regs->clrflashstatus;
+-      case NAND_DEV0_CFG0:
+-              return &regs->cfg0;
+-      case NAND_DEV0_CFG1:
+-              return &regs->cfg1;
+-      case NAND_DEV0_ECC_CFG:
+-              return &regs->ecc_bch_cfg;
+-      case NAND_READ_STATUS:
+-              return &regs->clrreadstatus;
+-      case NAND_DEV_CMD1:
+-              return &regs->cmd1;
+-      case NAND_DEV_CMD1_RESTORE:
+-              return &regs->orig_cmd1;
+-      case NAND_DEV_CMD_VLD:
+-              return &regs->vld;
+-      case NAND_DEV_CMD_VLD_RESTORE:
+-              return &regs->orig_vld;
+-      case NAND_EBI2_ECC_BUF_CFG:
+-              return &regs->ecc_buf_cfg;
+-      case NAND_READ_LOCATION_0:
+-              return &regs->read_location0;
+-      case NAND_READ_LOCATION_1:
+-              return &regs->read_location1;
+-      case NAND_READ_LOCATION_2:
+-              return &regs->read_location2;
+-      case NAND_READ_LOCATION_3:
+-              return &regs->read_location3;
+-      case NAND_READ_LOCATION_LAST_CW_0:
+-              return &regs->read_location_last0;
+-      case NAND_READ_LOCATION_LAST_CW_1:
+-              return &regs->read_location_last1;
+-      case NAND_READ_LOCATION_LAST_CW_2:
+-              return &regs->read_location_last2;
+-      case NAND_READ_LOCATION_LAST_CW_3:
+-              return &regs->read_location_last3;
+-      default:
+-              return NULL;
+-      }
+-}
+-
+-static void nandc_set_reg(struct nand_chip *chip, int offset,
+-                        u32 val)
+-{
+-      struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+-      struct nandc_regs *regs = nandc->regs;
+-      __le32 *reg;
+-
+-      reg = offset_to_nandc_reg(regs, offset);
+-
+-      if (reg)
+-              *reg = cpu_to_le32(val);
+-}
+-
+-/* Helper to check the code word, whether it is last cw or not */
++/* Helper to check whether this is the last CW or not */
+ static bool qcom_nandc_is_last_cw(struct nand_ecc_ctrl *ecc, int cw)
+ {
+       return cw == (ecc->steps - 1);
+ }
++/**
++ * nandc_set_read_loc_first() - to set read location first register
++ * @chip:             NAND Private Flash Chip Data
++ * @reg_base:         location register base
++ * @cw_offset:                code word offset
++ * @read_size:                code word read length
++ * @is_last_read_loc: is this the last read location
++ *
++ * This function will set location register value
++ */
++static void nandc_set_read_loc_first(struct nand_chip *chip,
++                                   int reg_base, u32 cw_offset,
++                                   u32 read_size, u32 is_last_read_loc)
++{
++      struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
++      __le32 locreg_val;
++      u32 val = (((cw_offset) << READ_LOCATION_OFFSET) |
++                ((read_size) << READ_LOCATION_SIZE) |
++                ((is_last_read_loc) << READ_LOCATION_LAST));
++
++      locreg_val = cpu_to_le32(val);
++
++      if (reg_base == NAND_READ_LOCATION_0)
++              nandc->regs->read_location0 = locreg_val;
++      else if (reg_base == NAND_READ_LOCATION_1)
++              nandc->regs->read_location1 = locreg_val;
++      else if (reg_base == NAND_READ_LOCATION_2)
++              nandc->regs->read_location2 = locreg_val;
++      else if (reg_base == NAND_READ_LOCATION_3)
++              nandc->regs->read_location3 = locreg_val;
++}
++
++/**
++ * nandc_set_read_loc_last - to set read location last register
++ * @chip:             NAND Private Flash Chip Data
++ * @reg_base:         location register base
++ * @cw_offset:                code word offset
++ * @read_size:                code word read length
++ * @is_last_read_loc: is this the last read location
++ *
++ * This function will set location last register value
++ */
++static void nandc_set_read_loc_last(struct nand_chip *chip,
++                                  int reg_base, u32 cw_offset,
++                                  u32 read_size, u32 is_last_read_loc)
++{
++      struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
++      __le32 locreg_val;
++      u32 val = (((cw_offset) << READ_LOCATION_OFFSET) |
++                ((read_size) << READ_LOCATION_SIZE) |
++                ((is_last_read_loc) << READ_LOCATION_LAST));
++
++      locreg_val = cpu_to_le32(val);
++
++      if (reg_base == NAND_READ_LOCATION_LAST_CW_0)
++              nandc->regs->read_location_last0 = locreg_val;
++      else if (reg_base == NAND_READ_LOCATION_LAST_CW_1)
++              nandc->regs->read_location_last1 = locreg_val;
++      else if (reg_base == NAND_READ_LOCATION_LAST_CW_2)
++              nandc->regs->read_location_last2 = locreg_val;
++      else if (reg_base == NAND_READ_LOCATION_LAST_CW_3)
++              nandc->regs->read_location_last3 = locreg_val;
++}
++
+ /* helper to configure location register values */
+ static void nandc_set_read_loc(struct nand_chip *chip, int cw, int reg,
+-                             int cw_offset, int read_size, int is_last_read_loc)
++                             u32 cw_offset, u32 read_size, u32 is_last_read_loc)
+ {
+       struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+       struct nand_ecc_ctrl *ecc = &chip->ecc;
+       int reg_base = NAND_READ_LOCATION_0;
+-      if (nandc->props->qpic_v2 && qcom_nandc_is_last_cw(ecc, cw))
++      if (nandc->props->qpic_version2 && qcom_nandc_is_last_cw(ecc, cw))
+               reg_base = NAND_READ_LOCATION_LAST_CW_0;
+       reg_base += reg * 4;
+-      if (nandc->props->qpic_v2 && qcom_nandc_is_last_cw(ecc, cw))
++      if (nandc->props->qpic_version2 && qcom_nandc_is_last_cw(ecc, cw))
+               return nandc_set_read_loc_last(chip, reg_base, cw_offset,
+                               read_size, is_last_read_loc);
+       else
+@@ -792,12 +756,13 @@ static void nandc_set_read_loc(struct na
+ static void set_address(struct qcom_nand_host *host, u16 column, int page)
+ {
+       struct nand_chip *chip = &host->chip;
++      struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+       if (chip->options & NAND_BUSWIDTH_16)
+               column >>= 1;
+-      nandc_set_reg(chip, NAND_ADDR0, page << 16 | column);
+-      nandc_set_reg(chip, NAND_ADDR1, page >> 16 & 0xff);
++      nandc->regs->addr0 = cpu_to_le32(page << 16 | column);
++      nandc->regs->addr1 = cpu_to_le32(page >> 16 & 0xff);
+ }
+ /*
+@@ -811,41 +776,43 @@ static void set_address(struct qcom_nand
+ static void update_rw_regs(struct qcom_nand_host *host, int num_cw, bool read, int cw)
+ {
+       struct nand_chip *chip = &host->chip;
+-      u32 cmd, cfg0, cfg1, ecc_bch_cfg;
++      __le32 cmd, cfg0, cfg1, ecc_bch_cfg;
+       struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+       if (read) {
+               if (host->use_ecc)
+-                      cmd = OP_PAGE_READ_WITH_ECC | PAGE_ACC | LAST_PAGE;
++                      cmd = cpu_to_le32(OP_PAGE_READ_WITH_ECC | PAGE_ACC | LAST_PAGE);
+               else
+-                      cmd = OP_PAGE_READ | PAGE_ACC | LAST_PAGE;
++                      cmd = cpu_to_le32(OP_PAGE_READ | PAGE_ACC | LAST_PAGE);
+       } else {
+-              cmd = OP_PROGRAM_PAGE | PAGE_ACC | LAST_PAGE;
++              cmd = cpu_to_le32(OP_PROGRAM_PAGE | PAGE_ACC | LAST_PAGE);
+       }
+       if (host->use_ecc) {
+-              cfg0 = (host->cfg0 & ~(7U << CW_PER_PAGE)) |
+-                              (num_cw - 1) << CW_PER_PAGE;
++              cfg0 = cpu_to_le32((host->cfg0 & ~(7U << CW_PER_PAGE)) |
++                              (num_cw - 1) << CW_PER_PAGE);
+-              cfg1 = host->cfg1;
+-              ecc_bch_cfg = host->ecc_bch_cfg;
++              cfg1 = cpu_to_le32(host->cfg1);
++              ecc_bch_cfg = cpu_to_le32(host->ecc_bch_cfg);
+       } else {
+-              cfg0 = (host->cfg0_raw & ~(7U << CW_PER_PAGE)) |
+-                              (num_cw - 1) << CW_PER_PAGE;
++              cfg0 = cpu_to_le32((host->cfg0_raw & ~(7U << CW_PER_PAGE)) |
++                              (num_cw - 1) << CW_PER_PAGE);
+-              cfg1 = host->cfg1_raw;
+-              ecc_bch_cfg = 1 << ECC_CFG_ECC_DISABLE;
++              cfg1 = cpu_to_le32(host->cfg1_raw);
++              ecc_bch_cfg = cpu_to_le32(1 << ECC_CFG_ECC_DISABLE);
+       }
+-      nandc_set_reg(chip, NAND_FLASH_CMD, cmd);
+-      nandc_set_reg(chip, NAND_DEV0_CFG0, cfg0);
+-      nandc_set_reg(chip, NAND_DEV0_CFG1, cfg1);
+-      nandc_set_reg(chip, NAND_DEV0_ECC_CFG, ecc_bch_cfg);
+-      if (!nandc->props->qpic_v2)
+-              nandc_set_reg(chip, NAND_EBI2_ECC_BUF_CFG, host->ecc_buf_cfg);
+-      nandc_set_reg(chip, NAND_FLASH_STATUS, host->clrflashstatus);
+-      nandc_set_reg(chip, NAND_READ_STATUS, host->clrreadstatus);
+-      nandc_set_reg(chip, NAND_EXEC_CMD, 1);
++      nandc->regs->cmd = cmd;
++      nandc->regs->cfg0 = cfg0;
++      nandc->regs->cfg1 = cfg1;
++      nandc->regs->ecc_bch_cfg = ecc_bch_cfg;
++
++      if (!nandc->props->qpic_version2)
++              nandc->regs->ecc_buf_cfg = cpu_to_le32(host->ecc_buf_cfg);
++
++      nandc->regs->clrflashstatus = cpu_to_le32(host->clrflashstatus);
++      nandc->regs->clrreadstatus = cpu_to_le32(host->clrreadstatus);
++      nandc->regs->exec = cpu_to_le32(1);
+       if (read)
+               nandc_set_read_loc(chip, cw, 0, 0, host->use_ecc ?
+@@ -1121,7 +1088,7 @@ static int read_reg_dma(struct qcom_nand
+       if (first == NAND_DEV_CMD_VLD || first == NAND_DEV_CMD1)
+               first = dev_cmd_reg_addr(nandc, first);
+-      if (nandc->props->is_bam)
++      if (nandc->props->supports_bam)
+               return prep_bam_dma_desc_cmd(nandc, true, first, vaddr,
+                                            num_regs, flags);
+@@ -1136,25 +1103,16 @@ static int read_reg_dma(struct qcom_nand
+  * write_reg_dma:     prepares a descriptor to write a given number of
+  *                    contiguous registers
+  *
++ * @vaddr:            contiguous memory from where register value will
++ *                    be written
+  * @first:            offset of the first register in the contiguous block
+  * @num_regs:         number of registers to write
+  * @flags:            flags to control DMA descriptor preparation
+  */
+-static int write_reg_dma(struct qcom_nand_controller *nandc, int first,
+-                       int num_regs, unsigned int flags)
++static int write_reg_dma(struct qcom_nand_controller *nandc, __le32 *vaddr,
++                       int first, int num_regs, unsigned int flags)
+ {
+       bool flow_control = false;
+-      struct nandc_regs *regs = nandc->regs;
+-      void *vaddr;
+-
+-      vaddr = offset_to_nandc_reg(regs, first);
+-
+-      if (first == NAND_ERASED_CW_DETECT_CFG) {
+-              if (flags & NAND_ERASED_CW_SET)
+-                      vaddr = &regs->erased_cw_detect_cfg_set;
+-              else
+-                      vaddr = &regs->erased_cw_detect_cfg_clr;
+-      }
+       if (first == NAND_EXEC_CMD)
+               flags |= NAND_BAM_NWD;
+@@ -1165,7 +1123,7 @@ static int write_reg_dma(struct qcom_nan
+       if (first == NAND_DEV_CMD_VLD_RESTORE || first == NAND_DEV_CMD_VLD)
+               first = dev_cmd_reg_addr(nandc, NAND_DEV_CMD_VLD);
+-      if (nandc->props->is_bam)
++      if (nandc->props->supports_bam)
+               return prep_bam_dma_desc_cmd(nandc, false, first, vaddr,
+                                            num_regs, flags);
+@@ -1188,7 +1146,7 @@ static int write_reg_dma(struct qcom_nan
+ static int read_data_dma(struct qcom_nand_controller *nandc, int reg_off,
+                        const u8 *vaddr, int size, unsigned int flags)
+ {
+-      if (nandc->props->is_bam)
++      if (nandc->props->supports_bam)
+               return prep_bam_dma_desc_data(nandc, true, vaddr, size, flags);
+       return prep_adm_dma_desc(nandc, true, reg_off, vaddr, size, false);
+@@ -1206,7 +1164,7 @@ static int read_data_dma(struct qcom_nan
+ static int write_data_dma(struct qcom_nand_controller *nandc, int reg_off,
+                         const u8 *vaddr, int size, unsigned int flags)
+ {
+-      if (nandc->props->is_bam)
++      if (nandc->props->supports_bam)
+               return prep_bam_dma_desc_data(nandc, false, vaddr, size, flags);
+       return prep_adm_dma_desc(nandc, false, reg_off, vaddr, size, false);
+@@ -1220,13 +1178,14 @@ static void config_nand_page_read(struct
+ {
+       struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+-      write_reg_dma(nandc, NAND_ADDR0, 2, 0);
+-      write_reg_dma(nandc, NAND_DEV0_CFG0, 3, 0);
+-      if (!nandc->props->qpic_v2)
+-              write_reg_dma(nandc, NAND_EBI2_ECC_BUF_CFG, 1, 0);
+-      write_reg_dma(nandc, NAND_ERASED_CW_DETECT_CFG, 1, 0);
+-      write_reg_dma(nandc, NAND_ERASED_CW_DETECT_CFG, 1,
+-                    NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL);
++      write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0);
++      write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0);
++      if (!nandc->props->qpic_version2)
++              write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, 0);
++      write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_clr,
++                    NAND_ERASED_CW_DETECT_CFG, 1, 0);
++      write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_set,
++                    NAND_ERASED_CW_DETECT_CFG, 1, NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL);
+ }
+ /*
+@@ -1239,16 +1198,16 @@ config_nand_cw_read(struct nand_chip *ch
+       struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+       struct nand_ecc_ctrl *ecc = &chip->ecc;
+-      int reg = NAND_READ_LOCATION_0;
++      __le32 *reg = &nandc->regs->read_location0;
+-      if (nandc->props->qpic_v2 && qcom_nandc_is_last_cw(ecc, cw))
+-              reg = NAND_READ_LOCATION_LAST_CW_0;
++      if (nandc->props->qpic_version2 && qcom_nandc_is_last_cw(ecc, cw))
++              reg = &nandc->regs->read_location_last0;
+-      if (nandc->props->is_bam)
+-              write_reg_dma(nandc, reg, 4, NAND_BAM_NEXT_SGL);
++      if (nandc->props->supports_bam)
++              write_reg_dma(nandc, reg, NAND_READ_LOCATION_0, 4, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++      write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
++      write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
+       if (use_ecc) {
+               read_reg_dma(nandc, NAND_FLASH_STATUS, 2, 0);
+@@ -1279,10 +1238,10 @@ static void config_nand_page_write(struc
+ {
+       struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+-      write_reg_dma(nandc, NAND_ADDR0, 2, 0);
+-      write_reg_dma(nandc, NAND_DEV0_CFG0, 3, 0);
+-      if (!nandc->props->qpic_v2)
+-              write_reg_dma(nandc, NAND_EBI2_ECC_BUF_CFG, 1,
++      write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0);
++      write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0);
++      if (!nandc->props->qpic_version2)
++              write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1,
+                             NAND_BAM_NEXT_SGL);
+ }
+@@ -1294,13 +1253,13 @@ static void config_nand_cw_write(struct
+ {
+       struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+-      write_reg_dma(nandc, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++      write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
++      write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
+       read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, NAND_FLASH_STATUS, 1, 0);
+-      write_reg_dma(nandc, NAND_READ_STATUS, 1, NAND_BAM_NEXT_SGL);
++      write_reg_dma(nandc, &nandc->regs->clrflashstatus, NAND_FLASH_STATUS, 1, 0);
++      write_reg_dma(nandc, &nandc->regs->clrreadstatus, NAND_READ_STATUS, 1, NAND_BAM_NEXT_SGL);
+ }
+ /* helpers to submit/free our list of dma descriptors */
+@@ -1311,7 +1270,7 @@ static int submit_descs(struct qcom_nand
+       struct bam_transaction *bam_txn = nandc->bam_txn;
+       int ret = 0;
+-      if (nandc->props->is_bam) {
++      if (nandc->props->supports_bam) {
+               if (bam_txn->rx_sgl_pos > bam_txn->rx_sgl_start) {
+                       ret = prepare_bam_async_desc(nandc, nandc->rx_chan, 0);
+                       if (ret)
+@@ -1336,14 +1295,9 @@ static int submit_descs(struct qcom_nand
+       list_for_each_entry(desc, &nandc->desc_list, node)
+               cookie = dmaengine_submit(desc->dma_desc);
+-      if (nandc->props->is_bam) {
++      if (nandc->props->supports_bam) {
+               bam_txn->last_cmd_desc->callback = qpic_bam_dma_done;
+               bam_txn->last_cmd_desc->callback_param = bam_txn;
+-              if (bam_txn->last_data_desc) {
+-                      bam_txn->last_data_desc->callback = qpic_bam_dma_done;
+-                      bam_txn->last_data_desc->callback_param = bam_txn;
+-                      bam_txn->wait_second_completion = true;
+-              }
+               dma_async_issue_pending(nandc->tx_chan);
+               dma_async_issue_pending(nandc->rx_chan);
+@@ -1365,7 +1319,7 @@ err_unmap_free_desc:
+       list_for_each_entry_safe(desc, n, &nandc->desc_list, node) {
+               list_del(&desc->node);
+-              if (nandc->props->is_bam)
++              if (nandc->props->supports_bam)
+                       dma_unmap_sg(nandc->dev, desc->bam_sgl,
+                                    desc->sgl_cnt, desc->dir);
+               else
+@@ -1382,7 +1336,7 @@ err_unmap_free_desc:
+ static void clear_read_regs(struct qcom_nand_controller *nandc)
+ {
+       nandc->reg_read_pos = 0;
+-      nandc_read_buffer_sync(nandc, false);
++      nandc_dev_to_mem(nandc, false);
+ }
+ /*
+@@ -1446,7 +1400,7 @@ static int check_flash_errors(struct qco
+       struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+       int i;
+-      nandc_read_buffer_sync(nandc, true);
++      nandc_dev_to_mem(nandc, true);
+       for (i = 0; i < cw_cnt; i++) {
+               u32 flash = le32_to_cpu(nandc->reg_read_buf[i]);
+@@ -1476,7 +1430,7 @@ qcom_nandc_read_cw_raw(struct mtd_info *
+       clear_read_regs(nandc);
+       host->use_ecc = false;
+-      if (nandc->props->qpic_v2)
++      if (nandc->props->qpic_version2)
+               raw_cw = ecc->steps - 1;
+       clear_bam_transaction(nandc);
+@@ -1497,7 +1451,7 @@ qcom_nandc_read_cw_raw(struct mtd_info *
+               oob_size2 = host->ecc_bytes_hw + host->spare_bytes;
+       }
+-      if (nandc->props->is_bam) {
++      if (nandc->props->supports_bam) {
+               nandc_set_read_loc(chip, cw, 0, read_loc, data_size1, 0);
+               read_loc += data_size1;
+@@ -1621,7 +1575,7 @@ static int parse_read_errors(struct qcom
+       u8 *data_buf_start = data_buf, *oob_buf_start = oob_buf;
+       buf = (struct read_stats *)nandc->reg_read_buf;
+-      nandc_read_buffer_sync(nandc, true);
++      nandc_dev_to_mem(nandc, true);
+       for (i = 0; i < ecc->steps; i++, buf++) {
+               u32 flash, buffer, erased_cw;
+@@ -1734,7 +1688,7 @@ static int read_page_ecc(struct qcom_nan
+                       oob_size = host->ecc_bytes_hw + host->spare_bytes;
+               }
+-              if (nandc->props->is_bam) {
++              if (nandc->props->supports_bam) {
+                       if (data_buf && oob_buf) {
+                               nandc_set_read_loc(chip, i, 0, 0, data_size, 0);
+                               nandc_set_read_loc(chip, i, 1, data_size,
+@@ -2455,14 +2409,14 @@ static int qcom_nand_attach_chip(struct
+       mtd_set_ooblayout(mtd, &qcom_nand_ooblayout_ops);
+       /* Free the initially allocated BAM transaction for reading the ONFI params */
+-      if (nandc->props->is_bam)
++      if (nandc->props->supports_bam)
+               free_bam_transaction(nandc);
+       nandc->max_cwperpage = max_t(unsigned int, nandc->max_cwperpage,
+                                    cwperpage);
+       /* Now allocate the BAM transaction based on updated max_cwperpage */
+-      if (nandc->props->is_bam) {
++      if (nandc->props->supports_bam) {
+               nandc->bam_txn = alloc_bam_transaction(nandc);
+               if (!nandc->bam_txn) {
+                       dev_err(nandc->dev,
+@@ -2522,7 +2476,7 @@ static int qcom_nand_attach_chip(struct
+                               | ecc_mode << ECC_MODE
+                               | host->ecc_bytes_hw << ECC_PARITY_SIZE_BYTES_BCH;
+-      if (!nandc->props->qpic_v2)
++      if (!nandc->props->qpic_version2)
+               host->ecc_buf_cfg = 0x203 << NUM_STEPS;
+       host->clrflashstatus = FS_READY_BSY_N;
+@@ -2556,7 +2510,7 @@ static int qcom_op_cmd_mapping(struct na
+               cmd = OP_FETCH_ID;
+               break;
+       case NAND_CMD_PARAM:
+-              if (nandc->props->qpic_v2)
++              if (nandc->props->qpic_version2)
+                       cmd = OP_PAGE_READ_ONFI_READ;
+               else
+                       cmd = OP_PAGE_READ;
+@@ -2609,7 +2563,7 @@ static int qcom_parse_instructions(struc
+                       if (ret < 0)
+                               return ret;
+-                      q_op->cmd_reg = ret;
++                      q_op->cmd_reg = cpu_to_le32(ret);
+                       q_op->rdy_delay_ns = instr->delay_ns;
+                       break;
+@@ -2619,10 +2573,10 @@ static int qcom_parse_instructions(struc
+                       addrs = &instr->ctx.addr.addrs[offset];
+                       for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
+-                              q_op->addr1_reg |= addrs[i] << (i * 8);
++                              q_op->addr1_reg |= cpu_to_le32(addrs[i] << (i * 8));
+                       if (naddrs > 4)
+-                              q_op->addr2_reg |= addrs[4];
++                              q_op->addr2_reg |= cpu_to_le32(addrs[4]);
+                       q_op->rdy_delay_ns = instr->delay_ns;
+                       break;
+@@ -2663,7 +2617,7 @@ static int qcom_wait_rdy_poll(struct nan
+       unsigned long start = jiffies + msecs_to_jiffies(time_ms);
+       u32 flash;
+-      nandc_read_buffer_sync(nandc, true);
++      nandc_dev_to_mem(nandc, true);
+       do {
+               flash = le32_to_cpu(nandc->reg_read_buf[0]);
+@@ -2706,11 +2660,11 @@ static int qcom_read_status_exec(struct
+       clear_read_regs(nandc);
+       clear_bam_transaction(nandc);
+-      nandc_set_reg(chip, NAND_FLASH_CMD, q_op.cmd_reg);
+-      nandc_set_reg(chip, NAND_EXEC_CMD, 1);
++      nandc->regs->cmd = q_op.cmd_reg;
++      nandc->regs->exec = cpu_to_le32(1);
+-      write_reg_dma(nandc, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++      write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
++      write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
+       read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL);
+       ret = submit_descs(nandc);
+@@ -2719,7 +2673,7 @@ static int qcom_read_status_exec(struct
+               goto err_out;
+       }
+-      nandc_read_buffer_sync(nandc, true);
++      nandc_dev_to_mem(nandc, true);
+       for (i = 0; i < num_cw; i++) {
+               flash_status = le32_to_cpu(nandc->reg_read_buf[i]);
+@@ -2763,16 +2717,14 @@ static int qcom_read_id_type_exec(struct
+       clear_read_regs(nandc);
+       clear_bam_transaction(nandc);
+-      nandc_set_reg(chip, NAND_FLASH_CMD, q_op.cmd_reg);
+-      nandc_set_reg(chip, NAND_ADDR0, q_op.addr1_reg);
+-      nandc_set_reg(chip, NAND_ADDR1, q_op.addr2_reg);
+-      nandc_set_reg(chip, NAND_FLASH_CHIP_SELECT,
+-                    nandc->props->is_bam ? 0 : DM_EN);
++      nandc->regs->cmd = q_op.cmd_reg;
++      nandc->regs->addr0 = q_op.addr1_reg;
++      nandc->regs->addr1 = q_op.addr2_reg;
++      nandc->regs->chip_sel = cpu_to_le32(nandc->props->supports_bam ? 0 : DM_EN);
++      nandc->regs->exec = cpu_to_le32(1);
+-      nandc_set_reg(chip, NAND_EXEC_CMD, 1);
+-
+-      write_reg_dma(nandc, NAND_FLASH_CMD, 4, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++      write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 4, NAND_BAM_NEXT_SGL);
++      write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
+       read_reg_dma(nandc, NAND_READ_ID, 1, NAND_BAM_NEXT_SGL);
+@@ -2786,7 +2738,7 @@ static int qcom_read_id_type_exec(struct
+       op_id = q_op.data_instr_idx;
+       len = nand_subop_get_data_len(subop, op_id);
+-      nandc_read_buffer_sync(nandc, true);
++      nandc_dev_to_mem(nandc, true);
+       memcpy(instr->ctx.data.buf.in, nandc->reg_read_buf, len);
+ err_out:
+@@ -2807,15 +2759,14 @@ static int qcom_misc_cmd_type_exec(struc
+       if (q_op.flag == OP_PROGRAM_PAGE) {
+               goto wait_rdy;
+-      } else if (q_op.cmd_reg == OP_BLOCK_ERASE) {
+-              q_op.cmd_reg |= PAGE_ACC | LAST_PAGE;
+-              nandc_set_reg(chip, NAND_ADDR0, q_op.addr1_reg);
+-              nandc_set_reg(chip, NAND_ADDR1, q_op.addr2_reg);
+-              nandc_set_reg(chip, NAND_DEV0_CFG0,
+-                            host->cfg0_raw & ~(7 << CW_PER_PAGE));
+-              nandc_set_reg(chip, NAND_DEV0_CFG1, host->cfg1_raw);
++      } else if (q_op.cmd_reg == cpu_to_le32(OP_BLOCK_ERASE)) {
++              q_op.cmd_reg |= cpu_to_le32(PAGE_ACC | LAST_PAGE);
++              nandc->regs->addr0 = q_op.addr1_reg;
++              nandc->regs->addr1 = q_op.addr2_reg;
++              nandc->regs->cfg0 = cpu_to_le32(host->cfg0_raw & ~(7 << CW_PER_PAGE));
++              nandc->regs->cfg1 = cpu_to_le32(host->cfg1_raw);
+               instrs = 3;
+-      } else if (q_op.cmd_reg != OP_RESET_DEVICE) {
++      } else if (q_op.cmd_reg != cpu_to_le32(OP_RESET_DEVICE)) {
+               return 0;
+       }
+@@ -2826,14 +2777,14 @@ static int qcom_misc_cmd_type_exec(struc
+       clear_read_regs(nandc);
+       clear_bam_transaction(nandc);
+-      nandc_set_reg(chip, NAND_FLASH_CMD, q_op.cmd_reg);
+-      nandc_set_reg(chip, NAND_EXEC_CMD, 1);
++      nandc->regs->cmd = q_op.cmd_reg;
++      nandc->regs->exec = cpu_to_le32(1);
+-      write_reg_dma(nandc, NAND_FLASH_CMD, instrs, NAND_BAM_NEXT_SGL);
+-      if (q_op.cmd_reg == OP_BLOCK_ERASE)
+-              write_reg_dma(nandc, NAND_DEV0_CFG0, 2, NAND_BAM_NEXT_SGL);
++      write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, instrs, NAND_BAM_NEXT_SGL);
++      if (q_op.cmd_reg == cpu_to_le32(OP_BLOCK_ERASE))
++              write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 2, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++      write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
+       read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL);
+       ret = submit_descs(nandc);
+@@ -2862,14 +2813,14 @@ static int qcom_param_page_type_exec(str
+       reg_base = NAND_READ_LOCATION_0;
+-      if (nandc->props->qpic_v2)
++      if (nandc->props->qpic_version2)
+               reg_base = NAND_READ_LOCATION_LAST_CW_0;
+       ret = qcom_parse_instructions(chip, subop, &q_op);
+       if (ret)
+               return ret;
+-      q_op.cmd_reg |= PAGE_ACC | LAST_PAGE;
++      q_op.cmd_reg |= cpu_to_le32(PAGE_ACC | LAST_PAGE);
+       nandc->buf_count = 0;
+       nandc->buf_start = 0;
+@@ -2877,52 +2828,52 @@ static int qcom_param_page_type_exec(str
+       clear_read_regs(nandc);
+       clear_bam_transaction(nandc);
+-      nandc_set_reg(chip, NAND_FLASH_CMD, q_op.cmd_reg);
++      nandc->regs->cmd = q_op.cmd_reg;
++      nandc->regs->addr0 = 0;
++      nandc->regs->addr1 = 0;
++
++      nandc->regs->cfg0 = cpu_to_le32(0 << CW_PER_PAGE |
++                                      512 << UD_SIZE_BYTES |
++                                      5 << NUM_ADDR_CYCLES |
++                                      0 << SPARE_SIZE_BYTES);
++
++      nandc->regs->cfg1 = cpu_to_le32(7 << NAND_RECOVERY_CYCLES |
++                                      0 << CS_ACTIVE_BSY |
++                                      17 << BAD_BLOCK_BYTE_NUM |
++                                      1 << BAD_BLOCK_IN_SPARE_AREA |
++                                      2 << WR_RD_BSY_GAP |
++                                      0 << WIDE_FLASH |
++                                      1 << DEV0_CFG1_ECC_DISABLE);
+-      nandc_set_reg(chip, NAND_ADDR0, 0);
+-      nandc_set_reg(chip, NAND_ADDR1, 0);
+-      nandc_set_reg(chip, NAND_DEV0_CFG0, 0 << CW_PER_PAGE
+-                                      | 512 << UD_SIZE_BYTES
+-                                      | 5 << NUM_ADDR_CYCLES
+-                                      | 0 << SPARE_SIZE_BYTES);
+-      nandc_set_reg(chip, NAND_DEV0_CFG1, 7 << NAND_RECOVERY_CYCLES
+-                                      | 0 << CS_ACTIVE_BSY
+-                                      | 17 << BAD_BLOCK_BYTE_NUM
+-                                      | 1 << BAD_BLOCK_IN_SPARE_AREA
+-                                      | 2 << WR_RD_BSY_GAP
+-                                      | 0 << WIDE_FLASH
+-                                      | 1 << DEV0_CFG1_ECC_DISABLE);
+-      if (!nandc->props->qpic_v2)
+-              nandc_set_reg(chip, NAND_EBI2_ECC_BUF_CFG, 1 << ECC_CFG_ECC_DISABLE);
++      if (!nandc->props->qpic_version2)
++              nandc->regs->ecc_buf_cfg = cpu_to_le32(1 << ECC_CFG_ECC_DISABLE);
+       /* configure CMD1 and VLD for ONFI param probing in QPIC v1 */
+-      if (!nandc->props->qpic_v2) {
+-              nandc_set_reg(chip, NAND_DEV_CMD_VLD,
+-                            (nandc->vld & ~READ_START_VLD));
+-              nandc_set_reg(chip, NAND_DEV_CMD1,
+-                            (nandc->cmd1 & ~(0xFF << READ_ADDR))
+-                            | NAND_CMD_PARAM << READ_ADDR);
++      if (!nandc->props->qpic_version2) {
++              nandc->regs->vld = cpu_to_le32((nandc->vld & ~READ_START_VLD));
++              nandc->regs->cmd1 = cpu_to_le32((nandc->cmd1 & ~(0xFF << READ_ADDR))
++                                  | NAND_CMD_PARAM << READ_ADDR);
+       }
+-      nandc_set_reg(chip, NAND_EXEC_CMD, 1);
+-
+-      if (!nandc->props->qpic_v2) {
+-              nandc_set_reg(chip, NAND_DEV_CMD1_RESTORE, nandc->cmd1);
+-              nandc_set_reg(chip, NAND_DEV_CMD_VLD_RESTORE, nandc->vld);
++      nandc->regs->exec = cpu_to_le32(1);
++
++      if (!nandc->props->qpic_version2) {
++              nandc->regs->orig_cmd1 = cpu_to_le32(nandc->cmd1);
++              nandc->regs->orig_vld = cpu_to_le32(nandc->vld);
+       }
+       instr = q_op.data_instr;
+       op_id = q_op.data_instr_idx;
+       len = nand_subop_get_data_len(subop, op_id);
+-      if (nandc->props->qpic_v2)
++      if (nandc->props->qpic_version2)
+               nandc_set_read_loc_last(chip, reg_base, 0, len, 1);
+       else
+               nandc_set_read_loc_first(chip, reg_base, 0, len, 1);
+-      if (!nandc->props->qpic_v2) {
+-              write_reg_dma(nandc, NAND_DEV_CMD_VLD, 1, 0);
+-              write_reg_dma(nandc, NAND_DEV_CMD1, 1, NAND_BAM_NEXT_SGL);
++      if (!nandc->props->qpic_version2) {
++              write_reg_dma(nandc, &nandc->regs->vld, NAND_DEV_CMD_VLD, 1, 0);
++              write_reg_dma(nandc, &nandc->regs->cmd1, NAND_DEV_CMD1, 1, NAND_BAM_NEXT_SGL);
+       }
+       nandc->buf_count = 512;
+@@ -2934,9 +2885,10 @@ static int qcom_param_page_type_exec(str
+                     nandc->buf_count, 0);
+       /* restore CMD1 and VLD regs */
+-      if (!nandc->props->qpic_v2) {
+-              write_reg_dma(nandc, NAND_DEV_CMD1_RESTORE, 1, 0);
+-              write_reg_dma(nandc, NAND_DEV_CMD_VLD_RESTORE, 1, NAND_BAM_NEXT_SGL);
++      if (!nandc->props->qpic_version2) {
++              write_reg_dma(nandc, &nandc->regs->orig_cmd1, NAND_DEV_CMD1_RESTORE, 1, 0);
++              write_reg_dma(nandc, &nandc->regs->orig_vld, NAND_DEV_CMD_VLD_RESTORE, 1,
++                            NAND_BAM_NEXT_SGL);
+       }
+       ret = submit_descs(nandc);
+@@ -3025,7 +2977,7 @@ static const struct nand_controller_ops
+ static void qcom_nandc_unalloc(struct qcom_nand_controller *nandc)
+ {
+-      if (nandc->props->is_bam) {
++      if (nandc->props->supports_bam) {
+               if (!dma_mapping_error(nandc->dev, nandc->reg_read_dma))
+                       dma_unmap_single(nandc->dev, nandc->reg_read_dma,
+                                        MAX_REG_RD *
+@@ -3078,7 +3030,7 @@ static int qcom_nandc_alloc(struct qcom_
+       if (!nandc->reg_read_buf)
+               return -ENOMEM;
+-      if (nandc->props->is_bam) {
++      if (nandc->props->supports_bam) {
+               nandc->reg_read_dma =
+                       dma_map_single(nandc->dev, nandc->reg_read_buf,
+                                      MAX_REG_RD *
+@@ -3159,15 +3111,15 @@ static int qcom_nandc_setup(struct qcom_
+       u32 nand_ctrl;
+       /* kill onenand */
+-      if (!nandc->props->is_qpic)
++      if (!nandc->props->nandc_part_of_qpic)
+               nandc_write(nandc, SFLASHC_BURST_CFG, 0);
+-      if (!nandc->props->qpic_v2)
++      if (!nandc->props->qpic_version2)
+               nandc_write(nandc, dev_cmd_reg_addr(nandc, NAND_DEV_CMD_VLD),
+                           NAND_DEV_CMD_VLD_VAL);
+       /* enable ADM or BAM DMA */
+-      if (nandc->props->is_bam) {
++      if (nandc->props->supports_bam) {
+               nand_ctrl = nandc_read(nandc, NAND_CTRL);
+               /*
+@@ -3184,7 +3136,7 @@ static int qcom_nandc_setup(struct qcom_
+       }
+       /* save the original values of these registers */
+-      if (!nandc->props->qpic_v2) {
++      if (!nandc->props->qpic_version2) {
+               nandc->cmd1 = nandc_read(nandc, dev_cmd_reg_addr(nandc, NAND_DEV_CMD1));
+               nandc->vld = NAND_DEV_CMD_VLD_VAL;
+       }
+@@ -3357,7 +3309,7 @@ static int qcom_nandc_parse_dt(struct pl
+       struct device_node *np = nandc->dev->of_node;
+       int ret;
+-      if (!nandc->props->is_bam) {
++      if (!nandc->props->supports_bam) {
+               ret = of_property_read_u32(np, "qcom,cmd-crci",
+                                          &nandc->cmd_crci);
+               if (ret) {
+@@ -3482,30 +3434,30 @@ static void qcom_nandc_remove(struct pla
+ static const struct qcom_nandc_props ipq806x_nandc_props = {
+       .ecc_modes = (ECC_RS_4BIT | ECC_BCH_8BIT),
+-      .is_bam = false,
++      .supports_bam = false,
+       .use_codeword_fixup = true,
+       .dev_cmd_reg_start = 0x0,
+ };
+ static const struct qcom_nandc_props ipq4019_nandc_props = {
+       .ecc_modes = (ECC_BCH_4BIT | ECC_BCH_8BIT),
+-      .is_bam = true,
+-      .is_qpic = true,
++      .supports_bam = true,
++      .nandc_part_of_qpic = true,
+       .dev_cmd_reg_start = 0x0,
+ };
+ static const struct qcom_nandc_props ipq8074_nandc_props = {
+       .ecc_modes = (ECC_BCH_4BIT | ECC_BCH_8BIT),
+-      .is_bam = true,
+-      .is_qpic = true,
++      .supports_bam = true,
++      .nandc_part_of_qpic = true,
+       .dev_cmd_reg_start = 0x7000,
+ };
+ static const struct qcom_nandc_props sdx55_nandc_props = {
+       .ecc_modes = (ECC_BCH_4BIT | ECC_BCH_8BIT),
+-      .is_bam = true,
+-      .is_qpic = true,
+-      .qpic_v2 = true,
++      .supports_bam = true,
++      .nandc_part_of_qpic = true,
++      .qpic_version2 = true,
+       .dev_cmd_reg_start = 0x7000,
+ };
diff --git a/target/linux/generic/backport-6.12/410-02-v6.14-mtd-rawnand-qcom-Add-qcom-prefix-to-common-api.patch b/target/linux/generic/backport-6.12/410-02-v6.14-mtd-rawnand-qcom-Add-qcom-prefix-to-common-api.patch
new file mode 100644 (file)
index 0000000..23ef86f
--- /dev/null
@@ -0,0 +1,880 @@
+From 1d479f5b345e0c3650fec4dddeef9fc6fab30c8b Mon Sep 17 00:00:00 2001
+From: Md Sadre Alam <quic_mdalam@quicinc.com>
+Date: Wed, 20 Nov 2024 14:45:01 +0530
+Subject: [PATCH 2/4] mtd: rawnand: qcom: Add qcom prefix to common api
+
+Add qcom prefix to all the api which will be commonly
+used by spi nand driver and raw nand driver.
+
+Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
+Signed-off-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/raw/qcom_nandc.c | 320 +++++++++++++++---------------
+ 1 file changed, 160 insertions(+), 160 deletions(-)
+
+--- a/drivers/mtd/nand/raw/qcom_nandc.c
++++ b/drivers/mtd/nand/raw/qcom_nandc.c
+@@ -53,7 +53,7 @@
+ #define       NAND_READ_LOCATION_LAST_CW_2    0xf48
+ #define       NAND_READ_LOCATION_LAST_CW_3    0xf4c
+-/* dummy register offsets, used by write_reg_dma */
++/* dummy register offsets, used by qcom_write_reg_dma */
+ #define       NAND_DEV_CMD1_RESTORE           0xdead
+ #define       NAND_DEV_CMD_VLD_RESTORE        0xbeef
+@@ -211,7 +211,7 @@
+ /*
+  * Flags used in DMA descriptor preparation helper functions
+- * (i.e. read_reg_dma/write_reg_dma/read_data_dma/write_data_dma)
++ * (i.e. qcom_read_reg_dma/qcom_write_reg_dma/qcom_read_data_dma/qcom_write_data_dma)
+  */
+ /* Don't set the EOT in current tx BAM sgl */
+ #define NAND_BAM_NO_EOT                       BIT(0)
+@@ -550,7 +550,7 @@ struct qcom_nandc_props {
+ };
+ /* Frees the BAM transaction memory */
+-static void free_bam_transaction(struct qcom_nand_controller *nandc)
++static void qcom_free_bam_transaction(struct qcom_nand_controller *nandc)
+ {
+       struct bam_transaction *bam_txn = nandc->bam_txn;
+@@ -559,7 +559,7 @@ static void free_bam_transaction(struct
+ /* Allocates and Initializes the BAM transaction */
+ static struct bam_transaction *
+-alloc_bam_transaction(struct qcom_nand_controller *nandc)
++qcom_alloc_bam_transaction(struct qcom_nand_controller *nandc)
+ {
+       struct bam_transaction *bam_txn;
+       size_t bam_txn_size;
+@@ -595,7 +595,7 @@ alloc_bam_transaction(struct qcom_nand_c
+ }
+ /* Clears the BAM transaction indexes */
+-static void clear_bam_transaction(struct qcom_nand_controller *nandc)
++static void qcom_clear_bam_transaction(struct qcom_nand_controller *nandc)
+ {
+       struct bam_transaction *bam_txn = nandc->bam_txn;
+@@ -614,7 +614,7 @@ static void clear_bam_transaction(struct
+ }
+ /* Callback for DMA descriptor completion */
+-static void qpic_bam_dma_done(void *data)
++static void qcom_qpic_bam_dma_done(void *data)
+ {
+       struct bam_transaction *bam_txn = data;
+@@ -644,7 +644,7 @@ static void nandc_write(struct qcom_nand
+       iowrite32(val, nandc->base + offset);
+ }
+-static void nandc_dev_to_mem(struct qcom_nand_controller *nandc, bool is_cpu)
++static void qcom_nandc_dev_to_mem(struct qcom_nand_controller *nandc, bool is_cpu)
+ {
+       if (!nandc->props->supports_bam)
+               return;
+@@ -824,9 +824,9 @@ static void update_rw_regs(struct qcom_n
+  * for BAM. This descriptor will be added in the NAND DMA descriptor queue
+  * which will be submitted to DMA engine.
+  */
+-static int prepare_bam_async_desc(struct qcom_nand_controller *nandc,
+-                                struct dma_chan *chan,
+-                                unsigned long flags)
++static int qcom_prepare_bam_async_desc(struct qcom_nand_controller *nandc,
++                                     struct dma_chan *chan,
++                                     unsigned long flags)
+ {
+       struct desc_info *desc;
+       struct scatterlist *sgl;
+@@ -903,9 +903,9 @@ static int prepare_bam_async_desc(struct
+  * NAND_BAM_NEXT_SGL will be used for starting the separate SGL
+  * after the current command element.
+  */
+-static int prep_bam_dma_desc_cmd(struct qcom_nand_controller *nandc, bool read,
+-                               int reg_off, const void *vaddr,
+-                               int size, unsigned int flags)
++static int qcom_prep_bam_dma_desc_cmd(struct qcom_nand_controller *nandc, bool read,
++                                    int reg_off, const void *vaddr,
++                                    int size, unsigned int flags)
+ {
+       int bam_ce_size;
+       int i, ret;
+@@ -943,9 +943,9 @@ static int prep_bam_dma_desc_cmd(struct
+               bam_txn->bam_ce_start = bam_txn->bam_ce_pos;
+               if (flags & NAND_BAM_NWD) {
+-                      ret = prepare_bam_async_desc(nandc, nandc->cmd_chan,
+-                                                   DMA_PREP_FENCE |
+-                                                   DMA_PREP_CMD);
++                      ret = qcom_prepare_bam_async_desc(nandc, nandc->cmd_chan,
++                                                        DMA_PREP_FENCE |
++                                                        DMA_PREP_CMD);
+                       if (ret)
+                               return ret;
+               }
+@@ -958,9 +958,8 @@ static int prep_bam_dma_desc_cmd(struct
+  * Prepares the data descriptor for BAM DMA which will be used for NAND
+  * data reads and writes.
+  */
+-static int prep_bam_dma_desc_data(struct qcom_nand_controller *nandc, bool read,
+-                                const void *vaddr,
+-                                int size, unsigned int flags)
++static int qcom_prep_bam_dma_desc_data(struct qcom_nand_controller *nandc, bool read,
++                                     const void *vaddr, int size, unsigned int flags)
+ {
+       int ret;
+       struct bam_transaction *bam_txn = nandc->bam_txn;
+@@ -979,8 +978,8 @@ static int prep_bam_dma_desc_data(struct
+                * is not set, form the DMA descriptor
+                */
+               if (!(flags & NAND_BAM_NO_EOT)) {
+-                      ret = prepare_bam_async_desc(nandc, nandc->tx_chan,
+-                                                   DMA_PREP_INTERRUPT);
++                      ret = qcom_prepare_bam_async_desc(nandc, nandc->tx_chan,
++                                                        DMA_PREP_INTERRUPT);
+                       if (ret)
+                               return ret;
+               }
+@@ -989,9 +988,9 @@ static int prep_bam_dma_desc_data(struct
+       return 0;
+ }
+-static int prep_adm_dma_desc(struct qcom_nand_controller *nandc, bool read,
+-                           int reg_off, const void *vaddr, int size,
+-                           bool flow_control)
++static int qcom_prep_adm_dma_desc(struct qcom_nand_controller *nandc, bool read,
++                                int reg_off, const void *vaddr, int size,
++                                bool flow_control)
+ {
+       struct desc_info *desc;
+       struct dma_async_tx_descriptor *dma_desc;
+@@ -1069,15 +1068,15 @@ err:
+ }
+ /*
+- * read_reg_dma:      prepares a descriptor to read a given number of
++ * qcom_read_reg_dma: prepares a descriptor to read a given number of
+  *                    contiguous registers to the reg_read_buf pointer
+  *
+  * @first:            offset of the first register in the contiguous block
+  * @num_regs:         number of registers to read
+  * @flags:            flags to control DMA descriptor preparation
+  */
+-static int read_reg_dma(struct qcom_nand_controller *nandc, int first,
+-                      int num_regs, unsigned int flags)
++static int qcom_read_reg_dma(struct qcom_nand_controller *nandc, int first,
++                           int num_regs, unsigned int flags)
+ {
+       bool flow_control = false;
+       void *vaddr;
+@@ -1089,18 +1088,18 @@ static int read_reg_dma(struct qcom_nand
+               first = dev_cmd_reg_addr(nandc, first);
+       if (nandc->props->supports_bam)
+-              return prep_bam_dma_desc_cmd(nandc, true, first, vaddr,
++              return qcom_prep_bam_dma_desc_cmd(nandc, true, first, vaddr,
+                                            num_regs, flags);
+       if (first == NAND_READ_ID || first == NAND_FLASH_STATUS)
+               flow_control = true;
+-      return prep_adm_dma_desc(nandc, true, first, vaddr,
++      return qcom_prep_adm_dma_desc(nandc, true, first, vaddr,
+                                num_regs * sizeof(u32), flow_control);
+ }
+ /*
+- * write_reg_dma:     prepares a descriptor to write a given number of
++ * qcom_write_reg_dma:        prepares a descriptor to write a given number of
+  *                    contiguous registers
+  *
+  * @vaddr:            contiguous memory from where register value will
+@@ -1109,8 +1108,8 @@ static int read_reg_dma(struct qcom_nand
+  * @num_regs:         number of registers to write
+  * @flags:            flags to control DMA descriptor preparation
+  */
+-static int write_reg_dma(struct qcom_nand_controller *nandc, __le32 *vaddr,
+-                       int first, int num_regs, unsigned int flags)
++static int qcom_write_reg_dma(struct qcom_nand_controller *nandc, __le32 *vaddr,
++                            int first, int num_regs, unsigned int flags)
+ {
+       bool flow_control = false;
+@@ -1124,18 +1123,18 @@ static int write_reg_dma(struct qcom_nan
+               first = dev_cmd_reg_addr(nandc, NAND_DEV_CMD_VLD);
+       if (nandc->props->supports_bam)
+-              return prep_bam_dma_desc_cmd(nandc, false, first, vaddr,
++              return qcom_prep_bam_dma_desc_cmd(nandc, false, first, vaddr,
+                                            num_regs, flags);
+       if (first == NAND_FLASH_CMD)
+               flow_control = true;
+-      return prep_adm_dma_desc(nandc, false, first, vaddr,
++      return qcom_prep_adm_dma_desc(nandc, false, first, vaddr,
+                                num_regs * sizeof(u32), flow_control);
+ }
+ /*
+- * read_data_dma:     prepares a DMA descriptor to transfer data from the
++ * qcom_read_data_dma:        prepares a DMA descriptor to transfer data from the
+  *                    controller's internal buffer to the buffer 'vaddr'
+  *
+  * @reg_off:          offset within the controller's data buffer
+@@ -1143,17 +1142,17 @@ static int write_reg_dma(struct qcom_nan
+  * @size:             DMA transaction size in bytes
+  * @flags:            flags to control DMA descriptor preparation
+  */
+-static int read_data_dma(struct qcom_nand_controller *nandc, int reg_off,
+-                       const u8 *vaddr, int size, unsigned int flags)
++static int qcom_read_data_dma(struct qcom_nand_controller *nandc, int reg_off,
++                            const u8 *vaddr, int size, unsigned int flags)
+ {
+       if (nandc->props->supports_bam)
+-              return prep_bam_dma_desc_data(nandc, true, vaddr, size, flags);
++              return qcom_prep_bam_dma_desc_data(nandc, true, vaddr, size, flags);
+-      return prep_adm_dma_desc(nandc, true, reg_off, vaddr, size, false);
++      return qcom_prep_adm_dma_desc(nandc, true, reg_off, vaddr, size, false);
+ }
+ /*
+- * write_data_dma:    prepares a DMA descriptor to transfer data from
++ * qcom_write_data_dma:       prepares a DMA descriptor to transfer data from
+  *                    'vaddr' to the controller's internal buffer
+  *
+  * @reg_off:          offset within the controller's data buffer
+@@ -1161,13 +1160,13 @@ static int read_data_dma(struct qcom_nan
+  * @size:             DMA transaction size in bytes
+  * @flags:            flags to control DMA descriptor preparation
+  */
+-static int write_data_dma(struct qcom_nand_controller *nandc, int reg_off,
+-                        const u8 *vaddr, int size, unsigned int flags)
++static int qcom_write_data_dma(struct qcom_nand_controller *nandc, int reg_off,
++                             const u8 *vaddr, int size, unsigned int flags)
+ {
+       if (nandc->props->supports_bam)
+-              return prep_bam_dma_desc_data(nandc, false, vaddr, size, flags);
++              return qcom_prep_bam_dma_desc_data(nandc, false, vaddr, size, flags);
+-      return prep_adm_dma_desc(nandc, false, reg_off, vaddr, size, false);
++      return qcom_prep_adm_dma_desc(nandc, false, reg_off, vaddr, size, false);
+ }
+ /*
+@@ -1178,14 +1177,14 @@ static void config_nand_page_read(struct
+ {
+       struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+-      write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0);
+-      write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0);
++      qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0);
++      qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0);
+       if (!nandc->props->qpic_version2)
+-              write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, 0);
+-      write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_clr,
+-                    NAND_ERASED_CW_DETECT_CFG, 1, 0);
+-      write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_set,
+-                    NAND_ERASED_CW_DETECT_CFG, 1, NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL);
++              qcom_write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, 0);
++      qcom_write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_clr,
++                         NAND_ERASED_CW_DETECT_CFG, 1, 0);
++      qcom_write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_set,
++                         NAND_ERASED_CW_DETECT_CFG, 1, NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL);
+ }
+ /*
+@@ -1204,17 +1203,17 @@ config_nand_cw_read(struct nand_chip *ch
+               reg = &nandc->regs->read_location_last0;
+       if (nandc->props->supports_bam)
+-              write_reg_dma(nandc, reg, NAND_READ_LOCATION_0, 4, NAND_BAM_NEXT_SGL);
++              qcom_write_reg_dma(nandc, reg, NAND_READ_LOCATION_0, 4, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
+       if (use_ecc) {
+-              read_reg_dma(nandc, NAND_FLASH_STATUS, 2, 0);
+-              read_reg_dma(nandc, NAND_ERASED_CW_DETECT_STATUS, 1,
+-                           NAND_BAM_NEXT_SGL);
++              qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 2, 0);
++              qcom_read_reg_dma(nandc, NAND_ERASED_CW_DETECT_STATUS, 1,
++                                NAND_BAM_NEXT_SGL);
+       } else {
+-              read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL);
++              qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL);
+       }
+ }
+@@ -1238,11 +1237,11 @@ static void config_nand_page_write(struc
+ {
+       struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+-      write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0);
+-      write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0);
++      qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0);
++      qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0);
+       if (!nandc->props->qpic_version2)
+-              write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1,
+-                            NAND_BAM_NEXT_SGL);
++              qcom_write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1,
++                                 NAND_BAM_NEXT_SGL);
+ }
+ /*
+@@ -1253,17 +1252,18 @@ static void config_nand_cw_write(struct
+ {
+       struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+-      write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
+-      read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL);
++      qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, &nandc->regs->clrflashstatus, NAND_FLASH_STATUS, 1, 0);
+-      write_reg_dma(nandc, &nandc->regs->clrreadstatus, NAND_READ_STATUS, 1, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(nandc, &nandc->regs->clrflashstatus, NAND_FLASH_STATUS, 1, 0);
++      qcom_write_reg_dma(nandc, &nandc->regs->clrreadstatus, NAND_READ_STATUS, 1,
++                         NAND_BAM_NEXT_SGL);
+ }
+ /* helpers to submit/free our list of dma descriptors */
+-static int submit_descs(struct qcom_nand_controller *nandc)
++static int qcom_submit_descs(struct qcom_nand_controller *nandc)
+ {
+       struct desc_info *desc, *n;
+       dma_cookie_t cookie = 0;
+@@ -1272,21 +1272,21 @@ static int submit_descs(struct qcom_nand
+       if (nandc->props->supports_bam) {
+               if (bam_txn->rx_sgl_pos > bam_txn->rx_sgl_start) {
+-                      ret = prepare_bam_async_desc(nandc, nandc->rx_chan, 0);
++                      ret = qcom_prepare_bam_async_desc(nandc, nandc->rx_chan, 0);
+                       if (ret)
+                               goto err_unmap_free_desc;
+               }
+               if (bam_txn->tx_sgl_pos > bam_txn->tx_sgl_start) {
+-                      ret = prepare_bam_async_desc(nandc, nandc->tx_chan,
+-                                                 DMA_PREP_INTERRUPT);
++                      ret = qcom_prepare_bam_async_desc(nandc, nandc->tx_chan,
++                                                        DMA_PREP_INTERRUPT);
+                       if (ret)
+                               goto err_unmap_free_desc;
+               }
+               if (bam_txn->cmd_sgl_pos > bam_txn->cmd_sgl_start) {
+-                      ret = prepare_bam_async_desc(nandc, nandc->cmd_chan,
+-                                                 DMA_PREP_CMD);
++                      ret = qcom_prepare_bam_async_desc(nandc, nandc->cmd_chan,
++                                                        DMA_PREP_CMD);
+                       if (ret)
+                               goto err_unmap_free_desc;
+               }
+@@ -1296,7 +1296,7 @@ static int submit_descs(struct qcom_nand
+               cookie = dmaengine_submit(desc->dma_desc);
+       if (nandc->props->supports_bam) {
+-              bam_txn->last_cmd_desc->callback = qpic_bam_dma_done;
++              bam_txn->last_cmd_desc->callback = qcom_qpic_bam_dma_done;
+               bam_txn->last_cmd_desc->callback_param = bam_txn;
+               dma_async_issue_pending(nandc->tx_chan);
+@@ -1314,7 +1314,7 @@ static int submit_descs(struct qcom_nand
+ err_unmap_free_desc:
+       /*
+        * Unmap the dma sg_list and free the desc allocated by both
+-       * prepare_bam_async_desc() and prep_adm_dma_desc() functions.
++       * qcom_prepare_bam_async_desc() and qcom_prep_adm_dma_desc() functions.
+        */
+       list_for_each_entry_safe(desc, n, &nandc->desc_list, node) {
+               list_del(&desc->node);
+@@ -1333,10 +1333,10 @@ err_unmap_free_desc:
+ }
+ /* reset the register read buffer for next NAND operation */
+-static void clear_read_regs(struct qcom_nand_controller *nandc)
++static void qcom_clear_read_regs(struct qcom_nand_controller *nandc)
+ {
+       nandc->reg_read_pos = 0;
+-      nandc_dev_to_mem(nandc, false);
++      qcom_nandc_dev_to_mem(nandc, false);
+ }
+ /*
+@@ -1400,7 +1400,7 @@ static int check_flash_errors(struct qco
+       struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+       int i;
+-      nandc_dev_to_mem(nandc, true);
++      qcom_nandc_dev_to_mem(nandc, true);
+       for (i = 0; i < cw_cnt; i++) {
+               u32 flash = le32_to_cpu(nandc->reg_read_buf[i]);
+@@ -1427,13 +1427,13 @@ qcom_nandc_read_cw_raw(struct mtd_info *
+       nand_read_page_op(chip, page, 0, NULL, 0);
+       nandc->buf_count = 0;
+       nandc->buf_start = 0;
+-      clear_read_regs(nandc);
++      qcom_clear_read_regs(nandc);
+       host->use_ecc = false;
+       if (nandc->props->qpic_version2)
+               raw_cw = ecc->steps - 1;
+-      clear_bam_transaction(nandc);
++      qcom_clear_bam_transaction(nandc);
+       set_address(host, host->cw_size * cw, page);
+       update_rw_regs(host, 1, true, raw_cw);
+       config_nand_page_read(chip);
+@@ -1466,18 +1466,18 @@ qcom_nandc_read_cw_raw(struct mtd_info *
+       config_nand_cw_read(chip, false, raw_cw);
+-      read_data_dma(nandc, reg_off, data_buf, data_size1, 0);
++      qcom_read_data_dma(nandc, reg_off, data_buf, data_size1, 0);
+       reg_off += data_size1;
+-      read_data_dma(nandc, reg_off, oob_buf, oob_size1, 0);
++      qcom_read_data_dma(nandc, reg_off, oob_buf, oob_size1, 0);
+       reg_off += oob_size1;
+-      read_data_dma(nandc, reg_off, data_buf + data_size1, data_size2, 0);
++      qcom_read_data_dma(nandc, reg_off, data_buf + data_size1, data_size2, 0);
+       reg_off += data_size2;
+-      read_data_dma(nandc, reg_off, oob_buf + oob_size1, oob_size2, 0);
++      qcom_read_data_dma(nandc, reg_off, oob_buf + oob_size1, oob_size2, 0);
+-      ret = submit_descs(nandc);
++      ret = qcom_submit_descs(nandc);
+       if (ret) {
+               dev_err(nandc->dev, "failure to read raw cw %d\n", cw);
+               return ret;
+@@ -1575,7 +1575,7 @@ static int parse_read_errors(struct qcom
+       u8 *data_buf_start = data_buf, *oob_buf_start = oob_buf;
+       buf = (struct read_stats *)nandc->reg_read_buf;
+-      nandc_dev_to_mem(nandc, true);
++      qcom_nandc_dev_to_mem(nandc, true);
+       for (i = 0; i < ecc->steps; i++, buf++) {
+               u32 flash, buffer, erased_cw;
+@@ -1704,8 +1704,8 @@ static int read_page_ecc(struct qcom_nan
+               config_nand_cw_read(chip, true, i);
+               if (data_buf)
+-                      read_data_dma(nandc, FLASH_BUF_ACC, data_buf,
+-                                    data_size, 0);
++                      qcom_read_data_dma(nandc, FLASH_BUF_ACC, data_buf,
++                                         data_size, 0);
+               /*
+                * when ecc is enabled, the controller doesn't read the real
+@@ -1720,8 +1720,8 @@ static int read_page_ecc(struct qcom_nan
+                       for (j = 0; j < host->bbm_size; j++)
+                               *oob_buf++ = 0xff;
+-                      read_data_dma(nandc, FLASH_BUF_ACC + data_size,
+-                                    oob_buf, oob_size, 0);
++                      qcom_read_data_dma(nandc, FLASH_BUF_ACC + data_size,
++                                         oob_buf, oob_size, 0);
+               }
+               if (data_buf)
+@@ -1730,7 +1730,7 @@ static int read_page_ecc(struct qcom_nan
+                       oob_buf += oob_size;
+       }
+-      ret = submit_descs(nandc);
++      ret = qcom_submit_descs(nandc);
+       if (ret) {
+               dev_err(nandc->dev, "failure to read page/oob\n");
+               return ret;
+@@ -1751,7 +1751,7 @@ static int copy_last_cw(struct qcom_nand
+       int size;
+       int ret;
+-      clear_read_regs(nandc);
++      qcom_clear_read_regs(nandc);
+       size = host->use_ecc ? host->cw_data : host->cw_size;
+@@ -1763,9 +1763,9 @@ static int copy_last_cw(struct qcom_nand
+       config_nand_single_cw_page_read(chip, host->use_ecc, ecc->steps - 1);
+-      read_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer, size, 0);
++      qcom_read_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer, size, 0);
+-      ret = submit_descs(nandc);
++      ret = qcom_submit_descs(nandc);
+       if (ret)
+               dev_err(nandc->dev, "failed to copy last codeword\n");
+@@ -1851,14 +1851,14 @@ static int qcom_nandc_read_page(struct n
+       nandc->buf_count = 0;
+       nandc->buf_start = 0;
+       host->use_ecc = true;
+-      clear_read_regs(nandc);
++      qcom_clear_read_regs(nandc);
+       set_address(host, 0, page);
+       update_rw_regs(host, ecc->steps, true, 0);
+       data_buf = buf;
+       oob_buf = oob_required ? chip->oob_poi : NULL;
+-      clear_bam_transaction(nandc);
++      qcom_clear_bam_transaction(nandc);
+       return read_page_ecc(host, data_buf, oob_buf, page);
+ }
+@@ -1899,8 +1899,8 @@ static int qcom_nandc_read_oob(struct na
+       if (host->nr_boot_partitions)
+               qcom_nandc_codeword_fixup(host, page);
+-      clear_read_regs(nandc);
+-      clear_bam_transaction(nandc);
++      qcom_clear_read_regs(nandc);
++      qcom_clear_bam_transaction(nandc);
+       host->use_ecc = true;
+       set_address(host, 0, page);
+@@ -1927,8 +1927,8 @@ static int qcom_nandc_write_page(struct
+       set_address(host, 0, page);
+       nandc->buf_count = 0;
+       nandc->buf_start = 0;
+-      clear_read_regs(nandc);
+-      clear_bam_transaction(nandc);
++      qcom_clear_read_regs(nandc);
++      qcom_clear_bam_transaction(nandc);
+       data_buf = (u8 *)buf;
+       oob_buf = chip->oob_poi;
+@@ -1949,8 +1949,8 @@ static int qcom_nandc_write_page(struct
+                       oob_size = ecc->bytes;
+               }
+-              write_data_dma(nandc, FLASH_BUF_ACC, data_buf, data_size,
+-                             i == (ecc->steps - 1) ? NAND_BAM_NO_EOT : 0);
++              qcom_write_data_dma(nandc, FLASH_BUF_ACC, data_buf, data_size,
++                                  i == (ecc->steps - 1) ? NAND_BAM_NO_EOT : 0);
+               /*
+                * when ECC is enabled, we don't really need to write anything
+@@ -1962,8 +1962,8 @@ static int qcom_nandc_write_page(struct
+               if (qcom_nandc_is_last_cw(ecc, i)) {
+                       oob_buf += host->bbm_size;
+-                      write_data_dma(nandc, FLASH_BUF_ACC + data_size,
+-                                     oob_buf, oob_size, 0);
++                      qcom_write_data_dma(nandc, FLASH_BUF_ACC + data_size,
++                                          oob_buf, oob_size, 0);
+               }
+               config_nand_cw_write(chip);
+@@ -1972,7 +1972,7 @@ static int qcom_nandc_write_page(struct
+               oob_buf += oob_size;
+       }
+-      ret = submit_descs(nandc);
++      ret = qcom_submit_descs(nandc);
+       if (ret) {
+               dev_err(nandc->dev, "failure to write page\n");
+               return ret;
+@@ -1997,8 +1997,8 @@ static int qcom_nandc_write_page_raw(str
+               qcom_nandc_codeword_fixup(host, page);
+       nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+-      clear_read_regs(nandc);
+-      clear_bam_transaction(nandc);
++      qcom_clear_read_regs(nandc);
++      qcom_clear_bam_transaction(nandc);
+       data_buf = (u8 *)buf;
+       oob_buf = chip->oob_poi;
+@@ -2024,28 +2024,28 @@ static int qcom_nandc_write_page_raw(str
+                       oob_size2 = host->ecc_bytes_hw + host->spare_bytes;
+               }
+-              write_data_dma(nandc, reg_off, data_buf, data_size1,
+-                             NAND_BAM_NO_EOT);
++              qcom_write_data_dma(nandc, reg_off, data_buf, data_size1,
++                                  NAND_BAM_NO_EOT);
+               reg_off += data_size1;
+               data_buf += data_size1;
+-              write_data_dma(nandc, reg_off, oob_buf, oob_size1,
+-                             NAND_BAM_NO_EOT);
++              qcom_write_data_dma(nandc, reg_off, oob_buf, oob_size1,
++                                  NAND_BAM_NO_EOT);
+               reg_off += oob_size1;
+               oob_buf += oob_size1;
+-              write_data_dma(nandc, reg_off, data_buf, data_size2,
+-                             NAND_BAM_NO_EOT);
++              qcom_write_data_dma(nandc, reg_off, data_buf, data_size2,
++                                  NAND_BAM_NO_EOT);
+               reg_off += data_size2;
+               data_buf += data_size2;
+-              write_data_dma(nandc, reg_off, oob_buf, oob_size2, 0);
++              qcom_write_data_dma(nandc, reg_off, oob_buf, oob_size2, 0);
+               oob_buf += oob_size2;
+               config_nand_cw_write(chip);
+       }
+-      ret = submit_descs(nandc);
++      ret = qcom_submit_descs(nandc);
+       if (ret) {
+               dev_err(nandc->dev, "failure to write raw page\n");
+               return ret;
+@@ -2075,7 +2075,7 @@ static int qcom_nandc_write_oob(struct n
+               qcom_nandc_codeword_fixup(host, page);
+       host->use_ecc = true;
+-      clear_bam_transaction(nandc);
++      qcom_clear_bam_transaction(nandc);
+       /* calculate the data and oob size for the last codeword/step */
+       data_size = ecc->size - ((ecc->steps - 1) << 2);
+@@ -2090,11 +2090,11 @@ static int qcom_nandc_write_oob(struct n
+       update_rw_regs(host, 1, false, 0);
+       config_nand_page_write(chip);
+-      write_data_dma(nandc, FLASH_BUF_ACC,
+-                     nandc->data_buffer, data_size + oob_size, 0);
++      qcom_write_data_dma(nandc, FLASH_BUF_ACC,
++                          nandc->data_buffer, data_size + oob_size, 0);
+       config_nand_cw_write(chip);
+-      ret = submit_descs(nandc);
++      ret = qcom_submit_descs(nandc);
+       if (ret) {
+               dev_err(nandc->dev, "failure to write oob\n");
+               return ret;
+@@ -2121,7 +2121,7 @@ static int qcom_nandc_block_bad(struct n
+        */
+       host->use_ecc = false;
+-      clear_bam_transaction(nandc);
++      qcom_clear_bam_transaction(nandc);
+       ret = copy_last_cw(host, page);
+       if (ret)
+               goto err;
+@@ -2148,8 +2148,8 @@ static int qcom_nandc_block_markbad(stru
+       struct nand_ecc_ctrl *ecc = &chip->ecc;
+       int page, ret;
+-      clear_read_regs(nandc);
+-      clear_bam_transaction(nandc);
++      qcom_clear_read_regs(nandc);
++      qcom_clear_bam_transaction(nandc);
+       /*
+        * to mark the BBM as bad, we flash the entire last codeword with 0s.
+@@ -2166,11 +2166,11 @@ static int qcom_nandc_block_markbad(stru
+       update_rw_regs(host, 1, false, ecc->steps - 1);
+       config_nand_page_write(chip);
+-      write_data_dma(nandc, FLASH_BUF_ACC,
+-                     nandc->data_buffer, host->cw_size, 0);
++      qcom_write_data_dma(nandc, FLASH_BUF_ACC,
++                          nandc->data_buffer, host->cw_size, 0);
+       config_nand_cw_write(chip);
+-      ret = submit_descs(nandc);
++      ret = qcom_submit_descs(nandc);
+       if (ret) {
+               dev_err(nandc->dev, "failure to update BBM\n");
+               return ret;
+@@ -2410,14 +2410,14 @@ static int qcom_nand_attach_chip(struct
+       mtd_set_ooblayout(mtd, &qcom_nand_ooblayout_ops);
+       /* Free the initially allocated BAM transaction for reading the ONFI params */
+       if (nandc->props->supports_bam)
+-              free_bam_transaction(nandc);
++              qcom_free_bam_transaction(nandc);
+       nandc->max_cwperpage = max_t(unsigned int, nandc->max_cwperpage,
+                                    cwperpage);
+       /* Now allocate the BAM transaction based on updated max_cwperpage */
+       if (nandc->props->supports_bam) {
+-              nandc->bam_txn = alloc_bam_transaction(nandc);
++              nandc->bam_txn = qcom_alloc_bam_transaction(nandc);
+               if (!nandc->bam_txn) {
+                       dev_err(nandc->dev,
+                               "failed to allocate bam transaction\n");
+@@ -2617,7 +2617,7 @@ static int qcom_wait_rdy_poll(struct nan
+       unsigned long start = jiffies + msecs_to_jiffies(time_ms);
+       u32 flash;
+-      nandc_dev_to_mem(nandc, true);
++      qcom_nandc_dev_to_mem(nandc, true);
+       do {
+               flash = le32_to_cpu(nandc->reg_read_buf[0]);
+@@ -2657,23 +2657,23 @@ static int qcom_read_status_exec(struct
+       nandc->buf_start = 0;
+       host->use_ecc = false;
+-      clear_read_regs(nandc);
+-      clear_bam_transaction(nandc);
++      qcom_clear_read_regs(nandc);
++      qcom_clear_bam_transaction(nandc);
+       nandc->regs->cmd = q_op.cmd_reg;
+       nandc->regs->exec = cpu_to_le32(1);
+-      write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
+-      read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++      qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL);
+-      ret = submit_descs(nandc);
++      ret = qcom_submit_descs(nandc);
+       if (ret) {
+               dev_err(nandc->dev, "failure in submitting status descriptor\n");
+               goto err_out;
+       }
+-      nandc_dev_to_mem(nandc, true);
++      qcom_nandc_dev_to_mem(nandc, true);
+       for (i = 0; i < num_cw; i++) {
+               flash_status = le32_to_cpu(nandc->reg_read_buf[i]);
+@@ -2714,8 +2714,8 @@ static int qcom_read_id_type_exec(struct
+       nandc->buf_start = 0;
+       host->use_ecc = false;
+-      clear_read_regs(nandc);
+-      clear_bam_transaction(nandc);
++      qcom_clear_read_regs(nandc);
++      qcom_clear_bam_transaction(nandc);
+       nandc->regs->cmd = q_op.cmd_reg;
+       nandc->regs->addr0 = q_op.addr1_reg;
+@@ -2723,12 +2723,12 @@ static int qcom_read_id_type_exec(struct
+       nandc->regs->chip_sel = cpu_to_le32(nandc->props->supports_bam ? 0 : DM_EN);
+       nandc->regs->exec = cpu_to_le32(1);
+-      write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 4, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 4, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
+-      read_reg_dma(nandc, NAND_READ_ID, 1, NAND_BAM_NEXT_SGL);
++      qcom_read_reg_dma(nandc, NAND_READ_ID, 1, NAND_BAM_NEXT_SGL);
+-      ret = submit_descs(nandc);
++      ret = qcom_submit_descs(nandc);
+       if (ret) {
+               dev_err(nandc->dev, "failure in submitting read id descriptor\n");
+               goto err_out;
+@@ -2738,7 +2738,7 @@ static int qcom_read_id_type_exec(struct
+       op_id = q_op.data_instr_idx;
+       len = nand_subop_get_data_len(subop, op_id);
+-      nandc_dev_to_mem(nandc, true);
++      qcom_nandc_dev_to_mem(nandc, true);
+       memcpy(instr->ctx.data.buf.in, nandc->reg_read_buf, len);
+ err_out:
+@@ -2774,20 +2774,20 @@ static int qcom_misc_cmd_type_exec(struc
+       nandc->buf_start = 0;
+       host->use_ecc = false;
+-      clear_read_regs(nandc);
+-      clear_bam_transaction(nandc);
++      qcom_clear_read_regs(nandc);
++      qcom_clear_bam_transaction(nandc);
+       nandc->regs->cmd = q_op.cmd_reg;
+       nandc->regs->exec = cpu_to_le32(1);
+-      write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, instrs, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, instrs, NAND_BAM_NEXT_SGL);
+       if (q_op.cmd_reg == cpu_to_le32(OP_BLOCK_ERASE))
+-              write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 2, NAND_BAM_NEXT_SGL);
++              qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 2, NAND_BAM_NEXT_SGL);
+-      write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
+-      read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++      qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL);
+-      ret = submit_descs(nandc);
++      ret = qcom_submit_descs(nandc);
+       if (ret) {
+               dev_err(nandc->dev, "failure in submitting misc descriptor\n");
+               goto err_out;
+@@ -2825,8 +2825,8 @@ static int qcom_param_page_type_exec(str
+       nandc->buf_count = 0;
+       nandc->buf_start = 0;
+       host->use_ecc = false;
+-      clear_read_regs(nandc);
+-      clear_bam_transaction(nandc);
++      qcom_clear_read_regs(nandc);
++      qcom_clear_bam_transaction(nandc);
+       nandc->regs->cmd = q_op.cmd_reg;
+       nandc->regs->addr0 = 0;
+@@ -2872,8 +2872,8 @@ static int qcom_param_page_type_exec(str
+               nandc_set_read_loc_first(chip, reg_base, 0, len, 1);
+       if (!nandc->props->qpic_version2) {
+-              write_reg_dma(nandc, &nandc->regs->vld, NAND_DEV_CMD_VLD, 1, 0);
+-              write_reg_dma(nandc, &nandc->regs->cmd1, NAND_DEV_CMD1, 1, NAND_BAM_NEXT_SGL);
++              qcom_write_reg_dma(nandc, &nandc->regs->vld, NAND_DEV_CMD_VLD, 1, 0);
++              qcom_write_reg_dma(nandc, &nandc->regs->cmd1, NAND_DEV_CMD1, 1, NAND_BAM_NEXT_SGL);
+       }
+       nandc->buf_count = 512;
+@@ -2881,17 +2881,17 @@ static int qcom_param_page_type_exec(str
+       config_nand_single_cw_page_read(chip, false, 0);
+-      read_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer,
+-                    nandc->buf_count, 0);
++      qcom_read_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer,
++                         nandc->buf_count, 0);
+       /* restore CMD1 and VLD regs */
+       if (!nandc->props->qpic_version2) {
+-              write_reg_dma(nandc, &nandc->regs->orig_cmd1, NAND_DEV_CMD1_RESTORE, 1, 0);
+-              write_reg_dma(nandc, &nandc->regs->orig_vld, NAND_DEV_CMD_VLD_RESTORE, 1,
+-                            NAND_BAM_NEXT_SGL);
++              qcom_write_reg_dma(nandc, &nandc->regs->orig_cmd1, NAND_DEV_CMD1_RESTORE, 1, 0);
++              qcom_write_reg_dma(nandc, &nandc->regs->orig_vld, NAND_DEV_CMD_VLD_RESTORE, 1,
++                                 NAND_BAM_NEXT_SGL);
+       }
+-      ret = submit_descs(nandc);
++      ret = qcom_submit_descs(nandc);
+       if (ret) {
+               dev_err(nandc->dev, "failure in submitting param page descriptor\n");
+               goto err_out;
+@@ -3075,7 +3075,7 @@ static int qcom_nandc_alloc(struct qcom_
+                * maximum codeword size
+                */
+               nandc->max_cwperpage = 1;
+-              nandc->bam_txn = alloc_bam_transaction(nandc);
++              nandc->bam_txn = qcom_alloc_bam_transaction(nandc);
+               if (!nandc->bam_txn) {
+                       dev_err(nandc->dev,
+                               "failed to allocate bam transaction\n");
diff --git a/target/linux/generic/backport-6.12/410-03-v6.14-mtd-nand-Add-qpic_common-API-file.patch b/target/linux/generic/backport-6.12/410-03-v6.14-mtd-nand-Add-qpic_common-API-file.patch
new file mode 100644 (file)
index 0000000..9272f98
--- /dev/null
@@ -0,0 +1,2436 @@
+From fdf3ee5c6e5278dab4f60b998b47ed2d510bf80f Mon Sep 17 00:00:00 2001
+From: Md Sadre Alam <quic_mdalam@quicinc.com>
+Date: Wed, 20 Nov 2024 14:45:02 +0530
+Subject: [PATCH 3/4] mtd: nand: Add qpic_common API file
+
+Add qpic_common.c file which hold all the common
+qpic APIs which will be used by both qpic raw nand
+driver and qpic spi nand driver.
+
+Signed-off-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/Makefile            |    2 +-
+ drivers/mtd/nand/qpic_common.c       |  759 ++++++++++++++++++
+ drivers/mtd/nand/raw/qcom_nandc.c    | 1092 +-------------------------
+ include/linux/mtd/nand-qpic-common.h |  468 +++++++++++
+ 4 files changed, 1240 insertions(+), 1081 deletions(-)
+ create mode 100644 drivers/mtd/nand/qpic_common.c
+ create mode 100644 include/linux/mtd/nand-qpic-common.h
+
+--- a/drivers/mtd/nand/Makefile
++++ b/drivers/mtd/nand/Makefile
+@@ -3,7 +3,7 @@
+ nandcore-objs := core.o bbt.o
+ obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
+ obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o
+-
++obj-$(CONFIG_MTD_NAND_QCOM) += qpic_common.o
+ obj-y += onenand/
+ obj-y += raw/
+ obj-y += spi/
+--- /dev/null
++++ b/drivers/mtd/nand/qpic_common.c
+@@ -0,0 +1,759 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved
++ */
++#include <linux/clk.h>
++#include <linux/delay.h>
++#include <linux/dmaengine.h>
++#include <linux/dma-mapping.h>
++#include <linux/dma/qcom_adm.h>
++#include <linux/dma/qcom_bam_dma.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/slab.h>
++#include <linux/mtd/nand-qpic-common.h>
++
++/**
++ * qcom_free_bam_transaction() - Frees the BAM transaction memory
++ * @nandc: qpic nand controller
++ *
++ * This function frees the bam transaction memory
++ */
++void qcom_free_bam_transaction(struct qcom_nand_controller *nandc)
++{
++      struct bam_transaction *bam_txn = nandc->bam_txn;
++
++      kfree(bam_txn);
++}
++EXPORT_SYMBOL(qcom_free_bam_transaction);
++
++/**
++ * qcom_alloc_bam_transaction() - allocate BAM transaction
++ * @nandc: qpic nand controller
++ *
++ * This function will allocate and initialize the BAM transaction structure
++ */
++struct bam_transaction *
++qcom_alloc_bam_transaction(struct qcom_nand_controller *nandc)
++{
++      struct bam_transaction *bam_txn;
++      size_t bam_txn_size;
++      unsigned int num_cw = nandc->max_cwperpage;
++      void *bam_txn_buf;
++
++      bam_txn_size =
++              sizeof(*bam_txn) + num_cw *
++              ((sizeof(*bam_txn->bam_ce) * QPIC_PER_CW_CMD_ELEMENTS) +
++              (sizeof(*bam_txn->cmd_sgl) * QPIC_PER_CW_CMD_SGL) +
++              (sizeof(*bam_txn->data_sgl) * QPIC_PER_CW_DATA_SGL));
++
++      bam_txn_buf = kzalloc(bam_txn_size, GFP_KERNEL);
++      if (!bam_txn_buf)
++              return NULL;
++
++      bam_txn = bam_txn_buf;
++      bam_txn_buf += sizeof(*bam_txn);
++
++      bam_txn->bam_ce = bam_txn_buf;
++      bam_txn_buf +=
++              sizeof(*bam_txn->bam_ce) * QPIC_PER_CW_CMD_ELEMENTS * num_cw;
++
++      bam_txn->cmd_sgl = bam_txn_buf;
++      bam_txn_buf +=
++              sizeof(*bam_txn->cmd_sgl) * QPIC_PER_CW_CMD_SGL * num_cw;
++
++      bam_txn->data_sgl = bam_txn_buf;
++
++      init_completion(&bam_txn->txn_done);
++
++      return bam_txn;
++}
++EXPORT_SYMBOL(qcom_alloc_bam_transaction);
++
++/**
++ * qcom_clear_bam_transaction() - Clears the BAM transaction
++ * @nandc: qpic nand controller
++ *
++ * This function will clear the BAM transaction indexes.
++ */
++void qcom_clear_bam_transaction(struct qcom_nand_controller *nandc)
++{
++      struct bam_transaction *bam_txn = nandc->bam_txn;
++
++      if (!nandc->props->supports_bam)
++              return;
++
++      memset(&bam_txn->bam_ce_pos, 0, sizeof(u32) * 8);
++      bam_txn->last_data_desc = NULL;
++
++      sg_init_table(bam_txn->cmd_sgl, nandc->max_cwperpage *
++                    QPIC_PER_CW_CMD_SGL);
++      sg_init_table(bam_txn->data_sgl, nandc->max_cwperpage *
++                    QPIC_PER_CW_DATA_SGL);
++
++      reinit_completion(&bam_txn->txn_done);
++}
++EXPORT_SYMBOL(qcom_clear_bam_transaction);
++
++/**
++ * qcom_qpic_bam_dma_done() - Callback for DMA descriptor completion
++ * @data: data pointer
++ *
++ * This function is a callback for DMA descriptor completion
++ */
++void qcom_qpic_bam_dma_done(void *data)
++{
++      struct bam_transaction *bam_txn = data;
++
++      complete(&bam_txn->txn_done);
++}
++EXPORT_SYMBOL(qcom_qpic_bam_dma_done);
++
++/**
++ * qcom_nandc_dev_to_mem() - Check for dma sync for cpu or device
++ * @nandc: qpic nand controller
++ * @is_cpu: cpu or Device
++ *
++ * This function will check for dma sync for cpu or device
++ */
++inline void qcom_nandc_dev_to_mem(struct qcom_nand_controller *nandc, bool is_cpu)
++{
++      if (!nandc->props->supports_bam)
++              return;
++
++      if (is_cpu)
++              dma_sync_single_for_cpu(nandc->dev, nandc->reg_read_dma,
++                                      MAX_REG_RD *
++                                      sizeof(*nandc->reg_read_buf),
++                                      DMA_FROM_DEVICE);
++      else
++              dma_sync_single_for_device(nandc->dev, nandc->reg_read_dma,
++                                         MAX_REG_RD *
++                                         sizeof(*nandc->reg_read_buf),
++                                         DMA_FROM_DEVICE);
++}
++EXPORT_SYMBOL(qcom_nandc_dev_to_mem);
++
++/**
++ * qcom_prepare_bam_async_desc() - Prepare DMA descriptor
++ * @nandc: qpic nand controller
++ * @chan: dma channel
++ * @flags: flags to control DMA descriptor preparation
++ *
++ * This function maps the scatter gather list for DMA transfer and forms the
++ * DMA descriptor for BAM.This descriptor will be added in the NAND DMA
++ * descriptor queue which will be submitted to DMA engine.
++ */
++int qcom_prepare_bam_async_desc(struct qcom_nand_controller *nandc,
++                              struct dma_chan *chan, unsigned long flags)
++{
++      struct desc_info *desc;
++      struct scatterlist *sgl;
++      unsigned int sgl_cnt;
++      int ret;
++      struct bam_transaction *bam_txn = nandc->bam_txn;
++      enum dma_transfer_direction dir_eng;
++      struct dma_async_tx_descriptor *dma_desc;
++
++      desc = kzalloc(sizeof(*desc), GFP_KERNEL);
++      if (!desc)
++              return -ENOMEM;
++
++      if (chan == nandc->cmd_chan) {
++              sgl = &bam_txn->cmd_sgl[bam_txn->cmd_sgl_start];
++              sgl_cnt = bam_txn->cmd_sgl_pos - bam_txn->cmd_sgl_start;
++              bam_txn->cmd_sgl_start = bam_txn->cmd_sgl_pos;
++              dir_eng = DMA_MEM_TO_DEV;
++              desc->dir = DMA_TO_DEVICE;
++      } else if (chan == nandc->tx_chan) {
++              sgl = &bam_txn->data_sgl[bam_txn->tx_sgl_start];
++              sgl_cnt = bam_txn->tx_sgl_pos - bam_txn->tx_sgl_start;
++              bam_txn->tx_sgl_start = bam_txn->tx_sgl_pos;
++              dir_eng = DMA_MEM_TO_DEV;
++              desc->dir = DMA_TO_DEVICE;
++      } else {
++              sgl = &bam_txn->data_sgl[bam_txn->rx_sgl_start];
++              sgl_cnt = bam_txn->rx_sgl_pos - bam_txn->rx_sgl_start;
++              bam_txn->rx_sgl_start = bam_txn->rx_sgl_pos;
++              dir_eng = DMA_DEV_TO_MEM;
++              desc->dir = DMA_FROM_DEVICE;
++      }
++
++      sg_mark_end(sgl + sgl_cnt - 1);
++      ret = dma_map_sg(nandc->dev, sgl, sgl_cnt, desc->dir);
++      if (ret == 0) {
++              dev_err(nandc->dev, "failure in mapping desc\n");
++              kfree(desc);
++              return -ENOMEM;
++      }
++
++      desc->sgl_cnt = sgl_cnt;
++      desc->bam_sgl = sgl;
++
++      dma_desc = dmaengine_prep_slave_sg(chan, sgl, sgl_cnt, dir_eng,
++                                         flags);
++
++      if (!dma_desc) {
++              dev_err(nandc->dev, "failure in prep desc\n");
++              dma_unmap_sg(nandc->dev, sgl, sgl_cnt, desc->dir);
++              kfree(desc);
++              return -EINVAL;
++      }
++
++      desc->dma_desc = dma_desc;
++
++      /* update last data/command descriptor */
++      if (chan == nandc->cmd_chan)
++              bam_txn->last_cmd_desc = dma_desc;
++      else
++              bam_txn->last_data_desc = dma_desc;
++
++      list_add_tail(&desc->node, &nandc->desc_list);
++
++      return 0;
++}
++EXPORT_SYMBOL(qcom_prepare_bam_async_desc);
++
++/**
++ * qcom_prep_bam_dma_desc_cmd() - Prepares the command descriptor for BAM DMA
++ * @nandc: qpic nand controller
++ * @read: read or write type
++ * @reg_off: offset within the controller's data buffer
++ * @vaddr: virtual address of the buffer we want to write to
++ * @size: DMA transaction size in bytes
++ * @flags: flags to control DMA descriptor preparation
++ *
++ * This function will prepares the command descriptor for BAM DMA
++ * which will be used for NAND register reads and writes.
++ */
++int qcom_prep_bam_dma_desc_cmd(struct qcom_nand_controller *nandc, bool read,
++                             int reg_off, const void *vaddr,
++                             int size, unsigned int flags)
++{
++      int bam_ce_size;
++      int i, ret;
++      struct bam_cmd_element *bam_ce_buffer;
++      struct bam_transaction *bam_txn = nandc->bam_txn;
++
++      bam_ce_buffer = &bam_txn->bam_ce[bam_txn->bam_ce_pos];
++
++      /* fill the command desc */
++      for (i = 0; i < size; i++) {
++              if (read)
++                      bam_prep_ce(&bam_ce_buffer[i],
++                                  nandc_reg_phys(nandc, reg_off + 4 * i),
++                                  BAM_READ_COMMAND,
++                                  reg_buf_dma_addr(nandc,
++                                                   (__le32 *)vaddr + i));
++              else
++                      bam_prep_ce_le32(&bam_ce_buffer[i],
++                                       nandc_reg_phys(nandc, reg_off + 4 * i),
++                                       BAM_WRITE_COMMAND,
++                                       *((__le32 *)vaddr + i));
++      }
++
++      bam_txn->bam_ce_pos += size;
++
++      /* use the separate sgl after this command */
++      if (flags & NAND_BAM_NEXT_SGL) {
++              bam_ce_buffer = &bam_txn->bam_ce[bam_txn->bam_ce_start];
++              bam_ce_size = (bam_txn->bam_ce_pos -
++                              bam_txn->bam_ce_start) *
++                              sizeof(struct bam_cmd_element);
++              sg_set_buf(&bam_txn->cmd_sgl[bam_txn->cmd_sgl_pos],
++                         bam_ce_buffer, bam_ce_size);
++              bam_txn->cmd_sgl_pos++;
++              bam_txn->bam_ce_start = bam_txn->bam_ce_pos;
++
++              if (flags & NAND_BAM_NWD) {
++                      ret = qcom_prepare_bam_async_desc(nandc, nandc->cmd_chan,
++                                                        DMA_PREP_FENCE | DMA_PREP_CMD);
++                      if (ret)
++                              return ret;
++              }
++      }
++
++      return 0;
++}
++EXPORT_SYMBOL(qcom_prep_bam_dma_desc_cmd);
++
++/**
++ * qcom_prep_bam_dma_desc_data() - Prepares the data descriptor for BAM DMA
++ * @nandc: qpic nand controller
++ * @read: read or write type
++ * @vaddr: virtual address of the buffer we want to write to
++ * @size: DMA transaction size in bytes
++ * @flags: flags to control DMA descriptor preparation
++ *
++ * This function will prepares the data descriptor for BAM DMA which
++ * will be used for NAND data reads and writes.
++ */
++int qcom_prep_bam_dma_desc_data(struct qcom_nand_controller *nandc, bool read,
++                              const void *vaddr, int size, unsigned int flags)
++{
++      int ret;
++      struct bam_transaction *bam_txn = nandc->bam_txn;
++
++      if (read) {
++              sg_set_buf(&bam_txn->data_sgl[bam_txn->rx_sgl_pos],
++                         vaddr, size);
++              bam_txn->rx_sgl_pos++;
++      } else {
++              sg_set_buf(&bam_txn->data_sgl[bam_txn->tx_sgl_pos],
++                         vaddr, size);
++              bam_txn->tx_sgl_pos++;
++
++              /*
++               * BAM will only set EOT for DMA_PREP_INTERRUPT so if this flag
++               * is not set, form the DMA descriptor
++               */
++              if (!(flags & NAND_BAM_NO_EOT)) {
++                      ret = qcom_prepare_bam_async_desc(nandc, nandc->tx_chan,
++                                                        DMA_PREP_INTERRUPT);
++                      if (ret)
++                              return ret;
++              }
++      }
++
++      return 0;
++}
++EXPORT_SYMBOL(qcom_prep_bam_dma_desc_data);
++
++/**
++ * qcom_prep_adm_dma_desc() - Prepare descriptor for adma
++ * @nandc: qpic nand controller
++ * @read: read or write type
++ * @reg_off: offset within the controller's data buffer
++ * @vaddr: virtual address of the buffer we want to write to
++ * @size: adm dma transaction size in bytes
++ * @flow_control: flow controller
++ *
++ * This function will prepare descriptor for adma
++ */
++int qcom_prep_adm_dma_desc(struct qcom_nand_controller *nandc, bool read,
++                         int reg_off, const void *vaddr, int size,
++                         bool flow_control)
++{
++      struct qcom_adm_peripheral_config periph_conf = {};
++      struct dma_async_tx_descriptor *dma_desc;
++      struct dma_slave_config slave_conf = {0};
++      enum dma_transfer_direction dir_eng;
++      struct desc_info *desc;
++      struct scatterlist *sgl;
++      int ret;
++
++      desc = kzalloc(sizeof(*desc), GFP_KERNEL);
++      if (!desc)
++              return -ENOMEM;
++
++      sgl = &desc->adm_sgl;
++
++      sg_init_one(sgl, vaddr, size);
++
++      if (read) {
++              dir_eng = DMA_DEV_TO_MEM;
++              desc->dir = DMA_FROM_DEVICE;
++      } else {
++              dir_eng = DMA_MEM_TO_DEV;
++              desc->dir = DMA_TO_DEVICE;
++      }
++
++      ret = dma_map_sg(nandc->dev, sgl, 1, desc->dir);
++      if (!ret) {
++              ret = -ENOMEM;
++              goto err;
++      }
++
++      slave_conf.device_fc = flow_control;
++      if (read) {
++              slave_conf.src_maxburst = 16;
++              slave_conf.src_addr = nandc->base_dma + reg_off;
++              if (nandc->data_crci) {
++                      periph_conf.crci = nandc->data_crci;
++                      slave_conf.peripheral_config = &periph_conf;
++                      slave_conf.peripheral_size = sizeof(periph_conf);
++              }
++      } else {
++              slave_conf.dst_maxburst = 16;
++              slave_conf.dst_addr = nandc->base_dma + reg_off;
++              if (nandc->cmd_crci) {
++                      periph_conf.crci = nandc->cmd_crci;
++                      slave_conf.peripheral_config = &periph_conf;
++                      slave_conf.peripheral_size = sizeof(periph_conf);
++              }
++      }
++
++      ret = dmaengine_slave_config(nandc->chan, &slave_conf);
++      if (ret) {
++              dev_err(nandc->dev, "failed to configure dma channel\n");
++              goto err;
++      }
++
++      dma_desc = dmaengine_prep_slave_sg(nandc->chan, sgl, 1, dir_eng, 0);
++      if (!dma_desc) {
++              dev_err(nandc->dev, "failed to prepare desc\n");
++              ret = -EINVAL;
++              goto err;
++      }
++
++      desc->dma_desc = dma_desc;
++
++      list_add_tail(&desc->node, &nandc->desc_list);
++
++      return 0;
++err:
++      kfree(desc);
++
++      return ret;
++}
++EXPORT_SYMBOL(qcom_prep_adm_dma_desc);
++
++/**
++ * qcom_read_reg_dma() - read a given number of registers to the reg_read_buf pointer
++ * @nandc: qpic nand controller
++ * @first: offset of the first register in the contiguous block
++ * @num_regs: number of registers to read
++ * @flags: flags to control DMA descriptor preparation
++ *
++ * This function will prepares a descriptor to read a given number of
++ * contiguous registers to the reg_read_buf pointer.
++ */
++int qcom_read_reg_dma(struct qcom_nand_controller *nandc, int first,
++                    int num_regs, unsigned int flags)
++{
++      bool flow_control = false;
++      void *vaddr;
++
++      vaddr = nandc->reg_read_buf + nandc->reg_read_pos;
++      nandc->reg_read_pos += num_regs;
++
++      if (first == NAND_DEV_CMD_VLD || first == NAND_DEV_CMD1)
++              first = dev_cmd_reg_addr(nandc, first);
++
++      if (nandc->props->supports_bam)
++              return qcom_prep_bam_dma_desc_cmd(nandc, true, first, vaddr,
++                                           num_regs, flags);
++
++      if (first == NAND_READ_ID || first == NAND_FLASH_STATUS)
++              flow_control = true;
++
++      return qcom_prep_adm_dma_desc(nandc, true, first, vaddr,
++                                    num_regs * sizeof(u32), flow_control);
++}
++EXPORT_SYMBOL(qcom_read_reg_dma);
++
++/**
++ * qcom_write_reg_dma() - write a given number of registers
++ * @nandc: qpic nand controller
++ * @vaddr: contiguous memory from where register value will
++ *       be written
++ * @first: offset of the first register in the contiguous block
++ * @num_regs: number of registers to write
++ * @flags: flags to control DMA descriptor preparation
++ *
++ * This function will prepares a descriptor to write a given number of
++ * contiguous registers
++ */
++int qcom_write_reg_dma(struct qcom_nand_controller *nandc, __le32 *vaddr,
++                     int first, int num_regs, unsigned int flags)
++{
++      bool flow_control = false;
++
++      if (first == NAND_EXEC_CMD)
++              flags |= NAND_BAM_NWD;
++
++      if (first == NAND_DEV_CMD1_RESTORE || first == NAND_DEV_CMD1)
++              first = dev_cmd_reg_addr(nandc, NAND_DEV_CMD1);
++
++      if (first == NAND_DEV_CMD_VLD_RESTORE || first == NAND_DEV_CMD_VLD)
++              first = dev_cmd_reg_addr(nandc, NAND_DEV_CMD_VLD);
++
++      if (nandc->props->supports_bam)
++              return qcom_prep_bam_dma_desc_cmd(nandc, false, first, vaddr,
++                                                num_regs, flags);
++
++      if (first == NAND_FLASH_CMD)
++              flow_control = true;
++
++      return qcom_prep_adm_dma_desc(nandc, false, first, vaddr,
++                                    num_regs * sizeof(u32), flow_control);
++}
++EXPORT_SYMBOL(qcom_write_reg_dma);
++
++/**
++ * qcom_read_data_dma() - transfer data
++ * @nandc: qpic nand controller
++ * @reg_off: offset within the controller's data buffer
++ * @vaddr: virtual address of the buffer we want to write to
++ * @size: DMA transaction size in bytes
++ * @flags: flags to control DMA descriptor preparation
++ *
++ * This function will prepares a DMA descriptor to transfer data from the
++ * controller's internal buffer to the buffer 'vaddr'
++ */
++int qcom_read_data_dma(struct qcom_nand_controller *nandc, int reg_off,
++                     const u8 *vaddr, int size, unsigned int flags)
++{
++      if (nandc->props->supports_bam)
++              return qcom_prep_bam_dma_desc_data(nandc, true, vaddr, size, flags);
++
++      return qcom_prep_adm_dma_desc(nandc, true, reg_off, vaddr, size, false);
++}
++EXPORT_SYMBOL(qcom_read_data_dma);
++
++/**
++ * qcom_write_data_dma() - transfer data
++ * @nandc: qpic nand controller
++ * @reg_off: offset within the controller's data buffer
++ * @vaddr: virtual address of the buffer we want to read from
++ * @size: DMA transaction size in bytes
++ * @flags: flags to control DMA descriptor preparation
++ *
++ * This function will prepares a DMA descriptor to transfer data from
++ * 'vaddr' to the controller's internal buffer
++ */
++int qcom_write_data_dma(struct qcom_nand_controller *nandc, int reg_off,
++                      const u8 *vaddr, int size, unsigned int flags)
++{
++      if (nandc->props->supports_bam)
++              return qcom_prep_bam_dma_desc_data(nandc, false, vaddr, size, flags);
++
++      return qcom_prep_adm_dma_desc(nandc, false, reg_off, vaddr, size, false);
++}
++EXPORT_SYMBOL(qcom_write_data_dma);
++
++/**
++ * qcom_submit_descs() - submit dma descriptor
++ * @nandc: qpic nand controller
++ *
++ * This function will submit all the prepared dma descriptor
++ * cmd or data descriptor
++ */
++int qcom_submit_descs(struct qcom_nand_controller *nandc)
++{
++      struct desc_info *desc, *n;
++      dma_cookie_t cookie = 0;
++      struct bam_transaction *bam_txn = nandc->bam_txn;
++      int ret = 0;
++
++      if (nandc->props->supports_bam) {
++              if (bam_txn->rx_sgl_pos > bam_txn->rx_sgl_start) {
++                      ret = qcom_prepare_bam_async_desc(nandc, nandc->rx_chan, 0);
++                      if (ret)
++                              goto err_unmap_free_desc;
++              }
++
++              if (bam_txn->tx_sgl_pos > bam_txn->tx_sgl_start) {
++                      ret = qcom_prepare_bam_async_desc(nandc, nandc->tx_chan,
++                                                        DMA_PREP_INTERRUPT);
++                      if (ret)
++                              goto err_unmap_free_desc;
++              }
++
++              if (bam_txn->cmd_sgl_pos > bam_txn->cmd_sgl_start) {
++                      ret = qcom_prepare_bam_async_desc(nandc, nandc->cmd_chan,
++                                                        DMA_PREP_CMD);
++                      if (ret)
++                              goto err_unmap_free_desc;
++              }
++      }
++
++      list_for_each_entry(desc, &nandc->desc_list, node)
++              cookie = dmaengine_submit(desc->dma_desc);
++
++      if (nandc->props->supports_bam) {
++              bam_txn->last_cmd_desc->callback = qcom_qpic_bam_dma_done;
++              bam_txn->last_cmd_desc->callback_param = bam_txn;
++
++              dma_async_issue_pending(nandc->tx_chan);
++              dma_async_issue_pending(nandc->rx_chan);
++              dma_async_issue_pending(nandc->cmd_chan);
++
++              if (!wait_for_completion_timeout(&bam_txn->txn_done,
++                                               QPIC_NAND_COMPLETION_TIMEOUT))
++                      ret = -ETIMEDOUT;
++      } else {
++              if (dma_sync_wait(nandc->chan, cookie) != DMA_COMPLETE)
++                      ret = -ETIMEDOUT;
++      }
++
++err_unmap_free_desc:
++      /*
++       * Unmap the dma sg_list and free the desc allocated by both
++       * qcom_prepare_bam_async_desc() and qcom_prep_adm_dma_desc() functions.
++       */
++      list_for_each_entry_safe(desc, n, &nandc->desc_list, node) {
++              list_del(&desc->node);
++
++              if (nandc->props->supports_bam)
++                      dma_unmap_sg(nandc->dev, desc->bam_sgl,
++                                   desc->sgl_cnt, desc->dir);
++              else
++                      dma_unmap_sg(nandc->dev, &desc->adm_sgl, 1,
++                                   desc->dir);
++
++              kfree(desc);
++      }
++
++      return ret;
++}
++EXPORT_SYMBOL(qcom_submit_descs);
++
++/**
++ * qcom_clear_read_regs() - reset the read register buffer
++ * @nandc: qpic nand controller
++ *
++ * This function reset the register read buffer for next NAND operation
++ */
++void qcom_clear_read_regs(struct qcom_nand_controller *nandc)
++{
++      nandc->reg_read_pos = 0;
++      qcom_nandc_dev_to_mem(nandc, false);
++}
++EXPORT_SYMBOL(qcom_clear_read_regs);
++
++/**
++ * qcom_nandc_unalloc() - unallocate qpic nand controller
++ * @nandc: qpic nand controller
++ *
++ * This function will unallocate memory alloacted for qpic nand controller
++ */
++void qcom_nandc_unalloc(struct qcom_nand_controller *nandc)
++{
++      if (nandc->props->supports_bam) {
++              if (!dma_mapping_error(nandc->dev, nandc->reg_read_dma))
++                      dma_unmap_single(nandc->dev, nandc->reg_read_dma,
++                                       MAX_REG_RD *
++                                       sizeof(*nandc->reg_read_buf),
++                                       DMA_FROM_DEVICE);
++
++              if (nandc->tx_chan)
++                      dma_release_channel(nandc->tx_chan);
++
++              if (nandc->rx_chan)
++                      dma_release_channel(nandc->rx_chan);
++
++              if (nandc->cmd_chan)
++                      dma_release_channel(nandc->cmd_chan);
++      } else {
++              if (nandc->chan)
++                      dma_release_channel(nandc->chan);
++      }
++}
++EXPORT_SYMBOL(qcom_nandc_unalloc);
++
++/**
++ * qcom_nandc_alloc() - Allocate qpic nand controller
++ * @nandc: qpic nand controller
++ *
++ * This function will allocate memory for qpic nand controller
++ */
++int qcom_nandc_alloc(struct qcom_nand_controller *nandc)
++{
++      int ret;
++
++      ret = dma_set_coherent_mask(nandc->dev, DMA_BIT_MASK(32));
++      if (ret) {
++              dev_err(nandc->dev, "failed to set DMA mask\n");
++              return ret;
++      }
++
++      /*
++       * we use the internal buffer for reading ONFI params, reading small
++       * data like ID and status, and preforming read-copy-write operations
++       * when writing to a codeword partially. 532 is the maximum possible
++       * size of a codeword for our nand controller
++       */
++      nandc->buf_size = 532;
++
++      nandc->data_buffer = devm_kzalloc(nandc->dev, nandc->buf_size, GFP_KERNEL);
++      if (!nandc->data_buffer)
++              return -ENOMEM;
++
++      nandc->regs = devm_kzalloc(nandc->dev, sizeof(*nandc->regs), GFP_KERNEL);
++      if (!nandc->regs)
++              return -ENOMEM;
++
++      nandc->reg_read_buf = devm_kcalloc(nandc->dev, MAX_REG_RD,
++                                         sizeof(*nandc->reg_read_buf),
++                                         GFP_KERNEL);
++      if (!nandc->reg_read_buf)
++              return -ENOMEM;
++
++      if (nandc->props->supports_bam) {
++              nandc->reg_read_dma =
++                      dma_map_single(nandc->dev, nandc->reg_read_buf,
++                                     MAX_REG_RD *
++                                     sizeof(*nandc->reg_read_buf),
++                                     DMA_FROM_DEVICE);
++              if (dma_mapping_error(nandc->dev, nandc->reg_read_dma)) {
++                      dev_err(nandc->dev, "failed to DMA MAP reg buffer\n");
++                      return -EIO;
++              }
++
++              nandc->tx_chan = dma_request_chan(nandc->dev, "tx");
++              if (IS_ERR(nandc->tx_chan)) {
++                      ret = PTR_ERR(nandc->tx_chan);
++                      nandc->tx_chan = NULL;
++                      dev_err_probe(nandc->dev, ret,
++                                    "tx DMA channel request failed\n");
++                      goto unalloc;
++              }
++
++              nandc->rx_chan = dma_request_chan(nandc->dev, "rx");
++              if (IS_ERR(nandc->rx_chan)) {
++                      ret = PTR_ERR(nandc->rx_chan);
++                      nandc->rx_chan = NULL;
++                      dev_err_probe(nandc->dev, ret,
++                                    "rx DMA channel request failed\n");
++                      goto unalloc;
++              }
++
++              nandc->cmd_chan = dma_request_chan(nandc->dev, "cmd");
++              if (IS_ERR(nandc->cmd_chan)) {
++                      ret = PTR_ERR(nandc->cmd_chan);
++                      nandc->cmd_chan = NULL;
++                      dev_err_probe(nandc->dev, ret,
++                                    "cmd DMA channel request failed\n");
++                      goto unalloc;
++              }
++
++              /*
++               * Initially allocate BAM transaction to read ONFI param page.
++               * After detecting all the devices, this BAM transaction will
++               * be freed and the next BAM transaction will be allocated with
++               * maximum codeword size
++               */
++              nandc->max_cwperpage = 1;
++              nandc->bam_txn = qcom_alloc_bam_transaction(nandc);
++              if (!nandc->bam_txn) {
++                      dev_err(nandc->dev,
++                              "failed to allocate bam transaction\n");
++                      ret = -ENOMEM;
++                      goto unalloc;
++              }
++      } else {
++              nandc->chan = dma_request_chan(nandc->dev, "rxtx");
++              if (IS_ERR(nandc->chan)) {
++                      ret = PTR_ERR(nandc->chan);
++                      nandc->chan = NULL;
++                      dev_err_probe(nandc->dev, ret,
++                                    "rxtx DMA channel request failed\n");
++                      return ret;
++              }
++      }
++
++      INIT_LIST_HEAD(&nandc->desc_list);
++      INIT_LIST_HEAD(&nandc->host_list);
++
++      return 0;
++unalloc:
++      qcom_nandc_unalloc(nandc);
++      return ret;
++}
++EXPORT_SYMBOL(qcom_nandc_alloc);
++
++MODULE_DESCRIPTION("QPIC controller common api");
++MODULE_LICENSE("GPL");
+--- a/drivers/mtd/nand/raw/qcom_nandc.c
++++ b/drivers/mtd/nand/raw/qcom_nandc.c
+@@ -15,417 +15,7 @@
+ #include <linux/of.h>
+ #include <linux/platform_device.h>
+ #include <linux/slab.h>
+-
+-/* NANDc reg offsets */
+-#define       NAND_FLASH_CMD                  0x00
+-#define       NAND_ADDR0                      0x04
+-#define       NAND_ADDR1                      0x08
+-#define       NAND_FLASH_CHIP_SELECT          0x0c
+-#define       NAND_EXEC_CMD                   0x10
+-#define       NAND_FLASH_STATUS               0x14
+-#define       NAND_BUFFER_STATUS              0x18
+-#define       NAND_DEV0_CFG0                  0x20
+-#define       NAND_DEV0_CFG1                  0x24
+-#define       NAND_DEV0_ECC_CFG               0x28
+-#define       NAND_AUTO_STATUS_EN             0x2c
+-#define       NAND_DEV1_CFG0                  0x30
+-#define       NAND_DEV1_CFG1                  0x34
+-#define       NAND_READ_ID                    0x40
+-#define       NAND_READ_STATUS                0x44
+-#define       NAND_DEV_CMD0                   0xa0
+-#define       NAND_DEV_CMD1                   0xa4
+-#define       NAND_DEV_CMD2                   0xa8
+-#define       NAND_DEV_CMD_VLD                0xac
+-#define       SFLASHC_BURST_CFG               0xe0
+-#define       NAND_ERASED_CW_DETECT_CFG       0xe8
+-#define       NAND_ERASED_CW_DETECT_STATUS    0xec
+-#define       NAND_EBI2_ECC_BUF_CFG           0xf0
+-#define       FLASH_BUF_ACC                   0x100
+-
+-#define       NAND_CTRL                       0xf00
+-#define       NAND_VERSION                    0xf08
+-#define       NAND_READ_LOCATION_0            0xf20
+-#define       NAND_READ_LOCATION_1            0xf24
+-#define       NAND_READ_LOCATION_2            0xf28
+-#define       NAND_READ_LOCATION_3            0xf2c
+-#define       NAND_READ_LOCATION_LAST_CW_0    0xf40
+-#define       NAND_READ_LOCATION_LAST_CW_1    0xf44
+-#define       NAND_READ_LOCATION_LAST_CW_2    0xf48
+-#define       NAND_READ_LOCATION_LAST_CW_3    0xf4c
+-
+-/* dummy register offsets, used by qcom_write_reg_dma */
+-#define       NAND_DEV_CMD1_RESTORE           0xdead
+-#define       NAND_DEV_CMD_VLD_RESTORE        0xbeef
+-
+-/* NAND_FLASH_CMD bits */
+-#define       PAGE_ACC                        BIT(4)
+-#define       LAST_PAGE                       BIT(5)
+-
+-/* NAND_FLASH_CHIP_SELECT bits */
+-#define       NAND_DEV_SEL                    0
+-#define       DM_EN                           BIT(2)
+-
+-/* NAND_FLASH_STATUS bits */
+-#define       FS_OP_ERR                       BIT(4)
+-#define       FS_READY_BSY_N                  BIT(5)
+-#define       FS_MPU_ERR                      BIT(8)
+-#define       FS_DEVICE_STS_ERR               BIT(16)
+-#define       FS_DEVICE_WP                    BIT(23)
+-
+-/* NAND_BUFFER_STATUS bits */
+-#define       BS_UNCORRECTABLE_BIT            BIT(8)
+-#define       BS_CORRECTABLE_ERR_MSK          0x1f
+-
+-/* NAND_DEVn_CFG0 bits */
+-#define       DISABLE_STATUS_AFTER_WRITE      4
+-#define       CW_PER_PAGE                     6
+-#define       UD_SIZE_BYTES                   9
+-#define       UD_SIZE_BYTES_MASK              GENMASK(18, 9)
+-#define       ECC_PARITY_SIZE_BYTES_RS        19
+-#define       SPARE_SIZE_BYTES                23
+-#define       SPARE_SIZE_BYTES_MASK           GENMASK(26, 23)
+-#define       NUM_ADDR_CYCLES                 27
+-#define       STATUS_BFR_READ                 30
+-#define       SET_RD_MODE_AFTER_STATUS        31
+-
+-/* NAND_DEVn_CFG0 bits */
+-#define       DEV0_CFG1_ECC_DISABLE           0
+-#define       WIDE_FLASH                      1
+-#define       NAND_RECOVERY_CYCLES            2
+-#define       CS_ACTIVE_BSY                   5
+-#define       BAD_BLOCK_BYTE_NUM              6
+-#define       BAD_BLOCK_IN_SPARE_AREA         16
+-#define       WR_RD_BSY_GAP                   17
+-#define       ENABLE_BCH_ECC                  27
+-
+-/* NAND_DEV0_ECC_CFG bits */
+-#define       ECC_CFG_ECC_DISABLE             0
+-#define       ECC_SW_RESET                    1
+-#define       ECC_MODE                        4
+-#define       ECC_PARITY_SIZE_BYTES_BCH       8
+-#define       ECC_NUM_DATA_BYTES              16
+-#define       ECC_NUM_DATA_BYTES_MASK         GENMASK(25, 16)
+-#define       ECC_FORCE_CLK_OPEN              30
+-
+-/* NAND_DEV_CMD1 bits */
+-#define       READ_ADDR                       0
+-
+-/* NAND_DEV_CMD_VLD bits */
+-#define       READ_START_VLD                  BIT(0)
+-#define       READ_STOP_VLD                   BIT(1)
+-#define       WRITE_START_VLD                 BIT(2)
+-#define       ERASE_START_VLD                 BIT(3)
+-#define       SEQ_READ_START_VLD              BIT(4)
+-
+-/* NAND_EBI2_ECC_BUF_CFG bits */
+-#define       NUM_STEPS                       0
+-
+-/* NAND_ERASED_CW_DETECT_CFG bits */
+-#define       ERASED_CW_ECC_MASK              1
+-#define       AUTO_DETECT_RES                 0
+-#define       MASK_ECC                        BIT(ERASED_CW_ECC_MASK)
+-#define       RESET_ERASED_DET                BIT(AUTO_DETECT_RES)
+-#define       ACTIVE_ERASED_DET               (0 << AUTO_DETECT_RES)
+-#define       CLR_ERASED_PAGE_DET             (RESET_ERASED_DET | MASK_ECC)
+-#define       SET_ERASED_PAGE_DET             (ACTIVE_ERASED_DET | MASK_ECC)
+-
+-/* NAND_ERASED_CW_DETECT_STATUS bits */
+-#define       PAGE_ALL_ERASED                 BIT(7)
+-#define       CODEWORD_ALL_ERASED             BIT(6)
+-#define       PAGE_ERASED                     BIT(5)
+-#define       CODEWORD_ERASED                 BIT(4)
+-#define       ERASED_PAGE                     (PAGE_ALL_ERASED | PAGE_ERASED)
+-#define       ERASED_CW                       (CODEWORD_ALL_ERASED | CODEWORD_ERASED)
+-
+-/* NAND_READ_LOCATION_n bits */
+-#define READ_LOCATION_OFFSET          0
+-#define READ_LOCATION_SIZE            16
+-#define READ_LOCATION_LAST            31
+-
+-/* Version Mask */
+-#define       NAND_VERSION_MAJOR_MASK         0xf0000000
+-#define       NAND_VERSION_MAJOR_SHIFT        28
+-#define       NAND_VERSION_MINOR_MASK         0x0fff0000
+-#define       NAND_VERSION_MINOR_SHIFT        16
+-
+-/* NAND OP_CMDs */
+-#define       OP_PAGE_READ                    0x2
+-#define       OP_PAGE_READ_WITH_ECC           0x3
+-#define       OP_PAGE_READ_WITH_ECC_SPARE     0x4
+-#define       OP_PAGE_READ_ONFI_READ          0x5
+-#define       OP_PROGRAM_PAGE                 0x6
+-#define       OP_PAGE_PROGRAM_WITH_ECC        0x7
+-#define       OP_PROGRAM_PAGE_SPARE           0x9
+-#define       OP_BLOCK_ERASE                  0xa
+-#define       OP_CHECK_STATUS                 0xc
+-#define       OP_FETCH_ID                     0xb
+-#define       OP_RESET_DEVICE                 0xd
+-
+-/* Default Value for NAND_DEV_CMD_VLD */
+-#define NAND_DEV_CMD_VLD_VAL          (READ_START_VLD | WRITE_START_VLD | \
+-                                       ERASE_START_VLD | SEQ_READ_START_VLD)
+-
+-/* NAND_CTRL bits */
+-#define       BAM_MODE_EN                     BIT(0)
+-
+-/*
+- * the NAND controller performs reads/writes with ECC in 516 byte chunks.
+- * the driver calls the chunks 'step' or 'codeword' interchangeably
+- */
+-#define       NANDC_STEP_SIZE                 512
+-
+-/*
+- * the largest page size we support is 8K, this will have 16 steps/codewords
+- * of 512 bytes each
+- */
+-#define       MAX_NUM_STEPS                   (SZ_8K / NANDC_STEP_SIZE)
+-
+-/* we read at most 3 registers per codeword scan */
+-#define       MAX_REG_RD                      (3 * MAX_NUM_STEPS)
+-
+-/* ECC modes supported by the controller */
+-#define       ECC_NONE        BIT(0)
+-#define       ECC_RS_4BIT     BIT(1)
+-#define       ECC_BCH_4BIT    BIT(2)
+-#define       ECC_BCH_8BIT    BIT(3)
+-
+-/*
+- * Returns the actual register address for all NAND_DEV_ registers
+- * (i.e. NAND_DEV_CMD0, NAND_DEV_CMD1, NAND_DEV_CMD2 and NAND_DEV_CMD_VLD)
+- */
+-#define dev_cmd_reg_addr(nandc, reg) ((nandc)->props->dev_cmd_reg_start + (reg))
+-
+-/* Returns the NAND register physical address */
+-#define nandc_reg_phys(chip, offset) ((chip)->base_phys + (offset))
+-
+-/* Returns the dma address for reg read buffer */
+-#define reg_buf_dma_addr(chip, vaddr) \
+-      ((chip)->reg_read_dma + \
+-      ((u8 *)(vaddr) - (u8 *)(chip)->reg_read_buf))
+-
+-#define QPIC_PER_CW_CMD_ELEMENTS      32
+-#define QPIC_PER_CW_CMD_SGL           32
+-#define QPIC_PER_CW_DATA_SGL          8
+-
+-#define QPIC_NAND_COMPLETION_TIMEOUT  msecs_to_jiffies(2000)
+-
+-/*
+- * Flags used in DMA descriptor preparation helper functions
+- * (i.e. qcom_read_reg_dma/qcom_write_reg_dma/qcom_read_data_dma/qcom_write_data_dma)
+- */
+-/* Don't set the EOT in current tx BAM sgl */
+-#define NAND_BAM_NO_EOT                       BIT(0)
+-/* Set the NWD flag in current BAM sgl */
+-#define NAND_BAM_NWD                  BIT(1)
+-/* Finish writing in the current BAM sgl and start writing in another BAM sgl */
+-#define NAND_BAM_NEXT_SGL             BIT(2)
+-/*
+- * Erased codeword status is being used two times in single transfer so this
+- * flag will determine the current value of erased codeword status register
+- */
+-#define NAND_ERASED_CW_SET            BIT(4)
+-
+-#define MAX_ADDRESS_CYCLE             5
+-
+-/*
+- * This data type corresponds to the BAM transaction which will be used for all
+- * NAND transfers.
+- * @bam_ce - the array of BAM command elements
+- * @cmd_sgl - sgl for NAND BAM command pipe
+- * @data_sgl - sgl for NAND BAM consumer/producer pipe
+- * @last_data_desc - last DMA desc in data channel (tx/rx).
+- * @last_cmd_desc - last DMA desc in command channel.
+- * @txn_done - completion for NAND transfer.
+- * @bam_ce_pos - the index in bam_ce which is available for next sgl
+- * @bam_ce_start - the index in bam_ce which marks the start position ce
+- *               for current sgl. It will be used for size calculation
+- *               for current sgl
+- * @cmd_sgl_pos - current index in command sgl.
+- * @cmd_sgl_start - start index in command sgl.
+- * @tx_sgl_pos - current index in data sgl for tx.
+- * @tx_sgl_start - start index in data sgl for tx.
+- * @rx_sgl_pos - current index in data sgl for rx.
+- * @rx_sgl_start - start index in data sgl for rx.
+- */
+-struct bam_transaction {
+-      struct bam_cmd_element *bam_ce;
+-      struct scatterlist *cmd_sgl;
+-      struct scatterlist *data_sgl;
+-      struct dma_async_tx_descriptor *last_data_desc;
+-      struct dma_async_tx_descriptor *last_cmd_desc;
+-      struct completion txn_done;
+-      u32 bam_ce_pos;
+-      u32 bam_ce_start;
+-      u32 cmd_sgl_pos;
+-      u32 cmd_sgl_start;
+-      u32 tx_sgl_pos;
+-      u32 tx_sgl_start;
+-      u32 rx_sgl_pos;
+-      u32 rx_sgl_start;
+-};
+-
+-/*
+- * This data type corresponds to the nand dma descriptor
+- * @dma_desc - low level DMA engine descriptor
+- * @list - list for desc_info
+- *
+- * @adm_sgl - sgl which will be used for single sgl dma descriptor. Only used by
+- *          ADM
+- * @bam_sgl - sgl which will be used for dma descriptor. Only used by BAM
+- * @sgl_cnt - number of SGL in bam_sgl. Only used by BAM
+- * @dir - DMA transfer direction
+- */
+-struct desc_info {
+-      struct dma_async_tx_descriptor *dma_desc;
+-      struct list_head node;
+-
+-      union {
+-              struct scatterlist adm_sgl;
+-              struct {
+-                      struct scatterlist *bam_sgl;
+-                      int sgl_cnt;
+-              };
+-      };
+-      enum dma_data_direction dir;
+-};
+-
+-/*
+- * holds the current register values that we want to write. acts as a contiguous
+- * chunk of memory which we use to write the controller registers through DMA.
+- */
+-struct nandc_regs {
+-      __le32 cmd;
+-      __le32 addr0;
+-      __le32 addr1;
+-      __le32 chip_sel;
+-      __le32 exec;
+-
+-      __le32 cfg0;
+-      __le32 cfg1;
+-      __le32 ecc_bch_cfg;
+-
+-      __le32 clrflashstatus;
+-      __le32 clrreadstatus;
+-
+-      __le32 cmd1;
+-      __le32 vld;
+-
+-      __le32 orig_cmd1;
+-      __le32 orig_vld;
+-
+-      __le32 ecc_buf_cfg;
+-      __le32 read_location0;
+-      __le32 read_location1;
+-      __le32 read_location2;
+-      __le32 read_location3;
+-      __le32 read_location_last0;
+-      __le32 read_location_last1;
+-      __le32 read_location_last2;
+-      __le32 read_location_last3;
+-
+-      __le32 erased_cw_detect_cfg_clr;
+-      __le32 erased_cw_detect_cfg_set;
+-};
+-
+-/*
+- * NAND controller data struct
+- *
+- * @dev:                      parent device
+- *
+- * @base:                     MMIO base
+- *
+- * @core_clk:                 controller clock
+- * @aon_clk:                  another controller clock
+- *
+- * @regs:                     a contiguous chunk of memory for DMA register
+- *                            writes. contains the register values to be
+- *                            written to controller
+- *
+- * @props:                    properties of current NAND controller,
+- *                            initialized via DT match data
+- *
+- * @controller:                       base controller structure
+- * @host_list:                        list containing all the chips attached to the
+- *                            controller
+- *
+- * @chan:                     dma channel
+- * @cmd_crci:                 ADM DMA CRCI for command flow control
+- * @data_crci:                        ADM DMA CRCI for data flow control
+- *
+- * @desc_list:                        DMA descriptor list (list of desc_infos)
+- *
+- * @data_buffer:              our local DMA buffer for page read/writes,
+- *                            used when we can't use the buffer provided
+- *                            by upper layers directly
+- * @reg_read_buf:             local buffer for reading back registers via DMA
+- *
+- * @base_phys:                        physical base address of controller registers
+- * @base_dma:                 dma base address of controller registers
+- * @reg_read_dma:             contains dma address for register read buffer
+- *
+- * @buf_size/count/start:     markers for chip->legacy.read_buf/write_buf
+- *                            functions
+- * @max_cwperpage:            maximum QPIC codewords required. calculated
+- *                            from all connected NAND devices pagesize
+- *
+- * @reg_read_pos:             marker for data read in reg_read_buf
+- *
+- * @cmd1/vld:                 some fixed controller register values
+- *
+- * @exec_opwrite:             flag to select correct number of code word
+- *                            while reading status
+- */
+-struct qcom_nand_controller {
+-      struct device *dev;
+-
+-      void __iomem *base;
+-
+-      struct clk *core_clk;
+-      struct clk *aon_clk;
+-
+-      struct nandc_regs *regs;
+-      struct bam_transaction *bam_txn;
+-
+-      const struct qcom_nandc_props *props;
+-
+-      struct nand_controller controller;
+-      struct list_head host_list;
+-
+-      union {
+-              /* will be used only by QPIC for BAM DMA */
+-              struct {
+-                      struct dma_chan *tx_chan;
+-                      struct dma_chan *rx_chan;
+-                      struct dma_chan *cmd_chan;
+-              };
+-
+-              /* will be used only by EBI2 for ADM DMA */
+-              struct {
+-                      struct dma_chan *chan;
+-                      unsigned int cmd_crci;
+-                      unsigned int data_crci;
+-              };
+-      };
+-
+-      struct list_head desc_list;
+-
+-      u8              *data_buffer;
+-      __le32          *reg_read_buf;
+-
+-      phys_addr_t base_phys;
+-      dma_addr_t base_dma;
+-      dma_addr_t reg_read_dma;
+-
+-      int             buf_size;
+-      int             buf_count;
+-      int             buf_start;
+-      unsigned int    max_cwperpage;
+-
+-      int reg_read_pos;
+-
+-      u32 cmd1, vld;
+-      bool exec_opwrite;
+-};
++#include <linux/mtd/nand-qpic-common.h>
+ /*
+  * NAND special boot partitions
+@@ -530,97 +120,6 @@ struct qcom_nand_host {
+       bool bch_enabled;
+ };
+-/*
+- * This data type corresponds to the NAND controller properties which varies
+- * among different NAND controllers.
+- * @ecc_modes - ecc mode for NAND
+- * @dev_cmd_reg_start - NAND_DEV_CMD_* registers starting offset
+- * @supports_bam - whether NAND controller is using Bus Access Manager (BAM)
+- * @nandc_part_of_qpic - whether NAND controller is part of qpic IP
+- * @qpic_version2 - flag to indicate QPIC IP version 2
+- * @use_codeword_fixup - whether NAND has different layout for boot partitions
+- */
+-struct qcom_nandc_props {
+-      u32 ecc_modes;
+-      u32 dev_cmd_reg_start;
+-      bool supports_bam;
+-      bool nandc_part_of_qpic;
+-      bool qpic_version2;
+-      bool use_codeword_fixup;
+-};
+-
+-/* Frees the BAM transaction memory */
+-static void qcom_free_bam_transaction(struct qcom_nand_controller *nandc)
+-{
+-      struct bam_transaction *bam_txn = nandc->bam_txn;
+-
+-      devm_kfree(nandc->dev, bam_txn);
+-}
+-
+-/* Allocates and Initializes the BAM transaction */
+-static struct bam_transaction *
+-qcom_alloc_bam_transaction(struct qcom_nand_controller *nandc)
+-{
+-      struct bam_transaction *bam_txn;
+-      size_t bam_txn_size;
+-      unsigned int num_cw = nandc->max_cwperpage;
+-      void *bam_txn_buf;
+-
+-      bam_txn_size =
+-              sizeof(*bam_txn) + num_cw *
+-              ((sizeof(*bam_txn->bam_ce) * QPIC_PER_CW_CMD_ELEMENTS) +
+-              (sizeof(*bam_txn->cmd_sgl) * QPIC_PER_CW_CMD_SGL) +
+-              (sizeof(*bam_txn->data_sgl) * QPIC_PER_CW_DATA_SGL));
+-
+-      bam_txn_buf = devm_kzalloc(nandc->dev, bam_txn_size, GFP_KERNEL);
+-      if (!bam_txn_buf)
+-              return NULL;
+-
+-      bam_txn = bam_txn_buf;
+-      bam_txn_buf += sizeof(*bam_txn);
+-
+-      bam_txn->bam_ce = bam_txn_buf;
+-      bam_txn_buf +=
+-              sizeof(*bam_txn->bam_ce) * QPIC_PER_CW_CMD_ELEMENTS * num_cw;
+-
+-      bam_txn->cmd_sgl = bam_txn_buf;
+-      bam_txn_buf +=
+-              sizeof(*bam_txn->cmd_sgl) * QPIC_PER_CW_CMD_SGL * num_cw;
+-
+-      bam_txn->data_sgl = bam_txn_buf;
+-
+-      init_completion(&bam_txn->txn_done);
+-
+-      return bam_txn;
+-}
+-
+-/* Clears the BAM transaction indexes */
+-static void qcom_clear_bam_transaction(struct qcom_nand_controller *nandc)
+-{
+-      struct bam_transaction *bam_txn = nandc->bam_txn;
+-
+-      if (!nandc->props->supports_bam)
+-              return;
+-
+-      memset(&bam_txn->bam_ce_pos, 0, sizeof(u32) * 8);
+-      bam_txn->last_data_desc = NULL;
+-
+-      sg_init_table(bam_txn->cmd_sgl, nandc->max_cwperpage *
+-                    QPIC_PER_CW_CMD_SGL);
+-      sg_init_table(bam_txn->data_sgl, nandc->max_cwperpage *
+-                    QPIC_PER_CW_DATA_SGL);
+-
+-      reinit_completion(&bam_txn->txn_done);
+-}
+-
+-/* Callback for DMA descriptor completion */
+-static void qcom_qpic_bam_dma_done(void *data)
+-{
+-      struct bam_transaction *bam_txn = data;
+-
+-      complete(&bam_txn->txn_done);
+-}
+-
+ static struct qcom_nand_host *to_qcom_nand_host(struct nand_chip *chip)
+ {
+       return container_of(chip, struct qcom_nand_host, chip);
+@@ -629,8 +128,8 @@ static struct qcom_nand_host *to_qcom_na
+ static struct qcom_nand_controller *
+ get_qcom_nand_controller(struct nand_chip *chip)
+ {
+-      return container_of(chip->controller, struct qcom_nand_controller,
+-                          controller);
++      return (struct qcom_nand_controller *)
++              ((u8 *)chip->controller - sizeof(struct qcom_nand_controller));
+ }
+ static u32 nandc_read(struct qcom_nand_controller *nandc, int offset)
+@@ -644,23 +143,6 @@ static void nandc_write(struct qcom_nand
+       iowrite32(val, nandc->base + offset);
+ }
+-static void qcom_nandc_dev_to_mem(struct qcom_nand_controller *nandc, bool is_cpu)
+-{
+-      if (!nandc->props->supports_bam)
+-              return;
+-
+-      if (is_cpu)
+-              dma_sync_single_for_cpu(nandc->dev, nandc->reg_read_dma,
+-                                      MAX_REG_RD *
+-                                      sizeof(*nandc->reg_read_buf),
+-                                      DMA_FROM_DEVICE);
+-      else
+-              dma_sync_single_for_device(nandc->dev, nandc->reg_read_dma,
+-                                         MAX_REG_RD *
+-                                         sizeof(*nandc->reg_read_buf),
+-                                         DMA_FROM_DEVICE);
+-}
+-
+ /* Helper to check whether this is the last CW or not */
+ static bool qcom_nandc_is_last_cw(struct nand_ecc_ctrl *ecc, int cw)
+ {
+@@ -820,356 +302,6 @@ static void update_rw_regs(struct qcom_n
+ }
+ /*
+- * Maps the scatter gather list for DMA transfer and forms the DMA descriptor
+- * for BAM. This descriptor will be added in the NAND DMA descriptor queue
+- * which will be submitted to DMA engine.
+- */
+-static int qcom_prepare_bam_async_desc(struct qcom_nand_controller *nandc,
+-                                     struct dma_chan *chan,
+-                                     unsigned long flags)
+-{
+-      struct desc_info *desc;
+-      struct scatterlist *sgl;
+-      unsigned int sgl_cnt;
+-      int ret;
+-      struct bam_transaction *bam_txn = nandc->bam_txn;
+-      enum dma_transfer_direction dir_eng;
+-      struct dma_async_tx_descriptor *dma_desc;
+-
+-      desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+-      if (!desc)
+-              return -ENOMEM;
+-
+-      if (chan == nandc->cmd_chan) {
+-              sgl = &bam_txn->cmd_sgl[bam_txn->cmd_sgl_start];
+-              sgl_cnt = bam_txn->cmd_sgl_pos - bam_txn->cmd_sgl_start;
+-              bam_txn->cmd_sgl_start = bam_txn->cmd_sgl_pos;
+-              dir_eng = DMA_MEM_TO_DEV;
+-              desc->dir = DMA_TO_DEVICE;
+-      } else if (chan == nandc->tx_chan) {
+-              sgl = &bam_txn->data_sgl[bam_txn->tx_sgl_start];
+-              sgl_cnt = bam_txn->tx_sgl_pos - bam_txn->tx_sgl_start;
+-              bam_txn->tx_sgl_start = bam_txn->tx_sgl_pos;
+-              dir_eng = DMA_MEM_TO_DEV;
+-              desc->dir = DMA_TO_DEVICE;
+-      } else {
+-              sgl = &bam_txn->data_sgl[bam_txn->rx_sgl_start];
+-              sgl_cnt = bam_txn->rx_sgl_pos - bam_txn->rx_sgl_start;
+-              bam_txn->rx_sgl_start = bam_txn->rx_sgl_pos;
+-              dir_eng = DMA_DEV_TO_MEM;
+-              desc->dir = DMA_FROM_DEVICE;
+-      }
+-
+-      sg_mark_end(sgl + sgl_cnt - 1);
+-      ret = dma_map_sg(nandc->dev, sgl, sgl_cnt, desc->dir);
+-      if (ret == 0) {
+-              dev_err(nandc->dev, "failure in mapping desc\n");
+-              kfree(desc);
+-              return -ENOMEM;
+-      }
+-
+-      desc->sgl_cnt = sgl_cnt;
+-      desc->bam_sgl = sgl;
+-
+-      dma_desc = dmaengine_prep_slave_sg(chan, sgl, sgl_cnt, dir_eng,
+-                                         flags);
+-
+-      if (!dma_desc) {
+-              dev_err(nandc->dev, "failure in prep desc\n");
+-              dma_unmap_sg(nandc->dev, sgl, sgl_cnt, desc->dir);
+-              kfree(desc);
+-              return -EINVAL;
+-      }
+-
+-      desc->dma_desc = dma_desc;
+-
+-      /* update last data/command descriptor */
+-      if (chan == nandc->cmd_chan)
+-              bam_txn->last_cmd_desc = dma_desc;
+-      else
+-              bam_txn->last_data_desc = dma_desc;
+-
+-      list_add_tail(&desc->node, &nandc->desc_list);
+-
+-      return 0;
+-}
+-
+-/*
+- * Prepares the command descriptor for BAM DMA which will be used for NAND
+- * register reads and writes. The command descriptor requires the command
+- * to be formed in command element type so this function uses the command
+- * element from bam transaction ce array and fills the same with required
+- * data. A single SGL can contain multiple command elements so
+- * NAND_BAM_NEXT_SGL will be used for starting the separate SGL
+- * after the current command element.
+- */
+-static int qcom_prep_bam_dma_desc_cmd(struct qcom_nand_controller *nandc, bool read,
+-                                    int reg_off, const void *vaddr,
+-                                    int size, unsigned int flags)
+-{
+-      int bam_ce_size;
+-      int i, ret;
+-      struct bam_cmd_element *bam_ce_buffer;
+-      struct bam_transaction *bam_txn = nandc->bam_txn;
+-
+-      bam_ce_buffer = &bam_txn->bam_ce[bam_txn->bam_ce_pos];
+-
+-      /* fill the command desc */
+-      for (i = 0; i < size; i++) {
+-              if (read)
+-                      bam_prep_ce(&bam_ce_buffer[i],
+-                                  nandc_reg_phys(nandc, reg_off + 4 * i),
+-                                  BAM_READ_COMMAND,
+-                                  reg_buf_dma_addr(nandc,
+-                                                   (__le32 *)vaddr + i));
+-              else
+-                      bam_prep_ce_le32(&bam_ce_buffer[i],
+-                                       nandc_reg_phys(nandc, reg_off + 4 * i),
+-                                       BAM_WRITE_COMMAND,
+-                                       *((__le32 *)vaddr + i));
+-      }
+-
+-      bam_txn->bam_ce_pos += size;
+-
+-      /* use the separate sgl after this command */
+-      if (flags & NAND_BAM_NEXT_SGL) {
+-              bam_ce_buffer = &bam_txn->bam_ce[bam_txn->bam_ce_start];
+-              bam_ce_size = (bam_txn->bam_ce_pos -
+-                              bam_txn->bam_ce_start) *
+-                              sizeof(struct bam_cmd_element);
+-              sg_set_buf(&bam_txn->cmd_sgl[bam_txn->cmd_sgl_pos],
+-                         bam_ce_buffer, bam_ce_size);
+-              bam_txn->cmd_sgl_pos++;
+-              bam_txn->bam_ce_start = bam_txn->bam_ce_pos;
+-
+-              if (flags & NAND_BAM_NWD) {
+-                      ret = qcom_prepare_bam_async_desc(nandc, nandc->cmd_chan,
+-                                                        DMA_PREP_FENCE |
+-                                                        DMA_PREP_CMD);
+-                      if (ret)
+-                              return ret;
+-              }
+-      }
+-
+-      return 0;
+-}
+-
+-/*
+- * Prepares the data descriptor for BAM DMA which will be used for NAND
+- * data reads and writes.
+- */
+-static int qcom_prep_bam_dma_desc_data(struct qcom_nand_controller *nandc, bool read,
+-                                     const void *vaddr, int size, unsigned int flags)
+-{
+-      int ret;
+-      struct bam_transaction *bam_txn = nandc->bam_txn;
+-
+-      if (read) {
+-              sg_set_buf(&bam_txn->data_sgl[bam_txn->rx_sgl_pos],
+-                         vaddr, size);
+-              bam_txn->rx_sgl_pos++;
+-      } else {
+-              sg_set_buf(&bam_txn->data_sgl[bam_txn->tx_sgl_pos],
+-                         vaddr, size);
+-              bam_txn->tx_sgl_pos++;
+-
+-              /*
+-               * BAM will only set EOT for DMA_PREP_INTERRUPT so if this flag
+-               * is not set, form the DMA descriptor
+-               */
+-              if (!(flags & NAND_BAM_NO_EOT)) {
+-                      ret = qcom_prepare_bam_async_desc(nandc, nandc->tx_chan,
+-                                                        DMA_PREP_INTERRUPT);
+-                      if (ret)
+-                              return ret;
+-              }
+-      }
+-
+-      return 0;
+-}
+-
+-static int qcom_prep_adm_dma_desc(struct qcom_nand_controller *nandc, bool read,
+-                                int reg_off, const void *vaddr, int size,
+-                                bool flow_control)
+-{
+-      struct desc_info *desc;
+-      struct dma_async_tx_descriptor *dma_desc;
+-      struct scatterlist *sgl;
+-      struct dma_slave_config slave_conf;
+-      struct qcom_adm_peripheral_config periph_conf = {};
+-      enum dma_transfer_direction dir_eng;
+-      int ret;
+-
+-      desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+-      if (!desc)
+-              return -ENOMEM;
+-
+-      sgl = &desc->adm_sgl;
+-
+-      sg_init_one(sgl, vaddr, size);
+-
+-      if (read) {
+-              dir_eng = DMA_DEV_TO_MEM;
+-              desc->dir = DMA_FROM_DEVICE;
+-      } else {
+-              dir_eng = DMA_MEM_TO_DEV;
+-              desc->dir = DMA_TO_DEVICE;
+-      }
+-
+-      ret = dma_map_sg(nandc->dev, sgl, 1, desc->dir);
+-      if (ret == 0) {
+-              ret = -ENOMEM;
+-              goto err;
+-      }
+-
+-      memset(&slave_conf, 0x00, sizeof(slave_conf));
+-
+-      slave_conf.device_fc = flow_control;
+-      if (read) {
+-              slave_conf.src_maxburst = 16;
+-              slave_conf.src_addr = nandc->base_dma + reg_off;
+-              if (nandc->data_crci) {
+-                      periph_conf.crci = nandc->data_crci;
+-                      slave_conf.peripheral_config = &periph_conf;
+-                      slave_conf.peripheral_size = sizeof(periph_conf);
+-              }
+-      } else {
+-              slave_conf.dst_maxburst = 16;
+-              slave_conf.dst_addr = nandc->base_dma + reg_off;
+-              if (nandc->cmd_crci) {
+-                      periph_conf.crci = nandc->cmd_crci;
+-                      slave_conf.peripheral_config = &periph_conf;
+-                      slave_conf.peripheral_size = sizeof(periph_conf);
+-              }
+-      }
+-
+-      ret = dmaengine_slave_config(nandc->chan, &slave_conf);
+-      if (ret) {
+-              dev_err(nandc->dev, "failed to configure dma channel\n");
+-              goto err;
+-      }
+-
+-      dma_desc = dmaengine_prep_slave_sg(nandc->chan, sgl, 1, dir_eng, 0);
+-      if (!dma_desc) {
+-              dev_err(nandc->dev, "failed to prepare desc\n");
+-              ret = -EINVAL;
+-              goto err;
+-      }
+-
+-      desc->dma_desc = dma_desc;
+-
+-      list_add_tail(&desc->node, &nandc->desc_list);
+-
+-      return 0;
+-err:
+-      kfree(desc);
+-
+-      return ret;
+-}
+-
+-/*
+- * qcom_read_reg_dma: prepares a descriptor to read a given number of
+- *                    contiguous registers to the reg_read_buf pointer
+- *
+- * @first:            offset of the first register in the contiguous block
+- * @num_regs:         number of registers to read
+- * @flags:            flags to control DMA descriptor preparation
+- */
+-static int qcom_read_reg_dma(struct qcom_nand_controller *nandc, int first,
+-                           int num_regs, unsigned int flags)
+-{
+-      bool flow_control = false;
+-      void *vaddr;
+-
+-      vaddr = nandc->reg_read_buf + nandc->reg_read_pos;
+-      nandc->reg_read_pos += num_regs;
+-
+-      if (first == NAND_DEV_CMD_VLD || first == NAND_DEV_CMD1)
+-              first = dev_cmd_reg_addr(nandc, first);
+-
+-      if (nandc->props->supports_bam)
+-              return qcom_prep_bam_dma_desc_cmd(nandc, true, first, vaddr,
+-                                           num_regs, flags);
+-
+-      if (first == NAND_READ_ID || first == NAND_FLASH_STATUS)
+-              flow_control = true;
+-
+-      return qcom_prep_adm_dma_desc(nandc, true, first, vaddr,
+-                               num_regs * sizeof(u32), flow_control);
+-}
+-
+-/*
+- * qcom_write_reg_dma:        prepares a descriptor to write a given number of
+- *                    contiguous registers
+- *
+- * @vaddr:            contiguous memory from where register value will
+- *                    be written
+- * @first:            offset of the first register in the contiguous block
+- * @num_regs:         number of registers to write
+- * @flags:            flags to control DMA descriptor preparation
+- */
+-static int qcom_write_reg_dma(struct qcom_nand_controller *nandc, __le32 *vaddr,
+-                            int first, int num_regs, unsigned int flags)
+-{
+-      bool flow_control = false;
+-
+-      if (first == NAND_EXEC_CMD)
+-              flags |= NAND_BAM_NWD;
+-
+-      if (first == NAND_DEV_CMD1_RESTORE || first == NAND_DEV_CMD1)
+-              first = dev_cmd_reg_addr(nandc, NAND_DEV_CMD1);
+-
+-      if (first == NAND_DEV_CMD_VLD_RESTORE || first == NAND_DEV_CMD_VLD)
+-              first = dev_cmd_reg_addr(nandc, NAND_DEV_CMD_VLD);
+-
+-      if (nandc->props->supports_bam)
+-              return qcom_prep_bam_dma_desc_cmd(nandc, false, first, vaddr,
+-                                           num_regs, flags);
+-
+-      if (first == NAND_FLASH_CMD)
+-              flow_control = true;
+-
+-      return qcom_prep_adm_dma_desc(nandc, false, first, vaddr,
+-                               num_regs * sizeof(u32), flow_control);
+-}
+-
+-/*
+- * qcom_read_data_dma:        prepares a DMA descriptor to transfer data from the
+- *                    controller's internal buffer to the buffer 'vaddr'
+- *
+- * @reg_off:          offset within the controller's data buffer
+- * @vaddr:            virtual address of the buffer we want to write to
+- * @size:             DMA transaction size in bytes
+- * @flags:            flags to control DMA descriptor preparation
+- */
+-static int qcom_read_data_dma(struct qcom_nand_controller *nandc, int reg_off,
+-                            const u8 *vaddr, int size, unsigned int flags)
+-{
+-      if (nandc->props->supports_bam)
+-              return qcom_prep_bam_dma_desc_data(nandc, true, vaddr, size, flags);
+-
+-      return qcom_prep_adm_dma_desc(nandc, true, reg_off, vaddr, size, false);
+-}
+-
+-/*
+- * qcom_write_data_dma:       prepares a DMA descriptor to transfer data from
+- *                    'vaddr' to the controller's internal buffer
+- *
+- * @reg_off:          offset within the controller's data buffer
+- * @vaddr:            virtual address of the buffer we want to read from
+- * @size:             DMA transaction size in bytes
+- * @flags:            flags to control DMA descriptor preparation
+- */
+-static int qcom_write_data_dma(struct qcom_nand_controller *nandc, int reg_off,
+-                             const u8 *vaddr, int size, unsigned int flags)
+-{
+-      if (nandc->props->supports_bam)
+-              return qcom_prep_bam_dma_desc_data(nandc, false, vaddr, size, flags);
+-
+-      return qcom_prep_adm_dma_desc(nandc, false, reg_off, vaddr, size, false);
+-}
+-
+-/*
+  * Helper to prepare DMA descriptors for configuring registers
+  * before reading a NAND page.
+  */
+@@ -1262,83 +394,6 @@ static void config_nand_cw_write(struct
+                          NAND_BAM_NEXT_SGL);
+ }
+-/* helpers to submit/free our list of dma descriptors */
+-static int qcom_submit_descs(struct qcom_nand_controller *nandc)
+-{
+-      struct desc_info *desc, *n;
+-      dma_cookie_t cookie = 0;
+-      struct bam_transaction *bam_txn = nandc->bam_txn;
+-      int ret = 0;
+-
+-      if (nandc->props->supports_bam) {
+-              if (bam_txn->rx_sgl_pos > bam_txn->rx_sgl_start) {
+-                      ret = qcom_prepare_bam_async_desc(nandc, nandc->rx_chan, 0);
+-                      if (ret)
+-                              goto err_unmap_free_desc;
+-              }
+-
+-              if (bam_txn->tx_sgl_pos > bam_txn->tx_sgl_start) {
+-                      ret = qcom_prepare_bam_async_desc(nandc, nandc->tx_chan,
+-                                                        DMA_PREP_INTERRUPT);
+-                      if (ret)
+-                              goto err_unmap_free_desc;
+-              }
+-
+-              if (bam_txn->cmd_sgl_pos > bam_txn->cmd_sgl_start) {
+-                      ret = qcom_prepare_bam_async_desc(nandc, nandc->cmd_chan,
+-                                                        DMA_PREP_CMD);
+-                      if (ret)
+-                              goto err_unmap_free_desc;
+-              }
+-      }
+-
+-      list_for_each_entry(desc, &nandc->desc_list, node)
+-              cookie = dmaengine_submit(desc->dma_desc);
+-
+-      if (nandc->props->supports_bam) {
+-              bam_txn->last_cmd_desc->callback = qcom_qpic_bam_dma_done;
+-              bam_txn->last_cmd_desc->callback_param = bam_txn;
+-
+-              dma_async_issue_pending(nandc->tx_chan);
+-              dma_async_issue_pending(nandc->rx_chan);
+-              dma_async_issue_pending(nandc->cmd_chan);
+-
+-              if (!wait_for_completion_timeout(&bam_txn->txn_done,
+-                                               QPIC_NAND_COMPLETION_TIMEOUT))
+-                      ret = -ETIMEDOUT;
+-      } else {
+-              if (dma_sync_wait(nandc->chan, cookie) != DMA_COMPLETE)
+-                      ret = -ETIMEDOUT;
+-      }
+-
+-err_unmap_free_desc:
+-      /*
+-       * Unmap the dma sg_list and free the desc allocated by both
+-       * qcom_prepare_bam_async_desc() and qcom_prep_adm_dma_desc() functions.
+-       */
+-      list_for_each_entry_safe(desc, n, &nandc->desc_list, node) {
+-              list_del(&desc->node);
+-
+-              if (nandc->props->supports_bam)
+-                      dma_unmap_sg(nandc->dev, desc->bam_sgl,
+-                                   desc->sgl_cnt, desc->dir);
+-              else
+-                      dma_unmap_sg(nandc->dev, &desc->adm_sgl, 1,
+-                                   desc->dir);
+-
+-              kfree(desc);
+-      }
+-
+-      return ret;
+-}
+-
+-/* reset the register read buffer for next NAND operation */
+-static void qcom_clear_read_regs(struct qcom_nand_controller *nandc)
+-{
+-      nandc->reg_read_pos = 0;
+-      qcom_nandc_dev_to_mem(nandc, false);
+-}
+-
+ /*
+  * when using BCH ECC, the HW flags an error in NAND_FLASH_STATUS if it read
+  * an erased CW, and reports an erased CW in NAND_ERASED_CW_DETECT_STATUS.
+@@ -2975,141 +2030,14 @@ static const struct nand_controller_ops
+       .exec_op = qcom_nand_exec_op,
+ };
+-static void qcom_nandc_unalloc(struct qcom_nand_controller *nandc)
+-{
+-      if (nandc->props->supports_bam) {
+-              if (!dma_mapping_error(nandc->dev, nandc->reg_read_dma))
+-                      dma_unmap_single(nandc->dev, nandc->reg_read_dma,
+-                                       MAX_REG_RD *
+-                                       sizeof(*nandc->reg_read_buf),
+-                                       DMA_FROM_DEVICE);
+-
+-              if (nandc->tx_chan)
+-                      dma_release_channel(nandc->tx_chan);
+-
+-              if (nandc->rx_chan)
+-                      dma_release_channel(nandc->rx_chan);
+-
+-              if (nandc->cmd_chan)
+-                      dma_release_channel(nandc->cmd_chan);
+-      } else {
+-              if (nandc->chan)
+-                      dma_release_channel(nandc->chan);
+-      }
+-}
+-
+-static int qcom_nandc_alloc(struct qcom_nand_controller *nandc)
+-{
+-      int ret;
+-
+-      ret = dma_set_coherent_mask(nandc->dev, DMA_BIT_MASK(32));
+-      if (ret) {
+-              dev_err(nandc->dev, "failed to set DMA mask\n");
+-              return ret;
+-      }
+-
+-      /*
+-       * we use the internal buffer for reading ONFI params, reading small
+-       * data like ID and status, and preforming read-copy-write operations
+-       * when writing to a codeword partially. 532 is the maximum possible
+-       * size of a codeword for our nand controller
+-       */
+-      nandc->buf_size = 532;
+-
+-      nandc->data_buffer = devm_kzalloc(nandc->dev, nandc->buf_size, GFP_KERNEL);
+-      if (!nandc->data_buffer)
+-              return -ENOMEM;
+-
+-      nandc->regs = devm_kzalloc(nandc->dev, sizeof(*nandc->regs), GFP_KERNEL);
+-      if (!nandc->regs)
+-              return -ENOMEM;
+-
+-      nandc->reg_read_buf = devm_kcalloc(nandc->dev, MAX_REG_RD,
+-                                         sizeof(*nandc->reg_read_buf),
+-                                         GFP_KERNEL);
+-      if (!nandc->reg_read_buf)
+-              return -ENOMEM;
+-
+-      if (nandc->props->supports_bam) {
+-              nandc->reg_read_dma =
+-                      dma_map_single(nandc->dev, nandc->reg_read_buf,
+-                                     MAX_REG_RD *
+-                                     sizeof(*nandc->reg_read_buf),
+-                                     DMA_FROM_DEVICE);
+-              if (dma_mapping_error(nandc->dev, nandc->reg_read_dma)) {
+-                      dev_err(nandc->dev, "failed to DMA MAP reg buffer\n");
+-                      return -EIO;
+-              }
+-
+-              nandc->tx_chan = dma_request_chan(nandc->dev, "tx");
+-              if (IS_ERR(nandc->tx_chan)) {
+-                      ret = PTR_ERR(nandc->tx_chan);
+-                      nandc->tx_chan = NULL;
+-                      dev_err_probe(nandc->dev, ret,
+-                                    "tx DMA channel request failed\n");
+-                      goto unalloc;
+-              }
+-
+-              nandc->rx_chan = dma_request_chan(nandc->dev, "rx");
+-              if (IS_ERR(nandc->rx_chan)) {
+-                      ret = PTR_ERR(nandc->rx_chan);
+-                      nandc->rx_chan = NULL;
+-                      dev_err_probe(nandc->dev, ret,
+-                                    "rx DMA channel request failed\n");
+-                      goto unalloc;
+-              }
+-
+-              nandc->cmd_chan = dma_request_chan(nandc->dev, "cmd");
+-              if (IS_ERR(nandc->cmd_chan)) {
+-                      ret = PTR_ERR(nandc->cmd_chan);
+-                      nandc->cmd_chan = NULL;
+-                      dev_err_probe(nandc->dev, ret,
+-                                    "cmd DMA channel request failed\n");
+-                      goto unalloc;
+-              }
+-
+-              /*
+-               * Initially allocate BAM transaction to read ONFI param page.
+-               * After detecting all the devices, this BAM transaction will
+-               * be freed and the next BAM transaction will be allocated with
+-               * maximum codeword size
+-               */
+-              nandc->max_cwperpage = 1;
+-              nandc->bam_txn = qcom_alloc_bam_transaction(nandc);
+-              if (!nandc->bam_txn) {
+-                      dev_err(nandc->dev,
+-                              "failed to allocate bam transaction\n");
+-                      ret = -ENOMEM;
+-                      goto unalloc;
+-              }
+-      } else {
+-              nandc->chan = dma_request_chan(nandc->dev, "rxtx");
+-              if (IS_ERR(nandc->chan)) {
+-                      ret = PTR_ERR(nandc->chan);
+-                      nandc->chan = NULL;
+-                      dev_err_probe(nandc->dev, ret,
+-                                    "rxtx DMA channel request failed\n");
+-                      return ret;
+-              }
+-      }
+-
+-      INIT_LIST_HEAD(&nandc->desc_list);
+-      INIT_LIST_HEAD(&nandc->host_list);
+-
+-      nand_controller_init(&nandc->controller);
+-      nandc->controller.ops = &qcom_nandc_ops;
+-
+-      return 0;
+-unalloc:
+-      qcom_nandc_unalloc(nandc);
+-      return ret;
+-}
+-
+ /* one time setup of a few nand controller registers */
+ static int qcom_nandc_setup(struct qcom_nand_controller *nandc)
+ {
+       u32 nand_ctrl;
++      nand_controller_init(nandc->controller);
++      nandc->controller->ops = &qcom_nandc_ops;
++
+       /* kill onenand */
+       if (!nandc->props->nandc_part_of_qpic)
+               nandc_write(nandc, SFLASHC_BURST_CFG, 0);
+@@ -3248,7 +2176,7 @@ static int qcom_nand_host_init_and_regis
+       chip->legacy.block_bad          = qcom_nandc_block_bad;
+       chip->legacy.block_markbad      = qcom_nandc_block_markbad;
+-      chip->controller = &nandc->controller;
++      chip->controller = nandc->controller;
+       chip->options |= NAND_NO_SUBPAGE_WRITE | NAND_USES_DMA |
+                        NAND_SKIP_BBTSCAN;
+@@ -3331,17 +2259,21 @@ static int qcom_nandc_parse_dt(struct pl
+ static int qcom_nandc_probe(struct platform_device *pdev)
+ {
+       struct qcom_nand_controller *nandc;
++      struct nand_controller *controller;
+       const void *dev_data;
+       struct device *dev = &pdev->dev;
+       struct resource *res;
+       int ret;
+-      nandc = devm_kzalloc(&pdev->dev, sizeof(*nandc), GFP_KERNEL);
++      nandc = devm_kzalloc(&pdev->dev, sizeof(*nandc) + sizeof(*controller),
++                           GFP_KERNEL);
+       if (!nandc)
+               return -ENOMEM;
++      controller = (struct nand_controller *)&nandc[1];
+       platform_set_drvdata(pdev, nandc);
+       nandc->dev = dev;
++      nandc->controller = controller;
+       dev_data = of_device_get_match_data(dev);
+       if (!dev_data) {
+--- /dev/null
++++ b/include/linux/mtd/nand-qpic-common.h
+@@ -0,0 +1,468 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++/*
++ * QCOM QPIC common APIs header file
++ *
++ * Copyright (c) 2023 Qualcomm Inc.
++ * Authors:   Md sadre Alam   <quic_mdalam@quicinc.com>
++ *
++ */
++#ifndef __MTD_NAND_QPIC_COMMON_H__
++#define __MTD_NAND_QPIC_COMMON_H__
++
++/* NANDc reg offsets */
++#define       NAND_FLASH_CMD                  0x00
++#define       NAND_ADDR0                      0x04
++#define       NAND_ADDR1                      0x08
++#define       NAND_FLASH_CHIP_SELECT          0x0c
++#define       NAND_EXEC_CMD                   0x10
++#define       NAND_FLASH_STATUS               0x14
++#define       NAND_BUFFER_STATUS              0x18
++#define       NAND_DEV0_CFG0                  0x20
++#define       NAND_DEV0_CFG1                  0x24
++#define       NAND_DEV0_ECC_CFG               0x28
++#define       NAND_AUTO_STATUS_EN             0x2c
++#define       NAND_DEV1_CFG0                  0x30
++#define       NAND_DEV1_CFG1                  0x34
++#define       NAND_READ_ID                    0x40
++#define       NAND_READ_STATUS                0x44
++#define       NAND_DEV_CMD0                   0xa0
++#define       NAND_DEV_CMD1                   0xa4
++#define       NAND_DEV_CMD2                   0xa8
++#define       NAND_DEV_CMD_VLD                0xac
++#define       SFLASHC_BURST_CFG               0xe0
++#define       NAND_ERASED_CW_DETECT_CFG       0xe8
++#define       NAND_ERASED_CW_DETECT_STATUS    0xec
++#define       NAND_EBI2_ECC_BUF_CFG           0xf0
++#define       FLASH_BUF_ACC                   0x100
++
++#define       NAND_CTRL                       0xf00
++#define       NAND_VERSION                    0xf08
++#define       NAND_READ_LOCATION_0            0xf20
++#define       NAND_READ_LOCATION_1            0xf24
++#define       NAND_READ_LOCATION_2            0xf28
++#define       NAND_READ_LOCATION_3            0xf2c
++#define       NAND_READ_LOCATION_LAST_CW_0    0xf40
++#define       NAND_READ_LOCATION_LAST_CW_1    0xf44
++#define       NAND_READ_LOCATION_LAST_CW_2    0xf48
++#define       NAND_READ_LOCATION_LAST_CW_3    0xf4c
++
++/* dummy register offsets, used by qcom_write_reg_dma */
++#define       NAND_DEV_CMD1_RESTORE           0xdead
++#define       NAND_DEV_CMD_VLD_RESTORE        0xbeef
++
++/* NAND_FLASH_CMD bits */
++#define       PAGE_ACC                        BIT(4)
++#define       LAST_PAGE                       BIT(5)
++
++/* NAND_FLASH_CHIP_SELECT bits */
++#define       NAND_DEV_SEL                    0
++#define       DM_EN                           BIT(2)
++
++/* NAND_FLASH_STATUS bits */
++#define       FS_OP_ERR                       BIT(4)
++#define       FS_READY_BSY_N                  BIT(5)
++#define       FS_MPU_ERR                      BIT(8)
++#define       FS_DEVICE_STS_ERR               BIT(16)
++#define       FS_DEVICE_WP                    BIT(23)
++
++/* NAND_BUFFER_STATUS bits */
++#define       BS_UNCORRECTABLE_BIT            BIT(8)
++#define       BS_CORRECTABLE_ERR_MSK          0x1f
++
++/* NAND_DEVn_CFG0 bits */
++#define       DISABLE_STATUS_AFTER_WRITE      4
++#define       CW_PER_PAGE                     6
++#define       UD_SIZE_BYTES                   9
++#define       UD_SIZE_BYTES_MASK              GENMASK(18, 9)
++#define       ECC_PARITY_SIZE_BYTES_RS        19
++#define       SPARE_SIZE_BYTES                23
++#define       SPARE_SIZE_BYTES_MASK           GENMASK(26, 23)
++#define       NUM_ADDR_CYCLES                 27
++#define       STATUS_BFR_READ                 30
++#define       SET_RD_MODE_AFTER_STATUS        31
++
++/* NAND_DEVn_CFG0 bits */
++#define       DEV0_CFG1_ECC_DISABLE           0
++#define       WIDE_FLASH                      1
++#define       NAND_RECOVERY_CYCLES            2
++#define       CS_ACTIVE_BSY                   5
++#define       BAD_BLOCK_BYTE_NUM              6
++#define       BAD_BLOCK_IN_SPARE_AREA         16
++#define       WR_RD_BSY_GAP                   17
++#define       ENABLE_BCH_ECC                  27
++
++/* NAND_DEV0_ECC_CFG bits */
++#define       ECC_CFG_ECC_DISABLE             0
++#define       ECC_SW_RESET                    1
++#define       ECC_MODE                        4
++#define       ECC_PARITY_SIZE_BYTES_BCH       8
++#define       ECC_NUM_DATA_BYTES              16
++#define       ECC_NUM_DATA_BYTES_MASK         GENMASK(25, 16)
++#define       ECC_FORCE_CLK_OPEN              30
++
++/* NAND_DEV_CMD1 bits */
++#define       READ_ADDR                       0
++
++/* NAND_DEV_CMD_VLD bits */
++#define       READ_START_VLD                  BIT(0)
++#define       READ_STOP_VLD                   BIT(1)
++#define       WRITE_START_VLD                 BIT(2)
++#define       ERASE_START_VLD                 BIT(3)
++#define       SEQ_READ_START_VLD              BIT(4)
++
++/* NAND_EBI2_ECC_BUF_CFG bits */
++#define       NUM_STEPS                       0
++
++/* NAND_ERASED_CW_DETECT_CFG bits */
++#define       ERASED_CW_ECC_MASK              1
++#define       AUTO_DETECT_RES                 0
++#define       MASK_ECC                        BIT(ERASED_CW_ECC_MASK)
++#define       RESET_ERASED_DET                BIT(AUTO_DETECT_RES)
++#define       ACTIVE_ERASED_DET               (0 << AUTO_DETECT_RES)
++#define       CLR_ERASED_PAGE_DET             (RESET_ERASED_DET | MASK_ECC)
++#define       SET_ERASED_PAGE_DET             (ACTIVE_ERASED_DET | MASK_ECC)
++
++/* NAND_ERASED_CW_DETECT_STATUS bits */
++#define       PAGE_ALL_ERASED                 BIT(7)
++#define       CODEWORD_ALL_ERASED             BIT(6)
++#define       PAGE_ERASED                     BIT(5)
++#define       CODEWORD_ERASED                 BIT(4)
++#define       ERASED_PAGE                     (PAGE_ALL_ERASED | PAGE_ERASED)
++#define       ERASED_CW                       (CODEWORD_ALL_ERASED | CODEWORD_ERASED)
++
++/* NAND_READ_LOCATION_n bits */
++#define READ_LOCATION_OFFSET          0
++#define READ_LOCATION_SIZE            16
++#define READ_LOCATION_LAST            31
++
++/* Version Mask */
++#define       NAND_VERSION_MAJOR_MASK         0xf0000000
++#define       NAND_VERSION_MAJOR_SHIFT        28
++#define       NAND_VERSION_MINOR_MASK         0x0fff0000
++#define       NAND_VERSION_MINOR_SHIFT        16
++
++/* NAND OP_CMDs */
++#define       OP_PAGE_READ                    0x2
++#define       OP_PAGE_READ_WITH_ECC           0x3
++#define       OP_PAGE_READ_WITH_ECC_SPARE     0x4
++#define       OP_PAGE_READ_ONFI_READ          0x5
++#define       OP_PROGRAM_PAGE                 0x6
++#define       OP_PAGE_PROGRAM_WITH_ECC        0x7
++#define       OP_PROGRAM_PAGE_SPARE           0x9
++#define       OP_BLOCK_ERASE                  0xa
++#define       OP_CHECK_STATUS                 0xc
++#define       OP_FETCH_ID                     0xb
++#define       OP_RESET_DEVICE                 0xd
++
++/* Default Value for NAND_DEV_CMD_VLD */
++#define NAND_DEV_CMD_VLD_VAL          (READ_START_VLD | WRITE_START_VLD | \
++                                       ERASE_START_VLD | SEQ_READ_START_VLD)
++
++/* NAND_CTRL bits */
++#define       BAM_MODE_EN                     BIT(0)
++
++/*
++ * the NAND controller performs reads/writes with ECC in 516 byte chunks.
++ * the driver calls the chunks 'step' or 'codeword' interchangeably
++ */
++#define       NANDC_STEP_SIZE                 512
++
++/*
++ * the largest page size we support is 8K, this will have 16 steps/codewords
++ * of 512 bytes each
++ */
++#define       MAX_NUM_STEPS                   (SZ_8K / NANDC_STEP_SIZE)
++
++/* we read at most 3 registers per codeword scan */
++#define       MAX_REG_RD                      (3 * MAX_NUM_STEPS)
++
++/* ECC modes supported by the controller */
++#define       ECC_NONE        BIT(0)
++#define       ECC_RS_4BIT     BIT(1)
++#define       ECC_BCH_4BIT    BIT(2)
++#define       ECC_BCH_8BIT    BIT(3)
++
++/*
++ * Returns the actual register address for all NAND_DEV_ registers
++ * (i.e. NAND_DEV_CMD0, NAND_DEV_CMD1, NAND_DEV_CMD2 and NAND_DEV_CMD_VLD)
++ */
++#define dev_cmd_reg_addr(nandc, reg) ((nandc)->props->dev_cmd_reg_start + (reg))
++
++/* Returns the NAND register physical address */
++#define nandc_reg_phys(chip, offset) ((chip)->base_phys + (offset))
++
++/* Returns the dma address for reg read buffer */
++#define reg_buf_dma_addr(chip, vaddr) \
++      ((chip)->reg_read_dma + \
++      ((u8 *)(vaddr) - (u8 *)(chip)->reg_read_buf))
++
++#define QPIC_PER_CW_CMD_ELEMENTS      32
++#define QPIC_PER_CW_CMD_SGL           32
++#define QPIC_PER_CW_DATA_SGL          8
++
++#define QPIC_NAND_COMPLETION_TIMEOUT  msecs_to_jiffies(2000)
++
++/*
++ * Flags used in DMA descriptor preparation helper functions
++ * (i.e. qcom_read_reg_dma/qcom_write_reg_dma/qcom_read_data_dma/qcom_write_data_dma)
++ */
++/* Don't set the EOT in current tx BAM sgl */
++#define NAND_BAM_NO_EOT                       BIT(0)
++/* Set the NWD flag in current BAM sgl */
++#define NAND_BAM_NWD                  BIT(1)
++/* Finish writing in the current BAM sgl and start writing in another BAM sgl */
++#define NAND_BAM_NEXT_SGL             BIT(2)
++/*
++ * Erased codeword status is being used two times in single transfer so this
++ * flag will determine the current value of erased codeword status register
++ */
++#define NAND_ERASED_CW_SET            BIT(4)
++
++#define MAX_ADDRESS_CYCLE             5
++
++/*
++ * This data type corresponds to the BAM transaction which will be used for all
++ * NAND transfers.
++ * @bam_ce - the array of BAM command elements
++ * @cmd_sgl - sgl for NAND BAM command pipe
++ * @data_sgl - sgl for NAND BAM consumer/producer pipe
++ * @last_data_desc - last DMA desc in data channel (tx/rx).
++ * @last_cmd_desc - last DMA desc in command channel.
++ * @txn_done - completion for NAND transfer.
++ * @bam_ce_pos - the index in bam_ce which is available for next sgl
++ * @bam_ce_start - the index in bam_ce which marks the start position ce
++ *               for current sgl. It will be used for size calculation
++ *               for current sgl
++ * @cmd_sgl_pos - current index in command sgl.
++ * @cmd_sgl_start - start index in command sgl.
++ * @tx_sgl_pos - current index in data sgl for tx.
++ * @tx_sgl_start - start index in data sgl for tx.
++ * @rx_sgl_pos - current index in data sgl for rx.
++ * @rx_sgl_start - start index in data sgl for rx.
++ */
++struct bam_transaction {
++      struct bam_cmd_element *bam_ce;
++      struct scatterlist *cmd_sgl;
++      struct scatterlist *data_sgl;
++      struct dma_async_tx_descriptor *last_data_desc;
++      struct dma_async_tx_descriptor *last_cmd_desc;
++      struct completion txn_done;
++      u32 bam_ce_pos;
++      u32 bam_ce_start;
++      u32 cmd_sgl_pos;
++      u32 cmd_sgl_start;
++      u32 tx_sgl_pos;
++      u32 tx_sgl_start;
++      u32 rx_sgl_pos;
++      u32 rx_sgl_start;
++};
++
++/*
++ * This data type corresponds to the nand dma descriptor
++ * @dma_desc - low level DMA engine descriptor
++ * @list - list for desc_info
++ *
++ * @adm_sgl - sgl which will be used for single sgl dma descriptor. Only used by
++ *          ADM
++ * @bam_sgl - sgl which will be used for dma descriptor. Only used by BAM
++ * @sgl_cnt - number of SGL in bam_sgl. Only used by BAM
++ * @dir - DMA transfer direction
++ */
++struct desc_info {
++      struct dma_async_tx_descriptor *dma_desc;
++      struct list_head node;
++
++      union {
++              struct scatterlist adm_sgl;
++              struct {
++                      struct scatterlist *bam_sgl;
++                      int sgl_cnt;
++              };
++      };
++      enum dma_data_direction dir;
++};
++
++/*
++ * holds the current register values that we want to write. acts as a contiguous
++ * chunk of memory which we use to write the controller registers through DMA.
++ */
++struct nandc_regs {
++      __le32 cmd;
++      __le32 addr0;
++      __le32 addr1;
++      __le32 chip_sel;
++      __le32 exec;
++
++      __le32 cfg0;
++      __le32 cfg1;
++      __le32 ecc_bch_cfg;
++
++      __le32 clrflashstatus;
++      __le32 clrreadstatus;
++
++      __le32 cmd1;
++      __le32 vld;
++
++      __le32 orig_cmd1;
++      __le32 orig_vld;
++
++      __le32 ecc_buf_cfg;
++      __le32 read_location0;
++      __le32 read_location1;
++      __le32 read_location2;
++      __le32 read_location3;
++      __le32 read_location_last0;
++      __le32 read_location_last1;
++      __le32 read_location_last2;
++      __le32 read_location_last3;
++
++      __le32 erased_cw_detect_cfg_clr;
++      __le32 erased_cw_detect_cfg_set;
++};
++
++/*
++ * NAND controller data struct
++ *
++ * @dev:                      parent device
++ *
++ * @base:                     MMIO base
++ *
++ * @core_clk:                 controller clock
++ * @aon_clk:                  another controller clock
++ *
++ * @regs:                     a contiguous chunk of memory for DMA register
++ *                            writes. contains the register values to be
++ *                            written to controller
++ *
++ * @props:                    properties of current NAND controller,
++ *                            initialized via DT match data
++ *
++ * @controller:                       base controller structure
++ * @host_list:                        list containing all the chips attached to the
++ *                            controller
++ *
++ * @chan:                     dma channel
++ * @cmd_crci:                 ADM DMA CRCI for command flow control
++ * @data_crci:                        ADM DMA CRCI for data flow control
++ *
++ * @desc_list:                        DMA descriptor list (list of desc_infos)
++ *
++ * @data_buffer:              our local DMA buffer for page read/writes,
++ *                            used when we can't use the buffer provided
++ *                            by upper layers directly
++ * @reg_read_buf:             local buffer for reading back registers via DMA
++ *
++ * @base_phys:                        physical base address of controller registers
++ * @base_dma:                 dma base address of controller registers
++ * @reg_read_dma:             contains dma address for register read buffer
++ *
++ * @buf_size/count/start:     markers for chip->legacy.read_buf/write_buf
++ *                            functions
++ * @max_cwperpage:            maximum QPIC codewords required. calculated
++ *                            from all connected NAND devices pagesize
++ *
++ * @reg_read_pos:             marker for data read in reg_read_buf
++ *
++ * @cmd1/vld:                 some fixed controller register values
++ *
++ * @exec_opwrite:             flag to select correct number of code word
++ *                            while reading status
++ */
++struct qcom_nand_controller {
++      struct device *dev;
++
++      void __iomem *base;
++
++      struct clk *core_clk;
++      struct clk *aon_clk;
++
++      struct nandc_regs *regs;
++      struct bam_transaction *bam_txn;
++
++      const struct qcom_nandc_props *props;
++
++      struct nand_controller *controller;
++      struct list_head host_list;
++
++      union {
++              /* will be used only by QPIC for BAM DMA */
++              struct {
++                      struct dma_chan *tx_chan;
++                      struct dma_chan *rx_chan;
++                      struct dma_chan *cmd_chan;
++              };
++
++              /* will be used only by EBI2 for ADM DMA */
++              struct {
++                      struct dma_chan *chan;
++                      unsigned int cmd_crci;
++                      unsigned int data_crci;
++              };
++      };
++
++      struct list_head desc_list;
++
++      u8              *data_buffer;
++      __le32          *reg_read_buf;
++
++      phys_addr_t base_phys;
++      dma_addr_t base_dma;
++      dma_addr_t reg_read_dma;
++
++      int             buf_size;
++      int             buf_count;
++      int             buf_start;
++      unsigned int    max_cwperpage;
++
++      int reg_read_pos;
++
++      u32 cmd1, vld;
++      bool exec_opwrite;
++};
++
++/*
++ * This data type corresponds to the NAND controller properties which varies
++ * among different NAND controllers.
++ * @ecc_modes - ecc mode for NAND
++ * @dev_cmd_reg_start - NAND_DEV_CMD_* registers starting offset
++ * @supports_bam - whether NAND controller is using BAM
++ * @nandc_part_of_qpic - whether NAND controller is part of qpic IP
++ * @qpic_version2 - flag to indicate QPIC IP version 2
++ * @use_codeword_fixup - whether NAND has different layout for boot partitions
++ */
++struct qcom_nandc_props {
++      u32 ecc_modes;
++      u32 dev_cmd_reg_start;
++      bool supports_bam;
++      bool nandc_part_of_qpic;
++      bool qpic_version2;
++      bool use_codeword_fixup;
++};
++
++void qcom_free_bam_transaction(struct qcom_nand_controller *nandc);
++struct bam_transaction *qcom_alloc_bam_transaction(struct qcom_nand_controller *nandc);
++void qcom_clear_bam_transaction(struct qcom_nand_controller *nandc);
++void qcom_qpic_bam_dma_done(void *data);
++void qcom_nandc_dev_to_mem(struct qcom_nand_controller *nandc, bool is_cpu);
++int qcom_prepare_bam_async_desc(struct qcom_nand_controller *nandc,
++                              struct dma_chan *chan, unsigned long flags);
++int qcom_prep_bam_dma_desc_cmd(struct qcom_nand_controller *nandc, bool read,
++                             int reg_off, const void *vaddr, int size, unsigned int flags);
++int qcom_prep_bam_dma_desc_data(struct qcom_nand_controller *nandc, bool read,
++                              const void *vaddr, int size, unsigned int flags);
++int qcom_prep_adm_dma_desc(struct qcom_nand_controller *nandc, bool read, int reg_off,
++                         const void *vaddr, int size, bool flow_control);
++int qcom_read_reg_dma(struct qcom_nand_controller *nandc, int first, int num_regs,
++                    unsigned int flags);
++int qcom_write_reg_dma(struct qcom_nand_controller *nandc, __le32 *vaddr, int first,
++                     int num_regs, unsigned int flags);
++int qcom_read_data_dma(struct qcom_nand_controller *nandc, int reg_off, const u8 *vaddr,
++                     int size, unsigned int flags);
++int qcom_write_data_dma(struct qcom_nand_controller *nandc, int reg_off, const u8 *vaddr,
++                      int size, unsigned int flags);
++int qcom_submit_descs(struct qcom_nand_controller *nandc);
++void qcom_clear_read_regs(struct qcom_nand_controller *nandc);
++void qcom_nandc_unalloc(struct qcom_nand_controller *nandc);
++int qcom_nandc_alloc(struct qcom_nand_controller *nandc);
++#endif
++
diff --git a/target/linux/generic/backport-6.12/410-04-v6.14-mtd-rawnand-qcom-use-FIELD_PREP-and-GENMASK.patch b/target/linux/generic/backport-6.12/410-04-v6.14-mtd-rawnand-qcom-use-FIELD_PREP-and-GENMASK.patch
new file mode 100644 (file)
index 0000000..c19f681
--- /dev/null
@@ -0,0 +1,198 @@
+From 0c08080fd71cd5dd59643104b39d3c89d793ab3c Mon Sep 17 00:00:00 2001
+From: Md Sadre Alam <quic_mdalam@quicinc.com>
+Date: Wed, 20 Nov 2024 14:45:03 +0530
+Subject: [PATCH 4/4] mtd: rawnand: qcom: use FIELD_PREP and GENMASK
+
+Use the bitfield macro FIELD_PREP, and GENMASK to
+do the shift and mask in one go. This makes the code
+more readable.
+
+Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
+Signed-off-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/raw/qcom_nandc.c    | 97 ++++++++++++++--------------
+ include/linux/mtd/nand-qpic-common.h | 31 +++++----
+ 2 files changed, 67 insertions(+), 61 deletions(-)
+
+--- a/drivers/mtd/nand/raw/qcom_nandc.c
++++ b/drivers/mtd/nand/raw/qcom_nandc.c
+@@ -281,7 +281,7 @@ static void update_rw_regs(struct qcom_n
+                               (num_cw - 1) << CW_PER_PAGE);
+               cfg1 = cpu_to_le32(host->cfg1_raw);
+-              ecc_bch_cfg = cpu_to_le32(1 << ECC_CFG_ECC_DISABLE);
++              ecc_bch_cfg = cpu_to_le32(ECC_CFG_ECC_DISABLE);
+       }
+       nandc->regs->cmd = cmd;
+@@ -1494,42 +1494,41 @@ static int qcom_nand_attach_chip(struct
+       host->cw_size = host->cw_data + ecc->bytes;
+       bad_block_byte = mtd->writesize - host->cw_size * (cwperpage - 1) + 1;
+-      host->cfg0 = (cwperpage - 1) << CW_PER_PAGE
+-                              | host->cw_data << UD_SIZE_BYTES
+-                              | 0 << DISABLE_STATUS_AFTER_WRITE
+-                              | 5 << NUM_ADDR_CYCLES
+-                              | host->ecc_bytes_hw << ECC_PARITY_SIZE_BYTES_RS
+-                              | 0 << STATUS_BFR_READ
+-                              | 1 << SET_RD_MODE_AFTER_STATUS
+-                              | host->spare_bytes << SPARE_SIZE_BYTES;
+-
+-      host->cfg1 = 7 << NAND_RECOVERY_CYCLES
+-                              | 0 <<  CS_ACTIVE_BSY
+-                              | bad_block_byte << BAD_BLOCK_BYTE_NUM
+-                              | 0 << BAD_BLOCK_IN_SPARE_AREA
+-                              | 2 << WR_RD_BSY_GAP
+-                              | wide_bus << WIDE_FLASH
+-                              | host->bch_enabled << ENABLE_BCH_ECC;
+-
+-      host->cfg0_raw = (cwperpage - 1) << CW_PER_PAGE
+-                              | host->cw_size << UD_SIZE_BYTES
+-                              | 5 << NUM_ADDR_CYCLES
+-                              | 0 << SPARE_SIZE_BYTES;
+-
+-      host->cfg1_raw = 7 << NAND_RECOVERY_CYCLES
+-                              | 0 << CS_ACTIVE_BSY
+-                              | 17 << BAD_BLOCK_BYTE_NUM
+-                              | 1 << BAD_BLOCK_IN_SPARE_AREA
+-                              | 2 << WR_RD_BSY_GAP
+-                              | wide_bus << WIDE_FLASH
+-                              | 1 << DEV0_CFG1_ECC_DISABLE;
+-
+-      host->ecc_bch_cfg = !host->bch_enabled << ECC_CFG_ECC_DISABLE
+-                              | 0 << ECC_SW_RESET
+-                              | host->cw_data << ECC_NUM_DATA_BYTES
+-                              | 1 << ECC_FORCE_CLK_OPEN
+-                              | ecc_mode << ECC_MODE
+-                              | host->ecc_bytes_hw << ECC_PARITY_SIZE_BYTES_BCH;
++      host->cfg0 = FIELD_PREP(CW_PER_PAGE_MASK, (cwperpage - 1)) |
++                   FIELD_PREP(UD_SIZE_BYTES_MASK, host->cw_data) |
++                   FIELD_PREP(DISABLE_STATUS_AFTER_WRITE, 0) |
++                   FIELD_PREP(NUM_ADDR_CYCLES_MASK, 5) |
++                   FIELD_PREP(ECC_PARITY_SIZE_BYTES_RS, host->ecc_bytes_hw) |
++                   FIELD_PREP(STATUS_BFR_READ, 0) |
++                   FIELD_PREP(SET_RD_MODE_AFTER_STATUS, 1) |
++                   FIELD_PREP(SPARE_SIZE_BYTES_MASK, host->spare_bytes);
++
++      host->cfg1 = FIELD_PREP(NAND_RECOVERY_CYCLES_MASK, 7) |
++                   FIELD_PREP(BAD_BLOCK_BYTE_NUM_MASK, bad_block_byte) |
++                   FIELD_PREP(BAD_BLOCK_IN_SPARE_AREA, 0) |
++                   FIELD_PREP(WR_RD_BSY_GAP_MASK, 2) |
++                   FIELD_PREP(WIDE_FLASH, wide_bus) |
++                   FIELD_PREP(ENABLE_BCH_ECC, host->bch_enabled);
++
++      host->cfg0_raw = FIELD_PREP(CW_PER_PAGE_MASK, (cwperpage - 1)) |
++                       FIELD_PREP(UD_SIZE_BYTES_MASK, host->cw_size) |
++                       FIELD_PREP(NUM_ADDR_CYCLES_MASK, 5) |
++                       FIELD_PREP(SPARE_SIZE_BYTES_MASK, 0);
++
++      host->cfg1_raw = FIELD_PREP(NAND_RECOVERY_CYCLES_MASK, 7) |
++                       FIELD_PREP(CS_ACTIVE_BSY, 0) |
++                       FIELD_PREP(BAD_BLOCK_BYTE_NUM_MASK, 17) |
++                       FIELD_PREP(BAD_BLOCK_IN_SPARE_AREA, 1) |
++                       FIELD_PREP(WR_RD_BSY_GAP_MASK, 2) |
++                       FIELD_PREP(WIDE_FLASH, wide_bus) |
++                       FIELD_PREP(DEV0_CFG1_ECC_DISABLE, 1);
++
++      host->ecc_bch_cfg = FIELD_PREP(ECC_CFG_ECC_DISABLE, !host->bch_enabled) |
++                          FIELD_PREP(ECC_SW_RESET, 0) |
++                          FIELD_PREP(ECC_NUM_DATA_BYTES_MASK, host->cw_data) |
++                          FIELD_PREP(ECC_FORCE_CLK_OPEN, 1) |
++                          FIELD_PREP(ECC_MODE_MASK, ecc_mode) |
++                          FIELD_PREP(ECC_PARITY_SIZE_BYTES_BCH_MASK, host->ecc_bytes_hw);
+       if (!nandc->props->qpic_version2)
+               host->ecc_buf_cfg = 0x203 << NUM_STEPS;
+@@ -1887,21 +1886,21 @@ static int qcom_param_page_type_exec(str
+       nandc->regs->addr0 = 0;
+       nandc->regs->addr1 = 0;
+-      nandc->regs->cfg0 = cpu_to_le32(0 << CW_PER_PAGE |
+-                                      512 << UD_SIZE_BYTES |
+-                                      5 << NUM_ADDR_CYCLES |
+-                                      0 << SPARE_SIZE_BYTES);
+-
+-      nandc->regs->cfg1 = cpu_to_le32(7 << NAND_RECOVERY_CYCLES |
+-                                      0 << CS_ACTIVE_BSY |
+-                                      17 << BAD_BLOCK_BYTE_NUM |
+-                                      1 << BAD_BLOCK_IN_SPARE_AREA |
+-                                      2 << WR_RD_BSY_GAP |
+-                                      0 << WIDE_FLASH |
+-                                      1 << DEV0_CFG1_ECC_DISABLE);
++      host->cfg0 = FIELD_PREP(CW_PER_PAGE_MASK, 0) |
++                   FIELD_PREP(UD_SIZE_BYTES_MASK, 512) |
++                   FIELD_PREP(NUM_ADDR_CYCLES_MASK, 5) |
++                   FIELD_PREP(SPARE_SIZE_BYTES_MASK, 0);
++
++      host->cfg1 = FIELD_PREP(NAND_RECOVERY_CYCLES_MASK, 7) |
++                   FIELD_PREP(BAD_BLOCK_BYTE_NUM_MASK, 17) |
++                   FIELD_PREP(CS_ACTIVE_BSY, 0) |
++                   FIELD_PREP(BAD_BLOCK_IN_SPARE_AREA, 1) |
++                   FIELD_PREP(WR_RD_BSY_GAP_MASK, 2) |
++                   FIELD_PREP(WIDE_FLASH, 0) |
++                   FIELD_PREP(DEV0_CFG1_ECC_DISABLE, 1);
+       if (!nandc->props->qpic_version2)
+-              nandc->regs->ecc_buf_cfg = cpu_to_le32(1 << ECC_CFG_ECC_DISABLE);
++              nandc->regs->ecc_buf_cfg = cpu_to_le32(ECC_CFG_ECC_DISABLE);
+       /* configure CMD1 and VLD for ONFI param probing in QPIC v1 */
+       if (!nandc->props->qpic_version2) {
+--- a/include/linux/mtd/nand-qpic-common.h
++++ b/include/linux/mtd/nand-qpic-common.h
+@@ -70,35 +70,42 @@
+ #define       BS_CORRECTABLE_ERR_MSK          0x1f
+ /* NAND_DEVn_CFG0 bits */
+-#define       DISABLE_STATUS_AFTER_WRITE      4
++#define       DISABLE_STATUS_AFTER_WRITE      BIT(4)
+ #define       CW_PER_PAGE                     6
++#define       CW_PER_PAGE_MASK                GENMASK(8, 6)
+ #define       UD_SIZE_BYTES                   9
+ #define       UD_SIZE_BYTES_MASK              GENMASK(18, 9)
+-#define       ECC_PARITY_SIZE_BYTES_RS        19
++#define       ECC_PARITY_SIZE_BYTES_RS        GENMASK(22, 19)
+ #define       SPARE_SIZE_BYTES                23
+ #define       SPARE_SIZE_BYTES_MASK           GENMASK(26, 23)
+ #define       NUM_ADDR_CYCLES                 27
+-#define       STATUS_BFR_READ                 30
+-#define       SET_RD_MODE_AFTER_STATUS        31
++#define       NUM_ADDR_CYCLES_MASK            GENMASK(29, 27)
++#define       STATUS_BFR_READ                 BIT(30)
++#define       SET_RD_MODE_AFTER_STATUS        BIT(31)
+ /* NAND_DEVn_CFG0 bits */
+-#define       DEV0_CFG1_ECC_DISABLE           0
+-#define       WIDE_FLASH                      1
++#define       DEV0_CFG1_ECC_DISABLE           BIT(0)
++#define       WIDE_FLASH                      BIT(1)
+ #define       NAND_RECOVERY_CYCLES            2
+-#define       CS_ACTIVE_BSY                   5
++#define       NAND_RECOVERY_CYCLES_MASK       GENMASK(4, 2)
++#define       CS_ACTIVE_BSY                   BIT(5)
+ #define       BAD_BLOCK_BYTE_NUM              6
+-#define       BAD_BLOCK_IN_SPARE_AREA         16
++#define       BAD_BLOCK_BYTE_NUM_MASK         GENMASK(15, 6)
++#define       BAD_BLOCK_IN_SPARE_AREA         BIT(16)
+ #define       WR_RD_BSY_GAP                   17
+-#define       ENABLE_BCH_ECC                  27
++#define       WR_RD_BSY_GAP_MASK              GENMASK(22, 17)
++#define       ENABLE_BCH_ECC                  BIT(27)
+ /* NAND_DEV0_ECC_CFG bits */
+-#define       ECC_CFG_ECC_DISABLE             0
+-#define       ECC_SW_RESET                    1
++#define       ECC_CFG_ECC_DISABLE             BIT(0)
++#define       ECC_SW_RESET                    BIT(1)
+ #define       ECC_MODE                        4
++#define       ECC_MODE_MASK                   GENMASK(5, 4)
+ #define       ECC_PARITY_SIZE_BYTES_BCH       8
++#define       ECC_PARITY_SIZE_BYTES_BCH_MASK  GENMASK(12, 8)
+ #define       ECC_NUM_DATA_BYTES              16
+ #define       ECC_NUM_DATA_BYTES_MASK         GENMASK(25, 16)
+-#define       ECC_FORCE_CLK_OPEN              30
++#define       ECC_FORCE_CLK_OPEN              BIT(30)
+ /* NAND_DEV_CMD1 bits */
+ #define       READ_ADDR                       0
diff --git a/target/linux/generic/backport-6.12/410-05-v6.14-mtd-rawnand-qcom-fix-broken-config-in-qcom_param_pag.patch b/target/linux/generic/backport-6.12/410-05-v6.14-mtd-rawnand-qcom-fix-broken-config-in-qcom_param_pag.patch
new file mode 100644 (file)
index 0000000..238a1f9
--- /dev/null
@@ -0,0 +1,64 @@
+From 9d4ffbcfde283f2a87ea45128ddf7e6651facdd9 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 7 Feb 2025 20:42:38 +0100
+Subject: [PATCH] mtd: rawnand: qcom: fix broken config in
+ qcom_param_page_type_exec
+
+Fix broken config in qcom_param_page_type_exec caused by copy-paste error
+from commit 0c08080fd71c ("mtd: rawnand: qcom: use FIELD_PREP and GENMASK")
+
+In qcom_param_page_type_exec the value needs to be set to
+nandc->regs->cfg0 instead of host->cfg0. This wrong configuration caused
+the Qcom NANDC driver to malfunction on any device that makes use of it
+(IPQ806x, IPQ40xx, IPQ807x, IPQ60xx) with the following error:
+
+[    0.885369] nand: device found, Manufacturer ID: 0x2c, Chip ID: 0xaa
+[    0.885909] nand: Micron NAND 256MiB 1,8V 8-bit
+[    0.892499] nand: 256 MiB, SLC, erase size: 128 KiB, page size: 2048, OOB size: 64
+[    0.896823] nand: ECC (step, strength) = (512, 8) does not fit in OOB
+[    0.896836] qcom-nandc 79b0000.nand-controller: No valid ECC settings possible
+[    0.910996] bam-dma-engine 7984000.dma-controller: Cannot free busy channel
+[    0.918070] qcom-nandc: probe of 79b0000.nand-controller failed with error -28
+
+Restore original configuration fix the problem and makes the driver work
+again.
+
+Cc: stable@vger.kernel.org
+Fixes: 0c08080fd71c ("mtd: rawnand: qcom: use FIELD_PREP and GENMASK")
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/mtd/nand/raw/qcom_nandc.c | 24 ++++++++++++------------
+ 1 file changed, 12 insertions(+), 12 deletions(-)
+
+--- a/drivers/mtd/nand/raw/qcom_nandc.c
++++ b/drivers/mtd/nand/raw/qcom_nandc.c
+@@ -1886,18 +1886,18 @@ static int qcom_param_page_type_exec(str
+       nandc->regs->addr0 = 0;
+       nandc->regs->addr1 = 0;
+-      host->cfg0 = FIELD_PREP(CW_PER_PAGE_MASK, 0) |
+-                   FIELD_PREP(UD_SIZE_BYTES_MASK, 512) |
+-                   FIELD_PREP(NUM_ADDR_CYCLES_MASK, 5) |
+-                   FIELD_PREP(SPARE_SIZE_BYTES_MASK, 0);
++      nandc->regs->cfg0 = FIELD_PREP(CW_PER_PAGE_MASK, 0) |
++                          FIELD_PREP(UD_SIZE_BYTES_MASK, 512) |
++                          FIELD_PREP(NUM_ADDR_CYCLES_MASK, 5) |
++                          FIELD_PREP(SPARE_SIZE_BYTES_MASK, 0);
+-      host->cfg1 = FIELD_PREP(NAND_RECOVERY_CYCLES_MASK, 7) |
+-                   FIELD_PREP(BAD_BLOCK_BYTE_NUM_MASK, 17) |
+-                   FIELD_PREP(CS_ACTIVE_BSY, 0) |
+-                   FIELD_PREP(BAD_BLOCK_IN_SPARE_AREA, 1) |
+-                   FIELD_PREP(WR_RD_BSY_GAP_MASK, 2) |
+-                   FIELD_PREP(WIDE_FLASH, 0) |
+-                   FIELD_PREP(DEV0_CFG1_ECC_DISABLE, 1);
++      nandc->regs->cfg1 = FIELD_PREP(NAND_RECOVERY_CYCLES_MASK, 7) |
++                          FIELD_PREP(BAD_BLOCK_BYTE_NUM_MASK, 17) |
++                          FIELD_PREP(CS_ACTIVE_BSY, 0) |
++                          FIELD_PREP(BAD_BLOCK_IN_SPARE_AREA, 1) |
++                          FIELD_PREP(WR_RD_BSY_GAP_MASK, 2) |
++                          FIELD_PREP(WIDE_FLASH, 0) |
++                          FIELD_PREP(DEV0_CFG1_ECC_DISABLE, 1);
+       if (!nandc->props->qpic_version2)
+               nandc->regs->ecc_buf_cfg = cpu_to_le32(ECC_CFG_ECC_DISABLE);
diff --git a/target/linux/generic/backport-6.12/410-06-v6.14-mtd-rawnand-qcom-Fix-build-issue-on-x86-architecture.patch b/target/linux/generic/backport-6.12/410-06-v6.14-mtd-rawnand-qcom-Fix-build-issue-on-x86-architecture.patch
new file mode 100644 (file)
index 0000000..67beed3
--- /dev/null
@@ -0,0 +1,77 @@
+From b9371866799d67a80be0ea9e01bd41987db22f26 Mon Sep 17 00:00:00 2001
+From: Md Sadre Alam <quic_mdalam@quicinc.com>
+Date: Mon, 6 Jan 2025 18:45:58 +0530
+Subject: [PATCH] mtd: rawnand: qcom: Fix build issue on x86 architecture
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Fix a buffer overflow issue in qcom_clear_bam_transaction by using
+struct_group to group related fields and avoid FORTIFY_SOURCE warnings.
+
+On x86 architecture, the following error occurs due to warnings being
+treated as errors:
+
+In function ‘fortify_memset_chk’,
+    inlined from ‘qcom_clear_bam_transaction’ at
+drivers/mtd/nand/qpic_common.c:88:2:
+./include/linux/fortify-string.h:480:25: error: call to ‘__write_overflow_field’
+declared with attribute warning: detected write beyond size of field
+(1st parameter); maybe use struct_group()? [-Werror=attribute-warning]
+  480 |                         __write_overflow_field(p_size_field, size);
+      |                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  LD [M]  drivers/mtd/nand/nandcore.o
+  CC [M]  drivers/w1/masters/mxc_w1.o
+cc1: all warnings being treated as errors
+
+This patch addresses the issue by grouping the related fields in
+struct bam_transaction using struct_group and updating the memset call
+accordingly.
+
+Fixes: 8c52932da5e6 ("mtd: rawnand: qcom: cleanup qcom_nandc driver")
+Signed-off-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/qpic_common.c       |  2 +-
+ include/linux/mtd/nand-qpic-common.h | 19 +++++++++++--------
+ 2 files changed, 12 insertions(+), 9 deletions(-)
+
+--- a/drivers/mtd/nand/qpic_common.c
++++ b/drivers/mtd/nand/qpic_common.c
+@@ -85,7 +85,7 @@ void qcom_clear_bam_transaction(struct q
+       if (!nandc->props->supports_bam)
+               return;
+-      memset(&bam_txn->bam_ce_pos, 0, sizeof(u32) * 8);
++      memset(&bam_txn->bam_positions, 0, sizeof(bam_txn->bam_positions));
+       bam_txn->last_data_desc = NULL;
+       sg_init_table(bam_txn->cmd_sgl, nandc->max_cwperpage *
+--- a/include/linux/mtd/nand-qpic-common.h
++++ b/include/linux/mtd/nand-qpic-common.h
+@@ -254,14 +254,17 @@ struct bam_transaction {
+       struct dma_async_tx_descriptor *last_data_desc;
+       struct dma_async_tx_descriptor *last_cmd_desc;
+       struct completion txn_done;
+-      u32 bam_ce_pos;
+-      u32 bam_ce_start;
+-      u32 cmd_sgl_pos;
+-      u32 cmd_sgl_start;
+-      u32 tx_sgl_pos;
+-      u32 tx_sgl_start;
+-      u32 rx_sgl_pos;
+-      u32 rx_sgl_start;
++      struct_group(bam_positions,
++              u32 bam_ce_pos;
++              u32 bam_ce_start;
++              u32 cmd_sgl_pos;
++              u32 cmd_sgl_start;
++              u32 tx_sgl_pos;
++              u32 tx_sgl_start;
++              u32 rx_sgl_pos;
++              u32 rx_sgl_start;
++
++      );
+ };
+ /*
diff --git a/target/linux/generic/backport-6.12/410-07-v6.15-spi-spi-qpic-add-driver-for-QCOM-SPI-NAND-flash-Inte.patch b/target/linux/generic/backport-6.12/410-07-v6.15-spi-spi-qpic-add-driver-for-QCOM-SPI-NAND-flash-Inte.patch
new file mode 100644 (file)
index 0000000..310d902
--- /dev/null
@@ -0,0 +1,1737 @@
+From 7304d1909080ef0c9da703500a97f46c98393fcd Mon Sep 17 00:00:00 2001
+From: Md Sadre Alam <quic_mdalam@quicinc.com>
+Date: Mon, 24 Feb 2025 16:44:14 +0530
+Subject: [PATCH] spi: spi-qpic: add driver for QCOM SPI NAND flash Interface
+
+This driver implements support for the SPI-NAND mode of QCOM NAND Flash
+Interface as a SPI-MEM controller with pipelined ECC capability.
+
+Co-developed-by: Sricharan Ramabadhran <quic_srichara@quicinc.com>
+Signed-off-by: Sricharan Ramabadhran <quic_srichara@quicinc.com>
+Co-developed-by: Varadarajan Narayanan <quic_varada@quicinc.com>
+Signed-off-by: Varadarajan Narayanan <quic_varada@quicinc.com>
+Signed-off-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+Link: https://patch.msgid.link/20250224111414.2809669-3-quic_mdalam@quicinc.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/mtd/nand/Makefile            |    4 +
+ drivers/spi/Kconfig                  |    9 +
+ drivers/spi/Makefile                 |    1 +
+ drivers/spi/spi-qpic-snand.c         | 1631 ++++++++++++++++++++++++++
+ include/linux/mtd/nand-qpic-common.h |    7 +
+ 5 files changed, 1652 insertions(+)
+ create mode 100644 drivers/spi/spi-qpic-snand.c
+
+--- a/drivers/mtd/nand/Makefile
++++ b/drivers/mtd/nand/Makefile
+@@ -3,7 +3,11 @@
+ nandcore-objs := core.o bbt.o
+ obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
+ obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o
++ifeq ($(CONFIG_SPI_QPIC_SNAND),y)
++obj-$(CONFIG_SPI_QPIC_SNAND) += qpic_common.o
++else
+ obj-$(CONFIG_MTD_NAND_QCOM) += qpic_common.o
++endif
+ obj-y += onenand/
+ obj-y += raw/
+ obj-y += spi/
+--- a/drivers/spi/Kconfig
++++ b/drivers/spi/Kconfig
+@@ -898,6 +898,15 @@ config SPI_QCOM_QSPI
+       help
+         QSPI(Quad SPI) driver for Qualcomm QSPI controller.
++config SPI_QPIC_SNAND
++      bool "QPIC SNAND controller"
++      depends on ARCH_QCOM || COMPILE_TEST
++      select MTD
++      help
++        QPIC_SNAND (QPIC SPI NAND) driver for Qualcomm QPIC controller.
++        QPIC controller supports both parallel nand and serial nand.
++        This config will enable serial nand driver for QPIC controller.
++
+ config SPI_QUP
+       tristate "Qualcomm SPI controller with QUP interface"
+       depends on ARCH_QCOM || COMPILE_TEST
+--- a/drivers/spi/Makefile
++++ b/drivers/spi/Makefile
+@@ -114,6 +114,7 @@ obj-$(CONFIG_SPI_PXA2XX)           += spi-pxa2xx-
+ obj-$(CONFIG_SPI_PXA2XX_PCI)          += spi-pxa2xx-pci.o
+ obj-$(CONFIG_SPI_QCOM_GENI)           += spi-geni-qcom.o
+ obj-$(CONFIG_SPI_QCOM_QSPI)           += spi-qcom-qspi.o
++obj-$(CONFIG_SPI_QPIC_SNAND)            += spi-qpic-snand.o
+ obj-$(CONFIG_SPI_QUP)                 += spi-qup.o
+ obj-$(CONFIG_SPI_ROCKCHIP)            += spi-rockchip.o
+ obj-$(CONFIG_SPI_ROCKCHIP_SFC)                += spi-rockchip-sfc.o
+--- /dev/null
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -0,0 +1,1631 @@
++/*
++ * SPDX-License-Identifier: GPL-2.0
++ *
++ * Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved.
++ *
++ * Authors:
++ *    Md Sadre Alam <quic_mdalam@quicinc.com>
++ *    Sricharan R <quic_srichara@quicinc.com>
++ *    Varadarajan Narayanan <quic_varada@quicinc.com>
++ */
++#include <linux/bitops.h>
++#include <linux/clk.h>
++#include <linux/delay.h>
++#include <linux/dmaengine.h>
++#include <linux/dma-mapping.h>
++#include <linux/dma/qcom_adm.h>
++#include <linux/dma/qcom_bam_dma.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/slab.h>
++#include <linux/mtd/nand-qpic-common.h>
++#include <linux/mtd/spinand.h>
++#include <linux/bitfield.h>
++
++#define NAND_FLASH_SPI_CFG            0xc0
++#define NAND_NUM_ADDR_CYCLES          0xc4
++#define NAND_BUSY_CHECK_WAIT_CNT      0xc8
++#define NAND_FLASH_FEATURES           0xf64
++
++/* QSPI NAND config reg bits */
++#define LOAD_CLK_CNTR_INIT_EN         BIT(28)
++#define CLK_CNTR_INIT_VAL_VEC         0x924
++#define CLK_CNTR_INIT_VAL_VEC_MASK    GENMASK(27, 16)
++#define FEA_STATUS_DEV_ADDR           0xc0
++#define FEA_STATUS_DEV_ADDR_MASK      GENMASK(15, 8)
++#define SPI_CFG                               BIT(0)
++#define SPI_NUM_ADDR                  0xDA4DB
++#define SPI_WAIT_CNT                  0x10
++#define QPIC_QSPI_NUM_CS              1
++#define SPI_TRANSFER_MODE_x1          BIT(29)
++#define SPI_TRANSFER_MODE_x4          (3 << 29)
++#define SPI_WP                                BIT(28)
++#define SPI_HOLD                      BIT(27)
++#define QPIC_SET_FEATURE              BIT(31)
++
++#define SPINAND_RESET                 0xff
++#define SPINAND_READID                        0x9f
++#define SPINAND_GET_FEATURE           0x0f
++#define SPINAND_SET_FEATURE           0x1f
++#define SPINAND_READ                  0x13
++#define SPINAND_ERASE                 0xd8
++#define SPINAND_WRITE_EN              0x06
++#define SPINAND_PROGRAM_EXECUTE               0x10
++#define SPINAND_PROGRAM_LOAD          0x84
++
++#define ACC_FEATURE                   0xe
++#define BAD_BLOCK_MARKER_SIZE         0x2
++#define OOB_BUF_SIZE                  128
++#define ecceng_to_qspi(eng)           container_of(eng, struct qpic_spi_nand, ecc_eng)
++
++struct qpic_snand_op {
++      u32 cmd_reg;
++      u32 addr1_reg;
++      u32 addr2_reg;
++};
++
++struct snandc_read_status {
++      __le32 snandc_flash;
++      __le32 snandc_buffer;
++      __le32 snandc_erased_cw;
++};
++
++/*
++ * ECC state struct
++ * @corrected:                ECC corrected
++ * @bitflips:         Max bit flip
++ * @failed:           ECC failed
++ */
++struct qcom_ecc_stats {
++      u32 corrected;
++      u32 bitflips;
++      u32 failed;
++};
++
++struct qpic_ecc {
++      struct device *dev;
++      int ecc_bytes_hw;
++      int spare_bytes;
++      int bbm_size;
++      int ecc_mode;
++      int bytes;
++      int steps;
++      int step_size;
++      int strength;
++      int cw_size;
++      int cw_data;
++      u32 cfg0;
++      u32 cfg1;
++      u32 cfg0_raw;
++      u32 cfg1_raw;
++      u32 ecc_buf_cfg;
++      u32 ecc_bch_cfg;
++      u32 clrflashstatus;
++      u32 clrreadstatus;
++      bool bch_enabled;
++};
++
++struct qpic_spi_nand {
++      struct qcom_nand_controller *snandc;
++      struct spi_controller *ctlr;
++      struct mtd_info *mtd;
++      struct clk *iomacro_clk;
++      struct qpic_ecc *ecc;
++      struct qcom_ecc_stats ecc_stats;
++      struct nand_ecc_engine ecc_eng;
++      u8 *data_buf;
++      u8 *oob_buf;
++      u32 wlen;
++      __le32 addr1;
++      __le32 addr2;
++      __le32 cmd;
++      u32 num_cw;
++      bool oob_rw;
++      bool page_rw;
++      bool raw_rw;
++};
++
++static void qcom_spi_set_read_loc_first(struct qcom_nand_controller *snandc,
++                                      int reg, int cw_offset, int read_size,
++                                      int is_last_read_loc)
++{
++      __le32 locreg_val;
++      u32 val = (((cw_offset) << READ_LOCATION_OFFSET) |
++                ((read_size) << READ_LOCATION_SIZE) | ((is_last_read_loc)
++                << READ_LOCATION_LAST));
++
++      locreg_val = cpu_to_le32(val);
++
++      if (reg == NAND_READ_LOCATION_0)
++              snandc->regs->read_location0 = locreg_val;
++      else if (reg == NAND_READ_LOCATION_1)
++              snandc->regs->read_location1 = locreg_val;
++      else if (reg == NAND_READ_LOCATION_2)
++              snandc->regs->read_location1 = locreg_val;
++      else if (reg == NAND_READ_LOCATION_3)
++              snandc->regs->read_location3 = locreg_val;
++}
++
++static void qcom_spi_set_read_loc_last(struct qcom_nand_controller *snandc,
++                                     int reg, int cw_offset, int read_size,
++                                     int is_last_read_loc)
++{
++      __le32 locreg_val;
++      u32 val = (((cw_offset) << READ_LOCATION_OFFSET) |
++                ((read_size) << READ_LOCATION_SIZE) | ((is_last_read_loc)
++                << READ_LOCATION_LAST));
++
++      locreg_val = cpu_to_le32(val);
++
++      if (reg == NAND_READ_LOCATION_LAST_CW_0)
++              snandc->regs->read_location_last0 = locreg_val;
++      else if (reg == NAND_READ_LOCATION_LAST_CW_1)
++              snandc->regs->read_location_last1 = locreg_val;
++      else if (reg == NAND_READ_LOCATION_LAST_CW_2)
++              snandc->regs->read_location_last2 = locreg_val;
++      else if (reg == NAND_READ_LOCATION_LAST_CW_3)
++              snandc->regs->read_location_last3 = locreg_val;
++}
++
++static struct qcom_nand_controller *nand_to_qcom_snand(struct nand_device *nand)
++{
++      struct nand_ecc_engine *eng = nand->ecc.engine;
++      struct qpic_spi_nand *qspi = ecceng_to_qspi(eng);
++
++      return qspi->snandc;
++}
++
++static int qcom_spi_init(struct qcom_nand_controller *snandc)
++{
++      u32 snand_cfg_val = 0x0;
++      int ret;
++
++      snand_cfg_val = FIELD_PREP(CLK_CNTR_INIT_VAL_VEC_MASK, CLK_CNTR_INIT_VAL_VEC) |
++                      FIELD_PREP(LOAD_CLK_CNTR_INIT_EN, 0) |
++                      FIELD_PREP(FEA_STATUS_DEV_ADDR_MASK, FEA_STATUS_DEV_ADDR) |
++                      FIELD_PREP(SPI_CFG, 0);
++
++      snandc->regs->spi_cfg = cpu_to_le32(snand_cfg_val);
++      snandc->regs->num_addr_cycle = cpu_to_le32(SPI_NUM_ADDR);
++      snandc->regs->busy_wait_cnt = cpu_to_le32(SPI_WAIT_CNT);
++
++      qcom_write_reg_dma(snandc, &snandc->regs->spi_cfg, NAND_FLASH_SPI_CFG, 1, 0);
++
++      snand_cfg_val &= ~LOAD_CLK_CNTR_INIT_EN;
++      snandc->regs->spi_cfg = cpu_to_le32(snand_cfg_val);
++
++      qcom_write_reg_dma(snandc, &snandc->regs->spi_cfg, NAND_FLASH_SPI_CFG, 1, 0);
++
++      qcom_write_reg_dma(snandc, &snandc->regs->num_addr_cycle, NAND_NUM_ADDR_CYCLES, 1, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->busy_wait_cnt, NAND_BUSY_CHECK_WAIT_CNT, 1,
++                         NAND_BAM_NEXT_SGL);
++
++      ret = qcom_submit_descs(snandc);
++      if (ret) {
++              dev_err(snandc->dev, "failure in submitting spi init descriptor\n");
++              return ret;
++      }
++
++      return ret;
++}
++
++static int qcom_spi_ooblayout_ecc(struct mtd_info *mtd, int section,
++                                struct mtd_oob_region *oobregion)
++{
++      struct nand_device *nand = mtd_to_nanddev(mtd);
++      struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand);
++      struct qpic_ecc *qecc = snandc->qspi->ecc;
++
++      if (section > 1)
++              return -ERANGE;
++
++      oobregion->length = qecc->ecc_bytes_hw + qecc->spare_bytes;
++      oobregion->offset = mtd->oobsize - oobregion->length;
++
++      return 0;
++}
++
++static int qcom_spi_ooblayout_free(struct mtd_info *mtd, int section,
++                                 struct mtd_oob_region *oobregion)
++{
++      struct nand_device *nand = mtd_to_nanddev(mtd);
++      struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand);
++      struct qpic_ecc *qecc = snandc->qspi->ecc;
++
++      if (section)
++              return -ERANGE;
++
++      oobregion->length = qecc->steps * 4;
++      oobregion->offset = ((qecc->steps - 1) * qecc->bytes) + qecc->bbm_size;
++
++      return 0;
++}
++
++static const struct mtd_ooblayout_ops qcom_spi_ooblayout = {
++      .ecc = qcom_spi_ooblayout_ecc,
++      .free = qcom_spi_ooblayout_free,
++};
++
++static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand)
++{
++      struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand);
++      struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
++      struct mtd_info *mtd = nanddev_to_mtd(nand);
++      int cwperpage, bad_block_byte;
++      struct qpic_ecc *ecc_cfg;
++
++      cwperpage = mtd->writesize / NANDC_STEP_SIZE;
++      snandc->qspi->num_cw = cwperpage;
++
++      ecc_cfg = kzalloc(sizeof(*ecc_cfg), GFP_KERNEL);
++      if (!ecc_cfg)
++              return -ENOMEM;
++      snandc->qspi->oob_buf = kzalloc(mtd->writesize + mtd->oobsize,
++                                      GFP_KERNEL);
++      if (!snandc->qspi->oob_buf)
++              return -ENOMEM;
++
++      memset(snandc->qspi->oob_buf, 0xff, mtd->writesize + mtd->oobsize);
++
++      nand->ecc.ctx.priv = ecc_cfg;
++      snandc->qspi->mtd = mtd;
++
++      ecc_cfg->ecc_bytes_hw = 7;
++      ecc_cfg->spare_bytes = 4;
++      ecc_cfg->bbm_size = 1;
++      ecc_cfg->bch_enabled = true;
++      ecc_cfg->bytes = ecc_cfg->ecc_bytes_hw + ecc_cfg->spare_bytes + ecc_cfg->bbm_size;
++
++      ecc_cfg->steps = 4;
++      ecc_cfg->strength = 4;
++      ecc_cfg->step_size = 512;
++      ecc_cfg->cw_data = 516;
++      ecc_cfg->cw_size = ecc_cfg->cw_data + ecc_cfg->bytes;
++      bad_block_byte = mtd->writesize - ecc_cfg->cw_size * (cwperpage - 1) + 1;
++
++      mtd_set_ooblayout(mtd, &qcom_spi_ooblayout);
++
++      ecc_cfg->cfg0 = FIELD_PREP(CW_PER_PAGE_MASK, (cwperpage - 1)) |
++                      FIELD_PREP(UD_SIZE_BYTES_MASK, ecc_cfg->cw_data) |
++                      FIELD_PREP(DISABLE_STATUS_AFTER_WRITE, 1) |
++                      FIELD_PREP(NUM_ADDR_CYCLES_MASK, 3) |
++                      FIELD_PREP(ECC_PARITY_SIZE_BYTES_RS, ecc_cfg->ecc_bytes_hw) |
++                      FIELD_PREP(STATUS_BFR_READ, 0) |
++                      FIELD_PREP(SET_RD_MODE_AFTER_STATUS, 1) |
++                      FIELD_PREP(SPARE_SIZE_BYTES_MASK, ecc_cfg->spare_bytes);
++
++      ecc_cfg->cfg1 = FIELD_PREP(NAND_RECOVERY_CYCLES_MASK, 0) |
++                      FIELD_PREP(CS_ACTIVE_BSY, 0) |
++                      FIELD_PREP(BAD_BLOCK_BYTE_NUM_MASK, bad_block_byte) |
++                      FIELD_PREP(BAD_BLOCK_IN_SPARE_AREA, 0) |
++                      FIELD_PREP(WR_RD_BSY_GAP_MASK, 20) |
++                      FIELD_PREP(WIDE_FLASH, 0) |
++                      FIELD_PREP(ENABLE_BCH_ECC, ecc_cfg->bch_enabled);
++
++      ecc_cfg->cfg0_raw = FIELD_PREP(CW_PER_PAGE_MASK, (cwperpage - 1)) |
++                          FIELD_PREP(NUM_ADDR_CYCLES_MASK, 3) |
++                          FIELD_PREP(UD_SIZE_BYTES_MASK, ecc_cfg->cw_size) |
++                          FIELD_PREP(SPARE_SIZE_BYTES_MASK, 0);
++
++      ecc_cfg->cfg1_raw = FIELD_PREP(NAND_RECOVERY_CYCLES_MASK, 0) |
++                          FIELD_PREP(CS_ACTIVE_BSY, 0) |
++                          FIELD_PREP(BAD_BLOCK_BYTE_NUM_MASK, 17) |
++                          FIELD_PREP(BAD_BLOCK_IN_SPARE_AREA, 1) |
++                          FIELD_PREP(WR_RD_BSY_GAP_MASK, 20) |
++                          FIELD_PREP(WIDE_FLASH, 0) |
++                          FIELD_PREP(DEV0_CFG1_ECC_DISABLE, 1);
++
++      ecc_cfg->ecc_bch_cfg = FIELD_PREP(ECC_CFG_ECC_DISABLE, !ecc_cfg->bch_enabled) |
++                             FIELD_PREP(ECC_SW_RESET, 0) |
++                             FIELD_PREP(ECC_NUM_DATA_BYTES_MASK, ecc_cfg->cw_data) |
++                             FIELD_PREP(ECC_FORCE_CLK_OPEN, 1) |
++                             FIELD_PREP(ECC_MODE_MASK, 0) |
++                             FIELD_PREP(ECC_PARITY_SIZE_BYTES_BCH_MASK, ecc_cfg->ecc_bytes_hw);
++
++      ecc_cfg->ecc_buf_cfg = 0x203 << NUM_STEPS;
++      ecc_cfg->clrflashstatus = FS_READY_BSY_N;
++      ecc_cfg->clrreadstatus = 0xc0;
++
++      conf->step_size = ecc_cfg->step_size;
++      conf->strength = ecc_cfg->strength;
++
++      snandc->regs->erased_cw_detect_cfg_clr = cpu_to_le32(CLR_ERASED_PAGE_DET);
++      snandc->regs->erased_cw_detect_cfg_set = cpu_to_le32(SET_ERASED_PAGE_DET);
++
++      dev_dbg(snandc->dev, "ECC strength: %u bits per %u bytes\n",
++              ecc_cfg->strength, ecc_cfg->step_size);
++
++      return 0;
++}
++
++static void qcom_spi_ecc_cleanup_ctx_pipelined(struct nand_device *nand)
++{
++      struct qpic_ecc *ecc_cfg = nand_to_ecc_ctx(nand);
++
++      kfree(ecc_cfg);
++}
++
++static int qcom_spi_ecc_prepare_io_req_pipelined(struct nand_device *nand,
++                                               struct nand_page_io_req *req)
++{
++      struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand);
++      struct qpic_ecc *ecc_cfg = nand_to_ecc_ctx(nand);
++
++      snandc->qspi->ecc = ecc_cfg;
++      snandc->qspi->raw_rw = false;
++      snandc->qspi->oob_rw = false;
++      snandc->qspi->page_rw = false;
++
++      if (req->datalen)
++              snandc->qspi->page_rw = true;
++
++      if (req->ooblen)
++              snandc->qspi->oob_rw = true;
++
++      if (req->mode == MTD_OPS_RAW)
++              snandc->qspi->raw_rw = true;
++
++      return 0;
++}
++
++static int qcom_spi_ecc_finish_io_req_pipelined(struct nand_device *nand,
++                                              struct nand_page_io_req *req)
++{
++      struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand);
++      struct mtd_info *mtd = nanddev_to_mtd(nand);
++
++      if (req->mode == MTD_OPS_RAW || req->type != NAND_PAGE_READ)
++              return 0;
++
++      if (snandc->qspi->ecc_stats.failed)
++              mtd->ecc_stats.failed += snandc->qspi->ecc_stats.failed;
++      else
++              mtd->ecc_stats.corrected += snandc->qspi->ecc_stats.corrected;
++
++      if (snandc->qspi->ecc_stats.failed)
++              return -EBADMSG;
++      else
++              return snandc->qspi->ecc_stats.bitflips;
++}
++
++static struct nand_ecc_engine_ops qcom_spi_ecc_engine_ops_pipelined = {
++      .init_ctx = qcom_spi_ecc_init_ctx_pipelined,
++      .cleanup_ctx = qcom_spi_ecc_cleanup_ctx_pipelined,
++      .prepare_io_req = qcom_spi_ecc_prepare_io_req_pipelined,
++      .finish_io_req = qcom_spi_ecc_finish_io_req_pipelined,
++};
++
++/* helper to configure location register values */
++static void qcom_spi_set_read_loc(struct qcom_nand_controller *snandc, int cw, int reg,
++                                int cw_offset, int read_size, int is_last_read_loc)
++{
++      int reg_base = NAND_READ_LOCATION_0;
++      int num_cw = snandc->qspi->num_cw;
++
++      if (cw == (num_cw - 1))
++              reg_base = NAND_READ_LOCATION_LAST_CW_0;
++
++      reg_base += reg * 4;
++
++      if (cw == (num_cw - 1))
++              return qcom_spi_set_read_loc_last(snandc, reg_base, cw_offset,
++                                                read_size, is_last_read_loc);
++      else
++              return qcom_spi_set_read_loc_first(snandc, reg_base, cw_offset,
++                                                 read_size, is_last_read_loc);
++}
++
++static void
++qcom_spi_config_cw_read(struct qcom_nand_controller *snandc, bool use_ecc, int cw)
++{
++      __le32 *reg = &snandc->regs->read_location0;
++      int num_cw = snandc->qspi->num_cw;
++
++      qcom_write_reg_dma(snandc, reg, NAND_READ_LOCATION_0, 4, NAND_BAM_NEXT_SGL);
++      if (cw == (num_cw - 1)) {
++              reg = &snandc->regs->read_location_last0;
++              qcom_write_reg_dma(snandc, reg, NAND_READ_LOCATION_LAST_CW_0, 4,
++                                 NAND_BAM_NEXT_SGL);
++      }
++
++      qcom_write_reg_dma(snandc, &snandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(snandc, &snandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++
++      qcom_read_reg_dma(snandc, NAND_FLASH_STATUS, 2, 0);
++      qcom_read_reg_dma(snandc, NAND_ERASED_CW_DETECT_STATUS, 1,
++                        NAND_BAM_NEXT_SGL);
++}
++
++static int qcom_spi_block_erase(struct qcom_nand_controller *snandc)
++{
++      struct qpic_ecc *ecc_cfg = snandc->qspi->ecc;
++      int ret;
++
++      snandc->buf_count = 0;
++      snandc->buf_start = 0;
++      qcom_clear_read_regs(snandc);
++      qcom_clear_bam_transaction(snandc);
++
++      snandc->regs->cmd = snandc->qspi->cmd;
++      snandc->regs->addr0 = snandc->qspi->addr1;
++      snandc->regs->addr1 = snandc->qspi->addr2;
++      snandc->regs->cfg0 = cpu_to_le32(ecc_cfg->cfg0_raw & ~(7 << CW_PER_PAGE));
++      snandc->regs->cfg1 = cpu_to_le32(ecc_cfg->cfg1_raw);
++      snandc->regs->exec = cpu_to_le32(1);
++
++      qcom_write_reg_dma(snandc, &snandc->regs->cmd, NAND_FLASH_CMD, 3, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 2, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(snandc, &snandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++
++      ret = qcom_submit_descs(snandc);
++      if (ret) {
++              dev_err(snandc->dev, "failure to erase block\n");
++              return ret;
++      }
++
++      return 0;
++}
++
++static void qcom_spi_config_single_cw_page_read(struct qcom_nand_controller *snandc,
++                                              bool use_ecc, int cw)
++{
++      __le32 *reg = &snandc->regs->read_location0;
++      int num_cw = snandc->qspi->num_cw;
++
++      qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr,
++                         NAND_ERASED_CW_DETECT_CFG, 1, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set,
++                         NAND_ERASED_CW_DETECT_CFG, 1,
++                         NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL);
++
++      if (cw == (num_cw - 1)) {
++              reg = &snandc->regs->read_location_last0;
++              qcom_write_reg_dma(snandc, reg, NAND_READ_LOCATION_LAST_CW_0, 4, NAND_BAM_NEXT_SGL);
++      }
++      qcom_write_reg_dma(snandc, &snandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(snandc, &snandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++
++      qcom_read_reg_dma(snandc, NAND_FLASH_STATUS, 1, 0);
++}
++
++static int qcom_spi_read_last_cw(struct qcom_nand_controller *snandc,
++                               const struct spi_mem_op *op)
++{
++      struct qpic_ecc *ecc_cfg = snandc->qspi->ecc;
++      struct mtd_info *mtd = snandc->qspi->mtd;
++      int size, ret = 0;
++      int col,  bbpos;
++      u32 cfg0, cfg1, ecc_bch_cfg;
++      u32 num_cw = snandc->qspi->num_cw;
++
++      qcom_clear_bam_transaction(snandc);
++      qcom_clear_read_regs(snandc);
++
++      size = ecc_cfg->cw_size;
++      col = ecc_cfg->cw_size * (num_cw - 1);
++
++      memset(snandc->data_buffer, 0xff, size);
++      snandc->regs->addr0 = (snandc->qspi->addr1 | cpu_to_le32(col));
++      snandc->regs->addr1 = snandc->qspi->addr2;
++
++      cfg0 = (ecc_cfg->cfg0_raw & ~(7U << CW_PER_PAGE)) |
++              0 << CW_PER_PAGE;
++      cfg1 = ecc_cfg->cfg1_raw;
++      ecc_bch_cfg = 1 << ECC_CFG_ECC_DISABLE;
++
++      snandc->regs->cmd = snandc->qspi->cmd;
++      snandc->regs->cfg0 = cpu_to_le32(cfg0);
++      snandc->regs->cfg1 = cpu_to_le32(cfg1);
++      snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg);
++      snandc->regs->clrflashstatus = cpu_to_le32(ecc_cfg->clrflashstatus);
++      snandc->regs->clrreadstatus = cpu_to_le32(ecc_cfg->clrreadstatus);
++      snandc->regs->exec = cpu_to_le32(1);
++
++      qcom_spi_set_read_loc(snandc, num_cw - 1, 0, 0, ecc_cfg->cw_size, 1);
++
++      qcom_spi_config_single_cw_page_read(snandc, false, num_cw - 1);
++
++      qcom_read_data_dma(snandc, FLASH_BUF_ACC, snandc->data_buffer, size, 0);
++
++      ret = qcom_submit_descs(snandc);
++      if (ret) {
++              dev_err(snandc->dev, "failed to read last cw\n");
++              return ret;
++      }
++
++      qcom_nandc_dev_to_mem(snandc, true);
++      u32 flash = le32_to_cpu(snandc->reg_read_buf[0]);
++
++      if (flash & (FS_OP_ERR | FS_MPU_ERR))
++              return -EIO;
++
++      bbpos = mtd->writesize - ecc_cfg->cw_size * (num_cw - 1);
++
++      if (snandc->data_buffer[bbpos] == 0xff)
++              snandc->data_buffer[bbpos + 1] = 0xff;
++      if (snandc->data_buffer[bbpos] != 0xff)
++              snandc->data_buffer[bbpos + 1] = snandc->data_buffer[bbpos];
++
++      memcpy(op->data.buf.in, snandc->data_buffer + bbpos, op->data.nbytes);
++
++      return ret;
++}
++
++static int qcom_spi_check_error(struct qcom_nand_controller *snandc, u8 *data_buf, u8 *oob_buf)
++{
++      struct snandc_read_status *buf;
++      struct qpic_ecc *ecc_cfg = snandc->qspi->ecc;
++      int i, num_cw = snandc->qspi->num_cw;
++      bool flash_op_err = false, erased;
++      unsigned int max_bitflips = 0;
++      unsigned int uncorrectable_cws = 0;
++
++      snandc->qspi->ecc_stats.failed = 0;
++      snandc->qspi->ecc_stats.corrected = 0;
++
++      qcom_nandc_dev_to_mem(snandc, true);
++      buf = (struct snandc_read_status *)snandc->reg_read_buf;
++
++      for (i = 0; i < num_cw; i++, buf++) {
++              u32 flash, buffer, erased_cw;
++              int data_len, oob_len;
++
++              if (i == (num_cw - 1)) {
++                      data_len = NANDC_STEP_SIZE - ((num_cw - 1) << 2);
++                      oob_len = num_cw << 2;
++              } else {
++                      data_len = ecc_cfg->cw_data;
++                      oob_len = 0;
++              }
++
++              flash = le32_to_cpu(buf->snandc_flash);
++              buffer = le32_to_cpu(buf->snandc_buffer);
++              erased_cw = le32_to_cpu(buf->snandc_erased_cw);
++
++              if ((flash & FS_OP_ERR) && (buffer & BS_UNCORRECTABLE_BIT)) {
++                      if (ecc_cfg->bch_enabled)
++                              erased = (erased_cw & ERASED_CW) == ERASED_CW;
++                      else
++                              erased = false;
++
++                      if (!erased)
++                              uncorrectable_cws |= BIT(i);
++
++              } else if (flash & (FS_OP_ERR | FS_MPU_ERR)) {
++                      flash_op_err = true;
++              } else {
++                      unsigned int stat;
++
++                      stat = buffer & BS_CORRECTABLE_ERR_MSK;
++                      snandc->qspi->ecc_stats.corrected += stat;
++                      max_bitflips = max(max_bitflips, stat);
++              }
++
++              if (data_buf)
++                      data_buf += data_len;
++              if (oob_buf)
++                      oob_buf += oob_len + ecc_cfg->bytes;
++      }
++
++      if (flash_op_err)
++              return -EIO;
++
++      if (!uncorrectable_cws)
++              snandc->qspi->ecc_stats.bitflips = max_bitflips;
++      else
++              snandc->qspi->ecc_stats.failed++;
++
++      return 0;
++}
++
++static int qcom_spi_check_raw_flash_errors(struct qcom_nand_controller *snandc, int cw_cnt)
++{
++      int i;
++
++      qcom_nandc_dev_to_mem(snandc, true);
++
++      for (i = 0; i < cw_cnt; i++) {
++              u32 flash = le32_to_cpu(snandc->reg_read_buf[i]);
++
++              if (flash & (FS_OP_ERR | FS_MPU_ERR))
++                      return -EIO;
++      }
++
++      return 0;
++}
++
++static int qcom_spi_read_cw_raw(struct qcom_nand_controller *snandc, u8 *data_buf,
++                              u8 *oob_buf, int cw)
++{
++      struct qpic_ecc *ecc_cfg = snandc->qspi->ecc;
++      struct mtd_info *mtd = snandc->qspi->mtd;
++      int data_size1, data_size2, oob_size1, oob_size2;
++      int ret, reg_off = FLASH_BUF_ACC, read_loc = 0;
++      int raw_cw = cw;
++      u32 cfg0, cfg1, ecc_bch_cfg, num_cw = snandc->qspi->num_cw;
++      int col;
++
++      snandc->buf_count = 0;
++      snandc->buf_start = 0;
++      qcom_clear_read_regs(snandc);
++      qcom_clear_bam_transaction(snandc);
++      raw_cw = num_cw - 1;
++
++      cfg0 = (ecc_cfg->cfg0_raw & ~(7U << CW_PER_PAGE)) |
++                              0 << CW_PER_PAGE;
++      cfg1 = ecc_cfg->cfg1_raw;
++      ecc_bch_cfg = ECC_CFG_ECC_DISABLE;
++
++      col = ecc_cfg->cw_size * cw;
++
++      snandc->regs->addr0 = (snandc->qspi->addr1 | cpu_to_le32(col));
++      snandc->regs->addr1 = snandc->qspi->addr2;
++      snandc->regs->cmd = snandc->qspi->cmd;
++      snandc->regs->cfg0 = cpu_to_le32(cfg0);
++      snandc->regs->cfg1 = cpu_to_le32(cfg1);
++      snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg);
++      snandc->regs->clrflashstatus = cpu_to_le32(ecc_cfg->clrflashstatus);
++      snandc->regs->clrreadstatus = cpu_to_le32(ecc_cfg->clrreadstatus);
++      snandc->regs->exec = cpu_to_le32(1);
++
++      qcom_spi_set_read_loc(snandc, raw_cw, 0, 0, ecc_cfg->cw_size, 1);
++
++      qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, 0);
++
++      qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr,
++                         NAND_ERASED_CW_DETECT_CFG, 1, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set,
++                         NAND_ERASED_CW_DETECT_CFG, 1,
++                         NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL);
++
++      data_size1 = mtd->writesize - ecc_cfg->cw_size * (num_cw - 1);
++      oob_size1 = ecc_cfg->bbm_size;
++
++      if (cw == (num_cw - 1)) {
++              data_size2 = NANDC_STEP_SIZE - data_size1 -
++                           ((num_cw - 1) * 4);
++              oob_size2 = (num_cw * 4) + ecc_cfg->ecc_bytes_hw +
++                          ecc_cfg->spare_bytes;
++      } else {
++              data_size2 = ecc_cfg->cw_data - data_size1;
++              oob_size2 = ecc_cfg->ecc_bytes_hw + ecc_cfg->spare_bytes;
++      }
++
++      qcom_spi_set_read_loc(snandc, cw, 0, read_loc, data_size1, 0);
++      read_loc += data_size1;
++
++      qcom_spi_set_read_loc(snandc, cw, 1, read_loc, oob_size1, 0);
++      read_loc += oob_size1;
++
++      qcom_spi_set_read_loc(snandc, cw, 2, read_loc, data_size2, 0);
++      read_loc += data_size2;
++
++      qcom_spi_set_read_loc(snandc, cw, 3, read_loc, oob_size2, 1);
++
++      qcom_spi_config_cw_read(snandc, false, raw_cw);
++
++      qcom_read_data_dma(snandc, reg_off, data_buf, data_size1, 0);
++      reg_off += data_size1;
++
++      qcom_read_data_dma(snandc, reg_off, oob_buf, oob_size1, 0);
++      reg_off += oob_size1;
++
++      qcom_read_data_dma(snandc, reg_off, data_buf + data_size1, data_size2, 0);
++      reg_off += data_size2;
++
++      qcom_read_data_dma(snandc, reg_off, oob_buf + oob_size1, oob_size2, 0);
++
++      ret = qcom_submit_descs(snandc);
++      if (ret) {
++              dev_err(snandc->dev, "failure to read raw cw %d\n", cw);
++              return ret;
++      }
++
++      return qcom_spi_check_raw_flash_errors(snandc, 1);
++}
++
++static int qcom_spi_read_page_raw(struct qcom_nand_controller *snandc,
++                                const struct spi_mem_op *op)
++{
++      struct qpic_ecc *ecc_cfg = snandc->qspi->ecc;
++      u8 *data_buf = NULL, *oob_buf = NULL;
++      int ret, cw;
++      u32 num_cw = snandc->qspi->num_cw;
++
++      if (snandc->qspi->page_rw)
++              data_buf = op->data.buf.in;
++
++      oob_buf = snandc->qspi->oob_buf;
++      memset(oob_buf, 0xff, OOB_BUF_SIZE);
++
++      for (cw = 0; cw < num_cw; cw++) {
++              ret = qcom_spi_read_cw_raw(snandc, data_buf, oob_buf, cw);
++              if (ret)
++                      return ret;
++
++              if (data_buf)
++                      data_buf += ecc_cfg->cw_data;
++              if (oob_buf)
++                      oob_buf += ecc_cfg->bytes;
++      }
++
++      return 0;
++}
++
++static int qcom_spi_read_page_ecc(struct qcom_nand_controller *snandc,
++                                const struct spi_mem_op *op)
++{
++      struct qpic_ecc *ecc_cfg = snandc->qspi->ecc;
++      u8 *data_buf = NULL, *data_buf_start, *oob_buf = NULL, *oob_buf_start;
++      int ret, i;
++      u32 cfg0, cfg1, ecc_bch_cfg, num_cw = snandc->qspi->num_cw;
++
++      data_buf = op->data.buf.in;
++      data_buf_start = data_buf;
++
++      oob_buf = snandc->qspi->oob_buf;
++      oob_buf_start = oob_buf;
++
++      snandc->buf_count = 0;
++      snandc->buf_start = 0;
++      qcom_clear_read_regs(snandc);
++
++      cfg0 = (ecc_cfg->cfg0 & ~(7U << CW_PER_PAGE)) |
++                              (num_cw - 1) << CW_PER_PAGE;
++      cfg1 = ecc_cfg->cfg1;
++      ecc_bch_cfg = ecc_cfg->ecc_bch_cfg;
++
++      snandc->regs->addr0 = snandc->qspi->addr1;
++      snandc->regs->addr1 = snandc->qspi->addr2;
++      snandc->regs->cmd = snandc->qspi->cmd;
++      snandc->regs->cfg0 = cpu_to_le32(cfg0);
++      snandc->regs->cfg1 = cpu_to_le32(cfg1);
++      snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg);
++      snandc->regs->clrflashstatus = cpu_to_le32(ecc_cfg->clrflashstatus);
++      snandc->regs->clrreadstatus = cpu_to_le32(ecc_cfg->clrreadstatus);
++      snandc->regs->exec = cpu_to_le32(1);
++
++      qcom_spi_set_read_loc(snandc, 0, 0, 0, ecc_cfg->cw_data, 1);
++
++      qcom_clear_bam_transaction(snandc);
++
++      qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr,
++                         NAND_ERASED_CW_DETECT_CFG, 1, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set,
++                         NAND_ERASED_CW_DETECT_CFG, 1,
++                         NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL);
++
++      for (i = 0; i < num_cw; i++) {
++              int data_size, oob_size;
++
++              if (i == (num_cw - 1)) {
++                      data_size = 512 - ((num_cw - 1) << 2);
++                      oob_size = (num_cw << 2) + ecc_cfg->ecc_bytes_hw +
++                                  ecc_cfg->spare_bytes;
++              } else {
++                      data_size = ecc_cfg->cw_data;
++                      oob_size = ecc_cfg->ecc_bytes_hw + ecc_cfg->spare_bytes;
++              }
++
++              if (data_buf && oob_buf) {
++                      qcom_spi_set_read_loc(snandc, i, 0, 0, data_size, 0);
++                      qcom_spi_set_read_loc(snandc, i, 1, data_size, oob_size, 1);
++              } else if (data_buf) {
++                      qcom_spi_set_read_loc(snandc, i, 0, 0, data_size, 1);
++              } else {
++                      qcom_spi_set_read_loc(snandc, i, 0, data_size, oob_size, 1);
++              }
++
++              qcom_spi_config_cw_read(snandc, true, i);
++
++              if (data_buf)
++                      qcom_read_data_dma(snandc, FLASH_BUF_ACC, data_buf,
++                                         data_size, 0);
++              if (oob_buf) {
++                      int j;
++
++                      for (j = 0; j < ecc_cfg->bbm_size; j++)
++                              *oob_buf++ = 0xff;
++
++                      qcom_read_data_dma(snandc, FLASH_BUF_ACC + data_size,
++                                         oob_buf, oob_size, 0);
++              }
++
++              if (data_buf)
++                      data_buf += data_size;
++              if (oob_buf)
++                      oob_buf += oob_size;
++      }
++
++      ret = qcom_submit_descs(snandc);
++      if (ret) {
++              dev_err(snandc->dev, "failure to read page\n");
++              return ret;
++      }
++
++      return qcom_spi_check_error(snandc, data_buf_start, oob_buf_start);
++}
++
++static int qcom_spi_read_page_oob(struct qcom_nand_controller *snandc,
++                                const struct spi_mem_op *op)
++{
++      struct qpic_ecc *ecc_cfg = snandc->qspi->ecc;
++      u8 *data_buf = NULL, *data_buf_start, *oob_buf = NULL, *oob_buf_start;
++      int ret, i;
++      u32 cfg0, cfg1, ecc_bch_cfg, num_cw = snandc->qspi->num_cw;
++
++      oob_buf = op->data.buf.in;
++      oob_buf_start = oob_buf;
++
++      data_buf_start = data_buf;
++
++      snandc->buf_count = 0;
++      snandc->buf_start = 0;
++      qcom_clear_read_regs(snandc);
++      qcom_clear_bam_transaction(snandc);
++
++      cfg0 = (ecc_cfg->cfg0 & ~(7U << CW_PER_PAGE)) |
++                              (num_cw - 1) << CW_PER_PAGE;
++      cfg1 = ecc_cfg->cfg1;
++      ecc_bch_cfg = ecc_cfg->ecc_bch_cfg;
++
++      snandc->regs->addr0 = snandc->qspi->addr1;
++      snandc->regs->addr1 = snandc->qspi->addr2;
++      snandc->regs->cmd = snandc->qspi->cmd;
++      snandc->regs->cfg0 = cpu_to_le32(cfg0);
++      snandc->regs->cfg1 = cpu_to_le32(cfg1);
++      snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg);
++      snandc->regs->clrflashstatus = cpu_to_le32(ecc_cfg->clrflashstatus);
++      snandc->regs->clrreadstatus = cpu_to_le32(ecc_cfg->clrreadstatus);
++      snandc->regs->exec = cpu_to_le32(1);
++
++      qcom_spi_set_read_loc(snandc, 0, 0, 0, ecc_cfg->cw_data, 1);
++
++      qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr,
++                         NAND_ERASED_CW_DETECT_CFG, 1, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set,
++                         NAND_ERASED_CW_DETECT_CFG, 1,
++                         NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL);
++
++      for (i = 0; i < num_cw; i++) {
++              int data_size, oob_size;
++
++              if (i == (num_cw - 1)) {
++                      data_size = NANDC_STEP_SIZE - ((num_cw - 1) << 2);
++                      oob_size = (num_cw << 2) + ecc_cfg->ecc_bytes_hw +
++                                  ecc_cfg->spare_bytes;
++              } else {
++                      data_size = ecc_cfg->cw_data;
++                      oob_size = ecc_cfg->ecc_bytes_hw + ecc_cfg->spare_bytes;
++              }
++
++              qcom_spi_set_read_loc(snandc, i, 0, data_size, oob_size, 1);
++
++              qcom_spi_config_cw_read(snandc, true, i);
++
++              if (oob_buf) {
++                      int j;
++
++                      for (j = 0; j < ecc_cfg->bbm_size; j++)
++                              *oob_buf++ = 0xff;
++
++                      qcom_read_data_dma(snandc, FLASH_BUF_ACC + data_size,
++                                         oob_buf, oob_size, 0);
++              }
++
++              if (oob_buf)
++                      oob_buf += oob_size;
++      }
++
++      ret = qcom_submit_descs(snandc);
++      if (ret) {
++              dev_err(snandc->dev, "failure to read oob\n");
++              return ret;
++      }
++
++      return qcom_spi_check_error(snandc, data_buf_start, oob_buf_start);
++}
++
++static int qcom_spi_read_page(struct qcom_nand_controller *snandc,
++                            const struct spi_mem_op *op)
++{
++      if (snandc->qspi->page_rw && snandc->qspi->raw_rw)
++              return qcom_spi_read_page_raw(snandc, op);
++
++      if (snandc->qspi->page_rw)
++              return qcom_spi_read_page_ecc(snandc, op);
++
++      if (snandc->qspi->oob_rw && snandc->qspi->raw_rw)
++              return qcom_spi_read_last_cw(snandc, op);
++
++      if (snandc->qspi->oob_rw)
++              return qcom_spi_read_page_oob(snandc, op);
++
++      return 0;
++}
++
++static void qcom_spi_config_page_write(struct qcom_nand_controller *snandc)
++{
++      qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG,
++                         1, NAND_BAM_NEXT_SGL);
++}
++
++static void qcom_spi_config_cw_write(struct qcom_nand_controller *snandc)
++{
++      qcom_write_reg_dma(snandc, &snandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(snandc, &snandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++      qcom_read_reg_dma(snandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL);
++
++      qcom_write_reg_dma(snandc, &snandc->regs->clrflashstatus, NAND_FLASH_STATUS, 1, 0);
++      qcom_write_reg_dma(snandc, &snandc->regs->clrreadstatus, NAND_READ_STATUS, 1,
++                         NAND_BAM_NEXT_SGL);
++}
++
++static int qcom_spi_program_raw(struct qcom_nand_controller *snandc,
++                              const struct spi_mem_op *op)
++{
++      struct qpic_ecc *ecc_cfg = snandc->qspi->ecc;
++      struct mtd_info *mtd = snandc->qspi->mtd;
++      u8 *data_buf = NULL, *oob_buf = NULL;
++      int i, ret;
++      int num_cw = snandc->qspi->num_cw;
++      u32 cfg0, cfg1, ecc_bch_cfg;
++
++      cfg0 = (ecc_cfg->cfg0_raw & ~(7U << CW_PER_PAGE)) |
++                      (num_cw - 1) << CW_PER_PAGE;
++      cfg1 = ecc_cfg->cfg1_raw;
++      ecc_bch_cfg = ECC_CFG_ECC_DISABLE;
++
++      data_buf = snandc->qspi->data_buf;
++
++      oob_buf = snandc->qspi->oob_buf;
++      memset(oob_buf, 0xff, OOB_BUF_SIZE);
++
++      snandc->buf_count = 0;
++      snandc->buf_start = 0;
++      qcom_clear_read_regs(snandc);
++      qcom_clear_bam_transaction(snandc);
++
++      snandc->regs->addr0 = snandc->qspi->addr1;
++      snandc->regs->addr1 = snandc->qspi->addr2;
++      snandc->regs->cmd = snandc->qspi->cmd;
++      snandc->regs->cfg0 = cpu_to_le32(cfg0);
++      snandc->regs->cfg1 = cpu_to_le32(cfg1);
++      snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg);
++      snandc->regs->clrflashstatus = cpu_to_le32(ecc_cfg->clrflashstatus);
++      snandc->regs->clrreadstatus = cpu_to_le32(ecc_cfg->clrreadstatus);
++      snandc->regs->exec = cpu_to_le32(1);
++
++      qcom_spi_config_page_write(snandc);
++
++      for (i = 0; i < num_cw; i++) {
++              int data_size1, data_size2, oob_size1, oob_size2;
++              int reg_off = FLASH_BUF_ACC;
++
++              data_size1 = mtd->writesize - ecc_cfg->cw_size * (num_cw - 1);
++              oob_size1 = ecc_cfg->bbm_size;
++
++              if (i == (num_cw - 1)) {
++                      data_size2 = NANDC_STEP_SIZE - data_size1 -
++                                   ((num_cw - 1) << 2);
++                      oob_size2 = (num_cw << 2) + ecc_cfg->ecc_bytes_hw +
++                                  ecc_cfg->spare_bytes;
++              } else {
++                      data_size2 = ecc_cfg->cw_data - data_size1;
++                      oob_size2 = ecc_cfg->ecc_bytes_hw + ecc_cfg->spare_bytes;
++              }
++
++              qcom_write_data_dma(snandc, reg_off, data_buf, data_size1,
++                                  NAND_BAM_NO_EOT);
++              reg_off += data_size1;
++              data_buf += data_size1;
++
++              qcom_write_data_dma(snandc, reg_off, oob_buf, oob_size1,
++                                  NAND_BAM_NO_EOT);
++              oob_buf += oob_size1;
++              reg_off += oob_size1;
++
++              qcom_write_data_dma(snandc, reg_off, data_buf, data_size2,
++                                  NAND_BAM_NO_EOT);
++              reg_off += data_size2;
++              data_buf += data_size2;
++
++              qcom_write_data_dma(snandc, reg_off, oob_buf, oob_size2, 0);
++              oob_buf += oob_size2;
++
++              qcom_spi_config_cw_write(snandc);
++      }
++
++      ret = qcom_submit_descs(snandc);
++      if (ret) {
++              dev_err(snandc->dev, "failure to write raw page\n");
++              return ret;
++      }
++
++      return 0;
++}
++
++static int qcom_spi_program_ecc(struct qcom_nand_controller *snandc,
++                              const struct spi_mem_op *op)
++{
++      struct qpic_ecc *ecc_cfg = snandc->qspi->ecc;
++      u8 *data_buf = NULL, *oob_buf = NULL;
++      int i, ret;
++      int num_cw = snandc->qspi->num_cw;
++      u32 cfg0, cfg1, ecc_bch_cfg, ecc_buf_cfg;
++
++      cfg0 = (ecc_cfg->cfg0 & ~(7U << CW_PER_PAGE)) |
++                              (num_cw - 1) << CW_PER_PAGE;
++      cfg1 = ecc_cfg->cfg1;
++      ecc_bch_cfg = ecc_cfg->ecc_bch_cfg;
++      ecc_buf_cfg = ecc_cfg->ecc_buf_cfg;
++
++      if (snandc->qspi->data_buf)
++              data_buf = snandc->qspi->data_buf;
++
++      oob_buf = snandc->qspi->oob_buf;
++
++      snandc->buf_count = 0;
++      snandc->buf_start = 0;
++      qcom_clear_read_regs(snandc);
++      qcom_clear_bam_transaction(snandc);
++
++      snandc->regs->addr0 = snandc->qspi->addr1;
++      snandc->regs->addr1 = snandc->qspi->addr2;
++      snandc->regs->cmd = snandc->qspi->cmd;
++      snandc->regs->cfg0 = cpu_to_le32(cfg0);
++      snandc->regs->cfg1 = cpu_to_le32(cfg1);
++      snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg);
++      snandc->regs->ecc_buf_cfg = cpu_to_le32(ecc_buf_cfg);
++      snandc->regs->exec = cpu_to_le32(1);
++
++      qcom_spi_config_page_write(snandc);
++
++      for (i = 0; i < num_cw; i++) {
++              int data_size, oob_size;
++
++              if (i == (num_cw - 1)) {
++                      data_size = NANDC_STEP_SIZE - ((num_cw - 1) << 2);
++                      oob_size = (num_cw << 2) + ecc_cfg->ecc_bytes_hw +
++                                  ecc_cfg->spare_bytes;
++              } else {
++                      data_size = ecc_cfg->cw_data;
++                      oob_size = ecc_cfg->bytes;
++              }
++
++              if (data_buf)
++                      qcom_write_data_dma(snandc, FLASH_BUF_ACC, data_buf, data_size,
++                                          i == (num_cw - 1) ? NAND_BAM_NO_EOT : 0);
++
++              if (i == (num_cw - 1)) {
++                      if (oob_buf) {
++                              oob_buf += ecc_cfg->bbm_size;
++                              qcom_write_data_dma(snandc, FLASH_BUF_ACC + data_size,
++                                                  oob_buf, oob_size, 0);
++                      }
++              }
++
++              qcom_spi_config_cw_write(snandc);
++
++              if (data_buf)
++                      data_buf += data_size;
++              if (oob_buf)
++                      oob_buf += oob_size;
++      }
++
++      ret = qcom_submit_descs(snandc);
++      if (ret) {
++              dev_err(snandc->dev, "failure to write page\n");
++              return ret;
++      }
++
++      return 0;
++}
++
++static int qcom_spi_program_oob(struct qcom_nand_controller *snandc,
++                              const struct spi_mem_op *op)
++{
++      struct qpic_ecc *ecc_cfg = snandc->qspi->ecc;
++      u8 *oob_buf = NULL;
++      int ret, col, data_size, oob_size;
++      int num_cw = snandc->qspi->num_cw;
++      u32 cfg0, cfg1, ecc_bch_cfg, ecc_buf_cfg;
++
++      cfg0 = (ecc_cfg->cfg0 & ~(7U << CW_PER_PAGE)) |
++                              (num_cw - 1) << CW_PER_PAGE;
++      cfg1 = ecc_cfg->cfg1;
++      ecc_bch_cfg = ecc_cfg->ecc_bch_cfg;
++      ecc_buf_cfg = ecc_cfg->ecc_buf_cfg;
++
++      col = ecc_cfg->cw_size * (num_cw - 1);
++
++      oob_buf = snandc->qspi->data_buf;
++
++      snandc->buf_count = 0;
++      snandc->buf_start = 0;
++      qcom_clear_read_regs(snandc);
++      qcom_clear_bam_transaction(snandc);
++      snandc->regs->addr0 = (snandc->qspi->addr1 | cpu_to_le32(col));
++      snandc->regs->addr1 = snandc->qspi->addr2;
++      snandc->regs->cmd = snandc->qspi->cmd;
++      snandc->regs->cfg0 = cpu_to_le32(cfg0);
++      snandc->regs->cfg1 = cpu_to_le32(cfg1);
++      snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg);
++      snandc->regs->ecc_buf_cfg = cpu_to_le32(ecc_buf_cfg);
++      snandc->regs->exec = cpu_to_le32(1);
++
++      /* calculate the data and oob size for the last codeword/step */
++      data_size = NANDC_STEP_SIZE - ((num_cw - 1) << 2);
++      oob_size = snandc->qspi->mtd->oobavail;
++
++      memset(snandc->data_buffer, 0xff, ecc_cfg->cw_data);
++      /* override new oob content to last codeword */
++      mtd_ooblayout_get_databytes(snandc->qspi->mtd, snandc->data_buffer + data_size,
++                                  oob_buf, 0, snandc->qspi->mtd->oobavail);
++      qcom_spi_config_page_write(snandc);
++      qcom_write_data_dma(snandc, FLASH_BUF_ACC, snandc->data_buffer, data_size + oob_size, 0);
++      qcom_spi_config_cw_write(snandc);
++
++      ret = qcom_submit_descs(snandc);
++      if (ret) {
++              dev_err(snandc->dev, "failure to write oob\n");
++              return ret;
++      }
++
++      return 0;
++}
++
++static int qcom_spi_program_execute(struct qcom_nand_controller *snandc,
++                                  const struct spi_mem_op *op)
++{
++      if (snandc->qspi->page_rw && snandc->qspi->raw_rw)
++              return qcom_spi_program_raw(snandc, op);
++
++      if (snandc->qspi->page_rw)
++              return qcom_spi_program_ecc(snandc, op);
++
++      if (snandc->qspi->oob_rw)
++              return qcom_spi_program_oob(snandc, op);
++
++      return 0;
++}
++
++static int qcom_spi_cmd_mapping(struct qcom_nand_controller *snandc, u32 opcode, u32 *cmd)
++{
++      switch (opcode) {
++      case SPINAND_RESET:
++              *cmd = (SPI_WP | SPI_HOLD | SPI_TRANSFER_MODE_x1 | OP_RESET_DEVICE);
++              break;
++      case SPINAND_READID:
++              *cmd = (SPI_WP | SPI_HOLD | SPI_TRANSFER_MODE_x1 | OP_FETCH_ID);
++              break;
++      case SPINAND_GET_FEATURE:
++              *cmd = (SPI_TRANSFER_MODE_x1 | SPI_WP | SPI_HOLD | ACC_FEATURE);
++              break;
++      case SPINAND_SET_FEATURE:
++              *cmd = (SPI_TRANSFER_MODE_x1 | SPI_WP | SPI_HOLD | ACC_FEATURE |
++                      QPIC_SET_FEATURE);
++              break;
++      case SPINAND_READ:
++              if (snandc->qspi->raw_rw) {
++                      *cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 |
++                                      SPI_WP | SPI_HOLD | OP_PAGE_READ);
++              } else {
++                      *cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 |
++                                      SPI_WP | SPI_HOLD | OP_PAGE_READ_WITH_ECC);
++              }
++
++              break;
++      case SPINAND_ERASE:
++              *cmd = OP_BLOCK_ERASE | PAGE_ACC | LAST_PAGE | SPI_WP |
++                      SPI_HOLD | SPI_TRANSFER_MODE_x1;
++              break;
++      case SPINAND_WRITE_EN:
++              *cmd = SPINAND_WRITE_EN;
++              break;
++      case SPINAND_PROGRAM_EXECUTE:
++              *cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 |
++                              SPI_WP | SPI_HOLD | OP_PROGRAM_PAGE);
++              break;
++      case SPINAND_PROGRAM_LOAD:
++              *cmd = SPINAND_PROGRAM_LOAD;
++              break;
++      default:
++              dev_err(snandc->dev, "Opcode not supported: %u\n", opcode);
++              return -EOPNOTSUPP;
++      }
++
++      return 0;
++}
++
++static int qcom_spi_write_page(struct qcom_nand_controller *snandc,
++                             const struct spi_mem_op *op)
++{
++      int ret;
++      u32 cmd;
++
++      ret = qcom_spi_cmd_mapping(snandc, op->cmd.opcode, &cmd);
++      if (ret < 0)
++              return ret;
++
++      if (op->cmd.opcode == SPINAND_PROGRAM_LOAD)
++              snandc->qspi->data_buf = (u8 *)op->data.buf.out;
++
++      return 0;
++}
++
++static int qcom_spi_send_cmdaddr(struct qcom_nand_controller *snandc,
++                               const struct spi_mem_op *op)
++{
++      struct qpic_snand_op s_op = {};
++      u32 cmd;
++      int ret, opcode;
++
++      ret = qcom_spi_cmd_mapping(snandc, op->cmd.opcode, &cmd);
++      if (ret < 0)
++              return ret;
++
++      s_op.cmd_reg = cmd;
++      s_op.addr1_reg = op->addr.val;
++      s_op.addr2_reg = 0;
++
++      opcode = op->cmd.opcode;
++
++      switch (opcode) {
++      case SPINAND_WRITE_EN:
++              return 0;
++      case SPINAND_PROGRAM_EXECUTE:
++              s_op.addr1_reg = op->addr.val << 16;
++              s_op.addr2_reg = op->addr.val >> 16 & 0xff;
++              snandc->qspi->addr1 = cpu_to_le32(s_op.addr1_reg);
++              snandc->qspi->addr2 = cpu_to_le32(s_op.addr2_reg);
++              snandc->qspi->cmd = cpu_to_le32(cmd);
++              return qcom_spi_program_execute(snandc, op);
++      case SPINAND_READ:
++              s_op.addr1_reg = (op->addr.val << 16);
++              s_op.addr2_reg = op->addr.val >> 16 & 0xff;
++              snandc->qspi->addr1 = cpu_to_le32(s_op.addr1_reg);
++              snandc->qspi->addr2 = cpu_to_le32(s_op.addr2_reg);
++              snandc->qspi->cmd = cpu_to_le32(cmd);
++              return 0;
++      case SPINAND_ERASE:
++              s_op.addr2_reg = (op->addr.val >> 16) & 0xffff;
++              s_op.addr1_reg = op->addr.val;
++              snandc->qspi->addr1 = cpu_to_le32(s_op.addr1_reg << 16);
++              snandc->qspi->addr2 = cpu_to_le32(s_op.addr2_reg);
++              snandc->qspi->cmd = cpu_to_le32(cmd);
++              qcom_spi_block_erase(snandc);
++              return 0;
++      default:
++              break;
++      }
++
++      snandc->buf_count = 0;
++      snandc->buf_start = 0;
++      qcom_clear_read_regs(snandc);
++      qcom_clear_bam_transaction(snandc);
++
++      snandc->regs->cmd = cpu_to_le32(s_op.cmd_reg);
++      snandc->regs->exec = cpu_to_le32(1);
++      snandc->regs->addr0 = cpu_to_le32(s_op.addr1_reg);
++      snandc->regs->addr1 = cpu_to_le32(s_op.addr2_reg);
++
++      qcom_write_reg_dma(snandc, &snandc->regs->cmd, NAND_FLASH_CMD, 3, NAND_BAM_NEXT_SGL);
++      qcom_write_reg_dma(snandc, &snandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
++
++      ret = qcom_submit_descs(snandc);
++      if (ret)
++              dev_err(snandc->dev, "failure in submitting cmd descriptor\n");
++
++      return ret;
++}
++
++static int qcom_spi_io_op(struct qcom_nand_controller *snandc, const struct spi_mem_op *op)
++{
++      int ret, val, opcode;
++      bool copy = false, copy_ftr = false;
++
++      ret = qcom_spi_send_cmdaddr(snandc, op);
++      if (ret)
++              return ret;
++
++      snandc->buf_count = 0;
++      snandc->buf_start = 0;
++      qcom_clear_read_regs(snandc);
++      qcom_clear_bam_transaction(snandc);
++      opcode = op->cmd.opcode;
++
++      switch (opcode) {
++      case SPINAND_READID:
++              snandc->buf_count = 4;
++              qcom_read_reg_dma(snandc, NAND_READ_ID, 1, NAND_BAM_NEXT_SGL);
++              copy = true;
++              break;
++      case SPINAND_GET_FEATURE:
++              snandc->buf_count = 4;
++              qcom_read_reg_dma(snandc, NAND_FLASH_FEATURES, 1, NAND_BAM_NEXT_SGL);
++              copy_ftr = true;
++              break;
++      case SPINAND_SET_FEATURE:
++              snandc->regs->flash_feature = cpu_to_le32(*(u32 *)op->data.buf.out);
++              qcom_write_reg_dma(snandc, &snandc->regs->flash_feature,
++                                 NAND_FLASH_FEATURES, 1, NAND_BAM_NEXT_SGL);
++              break;
++      case SPINAND_PROGRAM_EXECUTE:
++      case SPINAND_WRITE_EN:
++      case SPINAND_RESET:
++      case SPINAND_ERASE:
++      case SPINAND_READ:
++              return 0;
++      default:
++              return -EOPNOTSUPP;
++      }
++
++      ret = qcom_submit_descs(snandc);
++      if (ret)
++              dev_err(snandc->dev, "failure in submitting descriptor for:%d\n", opcode);
++
++      if (copy) {
++              qcom_nandc_dev_to_mem(snandc, true);
++              memcpy(op->data.buf.in, snandc->reg_read_buf, snandc->buf_count);
++      }
++
++      if (copy_ftr) {
++              qcom_nandc_dev_to_mem(snandc, true);
++              val = le32_to_cpu(*(__le32 *)snandc->reg_read_buf);
++              val >>= 8;
++              memcpy(op->data.buf.in, &val, snandc->buf_count);
++      }
++
++      return ret;
++}
++
++static bool qcom_spi_is_page_op(const struct spi_mem_op *op)
++{
++      if (op->addr.buswidth != 1 && op->addr.buswidth != 2 && op->addr.buswidth != 4)
++              return false;
++
++      if (op->data.dir == SPI_MEM_DATA_IN) {
++              if (op->addr.buswidth == 4 && op->data.buswidth == 4)
++                      return true;
++
++              if (op->addr.nbytes == 2 && op->addr.buswidth == 1)
++                      return true;
++
++      } else if (op->data.dir == SPI_MEM_DATA_OUT) {
++              if (op->data.buswidth == 4)
++                      return true;
++              if (op->addr.nbytes == 2 && op->addr.buswidth == 1)
++                      return true;
++      }
++
++      return false;
++}
++
++static bool qcom_spi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op)
++{
++      if (!spi_mem_default_supports_op(mem, op))
++              return false;
++
++      if (op->cmd.nbytes != 1 || op->cmd.buswidth != 1)
++              return false;
++
++      if (qcom_spi_is_page_op(op))
++              return true;
++
++      return ((!op->addr.nbytes || op->addr.buswidth == 1) &&
++              (!op->dummy.nbytes || op->dummy.buswidth == 1) &&
++              (!op->data.nbytes || op->data.buswidth == 1));
++}
++
++static int qcom_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
++{
++      struct qcom_nand_controller *snandc = spi_controller_get_devdata(mem->spi->controller);
++
++      dev_dbg(snandc->dev, "OP %02x ADDR %08llX@%d:%u DATA %d:%u", op->cmd.opcode,
++              op->addr.val, op->addr.buswidth, op->addr.nbytes,
++              op->data.buswidth, op->data.nbytes);
++
++      if (qcom_spi_is_page_op(op)) {
++              if (op->data.dir == SPI_MEM_DATA_IN)
++                      return qcom_spi_read_page(snandc, op);
++              if (op->data.dir == SPI_MEM_DATA_OUT)
++                      return qcom_spi_write_page(snandc, op);
++      } else {
++              return qcom_spi_io_op(snandc, op);
++      }
++
++      return 0;
++}
++
++static const struct spi_controller_mem_ops qcom_spi_mem_ops = {
++      .supports_op = qcom_spi_supports_op,
++      .exec_op = qcom_spi_exec_op,
++};
++
++static const struct spi_controller_mem_caps qcom_spi_mem_caps = {
++      .ecc = true,
++};
++
++static int qcom_spi_probe(struct platform_device *pdev)
++{
++      struct device *dev = &pdev->dev;
++      struct spi_controller *ctlr;
++      struct qcom_nand_controller *snandc;
++      struct qpic_spi_nand *qspi;
++      struct qpic_ecc *ecc;
++      struct resource *res;
++      const void *dev_data;
++      int ret;
++
++      ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL);
++      if (!ecc)
++              return -ENOMEM;
++
++      qspi = devm_kzalloc(dev, sizeof(*qspi), GFP_KERNEL);
++      if (!qspi)
++              return -ENOMEM;
++
++      ctlr = __devm_spi_alloc_controller(dev, sizeof(*snandc), false);
++      if (!ctlr)
++              return -ENOMEM;
++
++      platform_set_drvdata(pdev, ctlr);
++
++      snandc = spi_controller_get_devdata(ctlr);
++      qspi->snandc = snandc;
++
++      snandc->dev = dev;
++      snandc->qspi = qspi;
++      snandc->qspi->ctlr = ctlr;
++      snandc->qspi->ecc = ecc;
++
++      dev_data = of_device_get_match_data(dev);
++      if (!dev_data) {
++              dev_err(&pdev->dev, "failed to get device data\n");
++              return -ENODEV;
++      }
++
++      snandc->props = dev_data;
++      snandc->dev = &pdev->dev;
++
++      snandc->core_clk = devm_clk_get(dev, "core");
++      if (IS_ERR(snandc->core_clk))
++              return PTR_ERR(snandc->core_clk);
++
++      snandc->aon_clk = devm_clk_get(dev, "aon");
++      if (IS_ERR(snandc->aon_clk))
++              return PTR_ERR(snandc->aon_clk);
++
++      snandc->qspi->iomacro_clk = devm_clk_get(dev, "iom");
++      if (IS_ERR(snandc->qspi->iomacro_clk))
++              return PTR_ERR(snandc->qspi->iomacro_clk);
++
++      snandc->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
++      if (IS_ERR(snandc->base))
++              return PTR_ERR(snandc->base);
++
++      snandc->base_phys = res->start;
++      snandc->base_dma = dma_map_resource(dev, res->start, resource_size(res),
++                                          DMA_BIDIRECTIONAL, 0);
++      if (dma_mapping_error(dev, snandc->base_dma))
++              return -ENXIO;
++
++      ret = clk_prepare_enable(snandc->core_clk);
++      if (ret)
++              goto err_dis_core_clk;
++
++      ret = clk_prepare_enable(snandc->aon_clk);
++      if (ret)
++              goto err_dis_aon_clk;
++
++      ret = clk_prepare_enable(snandc->qspi->iomacro_clk);
++      if (ret)
++              goto err_dis_iom_clk;
++
++      ret = qcom_nandc_alloc(snandc);
++      if (ret)
++              goto err_snand_alloc;
++
++      ret = qcom_spi_init(snandc);
++      if (ret)
++              goto err_spi_init;
++
++      /* setup ECC engine */
++      snandc->qspi->ecc_eng.dev = &pdev->dev;
++      snandc->qspi->ecc_eng.integration = NAND_ECC_ENGINE_INTEGRATION_PIPELINED;
++      snandc->qspi->ecc_eng.ops = &qcom_spi_ecc_engine_ops_pipelined;
++      snandc->qspi->ecc_eng.priv = snandc;
++
++      ret = nand_ecc_register_on_host_hw_engine(&snandc->qspi->ecc_eng);
++      if (ret) {
++              dev_err(&pdev->dev, "failed to register ecc engine:%d\n", ret);
++              goto err_spi_init;
++      }
++
++      ctlr->num_chipselect = QPIC_QSPI_NUM_CS;
++      ctlr->mem_ops = &qcom_spi_mem_ops;
++      ctlr->mem_caps = &qcom_spi_mem_caps;
++      ctlr->dev.of_node = pdev->dev.of_node;
++      ctlr->mode_bits = SPI_TX_DUAL | SPI_RX_DUAL |
++                          SPI_TX_QUAD | SPI_RX_QUAD;
++
++      ret = spi_register_controller(ctlr);
++      if (ret) {
++              dev_err(&pdev->dev, "spi_register_controller failed.\n");
++              goto err_spi_init;
++      }
++
++      return 0;
++
++err_spi_init:
++      qcom_nandc_unalloc(snandc);
++err_snand_alloc:
++      clk_disable_unprepare(snandc->qspi->iomacro_clk);
++err_dis_iom_clk:
++      clk_disable_unprepare(snandc->aon_clk);
++err_dis_aon_clk:
++      clk_disable_unprepare(snandc->core_clk);
++err_dis_core_clk:
++      dma_unmap_resource(dev, res->start, resource_size(res),
++                         DMA_BIDIRECTIONAL, 0);
++      return ret;
++}
++
++static void qcom_spi_remove(struct platform_device *pdev)
++{
++      struct spi_controller *ctlr = platform_get_drvdata(pdev);
++      struct qcom_nand_controller *snandc = spi_controller_get_devdata(ctlr);
++      struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++
++      spi_unregister_controller(ctlr);
++
++      qcom_nandc_unalloc(snandc);
++
++      clk_disable_unprepare(snandc->aon_clk);
++      clk_disable_unprepare(snandc->core_clk);
++      clk_disable_unprepare(snandc->qspi->iomacro_clk);
++
++      dma_unmap_resource(&pdev->dev, snandc->base_dma, resource_size(res),
++                         DMA_BIDIRECTIONAL, 0);
++}
++
++static const struct qcom_nandc_props ipq9574_snandc_props = {
++      .dev_cmd_reg_start = 0x7000,
++      .supports_bam = true,
++};
++
++static const struct of_device_id qcom_snandc_of_match[] = {
++      {
++              .compatible = "qcom,ipq9574-snand",
++              .data = &ipq9574_snandc_props,
++      },
++      {}
++}
++MODULE_DEVICE_TABLE(of, qcom_snandc_of_match);
++
++static struct platform_driver qcom_spi_driver = {
++      .driver = {
++              .name           = "qcom_snand",
++              .of_match_table = qcom_snandc_of_match,
++      },
++      .probe = qcom_spi_probe,
++      .remove_new = qcom_spi_remove,
++};
++module_platform_driver(qcom_spi_driver);
++
++MODULE_DESCRIPTION("SPI driver for QPIC QSPI cores");
++MODULE_AUTHOR("Md Sadre Alam <quic_mdalam@quicinc.com>");
++MODULE_LICENSE("GPL");
++
+--- a/include/linux/mtd/nand-qpic-common.h
++++ b/include/linux/mtd/nand-qpic-common.h
+@@ -325,6 +325,10 @@ struct nandc_regs {
+       __le32 read_location_last1;
+       __le32 read_location_last2;
+       __le32 read_location_last3;
++      __le32 spi_cfg;
++      __le32 num_addr_cycle;
++      __le32 busy_wait_cnt;
++      __le32 flash_feature;
+       __le32 erased_cw_detect_cfg_clr;
+       __le32 erased_cw_detect_cfg_set;
+@@ -339,6 +343,7 @@ struct nandc_regs {
+  *
+  * @core_clk:                 controller clock
+  * @aon_clk:                  another controller clock
++ * @iomacro_clk:              io macro clock
+  *
+  * @regs:                     a contiguous chunk of memory for DMA register
+  *                            writes. contains the register values to be
+@@ -348,6 +353,7 @@ struct nandc_regs {
+  *                            initialized via DT match data
+  *
+  * @controller:                       base controller structure
++ * @qspi:                     qpic spi structure
+  * @host_list:                        list containing all the chips attached to the
+  *                            controller
+  *
+@@ -392,6 +398,7 @@ struct qcom_nand_controller {
+       const struct qcom_nandc_props *props;
+       struct nand_controller *controller;
++      struct qpic_spi_nand *qspi;
+       struct list_head host_list;
+       union {
diff --git a/target/linux/generic/backport-6.12/410-08-v6.15-spi-spi-qpic-snand-Fix-ECC_CFG_ECC_DISABLE-shift-in-.patch b/target/linux/generic/backport-6.12/410-08-v6.15-spi-spi-qpic-snand-Fix-ECC_CFG_ECC_DISABLE-shift-in-.patch
new file mode 100644 (file)
index 0000000..ad43f8d
--- /dev/null
@@ -0,0 +1,28 @@
+From cf1ba3cb245020459f2ca446b7a7b199839f5d83 Mon Sep 17 00:00:00 2001
+From: Dan Carpenter <dan.carpenter@linaro.org>
+Date: Thu, 6 Mar 2025 12:40:01 +0300
+Subject: [PATCH] spi: spi-qpic-snand: Fix ECC_CFG_ECC_DISABLE shift in
+ qcom_spi_read_last_cw()
+
+The ECC_CFG_ECC_DISABLE define is BIT(0).  It's supposed to be used
+directly instead of used as a shifter.
+
+Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface")
+Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org>
+Link: https://patch.msgid.link/2f4b0a0b-2c03-41c0-8a4a-3d789a83832d@stanley.mountain
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-qpic-snand.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -514,7 +514,7 @@ static int qcom_spi_read_last_cw(struct
+       cfg0 = (ecc_cfg->cfg0_raw & ~(7U << CW_PER_PAGE)) |
+               0 << CW_PER_PAGE;
+       cfg1 = ecc_cfg->cfg1_raw;
+-      ecc_bch_cfg = 1 << ECC_CFG_ECC_DISABLE;
++      ecc_bch_cfg = ECC_CFG_ECC_DISABLE;
+       snandc->regs->cmd = snandc->qspi->cmd;
+       snandc->regs->cfg0 = cpu_to_le32(cfg0);
diff --git a/target/linux/generic/backport-6.12/410-09-v6.15-spi-spi-qpic-snand-avoid-memleak-in-qcom_spi_ecc_ini.patch b/target/linux/generic/backport-6.12/410-09-v6.15-spi-spi-qpic-snand-avoid-memleak-in-qcom_spi_ecc_ini.patch
new file mode 100644 (file)
index 0000000..1e2a3b0
--- /dev/null
@@ -0,0 +1,35 @@
+From d450cdd9c4398add1f2aa7200f2c95f1e3b9f9fa Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Thu, 13 Mar 2025 19:31:21 +0100
+Subject: [PATCH] spi: spi-qpic-snand: avoid memleak in
+ qcom_spi_ecc_init_ctx_pipelined()
+
+When the allocation of the OOB buffer fails, the
+qcom_spi_ecc_init_ctx_pipelined() function returns without freeing
+the memory allocated for 'ecc_cfg' thus it can cause a memory leak.
+
+Call kfree() to free 'ecc_cfg' before returning from the function
+to avoid that.
+
+Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface")
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Link: https://patch.msgid.link/20250313-qpic-snand-memleak-fix-v1-1-e54e78d1da3a@gmail.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-qpic-snand.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -263,8 +263,10 @@ static int qcom_spi_ecc_init_ctx_pipelin
+               return -ENOMEM;
+       snandc->qspi->oob_buf = kzalloc(mtd->writesize + mtd->oobsize,
+                                       GFP_KERNEL);
+-      if (!snandc->qspi->oob_buf)
++      if (!snandc->qspi->oob_buf) {
++              kfree(ecc_cfg);
+               return -ENOMEM;
++      }
+       memset(snandc->qspi->oob_buf, 0xff, mtd->writesize + mtd->oobsize);
diff --git a/target/linux/generic/backport-6.12/410-10-v6.15-spi-SPI_QPIC_SNAND-should-be-tristate-and-depend-on-.patch b/target/linux/generic/backport-6.12/410-10-v6.15-spi-SPI_QPIC_SNAND-should-be-tristate-and-depend-on-.patch
new file mode 100644 (file)
index 0000000..5e92048
--- /dev/null
@@ -0,0 +1,49 @@
+From d32c4e58545f17caaa854415f854691e32d42075 Mon Sep 17 00:00:00 2001
+From: Geert Uytterhoeven <geert+renesas@glider.be>
+Date: Wed, 26 Mar 2025 15:22:19 +0100
+Subject: [PATCH] spi: SPI_QPIC_SNAND should be tristate and depend on MTD
+
+SPI_QPIC_SNAND is the only driver that selects MTD instead of depending
+on it, which could lead to circular dependencies.  Moreover, as
+SPI_QPIC_SNAND is bool, this forces MTD (and various related symbols) to
+be built-in, as can be seen in an allmodconfig kernel.
+
+Except for a missing semicolon, there is no reason why SPI_QPIC_SNAND
+cannot be tristate; all MODULE_*() boilerplate is already present.
+Hence make SPI_QPIC_SNAND tristate, let it depend on MTD, and add the
+missing semicolon.
+
+Fixes: 7304d1909080ef0c ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface")
+Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Link: https://patch.msgid.link/b63db431cbf35223a4400e44c296293d32c4543c.1742998909.git.geert+renesas@glider.be
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/Kconfig          | 4 ++--
+ drivers/spi/spi-qpic-snand.c | 2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/spi/Kconfig
++++ b/drivers/spi/Kconfig
+@@ -899,9 +899,9 @@ config SPI_QCOM_QSPI
+         QSPI(Quad SPI) driver for Qualcomm QSPI controller.
+ config SPI_QPIC_SNAND
+-      bool "QPIC SNAND controller"
++      tristate "QPIC SNAND controller"
+       depends on ARCH_QCOM || COMPILE_TEST
+-      select MTD
++      depends on MTD
+       help
+         QPIC_SNAND (QPIC SPI NAND) driver for Qualcomm QPIC controller.
+         QPIC controller supports both parallel nand and serial nand.
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -1614,7 +1614,7 @@ static const struct of_device_id qcom_sn
+               .data = &ipq9574_snandc_props,
+       },
+       {}
+-}
++};
+ MODULE_DEVICE_TABLE(of, qcom_snandc_of_match);
+ static struct platform_driver qcom_spi_driver = {
diff --git a/target/linux/generic/backport-6.12/410-11-v6.15-spi-spi-qpic-snand-propagate-errors-from-qcom_spi_block_erase.patch b/target/linux/generic/backport-6.12/410-11-v6.15-spi-spi-qpic-snand-propagate-errors-from-qcom_spi_block_erase.patch
new file mode 100644 (file)
index 0000000..9da531b
--- /dev/null
@@ -0,0 +1,36 @@
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Wed, 23 Apr 2025 21:31:57 +0200
+Subject: [PATCH] spi: spi-qpic-snand: propagate errors from
+ qcom_spi_block_erase()
+
+The qcom_spi_block_erase() function returns with error in case of
+failure. Change the qcom_spi_send_cmdaddr() function to propagate
+these errors to the callers instead of returning with success.
+
+Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface")
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Reviewed-by: Abel Vesa <abel.vesa@linaro.org>
+Reviewed-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+---
+ drivers/spi/spi-qpic-snand.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+
+---
+base-commit: 9c32cda43eb78f78c73aee4aa344b777714e259b
+change-id: 20250422-qpic-snand-propagate-error-9c95811ab811
+
+Best regards,
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -1307,8 +1307,7 @@ static int qcom_spi_send_cmdaddr(struct
+               snandc->qspi->addr1 = cpu_to_le32(s_op.addr1_reg << 16);
+               snandc->qspi->addr2 = cpu_to_le32(s_op.addr2_reg);
+               snandc->qspi->cmd = cpu_to_le32(cmd);
+-              qcom_spi_block_erase(snandc);
+-              return 0;
++              return qcom_spi_block_erase(snandc);
+       default:
+               break;
+       }
diff --git a/target/linux/generic/backport-6.12/410-12-v6.15-spi-spi-qpic-snand-fix-NAND_READ_LOCATION_2-register-handling.patch b/target/linux/generic/backport-6.12/410-12-v6.15-spi-spi-qpic-snand-fix-NAND_READ_LOCATION_2-register-handling.patch
new file mode 100644 (file)
index 0000000..a2cd636
--- /dev/null
@@ -0,0 +1,35 @@
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Mon, 28 Apr 2025 09:30:55 +0200
+Subject: [PATCH] spi: spi-qpic-snand: fix NAND_READ_LOCATION_2 register
+ handling
+
+The precomputed value for the NAND_READ_LOCATION_2 register should be
+stored in 'snandc->regs->read_location2'.
+
+Fix the qcom_spi_set_read_loc_first() function accordingly.
+
+Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface")
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Reviewed-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+---
+ drivers/spi/spi-qpic-snand.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+
+---
+base-commit: 15cfe55ec58ace931a73e19e5367598734ceb074
+change-id: 20250428-qpic-snand-readloc2-fix-bccd07cf26d3
+
+Best regards,
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -142,7 +142,7 @@ static void qcom_spi_set_read_loc_first(
+       else if (reg == NAND_READ_LOCATION_1)
+               snandc->regs->read_location1 = locreg_val;
+       else if (reg == NAND_READ_LOCATION_2)
+-              snandc->regs->read_location1 = locreg_val;
++              snandc->regs->read_location2 = locreg_val;
+       else if (reg == NAND_READ_LOCATION_3)
+               snandc->regs->read_location3 = locreg_val;
+ }
diff --git a/target/linux/generic/backport-6.12/410-13-v6.15-spi-spi-qpic-snand-use-kmalloc-for-OOB-buffer-alloca.patch b/target/linux/generic/backport-6.12/410-13-v6.15-spi-spi-qpic-snand-use-kmalloc-for-OOB-buffer-alloca.patch
new file mode 100644 (file)
index 0000000..5a3d498
--- /dev/null
@@ -0,0 +1,29 @@
+From f48d80503504257682e493dc17408f2f0b47bcfa Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Thu, 20 Mar 2025 19:11:59 +0100
+Subject: [PATCH] spi: spi-qpic-snand: use kmalloc() for OOB buffer allocation
+
+The qcom_spi_ecc_init_ctx_pipelined() function allocates zeroed
+memory for the OOB buffer, then it fills the buffer with '0xff'
+bytes right after the allocation. In this case zeroing the memory
+during allocation is superfluous, so use kmalloc() instead of
+kzalloc() to avoid that.
+
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Link: https://patch.msgid.link/20250320-qpic-snand-kmalloc-v1-1-94e267550675@gmail.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-qpic-snand.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -261,7 +261,7 @@ static int qcom_spi_ecc_init_ctx_pipelin
+       ecc_cfg = kzalloc(sizeof(*ecc_cfg), GFP_KERNEL);
+       if (!ecc_cfg)
+               return -ENOMEM;
+-      snandc->qspi->oob_buf = kzalloc(mtd->writesize + mtd->oobsize,
++      snandc->qspi->oob_buf = kmalloc(mtd->writesize + mtd->oobsize,
+                                       GFP_KERNEL);
+       if (!snandc->qspi->oob_buf) {
+               kfree(ecc_cfg);
diff --git a/target/linux/generic/backport-6.12/410-14-v6.16-spi-spi-qpic-snand-remove-unused-wlen-member-of-struct-qpic_spi_nand.patch b/target/linux/generic/backport-6.12/410-14-v6.16-spi-spi-qpic-snand-remove-unused-wlen-member-of-struct-qpic_spi_nand.patch
new file mode 100644 (file)
index 0000000..55e84da
--- /dev/null
@@ -0,0 +1,30 @@
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Thu, 24 Apr 2025 20:10:59 +0200
+Subject: [PATCH] spi: spi-qpic-snand: remove unused 'wlen' member of
+ 'struct qpic_spi_nand'
+
+The 'wlen' member of the qpic_spi_nand structure is never used in the
+code so remove that.
+
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+---
+ drivers/spi/spi-qpic-snand.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+
+---
+base-commit: 9c32cda43eb78f78c73aee4aa344b777714e259b
+change-id: 20250424-qpic-snand-remove-wlen-c0cef3801a7f
+
+Best regards,
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -116,7 +116,6 @@ struct qpic_spi_nand {
+       struct nand_ecc_engine ecc_eng;
+       u8 *data_buf;
+       u8 *oob_buf;
+-      u32 wlen;
+       __le32 addr1;
+       __le32 addr2;
+       __le32 cmd;
diff --git a/target/linux/generic/backport-6.12/411-v6.15-mtd-nand-Drop-explicit-test-for-built-in-CONFIG_SPI_.patch b/target/linux/generic/backport-6.12/411-v6.15-mtd-nand-Drop-explicit-test-for-built-in-CONFIG_SPI_.patch
new file mode 100644 (file)
index 0000000..8019d2c
--- /dev/null
@@ -0,0 +1,37 @@
+From 36c6468724aa98d33fea9a1d7e07ddda6302f5d4 Mon Sep 17 00:00:00 2001
+From: Geert Uytterhoeven <geert+renesas@glider.be>
+Date: Fri, 28 Mar 2025 09:24:01 +0100
+Subject: mtd: nand: Drop explicit test for built-in CONFIG_SPI_QPIC_SNAND
+
+If CONFIG_SPI_QPIC_SNAND=m, but CONFIG_MTD_NAND_QCOM=n:
+
+    ERROR: modpost: "qcom_nandc_unalloc" [drivers/spi/spi-qpic-snand.ko] undefined!
+    ...
+
+Fix this by dropping the explicit test for a built-in
+CONFIG_SPI_QPIC_SNAND completely.  Kbuild handles multiple and mixed
+obj-y/obj-m rules for the same object file fine.
+
+Reported-by: kernel test robot <lkp@intel.com>
+Closes: https://lore.kernel.org/oe-kbuild-all/202503280759.XhwLcV7m-lkp@intel.com/
+Fixes: 7304d1909080ef0c ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface")
+Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/Makefile | 3 ---
+ 1 file changed, 3 deletions(-)
+
+--- a/drivers/mtd/nand/Makefile
++++ b/drivers/mtd/nand/Makefile
+@@ -3,11 +3,8 @@
+ nandcore-objs := core.o bbt.o
+ obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
+ obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o
+-ifeq ($(CONFIG_SPI_QPIC_SNAND),y)
+ obj-$(CONFIG_SPI_QPIC_SNAND) += qpic_common.o
+-else
+ obj-$(CONFIG_MTD_NAND_QCOM) += qpic_common.o
+-endif
+ obj-y += onenand/
+ obj-y += raw/
+ obj-y += spi/
diff --git a/target/linux/generic/backport-6.12/412-v6.16-next-spi-spi-qpic-snand-validate-user-chip-specific-ECC-properties.patch b/target/linux/generic/backport-6.12/412-v6.16-next-spi-spi-qpic-snand-validate-user-chip-specific-ECC-properties.patch
new file mode 100644 (file)
index 0000000..2f95156
--- /dev/null
@@ -0,0 +1,166 @@
+From 65cb56d49f6edea409600a3c61effc70ee5d43d8 Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Thu, 1 May 2025 18:19:16 +0200
+Subject: spi: spi-qpic-snand: validate user/chip specific ECC properties
+
+The driver only supports 512 bytes ECC step size and 4 bit ECC strength
+at the moment, however it does not reject unsupported step/strength
+configurations. Due to this, whenever the driver is used with a flash
+chip which needs stronger ECC protection, the following warning is shown
+in the kernel log:
+
+  [    0.574648] spi-nand spi0.0: GigaDevice SPI NAND was found.
+  [    0.635748] spi-nand spi0.0: 256 MiB, block size: 128 KiB, page size: 2048, OOB size: 128
+  [    0.649079] nand: WARNING: (null): the ECC used on your system is too weak compared to the one required by the NAND chip
+
+Although the message indicates that something is wrong, but it often gets
+unnoticed, which can cause serious problems. For example when the user
+writes something into the flash chip despite the warning, the written data
+may won't be readable by the bootloader or by the boot ROM. In the worst
+case, when the attached SPI NAND chip is the boot device, the board may not
+be able to boot anymore.
+
+Also, it is not even possible to create a backup of the flash, because
+reading its content results in bogus data. For example, dumping the first
+page of the flash gives this:
+
+  # hexdump -C -n 2048 /dev/mtd0
+  00000000  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  *
+  00000040  0f 0f 0f 0f 0f 0f 0f 0d  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  00000050  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  *
+  000001c0  0f 0f 0f 0f ff 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  000001d0  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  *
+  00000200  0f 0f 0f 0f f5 5b ff ff  0f 0f 0f 0f 0f 0f 0f 0f  |.....[..........|
+  00000210  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  *
+  000002f0  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f 0f 1f 0f 0f  |................|
+  00000300  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  *
+  000003c0  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f ff 0f 0f 0f  |................|
+  000003d0  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  *
+  00000400  0f 0f 0f 0f 0f 0f 0f 0f  e9 74 c9 06 f5 5b ff ff  |.........t...[..|
+  00000410  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  *
+  000005d0  0f 0f 0f 0f ff 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  000005e0  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  *
+  00000600  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f c6 be 0f c3  |................|
+  00000610  e9 74 c9 06 f5 5b ff ff  0f 0f 0f 0f 0f 0f 0f 0f  |.t...[..........|
+  00000620  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  *
+  00000770  0f 0f 0f 0f 8f 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  00000780  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  *
+  00000800
+  #
+
+Doing the same by using the downstream kernel results in different output:
+
+  # hexdump -C -n 2048 /dev/mtd0
+  00000000  0f 0f 0f 0f 0f 0f 0f 0f  0f 0f 0f 0f 0f 0f 0f 0f  |................|
+  *
+  00000800
+  #
+
+This patch adds some sanity checks to the code to prevent using the driver
+with unsupported ECC step/strength configurations. After the change, probing
+of the driver fails in such cases:
+
+  [    0.655038] spi-nand spi0.0: GigaDevice SPI NAND was found.
+  [    0.659159] spi-nand spi0.0: 256 MiB, block size: 128 KiB, page size: 2048, OOB size: 128
+  [    0.669138] qcom_snand 79b0000.spi: only 4 bits ECC strength is supported
+  [    0.677476] nand: No suitable ECC configuration
+  [    0.689909] spi-nand spi0.0: probe with driver spi-nand failed with error -95
+
+This helps to avoid the aforementioned hassles until support for 8 bit ECC
+strength gets implemented.
+
+Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface")
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Link: https://patch.msgid.link/20250501-qpic-snand-validate-ecc-v1-1-532776581a66@gmail.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-qpic-snand.c | 42 +++++++++++++++++++++++++++++++-----
+ 1 file changed, 37 insertions(+), 5 deletions(-)
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -249,9 +249,11 @@ static const struct mtd_ooblayout_ops qc
+ static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand)
+ {
+       struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand);
++      struct nand_ecc_props *reqs = &nand->ecc.requirements;
++      struct nand_ecc_props *user = &nand->ecc.user_conf;
+       struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
+       struct mtd_info *mtd = nanddev_to_mtd(nand);
+-      int cwperpage, bad_block_byte;
++      int cwperpage, bad_block_byte, ret;
+       struct qpic_ecc *ecc_cfg;
+       cwperpage = mtd->writesize / NANDC_STEP_SIZE;
+@@ -260,11 +262,39 @@ static int qcom_spi_ecc_init_ctx_pipelin
+       ecc_cfg = kzalloc(sizeof(*ecc_cfg), GFP_KERNEL);
+       if (!ecc_cfg)
+               return -ENOMEM;
++
++      if (user->step_size && user->strength) {
++              ecc_cfg->step_size = user->step_size;
++              ecc_cfg->strength = user->strength;
++      } else if (reqs->step_size && reqs->strength) {
++              ecc_cfg->step_size = reqs->step_size;
++              ecc_cfg->strength = reqs->strength;
++      } else {
++              /* use defaults */
++              ecc_cfg->step_size = NANDC_STEP_SIZE;
++              ecc_cfg->strength = 4;
++      }
++
++      if (ecc_cfg->step_size != NANDC_STEP_SIZE) {
++              dev_err(snandc->dev,
++                      "only %u bytes ECC step size is supported\n",
++                      NANDC_STEP_SIZE);
++              ret = -EOPNOTSUPP;
++              goto err_free_ecc_cfg;
++      }
++
++      if (ecc_cfg->strength != 4) {
++              dev_err(snandc->dev,
++                      "only 4 bits ECC strength is supported\n");
++              ret = -EOPNOTSUPP;
++              goto err_free_ecc_cfg;
++      }
++
+       snandc->qspi->oob_buf = kmalloc(mtd->writesize + mtd->oobsize,
+                                       GFP_KERNEL);
+       if (!snandc->qspi->oob_buf) {
+-              kfree(ecc_cfg);
+-              return -ENOMEM;
++              ret = -ENOMEM;
++              goto err_free_ecc_cfg;
+       }
+       memset(snandc->qspi->oob_buf, 0xff, mtd->writesize + mtd->oobsize);
+@@ -279,8 +309,6 @@ static int qcom_spi_ecc_init_ctx_pipelin
+       ecc_cfg->bytes = ecc_cfg->ecc_bytes_hw + ecc_cfg->spare_bytes + ecc_cfg->bbm_size;
+       ecc_cfg->steps = 4;
+-      ecc_cfg->strength = 4;
+-      ecc_cfg->step_size = 512;
+       ecc_cfg->cw_data = 516;
+       ecc_cfg->cw_size = ecc_cfg->cw_data + ecc_cfg->bytes;
+       bad_block_byte = mtd->writesize - ecc_cfg->cw_size * (cwperpage - 1) + 1;
+@@ -338,6 +366,10 @@ static int qcom_spi_ecc_init_ctx_pipelin
+               ecc_cfg->strength, ecc_cfg->step_size);
+       return 0;
++
++err_free_ecc_cfg:
++      kfree(ecc_cfg);
++      return ret;
+ }
+ static void qcom_spi_ecc_cleanup_ctx_pipelined(struct nand_device *nand)
diff --git a/target/linux/generic/backport-6.12/413-v6.16-spi-spi-qpic-snand-use-CW_PER_PAGE_MASK-bitmask.patch b/target/linux/generic/backport-6.12/413-v6.16-spi-spi-qpic-snand-use-CW_PER_PAGE_MASK-bitmask.patch
new file mode 100644 (file)
index 0000000..10c1651
--- /dev/null
@@ -0,0 +1,113 @@
+From 2abf107dcd797c60c86e9f17319cd1658862f6b2 Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Thu, 15 May 2025 20:58:05 +0200
+Subject: spi: spi-qpic-snand: use CW_PER_PAGE_MASK bitmask
+
+Change the code to use the already defined CW_PER_PAGE_MASK
+bitmask along with the FIELD_PREP() macro instead of using
+magic values.
+
+This makes the code more readable. It also syncs the affected
+codes with their counterparts in the 'qcom_nandc' driver, so it
+makes it easier to spot the differences between the two
+implementations.
+
+No functional changes intended.
+
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Reviewed-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+Link: https://patch.msgid.link/20250515-qpic-snand-use-bitmasks-v1-1-11729aeae73b@gmail.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-qpic-snand.c | 31 ++++++++++++++++---------------
+ 1 file changed, 16 insertions(+), 15 deletions(-)
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -483,7 +483,8 @@ static int qcom_spi_block_erase(struct q
+       snandc->regs->cmd = snandc->qspi->cmd;
+       snandc->regs->addr0 = snandc->qspi->addr1;
+       snandc->regs->addr1 = snandc->qspi->addr2;
+-      snandc->regs->cfg0 = cpu_to_le32(ecc_cfg->cfg0_raw & ~(7 << CW_PER_PAGE));
++      snandc->regs->cfg0 = cpu_to_le32((ecc_cfg->cfg0_raw & ~CW_PER_PAGE_MASK) |
++                                       FIELD_PREP(CW_PER_PAGE_MASK, 0));
+       snandc->regs->cfg1 = cpu_to_le32(ecc_cfg->cfg1_raw);
+       snandc->regs->exec = cpu_to_le32(1);
+@@ -544,8 +545,8 @@ static int qcom_spi_read_last_cw(struct
+       snandc->regs->addr0 = (snandc->qspi->addr1 | cpu_to_le32(col));
+       snandc->regs->addr1 = snandc->qspi->addr2;
+-      cfg0 = (ecc_cfg->cfg0_raw & ~(7U << CW_PER_PAGE)) |
+-              0 << CW_PER_PAGE;
++      cfg0 = (ecc_cfg->cfg0_raw & ~CW_PER_PAGE_MASK) |
++             FIELD_PREP(CW_PER_PAGE_MASK, 0);
+       cfg1 = ecc_cfg->cfg1_raw;
+       ecc_bch_cfg = ECC_CFG_ECC_DISABLE;
+@@ -687,8 +688,8 @@ static int qcom_spi_read_cw_raw(struct q
+       qcom_clear_bam_transaction(snandc);
+       raw_cw = num_cw - 1;
+-      cfg0 = (ecc_cfg->cfg0_raw & ~(7U << CW_PER_PAGE)) |
+-                              0 << CW_PER_PAGE;
++      cfg0 = (ecc_cfg->cfg0_raw & ~CW_PER_PAGE_MASK) |
++             FIELD_PREP(CW_PER_PAGE_MASK, 0);
+       cfg1 = ecc_cfg->cfg1_raw;
+       ecc_bch_cfg = ECC_CFG_ECC_DISABLE;
+@@ -808,8 +809,8 @@ static int qcom_spi_read_page_ecc(struct
+       snandc->buf_start = 0;
+       qcom_clear_read_regs(snandc);
+-      cfg0 = (ecc_cfg->cfg0 & ~(7U << CW_PER_PAGE)) |
+-                              (num_cw - 1) << CW_PER_PAGE;
++      cfg0 = (ecc_cfg->cfg0 & ~CW_PER_PAGE_MASK) |
++             FIELD_PREP(CW_PER_PAGE_MASK, num_cw - 1);
+       cfg1 = ecc_cfg->cfg1;
+       ecc_bch_cfg = ecc_cfg->ecc_bch_cfg;
+@@ -904,8 +905,8 @@ static int qcom_spi_read_page_oob(struct
+       qcom_clear_read_regs(snandc);
+       qcom_clear_bam_transaction(snandc);
+-      cfg0 = (ecc_cfg->cfg0 & ~(7U << CW_PER_PAGE)) |
+-                              (num_cw - 1) << CW_PER_PAGE;
++      cfg0 = (ecc_cfg->cfg0 & ~CW_PER_PAGE_MASK) |
++             FIELD_PREP(CW_PER_PAGE_MASK, num_cw - 1);
+       cfg1 = ecc_cfg->cfg1;
+       ecc_bch_cfg = ecc_cfg->ecc_bch_cfg;
+@@ -1015,8 +1016,8 @@ static int qcom_spi_program_raw(struct q
+       int num_cw = snandc->qspi->num_cw;
+       u32 cfg0, cfg1, ecc_bch_cfg;
+-      cfg0 = (ecc_cfg->cfg0_raw & ~(7U << CW_PER_PAGE)) |
+-                      (num_cw - 1) << CW_PER_PAGE;
++      cfg0 = (ecc_cfg->cfg0_raw & ~CW_PER_PAGE_MASK) |
++             FIELD_PREP(CW_PER_PAGE_MASK, num_cw - 1);
+       cfg1 = ecc_cfg->cfg1_raw;
+       ecc_bch_cfg = ECC_CFG_ECC_DISABLE;
+@@ -1098,8 +1099,8 @@ static int qcom_spi_program_ecc(struct q
+       int num_cw = snandc->qspi->num_cw;
+       u32 cfg0, cfg1, ecc_bch_cfg, ecc_buf_cfg;
+-      cfg0 = (ecc_cfg->cfg0 & ~(7U << CW_PER_PAGE)) |
+-                              (num_cw - 1) << CW_PER_PAGE;
++      cfg0 = (ecc_cfg->cfg0 & ~CW_PER_PAGE_MASK) |
++             FIELD_PREP(CW_PER_PAGE_MASK, num_cw - 1);
+       cfg1 = ecc_cfg->cfg1;
+       ecc_bch_cfg = ecc_cfg->ecc_bch_cfg;
+       ecc_buf_cfg = ecc_cfg->ecc_buf_cfg;
+@@ -1175,8 +1176,8 @@ static int qcom_spi_program_oob(struct q
+       int num_cw = snandc->qspi->num_cw;
+       u32 cfg0, cfg1, ecc_bch_cfg, ecc_buf_cfg;
+-      cfg0 = (ecc_cfg->cfg0 & ~(7U << CW_PER_PAGE)) |
+-                              (num_cw - 1) << CW_PER_PAGE;
++      cfg0 = (ecc_cfg->cfg0 & ~CW_PER_PAGE_MASK) |
++             FIELD_PREP(CW_PER_PAGE_MASK, num_cw - 1);
+       cfg1 = ecc_cfg->cfg1;
+       ecc_bch_cfg = ecc_cfg->ecc_bch_cfg;
+       ecc_buf_cfg = ecc_cfg->ecc_buf_cfg;
diff --git a/target/linux/generic/backport-6.12/414-v6.16-spi-spi-qpic-snand-reallocate-BAM-transactions.patch b/target/linux/generic/backport-6.12/414-v6.16-spi-spi-qpic-snand-reallocate-BAM-transactions.patch
new file mode 100644 (file)
index 0000000..6d61016
--- /dev/null
@@ -0,0 +1,81 @@
+From d85d0380292a7e618915069c3579ae23c7c80339 Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Wed, 18 Jun 2025 22:22:49 +0200
+Subject: spi: spi-qpic-snand: reallocate BAM transactions
+
+Using the mtd_nandbiterrs module for testing the driver occasionally
+results in weird things like below.
+
+1. swiotlb mapping fails with the following message:
+
+  [   85.926216] qcom_snand 79b0000.spi: swiotlb buffer is full (sz: 4294967294 bytes), total 512 (slots), used 0 (slots)
+  [   85.932937] qcom_snand 79b0000.spi: failure in mapping desc
+  [   87.999314] qcom_snand 79b0000.spi: failure to write raw page
+  [   87.999352] mtd_nandbiterrs: error: write_oob failed (-110)
+
+  Rebooting the board after this causes a panic due to a NULL pointer
+  dereference.
+
+2. If the swiotlb mapping does not fail, rebooting the board may result
+   in a different panic due to a bad spinlock magic:
+
+  [  256.104459] BUG: spinlock bad magic on CPU#3, procd/2241
+  [  256.104488] Unable to handle kernel paging request at virtual address ffffffff0000049b
+  ...
+
+Investigating the issue revealed that these symptoms are results of
+memory corruption which is caused by out of bounds access within the
+driver.
+
+The driver uses a dynamically allocated structure for BAM transactions,
+which structure must have enough space for all possible variations of
+different flash operations initiated by the driver. The required space
+heavily depends on the actual number of 'codewords' which is calculated
+from the pagesize of the actual NAND chip.
+
+Although the qcom_nandc_alloc() function allocates memory for the BAM
+transactions during probe, but since the actual number of 'codewords'
+is not yet know the allocation is done for one 'codeword' only.
+
+Because of this, whenever the driver does a flash operation, and the
+number of the required transactions exceeds the size of the allocated
+arrays the driver accesses memory out of the allocated range.
+
+To avoid this, change the code to free the initially allocated BAM
+transactions memory, and allocate a new one once the actual number of
+'codewords' required for a given NAND chip is known.
+
+Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface")
+Reviewed-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Link: https://patch.msgid.link/20250618-qpic-snand-avoid-mem-corruption-v3-1-319c71296cda@gmail.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-qpic-snand.c | 16 ++++++++++++++++
+ 1 file changed, 16 insertions(+)
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -315,6 +315,22 @@ static int qcom_spi_ecc_init_ctx_pipelin
+       mtd_set_ooblayout(mtd, &qcom_spi_ooblayout);
++      /*
++       * Free the temporary BAM transaction allocated initially by
++       * qcom_nandc_alloc(), and allocate a new one based on the
++       * updated max_cwperpage value.
++       */
++      qcom_free_bam_transaction(snandc);
++
++      snandc->max_cwperpage = cwperpage;
++
++      snandc->bam_txn = qcom_alloc_bam_transaction(snandc);
++      if (!snandc->bam_txn) {
++              dev_err(snandc->dev, "failed to allocate BAM transaction\n");
++              ret = -ENOMEM;
++              goto err_free_ecc_cfg;
++      }
++
+       ecc_cfg->cfg0 = FIELD_PREP(CW_PER_PAGE_MASK, (cwperpage - 1)) |
+                       FIELD_PREP(UD_SIZE_BYTES_MASK, ecc_cfg->cw_data) |
+                       FIELD_PREP(DISABLE_STATUS_AFTER_WRITE, 1) |
diff --git a/target/linux/generic/backport-6.12/415-v6.16-spi-spi-qpic-snand-don-t-hardcode-ECC-steps.patch b/target/linux/generic/backport-6.12/415-v6.16-spi-spi-qpic-snand-don-t-hardcode-ECC-steps.patch
new file mode 100644 (file)
index 0000000..eab7b79
--- /dev/null
@@ -0,0 +1,31 @@
+From f820034864dd463cdcd2bebe7940f2eca0eb4223 Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Wed, 23 Jul 2025 10:06:43 +0200
+Subject: spi: spi-qpic-snand: don't hardcode ECC steps
+
+NAND devices with different page sizes requires different number
+of ECC steps, yet the qcom_spi_ecc_init_ctx_pipelined() function
+sets 4 steps in 'ecc_cfg' unconditionally.
+
+The correct number of the steps is calculated earlier in the
+function already, so use that instead of the hardcoded value.
+
+Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface")
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Link: https://patch.msgid.link/20250723-qpic-snand-fix-steps-v1-1-d800695dde4c@gmail.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-qpic-snand.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -308,7 +308,7 @@ static int qcom_spi_ecc_init_ctx_pipelin
+       ecc_cfg->bch_enabled = true;
+       ecc_cfg->bytes = ecc_cfg->ecc_bytes_hw + ecc_cfg->spare_bytes + ecc_cfg->bbm_size;
+-      ecc_cfg->steps = 4;
++      ecc_cfg->steps = cwperpage;
+       ecc_cfg->cw_data = 516;
+       ecc_cfg->cw_size = ecc_cfg->cw_data + ecc_cfg->bytes;
+       bad_block_byte = mtd->writesize - ecc_cfg->cw_size * (cwperpage - 1) + 1;
diff --git a/target/linux/generic/backport-6.12/416-v6.17-mtd-nand-qpic-common-add-defines-for-ECC_MODE-values.patch b/target/linux/generic/backport-6.12/416-v6.17-mtd-nand-qpic-common-add-defines-for-ECC_MODE-values.patch
new file mode 100644 (file)
index 0000000..7ddf15e
--- /dev/null
@@ -0,0 +1,74 @@
+From 0dc7e656ddd54c3267b7cc18c1ac8ec1297ed02f Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Wed, 2 Jul 2025 14:35:23 +0200
+Subject: mtd: nand: qpic-common: add defines for ECC_MODE values
+
+Add defines for the values of the ECC_MODE field of the NAND_DEV0_ECC_CFG
+register and change both the 'qcom-nandc' and 'spi-qpic-snand' drivers to
+use those instead of magic numbers.
+
+No functional changes. This is in preparation for adding 8 bit ECC strength
+support for the 'spi-qpic-snand' driver.
+
+Reviewed-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Acked-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Link: https://patch.msgid.link/20250702-qpic-snand-8bit-ecc-v2-1-ae2c17a30bb7@gmail.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/mtd/nand/raw/qcom_nandc.c    | 6 +++---
+ drivers/spi/spi-qpic-snand.c         | 2 +-
+ include/linux/mtd/nand-qpic-common.h | 2 ++
+ 3 files changed, 6 insertions(+), 4 deletions(-)
+
+--- a/drivers/mtd/nand/raw/qcom_nandc.c
++++ b/drivers/mtd/nand/raw/qcom_nandc.c
+@@ -1379,7 +1379,7 @@ static int qcom_nand_attach_chip(struct
+       struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
+       int cwperpage, bad_block_byte, ret;
+       bool wide_bus;
+-      int ecc_mode = 1;
++      int ecc_mode = ECC_MODE_8BIT;
+       /* controller only supports 512 bytes data steps */
+       ecc->size = NANDC_STEP_SIZE;
+@@ -1400,7 +1400,7 @@ static int qcom_nand_attach_chip(struct
+       if (ecc->strength >= 8) {
+               /* 8 bit ECC defaults to BCH ECC on all platforms */
+               host->bch_enabled = true;
+-              ecc_mode = 1;
++              ecc_mode = ECC_MODE_8BIT;
+               if (wide_bus) {
+                       host->ecc_bytes_hw = 14;
+@@ -1420,7 +1420,7 @@ static int qcom_nand_attach_chip(struct
+               if (nandc->props->ecc_modes & ECC_BCH_4BIT) {
+                       /* BCH */
+                       host->bch_enabled = true;
+-                      ecc_mode = 0;
++                      ecc_mode = ECC_MODE_4BIT;
+                       if (wide_bus) {
+                               host->ecc_bytes_hw = 8;
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -365,7 +365,7 @@ static int qcom_spi_ecc_init_ctx_pipelin
+                              FIELD_PREP(ECC_SW_RESET, 0) |
+                              FIELD_PREP(ECC_NUM_DATA_BYTES_MASK, ecc_cfg->cw_data) |
+                              FIELD_PREP(ECC_FORCE_CLK_OPEN, 1) |
+-                             FIELD_PREP(ECC_MODE_MASK, 0) |
++                             FIELD_PREP(ECC_MODE_MASK, ECC_MODE_4BIT) |
+                              FIELD_PREP(ECC_PARITY_SIZE_BYTES_BCH_MASK, ecc_cfg->ecc_bytes_hw);
+       ecc_cfg->ecc_buf_cfg = 0x203 << NUM_STEPS;
+--- a/include/linux/mtd/nand-qpic-common.h
++++ b/include/linux/mtd/nand-qpic-common.h
+@@ -101,6 +101,8 @@
+ #define       ECC_SW_RESET                    BIT(1)
+ #define       ECC_MODE                        4
+ #define       ECC_MODE_MASK                   GENMASK(5, 4)
++#define       ECC_MODE_4BIT                   0
++#define       ECC_MODE_8BIT                   1
+ #define       ECC_PARITY_SIZE_BYTES_BCH       8
+ #define       ECC_PARITY_SIZE_BYTES_BCH_MASK  GENMASK(12, 8)
+ #define       ECC_NUM_DATA_BYTES              16
diff --git a/target/linux/generic/backport-6.12/417-v6.17-spi-spi-qpic-snand-add-support-for-8-bits-ECC-strength.patch b/target/linux/generic/backport-6.12/417-v6.17-spi-spi-qpic-snand-add-support-for-8-bits-ECC-strength.patch
new file mode 100644 (file)
index 0000000..4eff84f
--- /dev/null
@@ -0,0 +1,66 @@
+From 913bf8d50cbd144c87e9660b591781179182ff59 Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Wed, 2 Jul 2025 14:35:24 +0200
+Subject: spi: spi-qpic-snand: add support for 8 bits ECC strength
+
+Even though the hardware supports 8 bits ECC strength, but that is not
+handled in the driver yet. This change adds the missing bits in order
+to allow using the driver with chips which require 8 bits ECC strength.
+
+No functional changes intended with regard to the existing 4 bits ECC
+strength support.
+
+Tested on an IPQ9574 platform using a GigaDevice GD5F2GM7REYIG chip.
+
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Link: https://patch.msgid.link/20250702-qpic-snand-8bit-ecc-v2-2-ae2c17a30bb7@gmail.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-qpic-snand.c | 21 ++++++++++++++++-----
+ 1 file changed, 16 insertions(+), 5 deletions(-)
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -283,9 +283,22 @@ static int qcom_spi_ecc_init_ctx_pipelin
+               goto err_free_ecc_cfg;
+       }
+-      if (ecc_cfg->strength != 4) {
++      switch (ecc_cfg->strength) {
++      case 4:
++              ecc_cfg->ecc_mode = ECC_MODE_4BIT;
++              ecc_cfg->ecc_bytes_hw = 7;
++              ecc_cfg->spare_bytes = 4;
++              break;
++
++      case 8:
++              ecc_cfg->ecc_mode = ECC_MODE_8BIT;
++              ecc_cfg->ecc_bytes_hw = 13;
++              ecc_cfg->spare_bytes = 2;
++              break;
++
++      default:
+               dev_err(snandc->dev,
+-                      "only 4 bits ECC strength is supported\n");
++                      "only 4 or 8 bits ECC strength is supported\n");
+               ret = -EOPNOTSUPP;
+               goto err_free_ecc_cfg;
+       }
+@@ -302,8 +315,6 @@ static int qcom_spi_ecc_init_ctx_pipelin
+       nand->ecc.ctx.priv = ecc_cfg;
+       snandc->qspi->mtd = mtd;
+-      ecc_cfg->ecc_bytes_hw = 7;
+-      ecc_cfg->spare_bytes = 4;
+       ecc_cfg->bbm_size = 1;
+       ecc_cfg->bch_enabled = true;
+       ecc_cfg->bytes = ecc_cfg->ecc_bytes_hw + ecc_cfg->spare_bytes + ecc_cfg->bbm_size;
+@@ -365,7 +376,7 @@ static int qcom_spi_ecc_init_ctx_pipelin
+                              FIELD_PREP(ECC_SW_RESET, 0) |
+                              FIELD_PREP(ECC_NUM_DATA_BYTES_MASK, ecc_cfg->cw_data) |
+                              FIELD_PREP(ECC_FORCE_CLK_OPEN, 1) |
+-                             FIELD_PREP(ECC_MODE_MASK, ECC_MODE_4BIT) |
++                             FIELD_PREP(ECC_MODE_MASK, ecc_cfg->ecc_mode) |
+                              FIELD_PREP(ECC_PARITY_SIZE_BYTES_BCH_MASK, ecc_cfg->ecc_bytes_hw);
+       ecc_cfg->ecc_buf_cfg = 0x203 << NUM_STEPS;
diff --git a/target/linux/generic/backport-6.12/418-v6.17-spi-spi-qpic-snand-use-correct-CW_PER_PAGE-value-for.patch b/target/linux/generic/backport-6.12/418-v6.17-spi-spi-qpic-snand-use-correct-CW_PER_PAGE-value-for.patch
new file mode 100644 (file)
index 0000000..b67645e
--- /dev/null
@@ -0,0 +1,56 @@
+From 6bc829220b33da8522572cc50fdf5067c51d3bf3 Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Fri, 1 Aug 2025 09:58:35 +0200
+Subject: spi: spi-qpic-snand: use correct CW_PER_PAGE value for OOB write
+
+The qcom_spi_program_oob() function uses only the last codeword to write
+the OOB data into the flash, but it sets the CW_PER_PAGE field in the
+CFG0 register as it would use all codewords.
+
+It seems that this confuses the hardware somehow, and any access to the
+flash fails with a timeout error after the function is called. The problem
+can be easily reproduced with the following commands:
+
+    # dd if=/dev/zero bs=2176 count=1 > /tmp/test.bin
+    1+0 records in
+    1+0 records out
+    # flash_erase /dev/mtd4 0 0
+    Erasing 128 Kibyte @ 0 -- 100 % complete
+    # nandwrite -O /dev/mtd4 /tmp/test.bin
+    Writing data to block 0 at offset 0x0
+    # nanddump -o /dev/mtd4 >/dev/null
+    ECC failed: 0
+    ECC corrected: 0
+    Number of bad blocks: 0
+    Number of bbt blocks: 0
+    Block size 131072, page size 2048, OOB size 128
+    Dumping data starting at 0x00000000 and ending at 0x00020000...
+    [   33.197605] qcom_snand 79b0000.spi: failure to read oob
+    libmtd: error!: MEMREADOOB64 ioctl failed for mtd4, offset 0 (eraseblock 0)
+            error 110 (Operation timed out)
+    [   35.277582] qcom_snand 79b0000.spi: failure in submitting cmd descriptor
+    libmtd: error!: cannot read 2048 bytes from mtd4 (eraseblock 0, offset 2048)
+            error 110 (Operation timed out)
+    nanddump: error!: mtd_read
+
+Change the code to use the correct CW_PER_PAGE value to avoid this.
+
+Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface")
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Link: https://patch.msgid.link/20250801-qpic-snand-oob-cwpp-fix-v1-1-f5a41b86af2e@gmail.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-qpic-snand.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -1204,7 +1204,7 @@ static int qcom_spi_program_oob(struct q
+       u32 cfg0, cfg1, ecc_bch_cfg, ecc_buf_cfg;
+       cfg0 = (ecc_cfg->cfg0 & ~CW_PER_PAGE_MASK) |
+-             FIELD_PREP(CW_PER_PAGE_MASK, num_cw - 1);
++             FIELD_PREP(CW_PER_PAGE_MASK, 0);
+       cfg1 = ecc_cfg->cfg1;
+       ecc_bch_cfg = ecc_cfg->ecc_bch_cfg;
+       ecc_buf_cfg = ecc_cfg->ecc_buf_cfg;
diff --git a/target/linux/generic/backport-6.12/419-v6.17-spi-spi-qpic-snand-fix-calculating-of-ECC-OOB-region.patch b/target/linux/generic/backport-6.12/419-v6.17-spi-spi-qpic-snand-fix-calculating-of-ECC-OOB-region.patch
new file mode 100644 (file)
index 0000000..35d5d30
--- /dev/null
@@ -0,0 +1,69 @@
+From 13d0fe84a214658254a7412b2b46ec1507dc51f0 Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Tue, 5 Aug 2025 18:05:42 +0200
+Subject: spi: spi-qpic-snand: fix calculating of ECC OOB regions' properties
+
+The OOB layout used by the driver has two distinct regions which contains
+hardware specific ECC data, yet the qcom_spi_ooblayout_ecc() function sets
+the same offset and length values for both regions which is clearly wrong.
+
+Change the code to calculate the correct values for both regions.
+
+For reference, the following table shows the computed offset and length
+values for various OOB size/ECC strength configurations:
+
+                              +-----------------+-----------------+
+                              |before the change| after the change|
+  +-------+----------+--------+--------+--------+--------+--------+
+  |  OOB  |   ECC    | region | region | region | region | region |
+  |  size | strength | index  | offset | length | offset | length |
+  +-------+----------+--------+--------+--------+--------+--------+
+  |  128  |     8    |    0   |   113  |   15   |    0   |   49   |
+  |       |          |    1   |   113  |   15   |   65   |   63   |
+  +-------+----------+--------+--------+--------+--------+--------+
+  |  128  |     4    |    0   |   117  |   11   |    0   |   37   |
+  |       |          |    1   |   117  |   11   |   53   |   75   |
+  +-------+----------+--------+--------+--------+--------+--------+
+  |   64  |     4    |    0   |    53  |   11   |    0   |   37   |
+  |       |          |    1   |    53  |   11   |   53   |   11   |
+  +-------+----------+--------+--------+--------+--------+--------+
+
+Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface")
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
+Link: https://patch.msgid.link/20250805-qpic-snand-oob-ecc-fix-v2-1-e6f811c70d6f@gmail.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-qpic-snand.c | 20 ++++++++++++++------
+ 1 file changed, 14 insertions(+), 6 deletions(-)
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -216,13 +216,21 @@ static int qcom_spi_ooblayout_ecc(struct
+       struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand);
+       struct qpic_ecc *qecc = snandc->qspi->ecc;
+-      if (section > 1)
+-              return -ERANGE;
++      switch (section) {
++      case 0:
++              oobregion->offset = 0;
++              oobregion->length = qecc->bytes * (qecc->steps - 1) +
++                                  qecc->bbm_size;
++              return 0;
++      case 1:
++              oobregion->offset = qecc->bytes * (qecc->steps - 1) +
++                                  qecc->bbm_size +
++                                  qecc->steps * 4;
++              oobregion->length = mtd->oobsize - oobregion->offset;
++              return 0;
++      }
+-      oobregion->length = qecc->ecc_bytes_hw + qecc->spare_bytes;
+-      oobregion->offset = mtd->oobsize - oobregion->length;
+-
+-      return 0;
++      return -ERANGE;
+ }
+ static int qcom_spi_ooblayout_free(struct mtd_info *mtd, int section,
diff --git a/target/linux/generic/backport-6.12/420-01-v6.16-mtd-rawnand-brcmnand-remove-unused-parameters.patch b/target/linux/generic/backport-6.12/420-01-v6.16-mtd-rawnand-brcmnand-remove-unused-parameters.patch
new file mode 100644 (file)
index 0000000..3bc6038
--- /dev/null
@@ -0,0 +1,88 @@
+From 56fce75470041b5b0d92ae10637416e1a4cceb1b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= <noltari@gmail.com>
+Date: Wed, 14 May 2025 08:14:54 +0200
+Subject: [PATCH] mtd: rawnand: brcmnand: remove unused parameters
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+last_cmd and last_byte are now unused brcmnand_host members.
+last_addr is only written and never read so we can remove it too.
+
+Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Reviewed-by: William Zhang <william.zhang@broadcom.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/raw/brcmnand/brcmnand.c | 24 ++++++------------------
+ 1 file changed, 6 insertions(+), 18 deletions(-)
+
+--- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c
++++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
+@@ -310,9 +310,6 @@ struct brcmnand_host {
+       struct platform_device  *pdev;
+       int                     cs;
+-      unsigned int            last_cmd;
+-      unsigned int            last_byte;
+-      u64                     last_addr;
+       struct brcmnand_cfg     hwcfg;
+       struct brcmnand_controller *ctrl;
+ };
+@@ -2233,14 +2230,11 @@ static int brcmnand_read_page(struct nan
+                             int oob_required, int page)
+ {
+       struct mtd_info *mtd = nand_to_mtd(chip);
+-      struct brcmnand_host *host = nand_get_controller_data(chip);
+       u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL;
+       u64 addr = (u64)page << chip->page_shift;
+-      host->last_addr = addr;
+-
+-      return brcmnand_read(mtd, chip, host->last_addr,
+-                      mtd->writesize >> FC_SHIFT, (u32 *)buf, oob);
++      return brcmnand_read(mtd, chip, addr, mtd->writesize >> FC_SHIFT,
++                           (u32 *)buf, oob);
+ }
+ static int brcmnand_read_page_raw(struct nand_chip *chip, uint8_t *buf,
+@@ -2252,11 +2246,9 @@ static int brcmnand_read_page_raw(struct
+       int ret;
+       u64 addr = (u64)page << chip->page_shift;
+-      host->last_addr = addr;
+-
+       brcmnand_set_ecc_enabled(host, 0);
+-      ret = brcmnand_read(mtd, chip, host->last_addr,
+-                      mtd->writesize >> FC_SHIFT, (u32 *)buf, oob);
++      ret = brcmnand_read(mtd, chip, addr, mtd->writesize >> FC_SHIFT,
++                          (u32 *)buf, oob);
+       brcmnand_set_ecc_enabled(host, 1);
+       return ret;
+ }
+@@ -2363,13 +2355,10 @@ static int brcmnand_write_page(struct na
+                              int oob_required, int page)
+ {
+       struct mtd_info *mtd = nand_to_mtd(chip);
+-      struct brcmnand_host *host = nand_get_controller_data(chip);
+       void *oob = oob_required ? chip->oob_poi : NULL;
+       u64 addr = (u64)page << chip->page_shift;
+-      host->last_addr = addr;
+-
+-      return brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
++      return brcmnand_write(mtd, chip, addr, (const u32 *)buf, oob);
+ }
+ static int brcmnand_write_page_raw(struct nand_chip *chip, const uint8_t *buf,
+@@ -2381,9 +2370,8 @@ static int brcmnand_write_page_raw(struc
+       u64 addr = (u64)page << chip->page_shift;
+       int ret = 0;
+-      host->last_addr = addr;
+       brcmnand_set_ecc_enabled(host, 0);
+-      ret = brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
++      ret = brcmnand_write(mtd, chip, addr, (const u32 *)buf, oob);
+       brcmnand_set_ecc_enabled(host, 1);
+       return ret;
diff --git a/target/linux/generic/backport-6.12/420-02-v6.16-mtd-nand-brcmnand-fix-NAND-timeout-when-accessing-eM.patch b/target/linux/generic/backport-6.12/420-02-v6.16-mtd-nand-brcmnand-fix-NAND-timeout-when-accessing-eM.patch
new file mode 100644 (file)
index 0000000..d3dbde8
--- /dev/null
@@ -0,0 +1,30 @@
+From 528b541b71cf03e263272b051b70696f92258e9d Mon Sep 17 00:00:00 2001
+From: David Regan <dregan@broadcom.com>
+Date: Thu, 22 May 2025 10:25:17 -0700
+Subject: [PATCH] mtd: nand: brcmnand: fix NAND timeout when accessing eMMC
+
+When booting a board to NAND and accessing NAND while eMMC
+transactions are occurring the NAND will sometimes timeout. This
+is due to both NAND and eMMC controller sharing the same data bus
+on BCMBCA chips. Fix is to extend NAND timeout to allow eMMC
+transactions time to complete.
+
+Signed-off-by: David Regan <dregan@broadcom.com>
+Reviewed-by: William Zhang <william.zhang@broadcom.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/raw/brcmnand/brcmnand.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c
++++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
+@@ -101,7 +101,7 @@ struct brcm_nand_dma_desc {
+ #define BRCMNAND_MIN_DEVSIZE  (4ULL * 1024 * 1024)
+ #define NAND_CTRL_RDY                 (INTFC_CTLR_READY | INTFC_FLASH_READY)
+-#define NAND_POLL_STATUS_TIMEOUT_MS   100
++#define NAND_POLL_STATUS_TIMEOUT_MS   500
+ #define EDU_CMD_WRITE          0x00
+ #define EDU_CMD_READ           0x01
diff --git a/target/linux/generic/backport-6.12/420-03-v6.16-mtd-rawnand-brcmnand-legacy-exec_op-implementation.patch b/target/linux/generic/backport-6.12/420-03-v6.16-mtd-rawnand-brcmnand-legacy-exec_op-implementation.patch
new file mode 100644 (file)
index 0000000..6c75c2b
--- /dev/null
@@ -0,0 +1,299 @@
+From 3bfb22cecfe6b6f0d8ee56ef4b533cf68599c5d9 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= <noltari@gmail.com>
+Date: Wed, 21 May 2025 10:03:25 +0200
+Subject: [PATCH] mtd: rawnand: brcmnand: legacy exec_op implementation
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Commit 3c8260ce7663 ("mtd: rawnand: brcmnand: exec_op implementation")
+removed legacy interface functions, breaking < v5.0 controllers support.
+In order to fix older controllers we need to add an alternative exec_op
+implementation which doesn't rely on low level registers.
+
+Fixes: 3c8260ce7663 ("mtd: rawnand: brcmnand: exec_op implementation")
+Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+Reviewed-by: David Regan <dregan@broadcom.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Reviewed-by: William Zhang <william.zhang@broadcom.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/raw/brcmnand/brcmnand.c | 222 ++++++++++++++++++++++-
+ 1 file changed, 215 insertions(+), 7 deletions(-)
+
+--- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c
++++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
+@@ -65,6 +65,7 @@ module_param(wp_on, int, 0444);
+ #define CMD_PARAMETER_READ            0x0e
+ #define CMD_PARAMETER_CHANGE_COL      0x0f
+ #define CMD_LOW_LEVEL_OP              0x10
++#define CMD_NOT_SUPPORTED             0xff
+ struct brcm_nand_dma_desc {
+       u32 next_desc;
+@@ -199,6 +200,30 @@ static const u16 flash_dma_regs_v4[] = {
+       [FLASH_DMA_CURRENT_DESC_EXT]    = 0x34,
+ };
++/* Native command conversion for legacy controllers (< v5.0) */
++static const u8 native_cmd_conv[] = {
++      [NAND_CMD_READ0]        = CMD_NOT_SUPPORTED,
++      [NAND_CMD_READ1]        = CMD_NOT_SUPPORTED,
++      [NAND_CMD_RNDOUT]       = CMD_PARAMETER_CHANGE_COL,
++      [NAND_CMD_PAGEPROG]     = CMD_NOT_SUPPORTED,
++      [NAND_CMD_READOOB]      = CMD_NOT_SUPPORTED,
++      [NAND_CMD_ERASE1]       = CMD_BLOCK_ERASE,
++      [NAND_CMD_STATUS]       = CMD_NOT_SUPPORTED,
++      [NAND_CMD_SEQIN]        = CMD_NOT_SUPPORTED,
++      [NAND_CMD_RNDIN]        = CMD_NOT_SUPPORTED,
++      [NAND_CMD_READID]       = CMD_DEVICE_ID_READ,
++      [NAND_CMD_ERASE2]       = CMD_NULL,
++      [NAND_CMD_PARAM]        = CMD_PARAMETER_READ,
++      [NAND_CMD_GET_FEATURES] = CMD_NOT_SUPPORTED,
++      [NAND_CMD_SET_FEATURES] = CMD_NOT_SUPPORTED,
++      [NAND_CMD_RESET]        = CMD_NOT_SUPPORTED,
++      [NAND_CMD_READSTART]    = CMD_NOT_SUPPORTED,
++      [NAND_CMD_READCACHESEQ] = CMD_NOT_SUPPORTED,
++      [NAND_CMD_READCACHEEND] = CMD_NOT_SUPPORTED,
++      [NAND_CMD_RNDOUTSTART]  = CMD_NULL,
++      [NAND_CMD_CACHEDPROG]   = CMD_NOT_SUPPORTED,
++};
++
+ /* Controller feature flags */
+ enum {
+       BRCMNAND_HAS_1K_SECTORS                 = BIT(0),
+@@ -237,6 +262,12 @@ struct brcmnand_controller {
+       /* List of NAND hosts (one for each chip-select) */
+       struct list_head host_list;
++      /* Functions to be called from exec_op */
++      int (*check_instr)(struct nand_chip *chip,
++                         const struct nand_operation *op);
++      int (*exec_instr)(struct nand_chip *chip,
++                        const struct nand_operation *op);
++
+       /* EDU info, per-transaction */
+       const u16               *edu_offsets;
+       void __iomem            *edu_base;
+@@ -2478,18 +2509,190 @@ static int brcmnand_op_is_reset(const st
+       return 0;
+ }
++static int brcmnand_check_instructions(struct nand_chip *chip,
++                                     const struct nand_operation *op)
++{
++      return 0;
++}
++
++static int brcmnand_exec_instructions(struct nand_chip *chip,
++                                    const struct nand_operation *op)
++{
++      struct brcmnand_host *host = nand_get_controller_data(chip);
++      unsigned int i;
++      int ret = 0;
++
++      for (i = 0; i < op->ninstrs; i++) {
++              ret = brcmnand_exec_instr(host, i, op);
++              if (ret)
++                      break;
++      }
++
++      return ret;
++}
++
++static int brcmnand_check_instructions_legacy(struct nand_chip *chip,
++                                            const struct nand_operation *op)
++{
++      const struct nand_op_instr *instr;
++      unsigned int i;
++      u8 cmd;
++
++      for (i = 0; i < op->ninstrs; i++) {
++              instr = &op->instrs[i];
++
++              switch (instr->type) {
++              case NAND_OP_CMD_INSTR:
++                      cmd = native_cmd_conv[instr->ctx.cmd.opcode];
++                      if (cmd == CMD_NOT_SUPPORTED)
++                              return -EOPNOTSUPP;
++                      break;
++              case NAND_OP_ADDR_INSTR:
++              case NAND_OP_DATA_IN_INSTR:
++              case NAND_OP_WAITRDY_INSTR:
++                      break;
++              default:
++                      return -EOPNOTSUPP;
++              }
++      }
++
++      return 0;
++}
++
++static int brcmnand_exec_instructions_legacy(struct nand_chip *chip,
++                                           const struct nand_operation *op)
++{
++      struct mtd_info *mtd = nand_to_mtd(chip);
++      struct brcmnand_host *host = nand_get_controller_data(chip);
++      struct brcmnand_controller *ctrl = host->ctrl;
++      const struct nand_op_instr *instr;
++      unsigned int i, j;
++      u8 cmd = CMD_NULL, last_cmd = CMD_NULL;
++      int ret = 0;
++      u64 last_addr;
++
++      for (i = 0; i < op->ninstrs; i++) {
++              instr = &op->instrs[i];
++
++              if (instr->type == NAND_OP_CMD_INSTR) {
++                      cmd = native_cmd_conv[instr->ctx.cmd.opcode];
++                      if (cmd == CMD_NOT_SUPPORTED) {
++                              dev_err(ctrl->dev, "unsupported cmd=%d\n",
++                                      instr->ctx.cmd.opcode);
++                              ret = -EOPNOTSUPP;
++                              break;
++                      }
++              } else if (instr->type == NAND_OP_ADDR_INSTR) {
++                      u64 addr = 0;
++
++                      if (cmd == CMD_NULL)
++                              continue;
++
++                      if (instr->ctx.addr.naddrs > 8) {
++                              dev_err(ctrl->dev, "unsupported naddrs=%u\n",
++                                      instr->ctx.addr.naddrs);
++                              ret = -EOPNOTSUPP;
++                              break;
++                      }
++
++                      for (j = 0; j < instr->ctx.addr.naddrs; j++)
++                              addr |= (instr->ctx.addr.addrs[j]) << (j << 3);
++
++                      if (cmd == CMD_BLOCK_ERASE)
++                              addr <<= chip->page_shift;
++                      else if (cmd == CMD_PARAMETER_CHANGE_COL)
++                              addr &= ~((u64)(FC_BYTES - 1));
++
++                      brcmnand_set_cmd_addr(mtd, addr);
++                      brcmnand_send_cmd(host, cmd);
++                      last_addr = addr;
++                      last_cmd = cmd;
++                      cmd = CMD_NULL;
++                      brcmnand_waitfunc(chip);
++
++                      if (last_cmd == CMD_PARAMETER_READ ||
++                          last_cmd == CMD_PARAMETER_CHANGE_COL) {
++                              /* Copy flash cache word-wise */
++                              u32 *flash_cache = (u32 *)ctrl->flash_cache;
++
++                              brcmnand_soc_data_bus_prepare(ctrl->soc, true);
++
++                              /*
++                               * Must cache the FLASH_CACHE now, since changes in
++                               * SECTOR_SIZE_1K may invalidate it
++                               */
++                              for (j = 0; j < FC_WORDS; j++)
++                                      /*
++                                       * Flash cache is big endian for parameter pages, at
++                                       * least on STB SoCs
++                                       */
++                                      flash_cache[j] = be32_to_cpu(brcmnand_read_fc(ctrl, j));
++
++                              brcmnand_soc_data_bus_unprepare(ctrl->soc, true);
++                      }
++              } else if (instr->type == NAND_OP_DATA_IN_INSTR) {
++                      u8 *in = instr->ctx.data.buf.in;
++
++                      if (last_cmd == CMD_DEVICE_ID_READ) {
++                              u32 val;
++
++                              if (instr->ctx.data.len > 8) {
++                                      dev_err(ctrl->dev, "unsupported len=%u\n",
++                                              instr->ctx.data.len);
++                                      ret = -EOPNOTSUPP;
++                                      break;
++                              }
++
++                              for (j = 0; j < instr->ctx.data.len; j++) {
++                                      if (j == 0)
++                                              val = brcmnand_read_reg(ctrl, BRCMNAND_ID);
++                                      else if (j == 4)
++                                              val = brcmnand_read_reg(ctrl, BRCMNAND_ID_EXT);
++
++                                      in[j] = (val >> (24 - ((j % 4) << 3))) & 0xff;
++                              }
++                      } else if (last_cmd == CMD_PARAMETER_READ ||
++                                 last_cmd == CMD_PARAMETER_CHANGE_COL) {
++                              u64 addr;
++                              u32 offs;
++
++                              for (j = 0; j < instr->ctx.data.len; j++) {
++                                      addr = last_addr + j;
++                                      offs = addr & (FC_BYTES - 1);
++
++                                      if (j > 0 && offs == 0)
++                                              nand_change_read_column_op(chip, addr, NULL, 0,
++                                                                         false);
++
++                                      in[j] = ctrl->flash_cache[offs];
++                              }
++                      }
++              } else if (instr->type == NAND_OP_WAITRDY_INSTR) {
++                      ret = bcmnand_ctrl_poll_status(host, NAND_CTRL_RDY, NAND_CTRL_RDY, 0);
++                      if (ret)
++                              break;
++              } else {
++                      dev_err(ctrl->dev, "unsupported instruction type: %d\n", instr->type);
++                      ret = -EOPNOTSUPP;
++                      break;
++              }
++      }
++
++      return ret;
++}
++
+ static int brcmnand_exec_op(struct nand_chip *chip,
+                           const struct nand_operation *op,
+                           bool check_only)
+ {
+       struct brcmnand_host *host = nand_get_controller_data(chip);
++      struct brcmnand_controller *ctrl = host->ctrl;
+       struct mtd_info *mtd = nand_to_mtd(chip);
+       u8 *status;
+-      unsigned int i;
+       int ret = 0;
+       if (check_only)
+-              return 0;
++              return ctrl->check_instr(chip, op);
+       if (brcmnand_op_is_status(op)) {
+               status = op->instrs[1].ctx.data.buf.in;
+@@ -2513,11 +2716,7 @@ static int brcmnand_exec_op(struct nand_
+       if (op->deassert_wp)
+               brcmnand_wp(mtd, 0);
+-      for (i = 0; i < op->ninstrs; i++) {
+-              ret = brcmnand_exec_instr(host, i, op);
+-              if (ret)
+-                      break;
+-      }
++      ret = ctrl->exec_instr(chip, op);
+       if (op->deassert_wp)
+               brcmnand_wp(mtd, 1);
+@@ -3130,6 +3329,15 @@ int brcmnand_probe(struct platform_devic
+       if (ret)
+               goto err;
++      /* Only v5.0+ controllers have low level ops support */
++      if (ctrl->nand_version >= 0x0500) {
++              ctrl->check_instr = brcmnand_check_instructions;
++              ctrl->exec_instr = brcmnand_exec_instructions;
++      } else {
++              ctrl->check_instr = brcmnand_check_instructions_legacy;
++              ctrl->exec_instr = brcmnand_exec_instructions_legacy;
++      }
++
+       /*
+        * Most chips have this cache at a fixed offset within 'nand' block.
+        * Some must specify this region separately.
diff --git a/target/linux/generic/backport-6.12/422-v6.17-spi-spi-qpic-snand-unregister-ECC-engine-on-probe-er.patch b/target/linux/generic/backport-6.12/422-v6.17-spi-spi-qpic-snand-unregister-ECC-engine-on-probe-er.patch
new file mode 100644 (file)
index 0000000..46184b5
--- /dev/null
@@ -0,0 +1,48 @@
+From 1991a458528588ff34e98b6365362560d208710f Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <j4g8y7@gmail.com>
+Date: Wed, 3 Sep 2025 13:56:24 +0200
+Subject: spi: spi-qpic-snand: unregister ECC engine on probe error and device
+ remove
+
+The on-host hardware ECC engine remains registered both when
+the spi_register_controller() function returns with an error
+and also on device removal.
+
+Change the qcom_spi_probe() function to unregister the engine
+on the error path, and add the missing unregistering call to
+qcom_spi_remove() to avoid possible use-after-free issues.
+
+Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface")
+Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
+Message-ID: <20250903-qpic-snand-unregister-ecceng-v1-1-ef5387b0abdc@gmail.com>
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-qpic-snand.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+--- a/drivers/spi/spi-qpic-snand.c
++++ b/drivers/spi/spi-qpic-snand.c
+@@ -1632,11 +1632,13 @@ static int qcom_spi_probe(struct platfor
+       ret = spi_register_controller(ctlr);
+       if (ret) {
+               dev_err(&pdev->dev, "spi_register_controller failed.\n");
+-              goto err_spi_init;
++              goto err_register_controller;
+       }
+       return 0;
++err_register_controller:
++      nand_ecc_unregister_on_host_hw_engine(&snandc->qspi->ecc_eng);
+ err_spi_init:
+       qcom_nandc_unalloc(snandc);
+ err_snand_alloc:
+@@ -1658,7 +1660,7 @@ static void qcom_spi_remove(struct platf
+       struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       spi_unregister_controller(ctlr);
+-
++      nand_ecc_unregister_on_host_hw_engine(&snandc->qspi->ecc_eng);
+       qcom_nandc_unalloc(snandc);
+       clk_disable_unprepare(snandc->aon_clk);
diff --git a/target/linux/generic/backport-6.12/429-01-v6.18-mtd-spinand-fix-direct-mapping-creation-sizes.patch b/target/linux/generic/backport-6.12/429-01-v6.18-mtd-spinand-fix-direct-mapping-creation-sizes.patch
new file mode 100644 (file)
index 0000000..6abc202
--- /dev/null
@@ -0,0 +1,63 @@
+From e4a0cf9f1d90e6888e5373da3314f761024f6c97 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Thu, 18 Sep 2025 00:53:59 +0300
+Subject: mtd: spinand: fix direct mapping creation sizes
+
+Continuous mode is only supported for data reads, thus writing
+requires only single flash page mapping.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/spi/core.c | 14 +++++++-------
+ 1 file changed, 7 insertions(+), 7 deletions(-)
+
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -1036,18 +1036,13 @@ static int spinand_create_dirmap(struct
+                                unsigned int plane)
+ {
+       struct nand_device *nand = spinand_to_nand(spinand);
+-      struct spi_mem_dirmap_info info = {
+-              .length = nanddev_page_size(nand) +
+-                        nanddev_per_page_oobsize(nand),
+-      };
++      struct spi_mem_dirmap_info info = { 0 };
+       struct spi_mem_dirmap_desc *desc;
+-      if (spinand->cont_read_possible)
+-              info.length = nanddev_eraseblock_size(nand);
+-
+       /* The plane number is passed in MSB just above the column address */
+       info.offset = plane << fls(nand->memorg.pagesize);
++      info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
+       info.op_tmpl = *spinand->op_templates.update_cache;
+       desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+                                         spinand->spimem, &info);
+@@ -1056,6 +1051,8 @@ static int spinand_create_dirmap(struct
+       spinand->dirmaps[plane].wdesc = desc;
++      if (spinand->cont_read_possible)
++              info.length = nanddev_eraseblock_size(nand);
+       info.op_tmpl = *spinand->op_templates.read_cache;
+       desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+                                         spinand->spimem, &info);
+@@ -1071,6 +1068,7 @@ static int spinand_create_dirmap(struct
+               return 0;
+       }
++      info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
+       info.op_tmpl = *spinand->op_templates.update_cache;
+       info.op_tmpl.data.ecc = true;
+       desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+@@ -1080,6 +1078,8 @@ static int spinand_create_dirmap(struct
+       spinand->dirmaps[plane].wdesc_ecc = desc;
++      if (spinand->cont_read_possible)
++              info.length = nanddev_eraseblock_size(nand);
+       info.op_tmpl = *spinand->op_templates.read_cache;
+       info.op_tmpl.data.ecc = true;
+       desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
diff --git a/target/linux/generic/backport-6.12/429-02-v6.18-mtd-spinand-try-a-regular-dirmap-if-creating-a-di.patch b/target/linux/generic/backport-6.12/429-02-v6.18-mtd-spinand-try-a-regular-dirmap-if-creating-a-di.patch
new file mode 100644 (file)
index 0000000..cc94e6d
--- /dev/null
@@ -0,0 +1,98 @@
+From 004f8ea0d9917398aabff7388b3bf62a84a4088b Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Thu, 18 Sep 2025 00:54:00 +0300
+Subject: mtd: spinand: try a regular dirmap if creating a dirmap for
+ continuous reading fails
+
+Continuous reading may result in multiple flash pages reading in one
+operation. Typically only one flash page has read/written (a little bit
+more than 2-4 Kb), but continuous reading requires the spi controller
+to read up to 512 Kb in one operation without toggling CS in beetween.
+
+Roughly speaking spi controllers can be divided on 2 categories:
+ * spi controllers without dirmap acceleration support
+ * spi controllers with dirmap acceleration support
+
+Firt of them will have issues with continuous reading if restriction on
+the transfer length is implemented in the adjust_op_size() handler.
+Second group often supports acceleration of single page only reading.
+Thus enabling of continuous reading can break flash reading.
+
+This patch tries to create dirmap for continuous reading first and
+fallback to regular reading if spi controller refuses to create it.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/spi/core.c | 43 ++++++++++++++++++++++++++++++-------
+ 1 file changed, 35 insertions(+), 8 deletions(-)
+
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -1032,6 +1032,39 @@ static int spinand_mtd_block_isreserved(
+       return ret;
+ }
++static struct spi_mem_dirmap_desc *spinand_create_rdesc(
++                                      struct spinand_device *spinand,
++                                      struct spi_mem_dirmap_info *info)
++{
++      struct nand_device *nand = spinand_to_nand(spinand);
++      struct spi_mem_dirmap_desc *desc = NULL;
++
++      if (spinand->cont_read_possible) {
++              /*
++               * spi controller may return an error if info->length is
++               * too large
++               */
++              info->length = nanddev_eraseblock_size(nand);
++              desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
++                                                spinand->spimem, info);
++      }
++
++      if (IS_ERR_OR_NULL(desc)) {
++              /*
++               * continuous reading is not supported by flash or
++               * its spi controller, use regular reading
++               */
++              spinand->cont_read_possible = false;
++
++              info->length = nanddev_page_size(nand) +
++                             nanddev_per_page_oobsize(nand);
++              desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
++                                                spinand->spimem, info);
++      }
++
++      return desc;
++}
++
+ static int spinand_create_dirmap(struct spinand_device *spinand,
+                                unsigned int plane)
+ {
+@@ -1051,11 +1084,8 @@ static int spinand_create_dirmap(struct
+       spinand->dirmaps[plane].wdesc = desc;
+-      if (spinand->cont_read_possible)
+-              info.length = nanddev_eraseblock_size(nand);
+       info.op_tmpl = *spinand->op_templates.read_cache;
+-      desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+-                                        spinand->spimem, &info);
++      desc = spinand_create_rdesc(spinand, &info);
+       if (IS_ERR(desc))
+               return PTR_ERR(desc);
+@@ -1078,12 +1108,9 @@ static int spinand_create_dirmap(struct
+       spinand->dirmaps[plane].wdesc_ecc = desc;
+-      if (spinand->cont_read_possible)
+-              info.length = nanddev_eraseblock_size(nand);
+       info.op_tmpl = *spinand->op_templates.read_cache;
+       info.op_tmpl.data.ecc = true;
+-      desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+-                                        spinand->spimem, &info);
++      desc = spinand_create_rdesc(spinand, &info);
+       if (IS_ERR(desc))
+               return PTR_ERR(desc);
diff --git a/target/linux/generic/backport-6.12/429-03-v6.18-mtd-spinand-repeat-reading-in-regular-mode-if-con.patch b/target/linux/generic/backport-6.12/429-03-v6.18-mtd-spinand-repeat-reading-in-regular-mode-if-con.patch
new file mode 100644 (file)
index 0000000..38d5712
--- /dev/null
@@ -0,0 +1,64 @@
+From 010dc7f2dd6a0078ade3f88f627ed5fbf45ceb94 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Thu, 18 Sep 2025 00:54:01 +0300
+Subject: mtd: spinand: repeat reading in regular mode if continuous reading
+ fails
+
+Continuous reading may result in multiple flash pages reading in one
+operation. Unfortunately, not all spinand controllers support such
+large reading. They will read less data. Unfortunately, the operation
+can't be continued.
+
+In this case:
+ * disable continuous reading on this (not good enough) spi controller
+ * repeat reading in regular mode.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/spi/core.c | 25 +++++++++++++++++++++----
+ 1 file changed, 21 insertions(+), 4 deletions(-)
+
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -427,8 +427,16 @@ static int spinand_read_from_cache_op(st
+                * Dirmap accesses are allowed to toggle the CS.
+                * Toggling the CS during a continuous read is forbidden.
+                */
+-              if (nbytes && req->continuous)
+-                      return -EIO;
++              if (nbytes && req->continuous) {
++                      /*
++                       * Spi controller with broken support of continuous
++                       * reading was detected. Disable future use of
++                       * continuous reading and return -EAGAIN to retry
++                       * reading within regular mode.
++                       */
++                      spinand->cont_read_possible = false;
++                      return -EAGAIN;
++              }
+       }
+       if (req->datalen)
+@@ -849,10 +857,19 @@ static int spinand_mtd_read(struct mtd_i
+       old_stats = mtd->ecc_stats;
+-      if (spinand_use_cont_read(mtd, from, ops))
++      if (spinand_use_cont_read(mtd, from, ops)) {
+               ret = spinand_mtd_continuous_page_read(mtd, from, ops, &max_bitflips);
+-      else
++              if (ret == -EAGAIN && !spinand->cont_read_possible) {
++                      /*
++                       * Spi controller with broken support of continuous
++                       * reading was detected (see spinand_read_from_cache_op()),
++                       * repeat reading in regular mode.
++                       */
++                      ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips);
++              }
++      } else {
+               ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips);
++      }
+       if (ops->stats) {
+               ops->stats->uncorrectable_errors +=
diff --git a/target/linux/generic/backport-6.12/430-v6.14-mtd-spinand-Introduce-a-way-to-avoid-raw-access.patch b/target/linux/generic/backport-6.12/430-v6.14-mtd-spinand-Introduce-a-way-to-avoid-raw-access.patch
new file mode 100644 (file)
index 0000000..094d89b
--- /dev/null
@@ -0,0 +1,81 @@
+From 6d9d6ab3a82af50e36e13e7bc8e2d1b970e39f79 Mon Sep 17 00:00:00 2001
+From: Takahiro Kuwano <Takahiro.Kuwano@infineon.com>
+Date: Tue, 3 Dec 2024 11:46:49 +0900
+Subject: [PATCH 1/1] mtd: spinand: Introduce a way to avoid raw access
+
+SkyHigh spinand device has ECC enable bit in configuration register but
+it must be always enabled. If ECC is disabled, read and write ops
+results in undetermined state. For such devices, a way to avoid raw
+access is needed.
+
+Introduce SPINAND_NO_RAW_ACCESS flag to advertise the device does not
+support raw access. In such devices, the on-die ECC engine ops returns
+error to I/O request in raw mode.
+
+Checking and marking BBM need to be cared as special case, by adding
+fallback mechanism that tries read/write OOB with ECC enabled.
+
+Signed-off-by: Takahiro Kuwano <Takahiro.Kuwano@infineon.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/spi/core.c | 22 ++++++++++++++++++++--
+ include/linux/mtd/spinand.h |  1 +
+ 2 files changed, 21 insertions(+), 2 deletions(-)
+
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -294,6 +294,9 @@ static int spinand_ondie_ecc_prepare_io_
+       struct spinand_device *spinand = nand_to_spinand(nand);
+       bool enable = (req->mode != MTD_OPS_RAW);
++      if (!enable && spinand->flags & SPINAND_NO_RAW_ACCESS)
++              return -EOPNOTSUPP;
++
+       memset(spinand->oobbuf, 0xff, nanddev_per_page_oobsize(nand));
+       /* Only enable or disable the engine */
+@@ -929,9 +932,17 @@ static bool spinand_isbad(struct nand_de
+               .oobbuf.in = marker,
+               .mode = MTD_OPS_RAW,
+       };
++      int ret;
+       spinand_select_target(spinand, pos->target);
+-      spinand_read_page(spinand, &req);
++
++      ret = spinand_read_page(spinand, &req);
++      if (ret == -EOPNOTSUPP) {
++              /* Retry with ECC in case raw access is not supported */
++              req.mode = MTD_OPS_PLACE_OOB;
++              spinand_read_page(spinand, &req);
++      }
++
+       if (marker[0] != 0xff || marker[1] != 0xff)
+               return true;
+@@ -974,7 +985,14 @@ static int spinand_markbad(struct nand_d
+       if (ret)
+               return ret;
+-      return spinand_write_page(spinand, &req);
++      ret = spinand_write_page(spinand, &req);
++      if (ret == -EOPNOTSUPP) {
++              /* Retry with ECC in case raw access is not supported */
++              req.mode = MTD_OPS_PLACE_OOB;
++              ret = spinand_write_page(spinand, &req);
++      }
++
++      return ret;
+ }
+ static int spinand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs)
+--- a/include/linux/mtd/spinand.h
++++ b/include/linux/mtd/spinand.h
+@@ -315,6 +315,7 @@ struct spinand_ecc_info {
+ #define SPINAND_HAS_CR_FEAT_BIT               BIT(1)
+ #define SPINAND_HAS_PROG_PLANE_SELECT_BIT             BIT(2)
+ #define SPINAND_HAS_READ_PLANE_SELECT_BIT             BIT(3)
++#define SPINAND_NO_RAW_ACCESS                         BIT(4)
+ /**
+  * struct spinand_ondie_ecc_conf - private SPI-NAND on-die ECC engine structure
diff --git a/target/linux/generic/backport-6.12/431-v6.14-mtd-spinand-Add-support-for-SkyHigh-S35ML-3-family.patch b/target/linux/generic/backport-6.12/431-v6.14-mtd-spinand-Add-support-for-SkyHigh-S35ML-3-family.patch
new file mode 100644 (file)
index 0000000..762978d
--- /dev/null
@@ -0,0 +1,201 @@
+From 1a50e3612de9187857f55ee14a573f7f8e7d4ebc Mon Sep 17 00:00:00 2001
+From: Takahiro Kuwano <Takahiro.Kuwano@infineon.com>
+Date: Tue, 3 Dec 2024 11:46:50 +0900
+Subject: [PATCH] mtd: spinand: Add support for SkyHigh S35ML-3 family
+
+SkyHigh S35ML01G300, S35ML01G301, S35ML02G300, and S35ML04G300 are 1Gb,
+2Gb, and 4Gb SLC SPI NAND flash family. This family of devices has
+on-die ECC which parity bits are stored to hidden area. In this family
+the on-die ECC cannot be disabled so raw access needs to be prevented.
+
+Link: https://www.skyhighmemory.com/download/SPI_S35ML01_04G3_002_19205.pdf?v=P
+Co-developed-by: KR Kim <kr.kim@skyhighmemory.com>
+Signed-off-by: KR Kim <kr.kim@skyhighmemory.com>
+Signed-off-by: Takahiro Kuwano <Takahiro.Kuwano@infineon.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/spi/Makefile  |   2 +-
+ drivers/mtd/nand/spi/core.c    |   1 +
+ drivers/mtd/nand/spi/skyhigh.c | 147 +++++++++++++++++++++++++++++++++
+ include/linux/mtd/spinand.h    |   1 +
+ 4 files changed, 150 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/mtd/nand/spi/skyhigh.c
+
+--- a/drivers/mtd/nand/spi/Makefile
++++ b/drivers/mtd/nand/spi/Makefile
+@@ -1,4 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0
+ spinand-objs := core.o alliancememory.o ato.o esmt.o fmsh.o foresee.o gigadevice.o macronix.o
+-spinand-objs += micron.o paragon.o toshiba.o winbond.o xtx.o
++spinand-objs += micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
+ obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -1191,6 +1191,7 @@ static const struct spinand_manufacturer
+       &macronix_spinand_manufacturer,
+       &micron_spinand_manufacturer,
+       &paragon_spinand_manufacturer,
++      &skyhigh_spinand_manufacturer,
+       &toshiba_spinand_manufacturer,
+       &winbond_spinand_manufacturer,
+       &xtx_spinand_manufacturer,
+--- /dev/null
++++ b/drivers/mtd/nand/spi/skyhigh.c
+@@ -0,0 +1,147 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (c) 2024 SkyHigh Memory Limited
++ *
++ * Author: Takahiro Kuwano <takahiro.kuwano@infineon.com>
++ * Co-Author: KR Kim <kr.kim@skyhighmemory.com>
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include <linux/mtd/spinand.h>
++
++#define SPINAND_MFR_SKYHIGH                   0x01
++#define SKYHIGH_STATUS_ECC_1TO2_BITFLIPS      (1 << 4)
++#define SKYHIGH_STATUS_ECC_3TO6_BITFLIPS      (2 << 4)
++#define SKYHIGH_STATUS_ECC_UNCOR_ERROR                (3 << 4)
++#define SKYHIGH_CONFIG_PROTECT_EN             BIT(1)
++
++static SPINAND_OP_VARIANTS(read_cache_variants,
++              SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 4, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 2, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
++
++static SPINAND_OP_VARIANTS(write_cache_variants,
++              SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
++              SPINAND_PROG_LOAD(true, 0, NULL, 0));
++
++static SPINAND_OP_VARIANTS(update_cache_variants,
++              SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
++              SPINAND_PROG_LOAD(false, 0, NULL, 0));
++
++static int skyhigh_spinand_ooblayout_ecc(struct mtd_info *mtd, int section,
++                                       struct mtd_oob_region *region)
++{
++      /* ECC bytes are stored in hidden area. */
++      return -ERANGE;
++}
++
++static int skyhigh_spinand_ooblayout_free(struct mtd_info *mtd, int section,
++                                        struct mtd_oob_region *region)
++{
++      if (section)
++              return -ERANGE;
++
++      /* ECC bytes are stored in hidden area. Reserve 2 bytes for the BBM. */
++      region->offset = 2;
++      region->length = mtd->oobsize - 2;
++
++      return 0;
++}
++
++static const struct mtd_ooblayout_ops skyhigh_spinand_ooblayout = {
++      .ecc = skyhigh_spinand_ooblayout_ecc,
++      .free = skyhigh_spinand_ooblayout_free,
++};
++
++static int skyhigh_spinand_ecc_get_status(struct spinand_device *spinand,
++                                        u8 status)
++{
++      switch (status & STATUS_ECC_MASK) {
++      case STATUS_ECC_NO_BITFLIPS:
++              return 0;
++
++      case SKYHIGH_STATUS_ECC_UNCOR_ERROR:
++              return -EBADMSG;
++
++      case SKYHIGH_STATUS_ECC_1TO2_BITFLIPS:
++              return 2;
++
++      case SKYHIGH_STATUS_ECC_3TO6_BITFLIPS:
++              return 6;
++
++      default:
++              break;
++      }
++
++      return -EINVAL;
++}
++
++static const struct spinand_info skyhigh_spinand_table[] = {
++      SPINAND_INFO("S35ML01G301",
++                   SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x15),
++                   NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
++                   NAND_ECCREQ(6, 32),
++                   SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++                                            &write_cache_variants,
++                                            &update_cache_variants),
++                   SPINAND_NO_RAW_ACCESS,
++                   SPINAND_ECCINFO(&skyhigh_spinand_ooblayout,
++                                   skyhigh_spinand_ecc_get_status)),
++      SPINAND_INFO("S35ML01G300",
++                   SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14),
++                   NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
++                   NAND_ECCREQ(6, 32),
++                   SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++                                            &write_cache_variants,
++                                            &update_cache_variants),
++                   SPINAND_NO_RAW_ACCESS,
++                   SPINAND_ECCINFO(&skyhigh_spinand_ooblayout,
++                                   skyhigh_spinand_ecc_get_status)),
++      SPINAND_INFO("S35ML02G300",
++                   SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x25),
++                   NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1),
++                   NAND_ECCREQ(6, 32),
++                   SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++                                            &write_cache_variants,
++                                            &update_cache_variants),
++                   SPINAND_NO_RAW_ACCESS,
++                   SPINAND_ECCINFO(&skyhigh_spinand_ooblayout,
++                                   skyhigh_spinand_ecc_get_status)),
++      SPINAND_INFO("S35ML04G300",
++                   SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35),
++                   NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 2, 1, 1),
++                   NAND_ECCREQ(6, 32),
++                   SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++                                            &write_cache_variants,
++                                            &update_cache_variants),
++                   SPINAND_NO_RAW_ACCESS,
++                   SPINAND_ECCINFO(&skyhigh_spinand_ooblayout,
++                                   skyhigh_spinand_ecc_get_status)),
++};
++
++static int skyhigh_spinand_init(struct spinand_device *spinand)
++{
++      /*
++       * Config_Protect_En (bit 1 in Block Lock register) must be set to 1
++       * before writing other bits. Do it here before core unlocks all blocks
++       * by writing block protection bits.
++       */
++      return spinand_write_reg_op(spinand, REG_BLOCK_LOCK,
++                                  SKYHIGH_CONFIG_PROTECT_EN);
++}
++
++static const struct spinand_manufacturer_ops skyhigh_spinand_manuf_ops = {
++      .init = skyhigh_spinand_init,
++};
++
++const struct spinand_manufacturer skyhigh_spinand_manufacturer = {
++      .id = SPINAND_MFR_SKYHIGH,
++      .name = "SkyHigh",
++      .chips = skyhigh_spinand_table,
++      .nchips = ARRAY_SIZE(skyhigh_spinand_table),
++      .ops = &skyhigh_spinand_manuf_ops,
++};
+--- a/include/linux/mtd/spinand.h
++++ b/include/linux/mtd/spinand.h
+@@ -269,6 +269,7 @@ extern const struct spinand_manufacturer
+ extern const struct spinand_manufacturer macronix_spinand_manufacturer;
+ extern const struct spinand_manufacturer micron_spinand_manufacturer;
+ extern const struct spinand_manufacturer paragon_spinand_manufacturer;
++extern const struct spinand_manufacturer skyhigh_spinand_manufacturer;
+ extern const struct spinand_manufacturer toshiba_spinand_manufacturer;
+ extern const struct spinand_manufacturer winbond_spinand_manufacturer;
+ extern const struct spinand_manufacturer xtx_spinand_manufacturer;
diff --git a/target/linux/generic/backport-6.12/434-v6.19-mtd-spinand-esmt-add-support-for-F50L1G41LC.patch b/target/linux/generic/backport-6.12/434-v6.19-mtd-spinand-esmt-add-support-for-F50L1G41LC.patch
new file mode 100644 (file)
index 0000000..ebd9eb5
--- /dev/null
@@ -0,0 +1,84 @@
+From b98994cb9bc24f5c7575c86650f96c384576fdfa Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 17 Nov 2025 02:54:19 +0000
+Subject: [PATCH] mtd: spinand: esmt: add support for F50L1G41LC
+
+This adds support for ESMT F50L1G41LC, which appears to be an updated
+version of the already supported F50L1G41LB.
+Add esmt_8c SPI_NAND manufacturer to account for the newly used vendor
+ID with support for the ESMT F50L1G41LC chip.
+
+Link: https://github.com/openwrt/openwrt/pull/15214#issuecomment-3514824435
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/spi/core.c |  1 +
+ drivers/mtd/nand/spi/esmt.c | 24 ++++++++++++++++++++++++
+ include/linux/mtd/spinand.h |  1 +
+ 3 files changed, 26 insertions(+)
+
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -1184,6 +1184,7 @@ static const struct nand_ops spinand_ops
+ static const struct spinand_manufacturer *spinand_manufacturers[] = {
+       &alliancememory_spinand_manufacturer,
+       &ato_spinand_manufacturer,
++      &esmt_8c_spinand_manufacturer,
+       &esmt_c8_spinand_manufacturer,
+       &fmsh_spinand_manufacturer,
+       &foresee_spinand_manufacturer,
+--- a/drivers/mtd/nand/spi/esmt.c
++++ b/drivers/mtd/nand/spi/esmt.c
+@@ -11,6 +11,7 @@
+ /* ESMT uses GigaDevice 0xc8 JECDEC ID on some SPI NANDs */
+ #define SPINAND_MFR_ESMT_C8                   0xc8
++#define SPINAND_MFR_ESMT_8C                   0x8c
+ static SPINAND_OP_VARIANTS(read_cache_variants,
+                          SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
+@@ -102,6 +103,19 @@ static const struct mtd_ooblayout_ops f5
+       .free = f50l1g41lb_ooblayout_free,
+ };
++
++static const struct spinand_info esmt_8c_spinand_table[] = {
++      SPINAND_INFO("F50L1G41LC",
++                   SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x2C),
++                   NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
++                   NAND_ECCREQ(1, 512),
++                   SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++                                            &write_cache_variants,
++                                            &update_cache_variants),
++                   0,
++                   SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL)),
++};
++
+ static const struct spinand_info esmt_c8_spinand_table[] = {
+       SPINAND_INFO("F50L1G41LB",
+                    SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x01, 0x7f,
+@@ -138,6 +152,14 @@ static const struct spinand_info esmt_c8
+ static const struct spinand_manufacturer_ops esmt_spinand_manuf_ops = {
+ };
++const struct spinand_manufacturer esmt_8c_spinand_manufacturer = {
++      .id = SPINAND_MFR_ESMT_8C,
++      .name = "ESMT",
++      .chips = esmt_8c_spinand_table,
++      .nchips = ARRAY_SIZE(esmt_8c_spinand_table),
++      .ops = &esmt_spinand_manuf_ops,
++};
++
+ const struct spinand_manufacturer esmt_c8_spinand_manufacturer = {
+       .id = SPINAND_MFR_ESMT_C8,
+       .name = "ESMT",
+--- a/include/linux/mtd/spinand.h
++++ b/include/linux/mtd/spinand.h
+@@ -262,6 +262,7 @@ struct spinand_manufacturer {
+ /* SPI NAND manufacturers */
+ extern const struct spinand_manufacturer alliancememory_spinand_manufacturer;
+ extern const struct spinand_manufacturer ato_spinand_manufacturer;
++extern const struct spinand_manufacturer esmt_8c_spinand_manufacturer;
+ extern const struct spinand_manufacturer esmt_c8_spinand_manufacturer;
+ extern const struct spinand_manufacturer fmsh_spinand_manufacturer;
+ extern const struct spinand_manufacturer foresee_spinand_manufacturer;
diff --git a/target/linux/generic/backport-6.12/435-v6.19-mtd-spinand-add-support-for-FudanMicro-FM25S01BI3.patch b/target/linux/generic/backport-6.12/435-v6.19-mtd-spinand-add-support-for-FudanMicro-FM25S01BI3.patch
new file mode 100644 (file)
index 0000000..7355efe
--- /dev/null
@@ -0,0 +1,115 @@
+From f6dffe2a9ed1bdcee1879e2728310fb1e08602cf Mon Sep 17 00:00:00 2001
+From: Mikhail Zhilkin <csharper2005@gmail.com>
+Date: Thu, 27 Nov 2025 22:59:00 +0300
+Subject: mtd: spinand: add support for FudanMicro FM25S01BI3
+
+Add support for FudanMicro FM25S01BI3 SPI NAND.
+
+Link: https://www.fmsh.com/nvm/FM25S01BI3_ds_eng.pdf
+
+Signed-off-by: Mikhail Zhilkin <csharper2005@gmail.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/nand/spi/fmsh.c | 72 +++++++++++++++++++++++++++++++++++++
+ 1 file changed, 72 insertions(+)
+
+--- a/drivers/mtd/nand/spi/fmsh.c
++++ b/drivers/mtd/nand/spi/fmsh.c
+@@ -9,6 +9,13 @@
+ #include <linux/kernel.h>
+ #include <linux/mtd/spinand.h>
++#define FM25S01BI3_STATUS_ECC_MASK            (7 << 4)
++      #define FM25S01BI3_STATUS_ECC_NO_BITFLIPS       (0 << 4)
++      #define FM25S01BI3_STATUS_ECC_1_3_BITFLIPS      (1 << 4)
++      #define FM25S01BI3_STATUS_ECC_UNCOR_ERROR       (2 << 4)
++      #define FM25S01BI3_STATUS_ECC_4_6_BITFLIPS      (3 << 4)
++      #define FM25S01BI3_STATUS_ECC_7_8_BITFLIPS      (5 << 4)
++
+ #define SPINAND_MFR_FMSH              0xA1
+ static SPINAND_OP_VARIANTS(read_cache_variants,
+@@ -45,11 +52,66 @@ static int fm25s01a_ooblayout_free(struc
+       return 0;
+ }
++static int fm25s01bi3_ecc_get_status(struct spinand_device *spinand,
++                                   u8 status)
++{
++      switch (status & FM25S01BI3_STATUS_ECC_MASK) {
++      case FM25S01BI3_STATUS_ECC_NO_BITFLIPS:
++              return 0;
++
++      case FM25S01BI3_STATUS_ECC_UNCOR_ERROR:
++              return -EBADMSG;
++
++      case FM25S01BI3_STATUS_ECC_1_3_BITFLIPS:
++              return 3;
++
++      case FM25S01BI3_STATUS_ECC_4_6_BITFLIPS:
++              return 6;
++
++      case FM25S01BI3_STATUS_ECC_7_8_BITFLIPS:
++              return 8;
++
++      default:
++              break;
++      }
++
++      return -EINVAL;
++}
++
++static int fm25s01bi3_ooblayout_ecc(struct mtd_info *mtd, int section,
++                                  struct mtd_oob_region *region)
++{
++      if (section)
++              return -ERANGE;
++
++      region->offset = 64;
++      region->length = 64;
++
++      return 0;
++}
++
++static int fm25s01bi3_ooblayout_free(struct mtd_info *mtd, int section,
++                                   struct mtd_oob_region *region)
++{
++      if (section > 3)
++              return -ERANGE;
++
++      region->offset = (16 * section) + 4;
++      region->length = 12;
++
++      return 0;
++}
++
+ static const struct mtd_ooblayout_ops fm25s01a_ooblayout = {
+       .ecc = fm25s01a_ooblayout_ecc,
+       .free = fm25s01a_ooblayout_free,
+ };
++static const struct mtd_ooblayout_ops fm25s01bi3_ooblayout = {
++      .ecc = fm25s01bi3_ooblayout_ecc,
++      .free = fm25s01bi3_ooblayout_free,
++};
++
+ static const struct spinand_info fmsh_spinand_table[] = {
+       SPINAND_INFO("FM25S01A",
+                    SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE4),
+@@ -60,6 +122,16 @@ static const struct spinand_info fmsh_sp
+                                             &update_cache_variants),
+                    0,
+                    SPINAND_ECCINFO(&fm25s01a_ooblayout, NULL)),
++      SPINAND_INFO("FM25S01BI3",
++                   SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xd4),
++                   NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
++                   NAND_ECCREQ(8, 512),
++                   SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++                                            &write_cache_variants,
++                                            &update_cache_variants),
++                   SPINAND_HAS_QE_BIT,
++                   SPINAND_ECCINFO(&fm25s01bi3_ooblayout,
++                                    fm25s01bi3_ecc_get_status)),
+ };
+ static const struct spinand_manufacturer_ops fmsh_spinand_manuf_ops = {
diff --git a/target/linux/generic/backport-6.12/500-01-v6.13-block-add-support-for-defining-read-only-partitions.patch b/target/linux/generic/backport-6.12/500-01-v6.13-block-add-support-for-defining-read-only-partitions.patch
new file mode 100644 (file)
index 0000000..cdf505f
--- /dev/null
@@ -0,0 +1,53 @@
+From 03cb793b26834ddca170ba87057c8f883772dd45 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 3 Oct 2024 00:11:41 +0200
+Subject: [PATCH 1/5] block: add support for defining read-only partitions
+
+Add support for defining read-only partitions and complete support for
+it in the cmdline partition parser as the additional "ro" after a
+partition is scanned but never actually applied.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Link: https://lore.kernel.org/r/20241002221306.4403-2-ansuelsmth@gmail.com
+Signed-off-by: Jens Axboe <axboe@kernel.dk>
+---
+ block/blk.h                | 1 +
+ block/partitions/cmdline.c | 3 +++
+ block/partitions/core.c    | 3 +++
+ 3 files changed, 7 insertions(+)
+
+--- a/block/blk.h
++++ b/block/blk.h
+@@ -574,6 +574,7 @@ void blk_free_ext_minor(unsigned int min
+ #define ADDPART_FLAG_NONE     0
+ #define ADDPART_FLAG_RAID     1
+ #define ADDPART_FLAG_WHOLEDISK        2
++#define ADDPART_FLAG_READONLY 4
+ int bdev_add_partition(struct gendisk *disk, int partno, sector_t start,
+               sector_t length);
+ int bdev_del_partition(struct gendisk *disk, int partno);
+--- a/block/partitions/cmdline.c
++++ b/block/partitions/cmdline.c
+@@ -237,6 +237,9 @@ static int add_part(int slot, struct cmd
+       put_partition(state, slot, subpart->from >> 9,
+                     subpart->size >> 9);
++      if (subpart->flags & PF_RDONLY)
++              state->parts[slot].flags |= ADDPART_FLAG_READONLY;
++
+       info = &state->parts[slot].info;
+       strscpy(info->volname, subpart->name, sizeof(info->volname));
+--- a/block/partitions/core.c
++++ b/block/partitions/core.c
+@@ -373,6 +373,9 @@ static struct block_device *add_partitio
+                       goto out_del;
+       }
++      if (flags & ADDPART_FLAG_READONLY)
++              bdev_set_flag(bdev, BD_READ_ONLY);
++
+       /* everything is up and running, commence */
+       err = xa_insert(&disk->part_tbl, partno, bdev, GFP_KERNEL);
+       if (err)
diff --git a/target/linux/generic/backport-6.12/500-02-v6.13-block-introduce-add_disk_fwnode.patch b/target/linux/generic/backport-6.12/500-02-v6.13-block-introduce-add_disk_fwnode.patch
new file mode 100644 (file)
index 0000000..381838d
--- /dev/null
@@ -0,0 +1,94 @@
+From e5f587242b6072ffab4f4a084a459a59f3035873 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 3 Oct 2024 00:11:43 +0200
+Subject: [PATCH 3/5] block: introduce add_disk_fwnode()
+
+Introduce add_disk_fwnode() as a replacement of device_add_disk() that
+permits to pass and attach a fwnode to disk dev.
+
+This variant can be useful for eMMC that might have the partition table
+for the disk defined in DT. A parser can later make use of the attached
+fwnode to parse the related table and init the hardcoded partition for
+the disk.
+
+device_add_disk() is converted to a simple wrapper of add_disk_fwnode()
+with the fwnode entry set as NULL.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Link: https://lore.kernel.org/r/20241002221306.4403-4-ansuelsmth@gmail.com
+Signed-off-by: Jens Axboe <axboe@kernel.dk>
+---
+ block/genhd.c          | 28 ++++++++++++++++++++++++----
+ include/linux/blkdev.h |  3 +++
+ 2 files changed, 27 insertions(+), 4 deletions(-)
+
+--- a/block/genhd.c
++++ b/block/genhd.c
+@@ -383,16 +383,18 @@ int disk_scan_partitions(struct gendisk
+ }
+ /**
+- * device_add_disk - add disk information to kernel list
++ * add_disk_fwnode - add disk information to kernel list with fwnode
+  * @parent: parent device for the disk
+  * @disk: per-device partitioning information
+  * @groups: Additional per-device sysfs groups
++ * @fwnode: attached disk fwnode
+  *
+  * This function registers the partitioning information in @disk
+- * with the kernel.
++ * with the kernel. Also attach a fwnode to the disk device.
+  */
+-int __must_check device_add_disk(struct device *parent, struct gendisk *disk,
+-                               const struct attribute_group **groups)
++int __must_check add_disk_fwnode(struct device *parent, struct gendisk *disk,
++                               const struct attribute_group **groups,
++                               struct fwnode_handle *fwnode)
+ {
+       struct device *ddev = disk_to_dev(disk);
+@@ -452,6 +454,8 @@ int __must_check device_add_disk(struct
+       ddev->parent = parent;
+       ddev->groups = groups;
+       dev_set_name(ddev, "%s", disk->disk_name);
++      if (fwnode)
++              device_set_node(ddev, fwnode);
+       if (!(disk->flags & GENHD_FL_HIDDEN))
+               ddev->devt = MKDEV(disk->major, disk->first_minor);
+       ret = device_add(ddev);
+@@ -553,6 +557,22 @@ out_exit_elevator:
+               elevator_exit(disk->queue);
+       return ret;
+ }
++EXPORT_SYMBOL_GPL(add_disk_fwnode);
++
++/**
++ * device_add_disk - add disk information to kernel list
++ * @parent: parent device for the disk
++ * @disk: per-device partitioning information
++ * @groups: Additional per-device sysfs groups
++ *
++ * This function registers the partitioning information in @disk
++ * with the kernel.
++ */
++int __must_check device_add_disk(struct device *parent, struct gendisk *disk,
++                               const struct attribute_group **groups)
++{
++      return add_disk_fwnode(parent, disk, groups, NULL);
++}
+ EXPORT_SYMBOL(device_add_disk);
+ static void blk_report_disk_dead(struct gendisk *disk, bool surprise)
+--- a/include/linux/blkdev.h
++++ b/include/linux/blkdev.h
+@@ -789,6 +789,9 @@ static inline unsigned int blk_queue_dep
+ #define for_each_bio(_bio)            \
+       for (; _bio; _bio = _bio->bi_next)
++int __must_check add_disk_fwnode(struct device *parent, struct gendisk *disk,
++                               const struct attribute_group **groups,
++                               struct fwnode_handle *fwnode);
+ int __must_check device_add_disk(struct device *parent, struct gendisk *disk,
+                                const struct attribute_group **groups);
+ static inline int __must_check add_disk(struct gendisk *disk)
diff --git a/target/linux/generic/backport-6.12/500-03-v6.13-mmc-block-attach-partitions-fwnode-if-found-in-mmc-c.patch b/target/linux/generic/backport-6.12/500-03-v6.13-mmc-block-attach-partitions-fwnode-if-found-in-mmc-c.patch
new file mode 100644 (file)
index 0000000..0bdeaa8
--- /dev/null
@@ -0,0 +1,104 @@
+From 45ff6c340ddfc2dade74d5b7a8962c778ab7042c Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 3 Oct 2024 00:11:44 +0200
+Subject: [PATCH 4/5] mmc: block: attach partitions fwnode if found in mmc-card
+
+Attach partitions fwnode if found in mmc-card and register disk with it.
+
+This permits block partition to reference the node and register a
+partition table defined in DT for the special case for embedded device
+that doesn't have a partition table flashed but have an hardcoded
+partition table passed from the system.
+
+JEDEC BOOT partition boot0/boot1 are supported but in DT we refer with
+the JEDEC name of boot1 and boot2 to better adhere to documentation.
+
+Also JEDEC GP partition gp0/1/2/3 are supported but in DT we refer with
+the JEDEC name of gp1/2/3/4 to better adhere to documentration.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Link: https://lore.kernel.org/r/20241002221306.4403-5-ansuelsmth@gmail.com
+Signed-off-by: Jens Axboe <axboe@kernel.dk>
+---
+ drivers/mmc/core/block.c | 55 +++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 54 insertions(+), 1 deletion(-)
+
+--- a/drivers/mmc/core/block.c
++++ b/drivers/mmc/core/block.c
+@@ -2517,6 +2517,56 @@ static inline int mmc_blk_readonly(struc
+              !(card->csd.cmdclass & CCC_BLOCK_WRITE);
+ }
++/*
++ * Search for a declared partitions node for the disk in mmc-card related node.
++ *
++ * This is to permit support for partition table defined in DT in special case
++ * where a partition table is not written in the disk and is expected to be
++ * passed from the running system.
++ *
++ * For the user disk, "partitions" node is searched.
++ * For the special HW disk, "partitions-" node with the appended name is used
++ * following this conversion table (to adhere to JEDEC naming)
++ * - boot0 -> partitions-boot1
++ * - boot1 -> partitions-boot2
++ * - gp0 -> partitions-gp1
++ * - gp1 -> partitions-gp2
++ * - gp2 -> partitions-gp3
++ * - gp3 -> partitions-gp4
++ */
++static struct fwnode_handle *mmc_blk_get_partitions_node(struct device *mmc_dev,
++                                                       const char *subname)
++{
++      const char *node_name = "partitions";
++
++      if (subname) {
++              mmc_dev = mmc_dev->parent;
++
++              /*
++               * Check if we are allocating a BOOT disk boot0/1 disk.
++               * In DT we use the JEDEC naming boot1/2.
++               */
++              if (!strcmp(subname, "boot0"))
++                      node_name = "partitions-boot1";
++              if (!strcmp(subname, "boot1"))
++                      node_name = "partitions-boot2";
++              /*
++               * Check if we are allocating a GP disk gp0/1/2/3 disk.
++               * In DT we use the JEDEC naming gp1/2/3/4.
++               */
++              if (!strcmp(subname, "gp0"))
++                      node_name = "partitions-gp1";
++              if (!strcmp(subname, "gp1"))
++                      node_name = "partitions-gp2";
++              if (!strcmp(subname, "gp2"))
++                      node_name = "partitions-gp3";
++              if (!strcmp(subname, "gp3"))
++                      node_name = "partitions-gp4";
++      }
++
++      return device_get_named_child_node(mmc_dev, node_name);
++}
++
+ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
+                                             struct device *parent,
+                                             sector_t size,
+@@ -2525,6 +2575,7 @@ static struct mmc_blk_data *mmc_blk_allo
+                                             int area_type,
+                                             unsigned int part_type)
+ {
++      struct fwnode_handle *disk_fwnode;
+       struct mmc_blk_data *md;
+       int devidx, ret;
+       char cap_str[10];
+@@ -2626,7 +2677,9 @@ static struct mmc_blk_data *mmc_blk_allo
+       /* used in ->open, must be set before add_disk: */
+       if (area_type == MMC_BLK_DATA_AREA_MAIN)
+               dev_set_drvdata(&card->dev, md);
+-      ret = device_add_disk(md->parent, md->disk, mmc_disk_attr_groups);
++      disk_fwnode = mmc_blk_get_partitions_node(parent, subname);
++      ret = add_disk_fwnode(md->parent, md->disk, mmc_disk_attr_groups,
++                            disk_fwnode);
+       if (ret)
+               goto err_put_disk;
+       return md;
diff --git a/target/linux/generic/backport-6.12/500-04-v6.13-block-add-support-for-partition-table-defined-in-OF.patch b/target/linux/generic/backport-6.12/500-04-v6.13-block-add-support-for-partition-table-defined-in-OF.patch
new file mode 100644 (file)
index 0000000..d260be1
--- /dev/null
@@ -0,0 +1,200 @@
+From 884555b557e5e6d41c866e2cd8d7b32f50ec974b Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 3 Oct 2024 00:11:45 +0200
+Subject: [PATCH 5/5] block: add support for partition table defined in OF
+
+Add support for partition table defined in Device Tree. Similar to how
+it's done with MTD, add support for defining a fixed partition table in
+device tree.
+
+A common scenario for this is fixed block (eMMC) embedded devices that
+have no MBR or GPT partition table to save storage space. Bootloader
+access the block device with absolute address of data.
+
+This is to complete the functionality with an equivalent implementation
+with providing partition table with bootargs, for case where the booargs
+can't be modified and tweaking the Device Tree is the only solution to
+have an usabe partition table.
+
+The implementation follow the fixed-partitions parser used on MTD
+devices where a "partitions" node is expected to be declared with
+"fixed-partitions" compatible in the OF node of the disk device
+(mmc-card for eMMC for example) and each child node declare a label
+and a reg with offset and size. If label is not declared, the node name
+is used as fallback. Eventually is also possible to declare the read-only
+property to flag the partition as read-only.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Link: https://lore.kernel.org/r/20241002221306.4403-6-ansuelsmth@gmail.com
+Signed-off-by: Jens Axboe <axboe@kernel.dk>
+---
+ block/partitions/Kconfig  |   9 ++++
+ block/partitions/Makefile |   1 +
+ block/partitions/check.h  |   1 +
+ block/partitions/core.c   |   3 ++
+ block/partitions/of.c     | 110 ++++++++++++++++++++++++++++++++++++++
+ 5 files changed, 124 insertions(+)
+ create mode 100644 block/partitions/of.c
+
+--- a/block/partitions/Kconfig
++++ b/block/partitions/Kconfig
+@@ -270,4 +270,13 @@ config CMDLINE_PARTITION
+         Say Y here if you want to read the partition table from bootargs.
+         The format for the command line is just like mtdparts.
++config OF_PARTITION
++      bool "Device Tree partition support" if PARTITION_ADVANCED
++      depends on OF
++      help
++        Say Y here if you want to enable support for partition table
++        defined in Device Tree. (mainly for eMMC)
++        The format for the device tree node is just like MTD fixed-partition
++        schema.
++
+ endmenu
+--- a/block/partitions/Makefile
++++ b/block/partitions/Makefile
+@@ -12,6 +12,7 @@ obj-$(CONFIG_CMDLINE_PARTITION) += cmdli
+ obj-$(CONFIG_MAC_PARTITION) += mac.o
+ obj-$(CONFIG_LDM_PARTITION) += ldm.o
+ obj-$(CONFIG_MSDOS_PARTITION) += msdos.o
++obj-$(CONFIG_OF_PARTITION) += of.o
+ obj-$(CONFIG_OSF_PARTITION) += osf.o
+ obj-$(CONFIG_SGI_PARTITION) += sgi.o
+ obj-$(CONFIG_SUN_PARTITION) += sun.o
+--- a/block/partitions/check.h
++++ b/block/partitions/check.h
+@@ -62,6 +62,7 @@ int karma_partition(struct parsed_partit
+ int ldm_partition(struct parsed_partitions *state);
+ int mac_partition(struct parsed_partitions *state);
+ int msdos_partition(struct parsed_partitions *state);
++int of_partition(struct parsed_partitions *state);
+ int osf_partition(struct parsed_partitions *state);
+ int sgi_partition(struct parsed_partitions *state);
+ int sun_partition(struct parsed_partitions *state);
+--- a/block/partitions/core.c
++++ b/block/partitions/core.c
+@@ -43,6 +43,9 @@ static int (*const check_part[])(struct
+ #ifdef CONFIG_CMDLINE_PARTITION
+       cmdline_partition,
+ #endif
++#ifdef CONFIG_OF_PARTITION
++      of_partition,           /* cmdline have priority to OF */
++#endif
+ #ifdef CONFIG_EFI_PARTITION
+       efi_partition,          /* this must come before msdos */
+ #endif
+--- /dev/null
++++ b/block/partitions/of.c
+@@ -0,0 +1,110 @@
++// SPDX-License-Identifier: GPL-2.0
++
++#include <linux/blkdev.h>
++#include <linux/major.h>
++#include <linux/of.h>
++#include <linux/string.h>
++#include "check.h"
++
++static int validate_of_partition(struct device_node *np, int slot)
++{
++      u64 offset, size;
++      int len;
++
++      const __be32 *reg = of_get_property(np, "reg", &len);
++      int a_cells = of_n_addr_cells(np);
++      int s_cells = of_n_size_cells(np);
++
++      /* Make sure reg len match the expected addr and size cells */
++      if (len / sizeof(*reg) != a_cells + s_cells)
++              return -EINVAL;
++
++      /* Validate offset conversion from bytes to sectors */
++      offset = of_read_number(reg, a_cells);
++      if (offset % SECTOR_SIZE)
++              return -EINVAL;
++
++      /* Validate size conversion from bytes to sectors */
++      size = of_read_number(reg + a_cells, s_cells);
++      if (!size || size % SECTOR_SIZE)
++              return -EINVAL;
++
++      return 0;
++}
++
++static void add_of_partition(struct parsed_partitions *state, int slot,
++                           struct device_node *np)
++{
++      struct partition_meta_info *info;
++      char tmp[sizeof(info->volname) + 4];
++      const char *partname;
++      int len;
++
++      const __be32 *reg = of_get_property(np, "reg", &len);
++      int a_cells = of_n_addr_cells(np);
++      int s_cells = of_n_size_cells(np);
++
++      /* Convert bytes to sector size */
++      u64 offset = of_read_number(reg, a_cells) / SECTOR_SIZE;
++      u64 size = of_read_number(reg + a_cells, s_cells) / SECTOR_SIZE;
++
++      put_partition(state, slot, offset, size);
++
++      if (of_property_read_bool(np, "read-only"))
++              state->parts[slot].flags |= ADDPART_FLAG_READONLY;
++
++      /*
++       * Follow MTD label logic, search for label property,
++       * fallback to node name if not found.
++       */
++      info = &state->parts[slot].info;
++      partname = of_get_property(np, "label", &len);
++      if (!partname)
++              partname = of_get_property(np, "name", &len);
++      strscpy(info->volname, partname, sizeof(info->volname));
++
++      snprintf(tmp, sizeof(tmp), "(%s)", info->volname);
++      strlcat(state->pp_buf, tmp, PAGE_SIZE);
++}
++
++int of_partition(struct parsed_partitions *state)
++{
++      struct device *ddev = disk_to_dev(state->disk);
++      struct device_node *np;
++      int slot;
++
++      struct device_node *partitions_np = of_node_get(ddev->of_node);
++
++      if (!partitions_np ||
++          !of_device_is_compatible(partitions_np, "fixed-partitions"))
++              return 0;
++
++      slot = 1;
++      /* Validate parition offset and size */
++      for_each_child_of_node(partitions_np, np) {
++              if (validate_of_partition(np, slot)) {
++                      of_node_put(np);
++                      of_node_put(partitions_np);
++
++                      return -1;
++              }
++
++              slot++;
++      }
++
++      slot = 1;
++      for_each_child_of_node(partitions_np, np) {
++              if (slot >= state->limit) {
++                      of_node_put(np);
++                      break;
++              }
++
++              add_of_partition(state, slot, np);
++
++              slot++;
++      }
++
++      strlcat(state->pp_buf, "\n", PAGE_SIZE);
++
++      return 1;
++}
diff --git a/target/linux/generic/backport-6.12/600-01-v6.13-net-phylink-move-manual-flow-control-setting.patch b/target/linux/generic/backport-6.12/600-01-v6.13-net-phylink-move-manual-flow-control-setting.patch
new file mode 100644 (file)
index 0000000..a9b86c2
--- /dev/null
@@ -0,0 +1,41 @@
+From 8cc5f4cb94c0b1c7c1ba8013c14fd02ffb1a25f3 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:01:44 +0000
+Subject: [PATCH 1/5] net: phylink: move manual flow control setting
+
+Move the handling of manual flow control configuration to a common
+location during resolve. We currently evaluate this for all but
+fixed links.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQe-002Feh-T1@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1484,7 +1484,6 @@ static void phylink_resolve(struct work_
+               switch (pl->cur_link_an_mode) {
+               case MLO_AN_PHY:
+                       link_state = pl->phy_state;
+-                      phylink_apply_manual_flow(pl, &link_state);
+                       mac_config = link_state.link;
+                       break;
+@@ -1545,11 +1544,13 @@ static void phylink_resolve(struct work_
+                               link_state.pause = pl->phy_state.pause;
+                               mac_config = true;
+                       }
+-                      phylink_apply_manual_flow(pl, &link_state);
+                       break;
+               }
+       }
++      if (pl->cur_link_an_mode != MLO_AN_FIXED)
++              phylink_apply_manual_flow(pl, &link_state);
++
+       if (mac_config) {
+               if (link_state.interface != pl->link_config.interface) {
+                       /* The interface has changed, force the link down and
diff --git a/target/linux/generic/backport-6.12/600-02-v6.13-net-phylink-move-MLO_AN_FIXED-resolve-handling-to-if.patch b/target/linux/generic/backport-6.12/600-02-v6.13-net-phylink-move-MLO_AN_FIXED-resolve-handling-to-if.patch
new file mode 100644 (file)
index 0000000..7ff091e
--- /dev/null
@@ -0,0 +1,42 @@
+From 92abfcb4ced482afbe65d18980e6734fe1e62a34 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:01:50 +0000
+Subject: [PATCH 2/5] net: phylink: move MLO_AN_FIXED resolve handling to if()
+ statement
+
+The switch() statement doesn't sit very well with the preceeding if()
+statements, and results in excessive indentation that spoils code
+readability. Begin cleaning this up by converting the MLO_AN_FIXED case
+to an if() statement.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQk-002Fen-1A@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1480,6 +1480,9 @@ static void phylink_resolve(struct work_
+       } else if (pl->link_failed) {
+               link_state.link = false;
+               retrigger = true;
++      } else if (pl->cur_link_an_mode == MLO_AN_FIXED) {
++              phylink_get_fixed_state(pl, &link_state);
++              mac_config = link_state.link;
+       } else {
+               switch (pl->cur_link_an_mode) {
+               case MLO_AN_PHY:
+@@ -1487,11 +1490,6 @@ static void phylink_resolve(struct work_
+                       mac_config = link_state.link;
+                       break;
+-              case MLO_AN_FIXED:
+-                      phylink_get_fixed_state(pl, &link_state);
+-                      mac_config = link_state.link;
+-                      break;
+-
+               case MLO_AN_INBAND:
+                       phylink_mac_pcs_get_state(pl, &link_state);
diff --git a/target/linux/generic/backport-6.12/600-03-v6.13-net-phylink-move-MLO_AN_PHY-resolve-handling-to-if-s.patch b/target/linux/generic/backport-6.12/600-03-v6.13-net-phylink-move-MLO_AN_PHY-resolve-handling-to-if-s.patch
new file mode 100644 (file)
index 0000000..76e76fb
--- /dev/null
@@ -0,0 +1,37 @@
+From f0f46c2a3d8ea9d1427298c8103a777d9e616c29 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:01:55 +0000
+Subject: [PATCH 3/5] net: phylink: move MLO_AN_PHY resolve handling to if()
+ statement
+
+The switch() statement doesn't sit very well with the preceeding if()
+statements, and results in excessive indentation that spoils code
+readability. Continue cleaning this up by converting the MLO_AN_PHY
+case to use an if() statmeent.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQp-002Fet-5W@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1483,13 +1483,11 @@ static void phylink_resolve(struct work_
+       } else if (pl->cur_link_an_mode == MLO_AN_FIXED) {
+               phylink_get_fixed_state(pl, &link_state);
+               mac_config = link_state.link;
++      } else if (pl->cur_link_an_mode == MLO_AN_PHY) {
++              link_state = pl->phy_state;
++              mac_config = link_state.link;
+       } else {
+               switch (pl->cur_link_an_mode) {
+-              case MLO_AN_PHY:
+-                      link_state = pl->phy_state;
+-                      mac_config = link_state.link;
+-                      break;
+-
+               case MLO_AN_INBAND:
+                       phylink_mac_pcs_get_state(pl, &link_state);
diff --git a/target/linux/generic/backport-6.12/600-04-v6.13-net-phylink-remove-switch-statement-in-resolve-handl.patch b/target/linux/generic/backport-6.12/600-04-v6.13-net-phylink-remove-switch-statement-in-resolve-handl.patch
new file mode 100644 (file)
index 0000000..989fc10
--- /dev/null
@@ -0,0 +1,127 @@
+From d1a16dbbd84e02d2a6dcfcb8d5c4b8b2c0289f00 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:02:00 +0000
+Subject: [PATCH 4/5] net: phylink: remove switch() statement in resolve
+ handling
+
+The switch() statement doesn't sit very well with the preceeding if()
+statements, so let's just convert everything to if()s. As a result of
+the two preceding commits, there is now only one case in the switch()
+statement. Remove the switch statement and reduce the code indentation.
+Code reformatting will be in the following commit.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQu-002Fez-AA@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 94 +++++++++++++++++++--------------------
+ 1 file changed, 45 insertions(+), 49 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1487,60 +1487,56 @@ static void phylink_resolve(struct work_
+               link_state = pl->phy_state;
+               mac_config = link_state.link;
+       } else {
+-              switch (pl->cur_link_an_mode) {
+-              case MLO_AN_INBAND:
+-                      phylink_mac_pcs_get_state(pl, &link_state);
+-
+-                      /* The PCS may have a latching link-fail indicator.
+-                       * If the link was up, bring the link down and
+-                       * re-trigger the resolve. Otherwise, re-read the
+-                       * PCS state to get the current status of the link.
++              phylink_mac_pcs_get_state(pl, &link_state);
++
++              /* The PCS may have a latching link-fail indicator.
++               * If the link was up, bring the link down and
++               * re-trigger the resolve. Otherwise, re-read the
++               * PCS state to get the current status of the link.
++               */
++              if (!link_state.link) {
++                      if (cur_link_state)
++                              retrigger = true;
++                      else
++                              phylink_mac_pcs_get_state(pl,
++                                                        &link_state);
++              }
++
++              /* If we have a phy, the "up" state is the union of
++               * both the PHY and the MAC
++               */
++              if (pl->phydev)
++                      link_state.link &= pl->phy_state.link;
++
++              /* Only update if the PHY link is up */
++              if (pl->phydev && pl->phy_state.link) {
++                      /* If the interface has changed, force a
++                       * link down event if the link isn't already
++                       * down, and re-resolve.
+                        */
+-                      if (!link_state.link) {
+-                              if (cur_link_state)
+-                                      retrigger = true;
+-                              else
+-                                      phylink_mac_pcs_get_state(pl,
+-                                                                &link_state);
++                      if (link_state.interface !=
++                          pl->phy_state.interface) {
++                              retrigger = true;
++                              link_state.link = false;
+                       }
++                      link_state.interface = pl->phy_state.interface;
+-                      /* If we have a phy, the "up" state is the union of
+-                       * both the PHY and the MAC
++                      /* If we are doing rate matching, then the
++                       * link speed/duplex comes from the PHY
+                        */
+-                      if (pl->phydev)
+-                              link_state.link &= pl->phy_state.link;
+-
+-                      /* Only update if the PHY link is up */
+-                      if (pl->phydev && pl->phy_state.link) {
+-                              /* If the interface has changed, force a
+-                               * link down event if the link isn't already
+-                               * down, and re-resolve.
+-                               */
+-                              if (link_state.interface !=
+-                                  pl->phy_state.interface) {
+-                                      retrigger = true;
+-                                      link_state.link = false;
+-                              }
+-                              link_state.interface = pl->phy_state.interface;
+-
+-                              /* If we are doing rate matching, then the
+-                               * link speed/duplex comes from the PHY
+-                               */
+-                              if (pl->phy_state.rate_matching) {
+-                                      link_state.rate_matching =
+-                                              pl->phy_state.rate_matching;
+-                                      link_state.speed = pl->phy_state.speed;
+-                                      link_state.duplex =
+-                                              pl->phy_state.duplex;
+-                              }
+-
+-                              /* If we have a PHY, we need to update with
+-                               * the PHY flow control bits.
+-                               */
+-                              link_state.pause = pl->phy_state.pause;
+-                              mac_config = true;
++                      if (pl->phy_state.rate_matching) {
++                              link_state.rate_matching =
++                                      pl->phy_state.rate_matching;
++                              link_state.speed = pl->phy_state.speed;
++                              link_state.duplex =
++                                      pl->phy_state.duplex;
+                       }
+-                      break;
++
++                      /* If we have a PHY, we need to update with
++                       * the PHY flow control bits.
++                       */
++                      link_state.pause = pl->phy_state.pause;
++                      mac_config = true;
+               }
+       }
diff --git a/target/linux/generic/backport-6.12/600-05-v6.13-net-phylink-clean-up-phylink_resolve.patch b/target/linux/generic/backport-6.12/600-05-v6.13-net-phylink-clean-up-phylink_resolve.patch
new file mode 100644 (file)
index 0000000..285eea8
--- /dev/null
@@ -0,0 +1,85 @@
+From bc08ce37d99a3992e975a0f397503cb23404f25a Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:02:05 +0000
+Subject: [PATCH 5/5] net: phylink: clean up phylink_resolve()
+
+Now that we have reduced the indentation level, clean up the code
+formatting.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQz-002Ff5-EA@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 35 ++++++++++++++++-------------------
+ 1 file changed, 16 insertions(+), 19 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1489,51 +1489,48 @@ static void phylink_resolve(struct work_
+       } else {
+               phylink_mac_pcs_get_state(pl, &link_state);
+-              /* The PCS may have a latching link-fail indicator.
+-               * If the link was up, bring the link down and
+-               * re-trigger the resolve. Otherwise, re-read the
+-               * PCS state to get the current status of the link.
++              /* The PCS may have a latching link-fail indicator. If the link
++               * was up, bring the link down and re-trigger the resolve.
++               * Otherwise, re-read the PCS state to get the current status
++               * of the link.
+                */
+               if (!link_state.link) {
+                       if (cur_link_state)
+                               retrigger = true;
+                       else
+-                              phylink_mac_pcs_get_state(pl,
+-                                                        &link_state);
++                              phylink_mac_pcs_get_state(pl, &link_state);
+               }
+-              /* If we have a phy, the "up" state is the union of
+-               * both the PHY and the MAC
++              /* If we have a phy, the "up" state is the union of both the
++               * PHY and the MAC
+                */
+               if (pl->phydev)
+                       link_state.link &= pl->phy_state.link;
+               /* Only update if the PHY link is up */
+               if (pl->phydev && pl->phy_state.link) {
+-                      /* If the interface has changed, force a
+-                       * link down event if the link isn't already
+-                       * down, and re-resolve.
++                      /* If the interface has changed, force a link down
++                       * event if the link isn't already down, and re-resolve.
+                        */
+-                      if (link_state.interface !=
+-                          pl->phy_state.interface) {
++                      if (link_state.interface != pl->phy_state.interface) {
+                               retrigger = true;
+                               link_state.link = false;
+                       }
++
+                       link_state.interface = pl->phy_state.interface;
+-                      /* If we are doing rate matching, then the
+-                       * link speed/duplex comes from the PHY
++                      /* If we are doing rate matching, then the link
++                       * speed/duplex comes from the PHY
+                        */
+                       if (pl->phy_state.rate_matching) {
+                               link_state.rate_matching =
+                                       pl->phy_state.rate_matching;
+                               link_state.speed = pl->phy_state.speed;
+-                              link_state.duplex =
+-                                      pl->phy_state.duplex;
++                              link_state.duplex = pl->phy_state.duplex;
+                       }
+-                      /* If we have a PHY, we need to update with
+-                       * the PHY flow control bits.
++                      /* If we have a PHY, we need to update with the PHY
++                       * flow control bits.
+                        */
+                       link_state.pause = pl->phy_state.pause;
+                       mac_config = true;
diff --git a/target/linux/generic/backport-6.12/601-01-v6.14-net-phylink-pass-phylink-and-pcs-into-phylink_pcs_ne.patch b/target/linux/generic/backport-6.12/601-01-v6.14-net-phylink-pass-phylink-and-pcs-into-phylink_pcs_ne.patch
new file mode 100644 (file)
index 0000000..6868cd1
--- /dev/null
@@ -0,0 +1,95 @@
+From 17ed1911f9c8d4f9af8e13b2c95103ee06dadc0f Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:30:47 +0000
+Subject: [PATCH 01/13] net: phylink: pass phylink and pcs into
+ phylink_pcs_neg_mode()
+
+Move the call to phylink_pcs_neg_mode() in phylink_major_config() after
+we have selected the appropriate PCS to allow the PCS to be passed in.
+
+Add struct phylink and struct phylink_pcs pointers to
+phylink_pcs_neg_mode() and pass in the appropriate structures. Set
+pl->pcs_neg_mode before returning, and remove the return value.
+
+This will allow the capabilities of the PCS and any PHY to be used when
+deciding which pcs_neg_mode should be used.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUrP-006ITh-6u@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 26 +++++++++++++-------------
+ 1 file changed, 13 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1102,7 +1102,8 @@ static void phylink_pcs_an_restart(struc
+ /**
+  * phylink_pcs_neg_mode() - helper to determine PCS inband mode
+- * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
++ * @pl: a pointer to a &struct phylink returned from phylink_create()
++ * @pcs: a pointer to &struct phylink_pcs
+  * @interface: interface mode to be used
+  * @advertising: adertisement ethtool link mode mask
+  *
+@@ -1119,11 +1120,13 @@ static void phylink_pcs_an_restart(struc
+  * Note: this is for cases where the PCS itself is involved in negotiation
+  * (e.g. Clause 37, SGMII and similar) not Clause 73.
+  */
+-static unsigned int phylink_pcs_neg_mode(unsigned int mode,
+-                                       phy_interface_t interface,
+-                                       const unsigned long *advertising)
++static void phylink_pcs_neg_mode(struct phylink *pl, struct phylink_pcs *pcs,
++                               phy_interface_t interface,
++                               const unsigned long *advertising)
+ {
+-      unsigned int neg_mode;
++      unsigned int neg_mode, mode;
++
++      mode = pl->cur_link_an_mode;
+       switch (interface) {
+       case PHY_INTERFACE_MODE_SGMII:
+@@ -1164,7 +1167,7 @@ static unsigned int phylink_pcs_neg_mode
+               break;
+       }
+-      return neg_mode;
++      pl->pcs_neg_mode = neg_mode;
+ }
+ static void phylink_major_config(struct phylink *pl, bool restart,
+@@ -1178,10 +1181,6 @@ static void phylink_major_config(struct
+       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)) {
+@@ -1194,6 +1193,8 @@ static void phylink_major_config(struct
+               pcs_changed = pcs && pl->pcs != pcs;
+       }
++      phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
++
+       phylink_pcs_poll_stop(pl);
+       if (pl->mac_ops->mac_prepare) {
+@@ -1284,9 +1285,8 @@ static int phylink_change_inband_advert(
+                   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);
++      phylink_pcs_neg_mode(pl, pl->pcs, pl->link_config.interface,
++                           pl->link_config.advertising);
+       neg_mode = pl->cur_link_an_mode;
+       if (pl->pcs->neg_mode)
diff --git a/target/linux/generic/backport-6.12/601-02-v6.14-net-phylink-split-cur_link_an_mode-into-requested-an.patch b/target/linux/generic/backport-6.12/601-02-v6.14-net-phylink-split-cur_link_an_mode-into-requested-an.patch
new file mode 100644 (file)
index 0000000..76f4d5a
--- /dev/null
@@ -0,0 +1,290 @@
+From 1f92ead7e15003f632b5f138e8138095e0997d3d Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:30:52 +0000
+Subject: [PATCH 02/13] net: phylink: split cur_link_an_mode into requested and
+ active
+
+There is an interdependence between the current link_an_mode and
+pcs_neg_mode that some drivers rely upon to know whether inband or PHY
+mode will be used.
+
+In order to support detection of PCS and PHY inband capabilities
+resulting in automatic selection of inband or PHY mode, we need to
+cater for this, and support changing the MAC link_an_mode. However, we
+end up with an inter-dependency between the current link_an_mode and
+pcs_neg_mode.
+
+To solve this, split the current link_an_mode into the requested
+link_an_mode and active link_an_mode. The requested link_an_mode will
+always be passed to phylink_pcs_neg_mode(), and the active link_an_mode
+will be used for everything else, and only updated during
+phylink_major_config(). This will ensure that phylink_pcs_neg_mode()'s
+link_an_mode will not depend on the active link_an_mode that will,
+in a future patch, depend on pcs_neg_mode.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUrU-006ITn-Ai@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 60 ++++++++++++++++++++-------------------
+ 1 file changed, 31 insertions(+), 29 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -56,7 +56,8 @@ struct phylink {
+       struct phy_device *phydev;
+       phy_interface_t link_interface; /* PHY_INTERFACE_xxx */
+       u8 cfg_link_an_mode;            /* MLO_AN_xxx */
+-      u8 cur_link_an_mode;
++      u8 req_link_an_mode;            /* Requested MLO_AN_xxx mode */
++      u8 act_link_an_mode;            /* Active MLO_AN_xxx mode */
+       u8 link_port;                   /* The current non-phy ethtool port */
+       __ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+@@ -1082,13 +1083,13 @@ static void phylink_mac_config(struct ph
+       phylink_dbg(pl,
+                   "%s: mode=%s/%s/%s adv=%*pb pause=%02x\n",
+-                  __func__, phylink_an_mode_str(pl->cur_link_an_mode),
++                  __func__, phylink_an_mode_str(pl->act_link_an_mode),
+                   phy_modes(st.interface),
+                   phy_rate_matching_to_str(st.rate_matching),
+                   __ETHTOOL_LINK_MODE_MASK_NBITS, st.advertising,
+                   st.pause);
+-      pl->mac_ops->mac_config(pl->config, pl->cur_link_an_mode, &st);
++      pl->mac_ops->mac_config(pl->config, pl->act_link_an_mode, &st);
+ }
+ static void phylink_pcs_an_restart(struct phylink *pl)
+@@ -1096,7 +1097,7 @@ static void phylink_pcs_an_restart(struc
+       if (pl->pcs && 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))
++          phylink_autoneg_inband(pl->act_link_an_mode))
+               pl->pcs->ops->pcs_an_restart(pl->pcs);
+ }
+@@ -1126,7 +1127,7 @@ static void phylink_pcs_neg_mode(struct
+ {
+       unsigned int neg_mode, mode;
+-      mode = pl->cur_link_an_mode;
++      mode = pl->req_link_an_mode;
+       switch (interface) {
+       case PHY_INTERFACE_MODE_SGMII:
+@@ -1168,6 +1169,7 @@ static void phylink_pcs_neg_mode(struct
+       }
+       pl->pcs_neg_mode = neg_mode;
++      pl->act_link_an_mode = mode;
+ }
+ static void phylink_major_config(struct phylink *pl, bool restart,
+@@ -1198,7 +1200,7 @@ static void phylink_major_config(struct
+       phylink_pcs_poll_stop(pl);
+       if (pl->mac_ops->mac_prepare) {
+-              err = pl->mac_ops->mac_prepare(pl->config, pl->cur_link_an_mode,
++              err = pl->mac_ops->mac_prepare(pl->config, pl->act_link_an_mode,
+                                              state->interface);
+               if (err < 0) {
+                       phylink_err(pl, "mac_prepare failed: %pe\n",
+@@ -1232,7 +1234,7 @@ static void phylink_major_config(struct
+       if (pl->pcs_state == PCS_STATE_STARTING || pcs_changed)
+               phylink_pcs_enable(pl->pcs);
+-      neg_mode = pl->cur_link_an_mode;
++      neg_mode = pl->act_link_an_mode;
+       if (pl->pcs && pl->pcs->neg_mode)
+               neg_mode = pl->pcs_neg_mode;
+@@ -1248,7 +1250,7 @@ static void phylink_major_config(struct
+               phylink_pcs_an_restart(pl);
+       if (pl->mac_ops->mac_finish) {
+-              err = pl->mac_ops->mac_finish(pl->config, pl->cur_link_an_mode,
++              err = pl->mac_ops->mac_finish(pl->config, pl->act_link_an_mode,
+                                             state->interface);
+               if (err < 0)
+                       phylink_err(pl, "mac_finish failed: %pe\n",
+@@ -1279,7 +1281,7 @@ static int phylink_change_inband_advert(
+               return 0;
+       phylink_dbg(pl, "%s: mode=%s/%s adv=%*pb pause=%02x\n", __func__,
+-                  phylink_an_mode_str(pl->cur_link_an_mode),
++                  phylink_an_mode_str(pl->req_link_an_mode),
+                   phy_modes(pl->link_config.interface),
+                   __ETHTOOL_LINK_MODE_MASK_NBITS, pl->link_config.advertising,
+                   pl->link_config.pause);
+@@ -1288,7 +1290,7 @@ static int phylink_change_inband_advert(
+       phylink_pcs_neg_mode(pl, pl->pcs, pl->link_config.interface,
+                            pl->link_config.advertising);
+-      neg_mode = pl->cur_link_an_mode;
++      neg_mode = pl->act_link_an_mode;
+       if (pl->pcs->neg_mode)
+               neg_mode = pl->pcs_neg_mode;
+@@ -1353,7 +1355,7 @@ static void phylink_mac_initial_config(s
+ {
+       struct phylink_link_state link_state;
+-      switch (pl->cur_link_an_mode) {
++      switch (pl->req_link_an_mode) {
+       case MLO_AN_PHY:
+               link_state = pl->phy_state;
+               break;
+@@ -1427,14 +1429,14 @@ static void phylink_link_up(struct phyli
+       pl->cur_interface = link_state.interface;
+-      neg_mode = pl->cur_link_an_mode;
++      neg_mode = pl->act_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->mac_ops->mac_link_up(pl->config, pl->phydev, pl->act_link_an_mode,
+                                pl->cur_interface, speed, duplex,
+                                !!(link_state.pause & MLO_PAUSE_TX), rx_pause);
+@@ -1454,7 +1456,7 @@ static void phylink_link_down(struct phy
+       if (ndev)
+               netif_carrier_off(ndev);
+-      pl->mac_ops->mac_link_down(pl->config, pl->cur_link_an_mode,
++      pl->mac_ops->mac_link_down(pl->config, pl->act_link_an_mode,
+                                  pl->cur_interface);
+       phylink_info(pl, "Link is Down\n");
+ }
+@@ -1480,10 +1482,10 @@ static void phylink_resolve(struct work_
+       } else if (pl->link_failed) {
+               link_state.link = false;
+               retrigger = true;
+-      } else if (pl->cur_link_an_mode == MLO_AN_FIXED) {
++      } else if (pl->act_link_an_mode == MLO_AN_FIXED) {
+               phylink_get_fixed_state(pl, &link_state);
+               mac_config = link_state.link;
+-      } else if (pl->cur_link_an_mode == MLO_AN_PHY) {
++      } else if (pl->act_link_an_mode == MLO_AN_PHY) {
+               link_state = pl->phy_state;
+               mac_config = link_state.link;
+       } else {
+@@ -1537,7 +1539,7 @@ static void phylink_resolve(struct work_
+               }
+       }
+-      if (pl->cur_link_an_mode != MLO_AN_FIXED)
++      if (pl->act_link_an_mode != MLO_AN_FIXED)
+               phylink_apply_manual_flow(pl, &link_state);
+       if (mac_config) {
+@@ -1661,7 +1663,7 @@ int phylink_set_fixed_link(struct phylin
+       pl->link_config.an_complete = 1;
+       pl->cfg_link_an_mode = MLO_AN_FIXED;
+-      pl->cur_link_an_mode = pl->cfg_link_an_mode;
++      pl->req_link_an_mode = pl->cfg_link_an_mode;
+       return 0;
+ }
+@@ -1756,7 +1758,7 @@ struct phylink *phylink_create(struct ph
+               }
+       }
+-      pl->cur_link_an_mode = pl->cfg_link_an_mode;
++      pl->req_link_an_mode = pl->cfg_link_an_mode;
+       ret = phylink_register_sfp(pl, fwnode);
+       if (ret < 0) {
+@@ -2213,7 +2215,7 @@ void phylink_start(struct phylink *pl)
+       ASSERT_RTNL();
+       phylink_info(pl, "configuring for %s/%s link mode\n",
+-                   phylink_an_mode_str(pl->cur_link_an_mode),
++                   phylink_an_mode_str(pl->req_link_an_mode),
+                    phy_modes(pl->link_config.interface));
+       /* Always set the carrier off */
+@@ -2472,7 +2474,7 @@ int phylink_ethtool_ksettings_get(struct
+       linkmode_copy(kset->link_modes.supported, pl->supported);
+-      switch (pl->cur_link_an_mode) {
++      switch (pl->act_link_an_mode) {
+       case MLO_AN_FIXED:
+               /* We are using fixed settings. Report these as the
+                * current link settings - and note that these also
+@@ -2564,7 +2566,7 @@ int phylink_ethtool_ksettings_set(struct
+               /* If we have a fixed link, refuse to change link parameters.
+                * If the link parameters match, accept them but do nothing.
+                */
+-              if (pl->cur_link_an_mode == MLO_AN_FIXED) {
++              if (pl->req_link_an_mode == MLO_AN_FIXED) {
+                       if (s->speed != pl->link_config.speed ||
+                           s->duplex != pl->link_config.duplex)
+                               return -EINVAL;
+@@ -2580,7 +2582,7 @@ int phylink_ethtool_ksettings_set(struct
+                * is our default case) but do not allow the advertisement to
+                * be changed. If the advertisement matches, simply return.
+                */
+-              if (pl->cur_link_an_mode == MLO_AN_FIXED) {
++              if (pl->req_link_an_mode == MLO_AN_FIXED) {
+                       if (!linkmode_equal(config.advertising,
+                                           pl->link_config.advertising))
+                               return -EINVAL;
+@@ -2620,7 +2622,7 @@ int phylink_ethtool_ksettings_set(struct
+               linkmode_copy(support, pl->supported);
+               if (phylink_validate(pl, support, &config)) {
+                       phylink_err(pl, "validation of %s/%s with support %*pb failed\n",
+-                                  phylink_an_mode_str(pl->cur_link_an_mode),
++                                  phylink_an_mode_str(pl->req_link_an_mode),
+                                   phy_modes(config.interface),
+                                   __ETHTOOL_LINK_MODE_MASK_NBITS, support);
+                       return -EINVAL;
+@@ -2720,7 +2722,7 @@ int phylink_ethtool_set_pauseparam(struc
+       ASSERT_RTNL();
+-      if (pl->cur_link_an_mode == MLO_AN_FIXED)
++      if (pl->req_link_an_mode == MLO_AN_FIXED)
+               return -EOPNOTSUPP;
+       if (!phylink_test(pl->supported, Pause) &&
+@@ -2984,7 +2986,7 @@ static int phylink_mii_read(struct phyli
+       struct phylink_link_state state;
+       int val = 0xffff;
+-      switch (pl->cur_link_an_mode) {
++      switch (pl->act_link_an_mode) {
+       case MLO_AN_FIXED:
+               if (phy_id == 0) {
+                       phylink_get_fixed_state(pl, &state);
+@@ -3009,7 +3011,7 @@ static int phylink_mii_read(struct phyli
+ static int phylink_mii_write(struct phylink *pl, unsigned int phy_id,
+                            unsigned int reg, unsigned int val)
+ {
+-      switch (pl->cur_link_an_mode) {
++      switch (pl->act_link_an_mode) {
+       case MLO_AN_FIXED:
+               break;
+@@ -3199,9 +3201,9 @@ static void phylink_sfp_set_config(struc
+               changed = true;
+       }
+-      if (pl->cur_link_an_mode != mode ||
++      if (pl->req_link_an_mode != mode ||
+           pl->link_config.interface != state->interface) {
+-              pl->cur_link_an_mode = mode;
++              pl->req_link_an_mode = mode;
+               pl->link_config.interface = state->interface;
+               changed = true;
diff --git a/target/linux/generic/backport-6.12/601-03-v6.14-net-phylink-add-debug-for-phylink_major_config.patch b/target/linux/generic/backport-6.12/601-03-v6.14-net-phylink-add-debug-for-phylink_major_config.patch
new file mode 100644 (file)
index 0000000..ee625c7
--- /dev/null
@@ -0,0 +1,66 @@
+From 4e7d000286fe8e12f2d88032711ffab3ab658b12 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:30:57 +0000
+Subject: [PATCH 03/13] net: phylink: add debug for phylink_major_config()
+
+Now that we have a more complexity in phylink_major_config(), augment
+the debugging so we can see what's going on there.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUrZ-006ITt-Fa@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 27 ++++++++++++++++++++++++++-
+ 1 file changed, 26 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -176,6 +176,24 @@ static const char *phylink_an_mode_str(u
+       return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
+ }
++static const char *phylink_pcs_mode_str(unsigned int mode)
++{
++      if (!mode)
++              return "none";
++
++      if (mode & PHYLINK_PCS_NEG_OUTBAND)
++              return "outband";
++
++      if (mode & PHYLINK_PCS_NEG_INBAND) {
++              if (mode & PHYLINK_PCS_NEG_ENABLED)
++                      return "inband,an-enabled";
++              else
++                      return "inband,an-disabled";
++      }
++
++      return "unknown";
++}
++
+ static unsigned int phylink_interface_signal_rate(phy_interface_t interface)
+ {
+       switch (interface) {
+@@ -1181,7 +1199,9 @@ static void phylink_major_config(struct
+       unsigned int neg_mode;
+       int err;
+-      phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
++      phylink_dbg(pl, "major config, requested %s/%s\n",
++                  phylink_an_mode_str(pl->req_link_an_mode),
++                  phy_modes(state->interface));
+       if (pl->using_mac_select_pcs) {
+               pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+@@ -1197,6 +1217,11 @@ static void phylink_major_config(struct
+       phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
++      phylink_dbg(pl, "major config, active %s/%s/%s\n",
++                  phylink_an_mode_str(pl->act_link_an_mode),
++                  phylink_pcs_mode_str(pl->pcs_neg_mode),
++                  phy_modes(state->interface));
++
+       phylink_pcs_poll_stop(pl);
+       if (pl->mac_ops->mac_prepare) {
diff --git a/target/linux/generic/backport-6.12/601-04-v6.14-net-phy-add-phy_inband_caps.patch b/target/linux/generic/backport-6.12/601-04-v6.14-net-phy-add-phy_inband_caps.patch
new file mode 100644 (file)
index 0000000..0cd38f0
--- /dev/null
@@ -0,0 +1,118 @@
+From b4c7698dd95f253c6958d8c6ac219098009bf28a Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:02 +0000
+Subject: [PATCH 04/13] net: phy: add phy_inband_caps()
+
+Add a method to query the PHY's in-band capabilities for a PHY
+interface mode.
+
+Where the interface mode does not have in-band capability, or the PHY
+driver has not been updated to return this information, then
+phy_inband_caps() should return zero. Otherwise, PHY drivers will
+return a value consisting of the following flags:
+
+LINK_INBAND_DISABLE indicates that the hardware does not support
+in-band signalling, or can have in-band signalling configured via
+software to be disabled.
+
+LINK_INBAND_ENABLE indicates that the hardware will use in-band
+signalling, or can have in-band signalling configured via software
+to be enabled.
+
+LINK_INBAND_BYPASS indicates that the hardware has the ability to
+bypass in-band signalling when enabled after a timeout if the link
+partner does not respond to its in-band signalling.
+
+This reports the PHY capabilities for the particular interface mode,
+not the current configuration.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUre-006ITz-KF@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phy.c | 21 +++++++++++++++++++++
+ include/linux/phy.h   | 28 ++++++++++++++++++++++++++++
+ 2 files changed, 49 insertions(+)
+
+--- a/drivers/net/phy/phy.c
++++ b/drivers/net/phy/phy.c
+@@ -1049,6 +1049,27 @@ static int phy_check_link_status(struct
+ }
+ /**
++ * phy_inband_caps - query which in-band signalling modes are supported
++ * @phydev: a pointer to a &struct phy_device
++ * @interface: the interface mode for the PHY
++ *
++ * Returns zero if it is unknown what in-band signalling is supported by the
++ * PHY (e.g. because the PHY driver doesn't implement the method.) Otherwise,
++ * returns a bit mask of the LINK_INBAND_* values from
++ * &enum link_inband_signalling to describe which inband modes are supported
++ * by the PHY for this interface mode.
++ */
++unsigned int phy_inband_caps(struct phy_device *phydev,
++                           phy_interface_t interface)
++{
++      if (phydev->drv && phydev->drv->inband_caps)
++              return phydev->drv->inband_caps(phydev, interface);
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(phy_inband_caps);
++
++/**
+  * _phy_start_aneg - start auto-negotiation for this PHY device
+  * @phydev: the phy_device struct
+  *
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -826,6 +826,24 @@ struct phy_tdr_config {
+ #define PHY_PAIR_ALL -1
+ /**
++ * enum link_inband_signalling - in-band signalling modes that are supported
++ *
++ * @LINK_INBAND_DISABLE: in-band signalling can be disabled
++ * @LINK_INBAND_ENABLE: in-band signalling can be enabled without bypass
++ * @LINK_INBAND_BYPASS: in-band signalling can be enabled with bypass
++ *
++ * The possible and required bits can only be used if the valid bit is set.
++ * If possible is clear, that means inband signalling can not be used.
++ * Required is only valid when possible is set, and means that inband
++ * signalling must be used.
++ */
++enum link_inband_signalling {
++      LINK_INBAND_DISABLE             = BIT(0),
++      LINK_INBAND_ENABLE              = BIT(1),
++      LINK_INBAND_BYPASS              = BIT(2),
++};
++
++/**
+  * struct phy_plca_cfg - Configuration of the PLCA (Physical Layer Collision
+  * Avoidance) Reconciliation Sublayer.
+  *
+@@ -964,6 +982,14 @@ struct phy_driver {
+       int (*get_features)(struct phy_device *phydev);
+       /**
++       * @inband_caps: query whether in-band is supported for the given PHY
++       * interface mode. Returns a bitmask of bits defined by enum
++       * link_inband_signalling.
++       */
++      unsigned int (*inband_caps)(struct phy_device *phydev,
++                                  phy_interface_t interface);
++
++      /**
+        * @get_rate_matching: Get the supported type of rate matching for a
+        * particular phy interface. This is used by phy consumers to determine
+        * whether to advertise lower-speed modes for that interface. It is
+@@ -1842,6 +1868,8 @@ int phy_config_aneg(struct phy_device *p
+ int _phy_start_aneg(struct phy_device *phydev);
+ int phy_start_aneg(struct phy_device *phydev);
+ int phy_aneg_done(struct phy_device *phydev);
++unsigned int phy_inband_caps(struct phy_device *phydev,
++                           phy_interface_t interface);
+ int phy_speed_down(struct phy_device *phydev, bool sync);
+ int phy_speed_up(struct phy_device *phydev);
+ bool phy_check_valid(int speed, int duplex, unsigned long *features);
diff --git a/target/linux/generic/backport-6.12/601-05-v6.14-net-phy-bcm84881-implement-phy_inband_caps-method.patch b/target/linux/generic/backport-6.12/601-05-v6.14-net-phy-bcm84881-implement-phy_inband_caps-method.patch
new file mode 100644 (file)
index 0000000..0ba3cd2
--- /dev/null
@@ -0,0 +1,41 @@
+From c64c7fa0a774d9da72071a8517e359992baac982 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:07 +0000
+Subject: [PATCH 05/13] net: phy: bcm84881: implement phy_inband_caps() method
+
+BCM84881 has no support for inband signalling, so this is a trivial
+implementation that returns no support for inband.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Acked-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/E1tIUrj-006IU6-ON@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/bcm84881.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+--- a/drivers/net/phy/bcm84881.c
++++ b/drivers/net/phy/bcm84881.c
+@@ -235,11 +235,21 @@ static int bcm84881_read_status(struct p
+       return genphy_c45_read_mdix(phydev);
+ }
++/* The Broadcom BCM84881 in the Methode DM7052 is unable to provide a SGMII
++ * or 802.3z control word, so inband will not work.
++ */
++static unsigned int bcm84881_inband_caps(struct phy_device *phydev,
++                                       phy_interface_t interface)
++{
++      return LINK_INBAND_DISABLE;
++}
++
+ static struct phy_driver bcm84881_drivers[] = {
+       {
+               .phy_id         = 0xae025150,
+               .phy_id_mask    = 0xfffffff0,
+               .name           = "Broadcom BCM84881",
++              .inband_caps    = bcm84881_inband_caps,
+               .config_init    = bcm84881_config_init,
+               .probe          = bcm84881_probe,
+               .get_features   = bcm84881_get_features,
diff --git a/target/linux/generic/backport-6.12/601-06-v6.14-net-phy-marvell-implement-phy_inband_caps-method.patch b/target/linux/generic/backport-6.12/601-06-v6.14-net-phy-marvell-implement-phy_inband_caps-method.patch
new file mode 100644 (file)
index 0000000..e2dfeba
--- /dev/null
@@ -0,0 +1,63 @@
+From 1c86828dff88e28b8ade6bddeee0163a023faf91 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:12 +0000
+Subject: [PATCH 06/13] net: phy: marvell: implement phy_inband_caps() method
+
+Provide an implementation for phy_inband_caps() for Marvell PHYs used
+on SFP modules, so that phylink knows the PHYs capabilities.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUro-006IUC-Rq@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/marvell.c | 17 +++++++++++++++++
+ 1 file changed, 17 insertions(+)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -716,6 +716,20 @@ static int marvell_config_aneg_fiber(str
+       return genphy_check_and_restart_aneg(phydev, changed);
+ }
++static unsigned int m88e1111_inband_caps(struct phy_device *phydev,
++                                       phy_interface_t interface)
++{
++      /* In 1000base-X and SGMII modes, the inband mode can be changed
++       * through the Fibre page BMCR ANENABLE bit.
++       */
++      if (interface == PHY_INTERFACE_MODE_1000BASEX ||
++          interface == PHY_INTERFACE_MODE_SGMII)
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE |
++                     LINK_INBAND_BYPASS;
++
++      return 0;
++}
++
+ static int m88e1111_config_aneg(struct phy_device *phydev)
+ {
+       int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+@@ -3704,6 +3718,7 @@ static struct phy_driver marvell_drivers
+               .name = "Marvell 88E1112",
+               /* PHY_GBIT_FEATURES */
+               .probe = marvell_probe,
++              .inband_caps = m88e1111_inband_caps,
+               .config_init = m88e1112_config_init,
+               .config_aneg = marvell_config_aneg,
+               .config_intr = marvell_config_intr,
+@@ -3725,6 +3740,7 @@ static struct phy_driver marvell_drivers
+               /* PHY_GBIT_FEATURES */
+               .flags = PHY_POLL_CABLE_TEST,
+               .probe = marvell_probe,
++              .inband_caps = m88e1111_inband_caps,
+               .config_init = m88e1111gbe_config_init,
+               .config_aneg = m88e1111_config_aneg,
+               .read_status = marvell_read_status,
+@@ -3748,6 +3764,7 @@ static struct phy_driver marvell_drivers
+               .name = "Marvell 88E1111 (Finisar)",
+               /* PHY_GBIT_FEATURES */
+               .probe = marvell_probe,
++              .inband_caps = m88e1111_inband_caps,
+               .config_init = m88e1111gbe_config_init,
+               .config_aneg = m88e1111_config_aneg,
+               .read_status = marvell_read_status,
diff --git a/target/linux/generic/backport-6.12/601-07-v6.14-net-phy-add-phy_config_inband.patch b/target/linux/generic/backport-6.12/601-07-v6.14-net-phy-add-phy_config_inband.patch
new file mode 100644 (file)
index 0000000..d667bcb
--- /dev/null
@@ -0,0 +1,79 @@
+From 5d58a890c02770ba8d790b1f3c6e8c0e20514dc2 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:18 +0000
+Subject: [PATCH 07/13] net: phy: add phy_config_inband()
+
+Add a method to configure the PHY's in-band mode.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUru-006IUI-08@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phy.c | 32 ++++++++++++++++++++++++++++++++
+ include/linux/phy.h   |  6 ++++++
+ 2 files changed, 38 insertions(+)
+
+--- a/drivers/net/phy/phy.c
++++ b/drivers/net/phy/phy.c
+@@ -1070,6 +1070,38 @@ unsigned int phy_inband_caps(struct phy_
+ EXPORT_SYMBOL_GPL(phy_inband_caps);
+ /**
++ * phy_config_inband - configure the desired PHY in-band mode
++ * @phydev: the phy_device struct
++ * @modes: in-band modes to configure
++ *
++ * Description: disables, enables or enables-with-bypass in-band signalling
++ *   between the PHY and host system.
++ *
++ * Returns: zero on success, or negative errno value.
++ */
++int phy_config_inband(struct phy_device *phydev, unsigned int modes)
++{
++      int err;
++
++      if (!!(modes & LINK_INBAND_DISABLE) +
++          !!(modes & LINK_INBAND_ENABLE) +
++          !!(modes & LINK_INBAND_BYPASS) != 1)
++              return -EINVAL;
++
++      mutex_lock(&phydev->lock);
++      if (!phydev->drv)
++              err = -EIO;
++      else if (!phydev->drv->config_inband)
++              err = -EOPNOTSUPP;
++      else
++              err = phydev->drv->config_inband(phydev, modes);
++      mutex_unlock(&phydev->lock);
++
++      return err;
++}
++EXPORT_SYMBOL(phy_config_inband);
++
++/**
+  * _phy_start_aneg - start auto-negotiation for this PHY device
+  * @phydev: the phy_device struct
+  *
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -990,6 +990,11 @@ struct phy_driver {
+                                   phy_interface_t interface);
+       /**
++       * @config_inband: configure in-band mode for the PHY
++       */
++      int (*config_inband)(struct phy_device *phydev, unsigned int modes);
++
++      /**
+        * @get_rate_matching: Get the supported type of rate matching for a
+        * particular phy interface. This is used by phy consumers to determine
+        * whether to advertise lower-speed modes for that interface. It is
+@@ -1870,6 +1875,7 @@ int phy_start_aneg(struct phy_device *ph
+ int phy_aneg_done(struct phy_device *phydev);
+ unsigned int phy_inband_caps(struct phy_device *phydev,
+                            phy_interface_t interface);
++int phy_config_inband(struct phy_device *phydev, unsigned int modes);
+ int phy_speed_down(struct phy_device *phydev, bool sync);
+ int phy_speed_up(struct phy_device *phydev);
+ bool phy_check_valid(int speed, int duplex, unsigned long *features);
diff --git a/target/linux/generic/backport-6.12/601-08-v6.14-net-phy-marvell-implement-config_inband-method.patch b/target/linux/generic/backport-6.12/601-08-v6.14-net-phy-marvell-implement-config_inband-method.patch
new file mode 100644 (file)
index 0000000..4531c50
--- /dev/null
@@ -0,0 +1,77 @@
+From a219912e0fec73c346e64ef47013cb2e152f88fc Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:23 +0000
+Subject: [PATCH 08/13] net: phy: marvell: implement config_inband() method
+
+Implement the config_inband() method for Marvell 88E1112, 88E1111,
+and Finisar's 88E1111 variant.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUrz-006IUO-3r@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/marvell.c | 31 +++++++++++++++++++++++++++++++
+ 1 file changed, 31 insertions(+)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -730,6 +730,34 @@ static unsigned int m88e1111_inband_caps
+       return 0;
+ }
++static int m88e1111_config_inband(struct phy_device *phydev, unsigned int modes)
++{
++      u16 extsr, bmcr;
++      int err;
++
++      if (phydev->interface != PHY_INTERFACE_MODE_1000BASEX &&
++          phydev->interface != PHY_INTERFACE_MODE_SGMII)
++              return -EINVAL;
++
++      if (modes == LINK_INBAND_BYPASS)
++              extsr = MII_M1111_HWCFG_SERIAL_AN_BYPASS;
++      else
++              extsr = 0;
++
++      if (modes == LINK_INBAND_DISABLE)
++              bmcr = 0;
++      else
++              bmcr = BMCR_ANENABLE;
++
++      err = phy_modify(phydev, MII_M1111_PHY_EXT_SR,
++                       MII_M1111_HWCFG_SERIAL_AN_BYPASS, extsr);
++      if (err < 0)
++              return extsr;
++
++      return phy_modify_paged(phydev, MII_MARVELL_FIBER_PAGE, MII_BMCR,
++                              BMCR_ANENABLE, bmcr);
++}
++
+ static int m88e1111_config_aneg(struct phy_device *phydev)
+ {
+       int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+@@ -3719,6 +3747,7 @@ static struct phy_driver marvell_drivers
+               /* PHY_GBIT_FEATURES */
+               .probe = marvell_probe,
+               .inband_caps = m88e1111_inband_caps,
++              .config_inband = m88e1111_config_inband,
+               .config_init = m88e1112_config_init,
+               .config_aneg = marvell_config_aneg,
+               .config_intr = marvell_config_intr,
+@@ -3741,6 +3770,7 @@ static struct phy_driver marvell_drivers
+               .flags = PHY_POLL_CABLE_TEST,
+               .probe = marvell_probe,
+               .inband_caps = m88e1111_inband_caps,
++              .config_inband = m88e1111_config_inband,
+               .config_init = m88e1111gbe_config_init,
+               .config_aneg = m88e1111_config_aneg,
+               .read_status = marvell_read_status,
+@@ -3765,6 +3795,7 @@ static struct phy_driver marvell_drivers
+               /* PHY_GBIT_FEATURES */
+               .probe = marvell_probe,
+               .inband_caps = m88e1111_inband_caps,
++              .config_inband = m88e1111_config_inband,
+               .config_init = m88e1111gbe_config_init,
+               .config_aneg = m88e1111_config_aneg,
+               .read_status = marvell_read_status,
diff --git a/target/linux/generic/backport-6.12/601-09-v6.14-net-phylink-add-pcs_inband_caps-method.patch b/target/linux/generic/backport-6.12/601-09-v6.14-net-phylink-add-pcs_inband_caps-method.patch
new file mode 100644 (file)
index 0000000..4228024
--- /dev/null
@@ -0,0 +1,159 @@
+From df874f9e52c340cc6f0a0014a97b778f67d46849 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:28 +0000
+Subject: [PATCH 09/13] net: phylink: add pcs_inband_caps() method
+
+Add a pcs_inband_caps() method to query the PCS for its inband link
+capabilities, and use this to determine whether link modes used with
+optical SFPs can be supported.
+
+When a PCS does not provide a method, we allow inband negotiation to
+be either on or off, making this a no-op until the pcs_inband_caps()
+method is implemented by a PCS driver.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUs4-006IUU-7K@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 60 +++++++++++++++++++++++++++++++++++++++
+ include/linux/phylink.h   | 17 +++++++++++
+ 2 files changed, 77 insertions(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1007,6 +1007,15 @@ static void phylink_resolve_an_pause(str
+       }
+ }
++static unsigned int phylink_pcs_inband_caps(struct phylink_pcs *pcs,
++                                  phy_interface_t interface)
++{
++      if (pcs && pcs->ops->pcs_inband_caps)
++              return pcs->ops->pcs_inband_caps(pcs, interface);
++
++      return 0;
++}
++
+ static void phylink_pcs_pre_config(struct phylink_pcs *pcs,
+                                  phy_interface_t interface)
+ {
+@@ -1060,6 +1069,24 @@ static void phylink_pcs_link_up(struct p
+               pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex);
+ }
++/* Query inband for a specific interface mode, asking the MAC for the
++ * PCS which will be used to handle the interface mode.
++ */
++static unsigned int phylink_inband_caps(struct phylink *pl,
++                                       phy_interface_t interface)
++{
++      struct phylink_pcs *pcs;
++
++      if (!pl->mac_ops->mac_select_pcs)
++              return 0;
++
++      pcs = pl->mac_ops->mac_select_pcs(pl->config, interface);
++      if (!pcs)
++              return 0;
++
++      return phylink_pcs_inband_caps(pcs, interface);
++}
++
+ static void phylink_pcs_poll_stop(struct phylink *pl)
+ {
+       if (pl->cfg_link_an_mode == MLO_AN_INBAND)
+@@ -2530,6 +2557,26 @@ int phylink_ethtool_ksettings_get(struct
+ }
+ EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get);
++static bool phylink_validate_pcs_inband_autoneg(struct phylink *pl,
++                                              phy_interface_t interface,
++                                              unsigned long *adv)
++{
++      unsigned int inband = phylink_inband_caps(pl, interface);
++      unsigned int mask;
++
++      /* If the PCS doesn't implement inband support, be permissive. */
++      if (!inband)
++              return true;
++
++      if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, adv))
++              mask = LINK_INBAND_ENABLE;
++      else
++              mask = LINK_INBAND_DISABLE;
++
++      /* Check whether the PCS implements the required mode */
++      return !!(inband & mask);
++}
++
+ /**
+  * phylink_ethtool_ksettings_set() - set the link settings
+  * @pl: a pointer to a &struct phylink returned from phylink_create()
+@@ -2665,6 +2712,13 @@ int phylink_ethtool_ksettings_set(struct
+           phylink_is_empty_linkmode(config.advertising))
+               return -EINVAL;
++      /* Validate the autonegotiation state. We don't have a PHY in this
++       * situation, so the PCS is the media-facing entity.
++       */
++      if (!phylink_validate_pcs_inband_autoneg(pl, config.interface,
++                                               config.advertising))
++              return -EINVAL;
++
+       mutex_lock(&pl->state_mutex);
+       pl->link_config.speed = config.speed;
+       pl->link_config.duplex = config.duplex;
+@@ -3349,6 +3403,12 @@ static int phylink_sfp_config_optical(st
+       phylink_dbg(pl, "optical SFP: chosen %s interface\n",
+                   phy_modes(interface));
++      if (!phylink_validate_pcs_inband_autoneg(pl, interface,
++                                               config.advertising)) {
++              phylink_err(pl, "autoneg setting not compatible with PCS");
++              return -EINVAL;
++      }
++
+       config.interface = interface;
+       /* Ignore errors if we're expecting a PHY to attach later */
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -419,6 +419,7 @@ struct phylink_pcs {
+ /**
+  * struct phylink_pcs_ops - MAC PCS operations structure.
+  * @pcs_validate: validate the link configuration.
++ * @pcs_inband_caps: query inband support for interface mode.
+  * @pcs_enable: enable the PCS.
+  * @pcs_disable: disable the PCS.
+  * @pcs_pre_config: pre-mac_config method (for errata)
+@@ -434,6 +435,8 @@ struct phylink_pcs {
+ struct phylink_pcs_ops {
+       int (*pcs_validate)(struct phylink_pcs *pcs, unsigned long *supported,
+                           const struct phylink_link_state *state);
++      unsigned int (*pcs_inband_caps)(struct phylink_pcs *pcs,
++                                      phy_interface_t interface);
+       int (*pcs_enable)(struct phylink_pcs *pcs);
+       void (*pcs_disable)(struct phylink_pcs *pcs);
+       void (*pcs_pre_config)(struct phylink_pcs *pcs,
+@@ -471,6 +474,20 @@ int pcs_validate(struct phylink_pcs *pcs
+                const struct phylink_link_state *state);
+ /**
++ * pcs_inband_caps - query PCS in-band capabilities for interface mode.
++ * @pcs: a pointer to a &struct phylink_pcs.
++ * @interface: interface mode to be queried
++ *
++ * Returns zero if it is unknown what in-band signalling is supported by the
++ * PHY (e.g. because the PHY driver doesn't implement the method.) Otherwise,
++ * returns a bit mask of the LINK_INBAND_* values from
++ * &enum link_inband_signalling to describe which inband modes are supported
++ * for this interface mode.
++ */
++unsigned int pcs_inband_caps(struct phylink_pcs *pcs,
++                           phy_interface_t interface);
++
++/**
+  * pcs_enable() - enable the PCS.
+  * @pcs: a pointer to a &struct phylink_pcs.
+  */
diff --git a/target/linux/generic/backport-6.12/601-10-v6.14-net-mvneta-implement-pcs_inband_caps-method.patch b/target/linux/generic/backport-6.12/601-10-v6.14-net-mvneta-implement-pcs_inband_caps-method.patch
new file mode 100644 (file)
index 0000000..0937ad1
--- /dev/null
@@ -0,0 +1,64 @@
+From 513e8fb8fa32035b3325e2e14fb9598f8cb545e9 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:33 +0000
+Subject: [PATCH 10/13] net: mvneta: implement pcs_inband_caps() method
+
+Report the PCS in-band capabilities to phylink for Marvell NETA
+interfaces.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUs9-006IUb-Au@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 27 +++++++++++++++++----------
+ 1 file changed, 17 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3960,20 +3960,27 @@ static struct mvneta_port *mvneta_pcs_to
+       return container_of(pcs, struct mvneta_port, phylink_pcs);
+ }
+-static int mvneta_pcs_validate(struct phylink_pcs *pcs,
+-                             unsigned long *supported,
+-                             const struct phylink_link_state *state)
++static unsigned int mvneta_pcs_inband_caps(struct phylink_pcs *pcs,
++                                         phy_interface_t interface)
+ {
+-      /* We only support QSGMII, SGMII, 802.3z and RGMII modes.
+-       * When in 802.3z mode, we must have AN enabled:
++      /* When operating in an 802.3z mode, we must have AN enabled:
+        * "Bit 2 Field InBandAnEn In-band Auto-Negotiation enable. ...
+        * When <PortType> = 1 (1000BASE-X) this field must be set to 1."
++       * Therefore, inband is "required".
+        */
+-      if (phy_interface_mode_is_8023z(state->interface) &&
+-          !phylink_test(state->advertising, Autoneg))
+-              return -EINVAL;
++      if (phy_interface_mode_is_8023z(interface))
++              return LINK_INBAND_ENABLE;
+-      return 0;
++      /* QSGMII, SGMII and RGMII can be configured to use inband
++       * signalling of the AN result. Indicate these as "possible".
++       */
++      if (interface == PHY_INTERFACE_MODE_SGMII ||
++          interface == PHY_INTERFACE_MODE_QSGMII ||
++          phy_interface_mode_is_rgmii(interface))
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++      /* For any other modes, indicate that inband is not supported. */
++      return LINK_INBAND_DISABLE;
+ }
+ static void mvneta_pcs_get_state(struct phylink_pcs *pcs,
+@@ -4071,7 +4078,7 @@ static void mvneta_pcs_an_restart(struct
+ }
+ static const struct phylink_pcs_ops mvneta_phylink_pcs_ops = {
+-      .pcs_validate = mvneta_pcs_validate,
++      .pcs_inband_caps = mvneta_pcs_inband_caps,
+       .pcs_get_state = mvneta_pcs_get_state,
+       .pcs_config = mvneta_pcs_config,
+       .pcs_an_restart = mvneta_pcs_an_restart,
diff --git a/target/linux/generic/backport-6.12/601-11-v6.14-net-mvpp2-implement-pcs_inband_caps-method.patch b/target/linux/generic/backport-6.12/601-11-v6.14-net-mvpp2-implement-pcs_inband_caps-method.patch
new file mode 100644 (file)
index 0000000..846d9df
--- /dev/null
@@ -0,0 +1,62 @@
+From d4169f0c7665afb8d8adb5e1b1df3db88517d0ad Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:38 +0000
+Subject: [PATCH 11/13] net: mvpp2: implement pcs_inband_caps() method
+
+Report the PCS in-band capabilities to phylink for Marvell PP2
+interfaces.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUsE-006IUh-E7@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ .../net/ethernet/marvell/mvpp2/mvpp2_main.c   | 25 ++++++++++++-------
+ 1 file changed, 16 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
++++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
+@@ -6237,19 +6237,26 @@ static const struct phylink_pcs_ops mvpp
+       .pcs_config = mvpp2_xlg_pcs_config,
+ };
+-static int mvpp2_gmac_pcs_validate(struct phylink_pcs *pcs,
+-                                 unsigned long *supported,
+-                                 const struct phylink_link_state *state)
++static unsigned int mvpp2_gmac_pcs_inband_caps(struct phylink_pcs *pcs,
++                                             phy_interface_t interface)
+ {
+-      /* When in 802.3z mode, we must have AN enabled:
++      /* When operating in an 802.3z mode, we must have AN enabled:
+        * Bit 2 Field InBandAnEn In-band Auto-Negotiation enable. ...
+        * When <PortType> = 1 (1000BASE-X) this field must be set to 1.
++       * Therefore, inband is "required".
+        */
+-      if (phy_interface_mode_is_8023z(state->interface) &&
+-          !phylink_test(state->advertising, Autoneg))
+-              return -EINVAL;
++      if (phy_interface_mode_is_8023z(interface))
++              return LINK_INBAND_ENABLE;
+-      return 0;
++      /* SGMII and RGMII can be configured to use inband signalling of the
++       * AN result. Indicate these as "possible".
++       */
++      if (interface == PHY_INTERFACE_MODE_SGMII ||
++          phy_interface_mode_is_rgmii(interface))
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++      /* For any other modes, indicate that inband is not supported. */
++      return LINK_INBAND_DISABLE;
+ }
+ static void mvpp2_gmac_pcs_get_state(struct phylink_pcs *pcs,
+@@ -6356,7 +6363,7 @@ static void mvpp2_gmac_pcs_an_restart(st
+ }
+ static const struct phylink_pcs_ops mvpp2_phylink_gmac_pcs_ops = {
+-      .pcs_validate = mvpp2_gmac_pcs_validate,
++      .pcs_inband_caps = mvpp2_gmac_pcs_inband_caps,
+       .pcs_get_state = mvpp2_gmac_pcs_get_state,
+       .pcs_config = mvpp2_gmac_pcs_config,
+       .pcs_an_restart = mvpp2_gmac_pcs_an_restart,
diff --git a/target/linux/generic/backport-6.12/601-12-v6.14-net-phylink-add-negotiation-of-in-band-capabilities.patch b/target/linux/generic/backport-6.12/601-12-v6.14-net-phylink-add-negotiation-of-in-band-capabilities.patch
new file mode 100644 (file)
index 0000000..f629133
--- /dev/null
@@ -0,0 +1,228 @@
+From 5fd0f1a02e750e2db4038dee60edea669ce5aab1 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:43 +0000
+Subject: [PATCH 12/13] net: phylink: add negotiation of in-band capabilities
+
+Support for in-band signalling with Serdes links is uncertain. Some
+PHYs do not support in-band for e.g. SGMII. Some PCS do not support
+in-band for 2500Base-X. Some PCS require in-band for Base-X protocols.
+
+Simply using what is in DT is insufficient when we have hot-pluggable
+PHYs e.g. in the form of SFP modules, which may not provide the
+in-band signalling.
+
+In order to address this, we have introduced phy_inband_caps() and
+pcs_inband_caps() functions to allow phylink to retrieve the
+capabilities from each end of the PCS/PHY link. This commit adds code
+to resolve whether in-band will be used in the various scenarios that
+we have: In-band not being used, PHY present using SGMII or Base-X,
+PHY not present. We also deal with no capabilties provided.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUsJ-006IUn-H3@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 154 +++++++++++++++++++++++++++++++++++---
+ 1 file changed, 144 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -75,6 +75,7 @@ struct phylink {
+       struct mutex state_mutex;
+       struct phylink_link_state phy_state;
++      unsigned int phy_ib_mode;
+       struct work_struct resolve;
+       unsigned int pcs_neg_mode;
+       unsigned int pcs_state;
+@@ -1170,10 +1171,18 @@ static void phylink_pcs_neg_mode(struct
+                                phy_interface_t interface,
+                                const unsigned long *advertising)
+ {
++      unsigned int pcs_ib_caps = 0;
++      unsigned int phy_ib_caps = 0;
+       unsigned int neg_mode, mode;
++      enum {
++              INBAND_CISCO_SGMII,
++              INBAND_BASEX,
++      } type;
+       mode = pl->req_link_an_mode;
++      pl->phy_ib_mode = 0;
++
+       switch (interface) {
+       case PHY_INTERFACE_MODE_SGMII:
+       case PHY_INTERFACE_MODE_QSGMII:
+@@ -1185,10 +1194,7 @@ static void phylink_pcs_neg_mode(struct
+                * 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;
++              type = INBAND_CISCO_SGMII;
+               break;
+       case PHY_INTERFACE_MODE_1000BASEX:
+@@ -1199,18 +1205,139 @@ static void phylink_pcs_neg_mode(struct
+                * as well, but drivers may not support this, so may
+                * need to override this.
+                */
+-              if (!phylink_autoneg_inband(mode))
++              type = INBAND_BASEX;
++              break;
++
++      default:
++              pl->pcs_neg_mode = PHYLINK_PCS_NEG_NONE;
++              pl->act_link_an_mode = mode;
++              return;
++      }
++
++      if (pcs)
++              pcs_ib_caps = phylink_pcs_inband_caps(pcs, interface);
++
++      if (pl->phydev)
++              phy_ib_caps = phy_inband_caps(pl->phydev, interface);
++
++      phylink_dbg(pl, "interface %s inband modes: pcs=%02x phy=%02x\n",
++                  phy_modes(interface), pcs_ib_caps, phy_ib_caps);
++
++      if (!phylink_autoneg_inband(mode)) {
++              bool pcs_ib_only = false;
++              bool phy_ib_only = false;
++
++              if (pcs_ib_caps && pcs_ib_caps != LINK_INBAND_DISABLE) {
++                      /* PCS supports reporting in-band capabilities, and
++                       * supports more than disable mode.
++                       */
++                      if (pcs_ib_caps & LINK_INBAND_DISABLE)
++                              neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++                      else if (pcs_ib_caps & LINK_INBAND_ENABLE)
++                              pcs_ib_only = true;
++              }
++
++              if (phy_ib_caps && phy_ib_caps != LINK_INBAND_DISABLE) {
++                      /* PHY supports in-band capabilities, and supports
++                       * more than disable mode.
++                       */
++                      if (phy_ib_caps & LINK_INBAND_DISABLE)
++                              pl->phy_ib_mode = LINK_INBAND_DISABLE;
++                      else if (phy_ib_caps & LINK_INBAND_BYPASS)
++                              pl->phy_ib_mode = LINK_INBAND_BYPASS;
++                      else if (phy_ib_caps & LINK_INBAND_ENABLE)
++                              phy_ib_only = true;
++              }
++
++              /* If either the PCS or PHY requires inband to be enabled,
++               * this is an invalid configuration. Provide a diagnostic
++               * message for this case, but don't try to force the issue.
++               */
++              if (pcs_ib_only || phy_ib_only)
++                      phylink_warn(pl,
++                                   "firmware wants %s mode, but %s%s%s requires inband\n",
++                                   phylink_an_mode_str(mode),
++                                   pcs_ib_only ? "PCS" : "",
++                                   pcs_ib_only && phy_ib_only ? " and " : "",
++                                   phy_ib_only ? "PHY" : "");
++
++              neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++      } else if (type == INBAND_CISCO_SGMII || pl->phydev) {
++              /* For SGMII modes which are designed to be used with PHYs, or
++               * Base-X with a PHY, we try to use in-band mode where-ever
++               * possible. However, there are some PHYs e.g. BCM84881 which
++               * do not support in-band.
++               */
++              const unsigned int inband_ok = LINK_INBAND_ENABLE |
++                                             LINK_INBAND_BYPASS;
++              const unsigned int outband_ok = LINK_INBAND_DISABLE |
++                                              LINK_INBAND_BYPASS;
++              /* PCS  PHY
++               * D E  D E
++               * 0 0  0 0     no information                  inband enabled
++               * 1 0  0 0     pcs doesn't support             outband
++               * 0 1  0 0     pcs required                    inband enabled
++               * 1 1  0 0     pcs optional                    inband enabled
++               * 0 0  1 0     phy doesn't support             outband
++               * 1 0  1 0     pcs+phy doesn't support         outband
++               * 0 1  1 0     pcs required, phy doesn't support, invalid
++               * 1 1  1 0     pcs optional, phy doesn't support, outband
++               * 0 0  0 1     phy required                    inband enabled
++               * 1 0  0 1     pcs doesn't support, phy required, invalid
++               * 0 1  0 1     pcs+phy required                inband enabled
++               * 1 1  0 1     pcs optional, phy required      inband enabled
++               * 0 0  1 1     phy optional                    inband enabled
++               * 1 0  1 1     pcs doesn't support, phy optional, outband
++               * 0 1  1 1     pcs required, phy optional      inband enabled
++               * 1 1  1 1     pcs+phy optional                inband enabled
++               */
++              if ((!pcs_ib_caps || pcs_ib_caps & inband_ok) &&
++                  (!phy_ib_caps || phy_ib_caps & inband_ok)) {
++                      /* In-band supported or unknown at both ends. Enable
++                       * in-band mode with or without bypass at the PHY.
++                       */
++                      if (phy_ib_caps & LINK_INBAND_ENABLE)
++                              pl->phy_ib_mode = LINK_INBAND_ENABLE;
++                      else if (phy_ib_caps & LINK_INBAND_BYPASS)
++                              pl->phy_ib_mode = LINK_INBAND_BYPASS;
++
++                      neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++              } else if ((!pcs_ib_caps || pcs_ib_caps & outband_ok) &&
++                         (!phy_ib_caps || phy_ib_caps & outband_ok)) {
++                      /* Either in-band not supported at at least one end.
++                       * In-band bypass at the other end is possible.
++                       */
++                      if (phy_ib_caps & LINK_INBAND_DISABLE)
++                              pl->phy_ib_mode = LINK_INBAND_DISABLE;
++                      else if (phy_ib_caps & LINK_INBAND_BYPASS)
++                              pl->phy_ib_mode = LINK_INBAND_BYPASS;
++
+                       neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++                      if (pl->phydev)
++                              mode = MLO_AN_PHY;
++              } else {
++                      /* invalid */
++                      phylink_warn(pl, "%s: incompatible in-band capabilities, trying in-band",
++                                   phy_modes(interface));
++                      neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++              }
++      } else {
++              /* For Base-X without a PHY */
++              if (pcs_ib_caps == LINK_INBAND_DISABLE)
++                      /* If the PCS doesn't support inband, then inband must
++                       * be disabled.
++                       */
++                      neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
++              else if (pcs_ib_caps == LINK_INBAND_ENABLE)
++                      /* If the PCS requires inband, then inband must always
++                       * be enabled.
++                       */
++                      neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
+               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;
+       }
+       pl->pcs_neg_mode = neg_mode;
+@@ -1309,6 +1436,13 @@ static void phylink_major_config(struct
+                                   ERR_PTR(err));
+       }
++      if (pl->phydev && pl->phy_ib_mode) {
++              err = phy_config_inband(pl->phydev, pl->phy_ib_mode);
++              if (err < 0)
++                      phylink_err(pl, "phy_config_inband: %pe\n",
++                                  ERR_PTR(err));
++      }
++
+       if (pl->sfp_bus) {
+               rate_kbd = phylink_interface_signal_rate(state->interface);
+               if (rate_kbd)
diff --git a/target/linux/generic/backport-6.12/601-13-v6.14-net-phylink-remove-phylink_phy_no_inband.patch b/target/linux/generic/backport-6.12/601-13-v6.14-net-phylink-remove-phylink_phy_no_inband.patch
new file mode 100644 (file)
index 0000000..8be74a2
--- /dev/null
@@ -0,0 +1,110 @@
+From 77ac9a8b2536e0eaca6c6f21070068458bf55981 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:48 +0000
+Subject: [PATCH 13/13] net: phylink: remove phylink_phy_no_inband()
+
+Remove phylink_phy_no_inband() now that we are handling the lack of
+inband negotiation by querying the capabilities of the PHY and PCS,
+and the BCM84881 PHY driver provides us the information necessary to
+make the decision.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUsO-006IUt-KN@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 27 ++++++---------------------
+ 1 file changed, 6 insertions(+), 21 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -3394,10 +3394,11 @@ static phy_interface_t phylink_choose_sf
+       return interface;
+ }
+-static void phylink_sfp_set_config(struct phylink *pl, u8 mode,
++static void phylink_sfp_set_config(struct phylink *pl,
+                                  unsigned long *supported,
+                                  struct phylink_link_state *state)
+ {
++      u8 mode = MLO_AN_INBAND;
+       bool changed = false;
+       phylink_dbg(pl, "requesting link mode %s/%s with support %*pb\n",
+@@ -3431,8 +3432,7 @@ static void phylink_sfp_set_config(struc
+               phylink_mac_initial_config(pl, false);
+ }
+-static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
+-                                struct phy_device *phy)
++static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy)
+ {
+       __ETHTOOL_DECLARE_LINK_MODE_MASK(support1);
+       __ETHTOOL_DECLARE_LINK_MODE_MASK(support);
+@@ -3472,7 +3472,7 @@ static int phylink_sfp_config_phy(struct
+       if (ret) {
+               phylink_err(pl,
+                           "validation of %s/%s with support %*pb failed: %pe\n",
+-                          phylink_an_mode_str(mode),
++                          phylink_an_mode_str(pl->req_link_an_mode),
+                           phy_modes(config.interface),
+                           __ETHTOOL_LINK_MODE_MASK_NBITS, support,
+                           ERR_PTR(ret));
+@@ -3481,7 +3481,7 @@ static int phylink_sfp_config_phy(struct
+       pl->link_port = pl->sfp_port;
+-      phylink_sfp_set_config(pl, mode, support, &config);
++      phylink_sfp_set_config(pl, support, &config);
+       return 0;
+ }
+@@ -3556,7 +3556,7 @@ static int phylink_sfp_config_optical(st
+       pl->link_port = pl->sfp_port;
+-      phylink_sfp_set_config(pl, MLO_AN_INBAND, pl->sfp_support, &config);
++      phylink_sfp_set_config(pl, pl->sfp_support, &config);
+       return 0;
+ }
+@@ -3627,20 +3627,10 @@ static void phylink_sfp_link_up(void *up
+       phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_LINK);
+ }
+-/* The Broadcom BCM84881 in the Methode DM7052 is unable to provide a SGMII
+- * or 802.3z control word, so inband will not work.
+- */
+-static bool phylink_phy_no_inband(struct phy_device *phy)
+-{
+-      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)
+ {
+       struct phylink *pl = upstream;
+       phy_interface_t interface;
+-      u8 mode;
+       int ret;
+       /*
+@@ -3652,17 +3642,12 @@ static int phylink_sfp_connect_phy(void
+        */
+       phy_support_asym_pause(phy);
+-      if (phylink_phy_no_inband(phy))
+-              mode = MLO_AN_PHY;
+-      else
+-              mode = MLO_AN_INBAND;
+-
+       /* Set the PHY's host supported interfaces */
+       phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces,
+                         pl->config->supported_interfaces);
+       /* Do the initial configuration */
+-      ret = phylink_sfp_config_phy(pl, mode, phy);
++      ret = phylink_sfp_config_phy(pl, phy);
+       if (ret < 0)
+               return ret;
diff --git a/target/linux/generic/backport-6.12/602-01-v6.14-net-pcs-pcs-lynx-implement-pcs_inband_caps-method.patch b/target/linux/generic/backport-6.12/602-01-v6.14-net-pcs-pcs-lynx-implement-pcs_inband_caps-method.patch
new file mode 100644 (file)
index 0000000..32ba605
--- /dev/null
@@ -0,0 +1,53 @@
+From 6561f0e547be221f411fda5eddfcc5bd8bb058a5 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 5 Dec 2024 09:42:24 +0000
+Subject: [PATCH 1/3] net: pcs: pcs-lynx: implement pcs_inband_caps() method
+
+Report the PCS in-band capabilities to phylink for the Lynx PCS.
+
+Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tJ8NM-006L5J-AH@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/pcs/pcs-lynx.c | 22 ++++++++++++++++++++++
+ 1 file changed, 22 insertions(+)
+
+--- a/drivers/net/pcs/pcs-lynx.c
++++ b/drivers/net/pcs/pcs-lynx.c
+@@ -35,6 +35,27 @@ enum sgmii_speed {
+ #define phylink_pcs_to_lynx(pl_pcs) container_of((pl_pcs), struct lynx_pcs, pcs)
+ #define lynx_to_phylink_pcs(lynx) (&(lynx)->pcs)
++static unsigned int lynx_pcs_inband_caps(struct phylink_pcs *pcs,
++                                       phy_interface_t interface)
++{
++      switch (interface) {
++      case PHY_INTERFACE_MODE_1000BASEX:
++      case PHY_INTERFACE_MODE_SGMII:
++      case PHY_INTERFACE_MODE_QSGMII:
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++      case PHY_INTERFACE_MODE_10GBASER:
++      case PHY_INTERFACE_MODE_2500BASEX:
++              return LINK_INBAND_DISABLE;
++
++      case PHY_INTERFACE_MODE_USXGMII:
++              return LINK_INBAND_ENABLE;
++
++      default:
++              return 0;
++      }
++}
++
+ static void lynx_pcs_get_state_usxgmii(struct mdio_device *pcs,
+                                      struct phylink_link_state *state)
+ {
+@@ -306,6 +327,7 @@ static void lynx_pcs_link_up(struct phyl
+ }
+ static const struct phylink_pcs_ops lynx_pcs_phylink_ops = {
++      .pcs_inband_caps = lynx_pcs_inband_caps,
+       .pcs_get_state = lynx_pcs_get_state,
+       .pcs_config = lynx_pcs_config,
+       .pcs_an_restart = lynx_pcs_an_restart,
diff --git a/target/linux/generic/backport-6.12/602-02-v6.14-net-pcs-pcs-mtk-lynxi-implement-pcs_inband_caps-meth.patch b/target/linux/generic/backport-6.12/602-02-v6.14-net-pcs-pcs-mtk-lynxi-implement-pcs_inband_caps-meth.patch
new file mode 100644 (file)
index 0000000..1cfd3ba
--- /dev/null
@@ -0,0 +1,47 @@
+From 520d29bdda86915b3caf8c72825a574bff212553 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 5 Dec 2024 09:42:29 +0000
+Subject: [PATCH 2/3] net: pcs: pcs-mtk-lynxi: implement pcs_inband_caps()
+ method
+
+Report the PCS in-band capabilities to phylink for the LynxI PCS.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tJ8NR-006L5P-E3@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/pcs/pcs-mtk-lynxi.c | 16 ++++++++++++++++
+ 1 file changed, 16 insertions(+)
+
+--- a/drivers/net/pcs/pcs-mtk-lynxi.c
++++ b/drivers/net/pcs/pcs-mtk-lynxi.c
+@@ -88,6 +88,21 @@ static struct mtk_pcs_lynxi *pcs_to_mtk_
+       return container_of(pcs, struct mtk_pcs_lynxi, pcs);
+ }
++static unsigned int mtk_pcs_lynxi_inband_caps(struct phylink_pcs *pcs,
++                                            phy_interface_t interface)
++{
++      switch (interface) {
++      case PHY_INTERFACE_MODE_1000BASEX:
++      case PHY_INTERFACE_MODE_2500BASEX:
++      case PHY_INTERFACE_MODE_SGMII:
++      case PHY_INTERFACE_MODE_QSGMII:
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++      default:
++              return 0;
++      }
++}
++
+ static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs,
+                                   struct phylink_link_state *state)
+ {
+@@ -241,6 +256,7 @@ static void mtk_pcs_lynxi_disable(struct
+ }
+ static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = {
++      .pcs_inband_caps = mtk_pcs_lynxi_inband_caps,
+       .pcs_get_state = mtk_pcs_lynxi_get_state,
+       .pcs_config = mtk_pcs_lynxi_config,
+       .pcs_an_restart = mtk_pcs_lynxi_restart_an,
diff --git a/target/linux/generic/backport-6.12/602-03-v6.14-net-pcs-xpcs-implement-pcs_inband_caps-method.patch b/target/linux/generic/backport-6.12/602-03-v6.14-net-pcs-xpcs-implement-pcs_inband_caps-method.patch
new file mode 100644 (file)
index 0000000..8dc495b
--- /dev/null
@@ -0,0 +1,58 @@
+From 484d0170d6c6bbb5213d037664e9a551f793bacd Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 5 Dec 2024 09:42:34 +0000
+Subject: [PATCH 3/3] net: pcs: xpcs: implement pcs_inband_caps() method
+
+Report the PCS inband capabilities to phylink for XPCS.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tJ8NW-006L5V-I9@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/pcs/pcs-xpcs.c | 28 ++++++++++++++++++++++++++++
+ 1 file changed, 28 insertions(+)
+
+--- a/drivers/net/pcs/pcs-xpcs.c
++++ b/drivers/net/pcs/pcs-xpcs.c
+@@ -608,6 +608,33 @@ static int xpcs_validate(struct phylink_
+       return 0;
+ }
++static unsigned int xpcs_inband_caps(struct phylink_pcs *pcs,
++                                   phy_interface_t interface)
++{
++      struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
++      const struct dw_xpcs_compat *compat;
++
++      compat = xpcs_find_compat(xpcs->desc, interface);
++      if (!compat)
++              return 0;
++
++      switch (compat->an_mode) {
++      case DW_AN_C73:
++              return LINK_INBAND_ENABLE;
++
++      case DW_AN_C37_SGMII:
++      case DW_AN_C37_1000BASEX:
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++      case DW_10GBASER:
++      case DW_2500BASEX:
++              return LINK_INBAND_DISABLE;
++
++      default:
++              return 0;
++      }
++}
++
+ void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces)
+ {
+       int i, j;
+@@ -1365,6 +1392,7 @@ static const struct dw_xpcs_desc xpcs_de
+ static const struct phylink_pcs_ops xpcs_phylink_ops = {
+       .pcs_validate = xpcs_validate,
++      .pcs_inband_caps = xpcs_inband_caps,
+       .pcs_config = xpcs_config,
+       .pcs_get_state = xpcs_get_state,
+       .pcs_an_restart = xpcs_an_restart,
diff --git a/target/linux/generic/backport-6.12/605-02-v6.17-net-phylink-provide-phylink_get_inband_type.patch b/target/linux/generic/backport-6.12/605-02-v6.17-net-phylink-provide-phylink_get_inband_type.patch
new file mode 100644 (file)
index 0000000..e944f57
--- /dev/null
@@ -0,0 +1,117 @@
+From 1bd905dfea9897eafef532000702e63a66849f54 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Sun, 31 Aug 2025 18:34:38 +0100
+Subject: net: phylink: provide phylink_get_inband_type()
+
+Provide a function to get the type of the inband signalling used for
+a PHY interface type. This will be used in the subsequent patch to
+address problems with 10G optical modules.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/E1uslws-00000001SP5-1R2R@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 79 ++++++++++++++++++++++-----------------
+ 1 file changed, 44 insertions(+), 35 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1147,6 +1147,42 @@ static void phylink_pcs_an_restart(struc
+               pl->pcs->ops->pcs_an_restart(pl->pcs);
+ }
++enum inband_type {
++      INBAND_NONE,
++      INBAND_CISCO_SGMII,
++      INBAND_BASEX,
++};
++
++static enum inband_type phylink_get_inband_type(phy_interface_t interface)
++{
++      switch (interface) {
++      case PHY_INTERFACE_MODE_SGMII:
++      case PHY_INTERFACE_MODE_QSGMII:
++      case PHY_INTERFACE_MODE_QUSGMII:
++      case PHY_INTERFACE_MODE_USXGMII:
++      case PHY_INTERFACE_MODE_10G_QXGMII:
++              /* 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.
++               */
++              return INBAND_CISCO_SGMII;
++
++      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.
++               */
++              return INBAND_BASEX;
++
++      default:
++              return INBAND_NONE;
++      }
++}
++
+ /**
+  * phylink_pcs_neg_mode() - helper to determine PCS inband mode
+  * @pl: a pointer to a &struct phylink returned from phylink_create()
+@@ -1174,46 +1210,19 @@ static void phylink_pcs_neg_mode(struct
+       unsigned int pcs_ib_caps = 0;
+       unsigned int phy_ib_caps = 0;
+       unsigned int neg_mode, mode;
+-      enum {
+-              INBAND_CISCO_SGMII,
+-              INBAND_BASEX,
+-      } type;
+-
+-      mode = pl->req_link_an_mode;
++      enum inband_type type;
+-      pl->phy_ib_mode = 0;
+-
+-      switch (interface) {
+-      case PHY_INTERFACE_MODE_SGMII:
+-      case PHY_INTERFACE_MODE_QSGMII:
+-      case PHY_INTERFACE_MODE_QUSGMII:
+-      case PHY_INTERFACE_MODE_USXGMII:
+-      case PHY_INTERFACE_MODE_10G_QXGMII:
+-              /* 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.
+-               */
+-              type = INBAND_CISCO_SGMII;
+-              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.
+-               */
+-              type = INBAND_BASEX;
+-              break;
+-
+-      default:
++      type = phylink_get_inband_type(interface);
++      if (type == INBAND_NONE) {
+               pl->pcs_neg_mode = PHYLINK_PCS_NEG_NONE;
+-              pl->act_link_an_mode = mode;
++              pl->act_link_an_mode = pl->req_link_an_mode;
+               return;
+       }
++      mode = pl->req_link_an_mode;
++
++      pl->phy_ib_mode = 0;
++
+       if (pcs)
+               pcs_ib_caps = phylink_pcs_inband_caps(pcs, interface);
diff --git a/target/linux/generic/backport-6.12/605-03-v6.17-net-phylink-disable-autoneg-for-interfaces-that-have.patch b/target/linux/generic/backport-6.12/605-03-v6.17-net-phylink-disable-autoneg-for-interfaces-that-have.patch
new file mode 100644 (file)
index 0000000..0861bb8
--- /dev/null
@@ -0,0 +1,68 @@
+From a21202743f9ce4063e86b99cccaef48ef9813379 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Sun, 31 Aug 2025 18:34:43 +0100
+Subject: net: phylink: disable autoneg for interfaces that have no inband
+
+Mathew reports that as a result of commit 6561f0e547be ("net: pcs:
+pcs-lynx: implement pcs_inband_caps() method"), 10G SFP modules no
+longer work with the Lynx PCS.
+
+This problem is not specific to the Lynx PCS, but is caused by commit
+df874f9e52c3 ("net: phylink: add pcs_inband_caps() method") which added
+validation of the autoneg state to the optical SFP configuration path.
+
+Fix this by handling interface modes that fundamentally have no
+inband negotiation more correctly - if we only have a single interface
+mode, clear the Autoneg support bit and the advertising mask. If the
+module can operate with several different interface modes, autoneg may
+be supported for other modes, so leave the support mask alone and just
+clear the Autoneg bit in the advertising mask.
+
+This restores 10G optical module functionality with PCS that supply
+their inband support, and makes ethtool output look sane.
+
+Reported-by: Mathew McBride <matt@traverse.com.au>
+Closes: https://lore.kernel.org/r/025c0ebe-5537-4fa3-b05a-8b835e5ad317@app.fastmail.com
+Fixes: df874f9e52c3 ("net: phylink: add pcs_inband_caps() method")
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Tested-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Link: https://patch.msgid.link/E1uslwx-00000001SPB-2kiM@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -3500,6 +3500,7 @@ static int phylink_sfp_config_optical(st
+       __ETHTOOL_DECLARE_LINK_MODE_MASK(support);
+       DECLARE_PHY_INTERFACE_MASK(interfaces);
+       struct phylink_link_state config;
++      enum inband_type inband_type;
+       phy_interface_t interface;
+       int ret;
+@@ -3546,6 +3547,23 @@ static int phylink_sfp_config_optical(st
+       phylink_dbg(pl, "optical SFP: chosen %s interface\n",
+                   phy_modes(interface));
++      inband_type = phylink_get_inband_type(interface);
++      if (inband_type == INBAND_NONE) {
++              /* If this is the sole interface, and there is no inband
++               * support, clear the advertising mask and Autoneg bit in
++               * the support mask. Otherwise, just clear the Autoneg bit
++               * in the advertising mask.
++               */
++              if (phy_interface_weight(pl->sfp_interfaces) == 1) {
++                      linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++                                         pl->sfp_support);
++                      linkmode_zero(config.advertising);
++              } else {
++                      linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++                                         config.advertising);
++              }
++      }
++
+       if (!phylink_validate_pcs_inband_autoneg(pl, interface,
+                                                config.advertising)) {
+               phylink_err(pl, "autoneg setting not compatible with PCS");
diff --git a/target/linux/generic/backport-6.12/610-01-v6.13-net-dsa-use-ethtool-string-helpers.patch b/target/linux/generic/backport-6.12/610-01-v6.13-net-dsa-use-ethtool-string-helpers.patch
new file mode 100644 (file)
index 0000000..a435e29
--- /dev/null
@@ -0,0 +1,30 @@
+From f12b363887c706c40611fba645265527a8415832 Mon Sep 17 00:00:00 2001
+From: Rosen Penev <rosenp@gmail.com>
+Date: Sun, 27 Oct 2024 21:48:28 -0700
+Subject: [PATCH] net: dsa: use ethtool string helpers
+
+These are the preferred way to copy ethtool strings.
+
+Avoids incrementing pointers all over the place.
+
+Signed-off-by: Rosen Penev <rosenp@gmail.com>
+(for hellcreek driver)
+Reviewed-by: Kurt Kanzenbach <kurt@linutronix.de>
+Link: https://patch.msgid.link/20241028044828.1639668-1-rosenp@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -1086,8 +1086,7 @@ void b53_get_strings(struct dsa_switch *
+       if (stringset == ETH_SS_STATS) {
+               for (i = 0; i < mib_size; i++)
+-                      strscpy(data + i * ETH_GSTRING_LEN,
+-                              mibs[i].name, ETH_GSTRING_LEN);
++                      ethtool_puts(&data, mibs[i].name);
+       } else if (stringset == ETH_SS_PHY_STATS) {
+               phydev = b53_get_phy_device(ds, port);
+               if (!phydev)
diff --git a/target/linux/generic/backport-6.12/610-02-v6.15-net-dsa-b53-mdio-add-support-for-BCM53101.patch b/target/linux/generic/backport-6.12/610-02-v6.15-net-dsa-b53-mdio-add-support-for-BCM53101.patch
new file mode 100644 (file)
index 0000000..6cbe547
--- /dev/null
@@ -0,0 +1,76 @@
+From c4f873c2b65c839ff5e7c996bd9ef5a1e7eae11a Mon Sep 17 00:00:00 2001
+From: Torben Nielsen <torben.nielsen@prevas.dk>
+Date: Mon, 17 Feb 2025 09:05:01 +0100
+Subject: [PATCH] net: dsa: b53: mdio: add support for BCM53101
+
+BCM53101 is a ethernet switch, very similar to the BCM53115.
+Enable support for it, in the existing b53 dsa driver.
+
+Signed-off-by: Torben Nielsen <torben.nielsen@prevas.dk>
+Signed-off-by: Claus Stovgaard <claus.stovgaard@prevas.dk>
+Link: https://patch.msgid.link/20250217080503.1390282-1-claus.stovgaard@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 14 ++++++++++++++
+ drivers/net/dsa/b53/b53_mdio.c   |  1 +
+ drivers/net/dsa/b53/b53_priv.h   |  2 ++
+ 3 files changed, 17 insertions(+)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -2606,6 +2606,19 @@ static const struct b53_chip_data b53_sw
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+       },
+       {
++              .chip_id = BCM53101_DEVICE_ID,
++              .dev_name = "BCM53101",
++              .vlans = 4096,
++              .enabled_ports = 0x11f,
++              .arl_bins = 4,
++              .arl_buckets = 512,
++              .vta_regs = B53_VTA_REGS,
++              .imp_port = 8,
++              .duplex_reg = B53_DUPLEX_STAT_GE,
++              .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
++              .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++      },
++      {
+               .chip_id = BCM53115_DEVICE_ID,
+               .dev_name = "BCM53115",
+               .vlans = 4096,
+@@ -2986,6 +2999,7 @@ int b53_switch_detect(struct b53_device
+                       return ret;
+               switch (id32) {
++              case BCM53101_DEVICE_ID:
+               case BCM53115_DEVICE_ID:
+               case BCM53125_DEVICE_ID:
+               case BCM53128_DEVICE_ID:
+--- a/drivers/net/dsa/b53/b53_mdio.c
++++ b/drivers/net/dsa/b53/b53_mdio.c
+@@ -374,6 +374,7 @@ static void b53_mdio_shutdown(struct mdi
+ static const struct of_device_id b53_of_match[] = {
+       { .compatible = "brcm,bcm5325" },
++      { .compatible = "brcm,bcm53101" },
+       { .compatible = "brcm,bcm53115" },
+       { .compatible = "brcm,bcm53125" },
+       { .compatible = "brcm,bcm53128" },
+--- a/drivers/net/dsa/b53/b53_priv.h
++++ b/drivers/net/dsa/b53/b53_priv.h
+@@ -66,6 +66,7 @@ enum {
+       BCM5395_DEVICE_ID = 0x95,
+       BCM5397_DEVICE_ID = 0x97,
+       BCM5398_DEVICE_ID = 0x98,
++      BCM53101_DEVICE_ID = 0x53101,
+       BCM53115_DEVICE_ID = 0x53115,
+       BCM53125_DEVICE_ID = 0x53125,
+       BCM53128_DEVICE_ID = 0x53128,
+@@ -190,6 +191,7 @@ static inline int is531x5(struct b53_dev
+ {
+       return dev->chip_id == BCM53115_DEVICE_ID ||
+               dev->chip_id == BCM53125_DEVICE_ID ||
++              dev->chip_id == BCM53101_DEVICE_ID ||
+               dev->chip_id == BCM53128_DEVICE_ID ||
+               dev->chip_id == BCM53134_DEVICE_ID;
+ }
diff --git a/target/linux/generic/backport-6.12/610-03-v6.16-net-dsa-b53-implement-setting-ageing-time.patch b/target/linux/generic/backport-6.12/610-03-v6.16-net-dsa-b53-implement-setting-ageing-time.patch
new file mode 100644 (file)
index 0000000..196e70c
--- /dev/null
@@ -0,0 +1,105 @@
+From e39d14a760c039af0653e3df967e7525413924a0 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Sat, 10 May 2025 11:22:11 +0200
+Subject: [PATCH] net: dsa: b53: implement setting ageing time
+
+b53 supported switches support configuring ageing time between 1 and
+1,048,575 seconds, so add an appropriate setter.
+
+This allows b53 to pass the FDB learning test for both vlan aware and
+vlan unaware bridges.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250510092211.276541-1-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 28 ++++++++++++++++++++++++++++
+ drivers/net/dsa/b53/b53_priv.h   |  1 +
+ drivers/net/dsa/b53/b53_regs.h   |  7 +++++++
+ 3 files changed, 36 insertions(+)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -21,6 +21,7 @@
+ #include <linux/export.h>
+ #include <linux/gpio.h>
+ #include <linux/kernel.h>
++#include <linux/math.h>
+ #include <linux/module.h>
+ #include <linux/platform_data/b53.h>
+ #include <linux/phy.h>
+@@ -1227,6 +1228,10 @@ static int b53_setup(struct dsa_switch *
+        */
+       ds->untag_vlan_aware_bridge_pvid = true;
++      /* Ageing time is set in seconds */
++      ds->ageing_time_min = 1 * 1000;
++      ds->ageing_time_max = AGE_TIME_MAX * 1000;
++
+       ret = b53_reset_switch(dev);
+       if (ret) {
+               dev_err(ds->dev, "failed to reset switch\n");
+@@ -2466,6 +2471,28 @@ static int b53_get_max_mtu(struct dsa_sw
+       return B53_MAX_MTU;
+ }
++int b53_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
++{
++      struct b53_device *dev = ds->priv;
++      u32 atc;
++      int reg;
++
++      if (is63xx(dev))
++              reg = B53_AGING_TIME_CONTROL_63XX;
++      else
++              reg = B53_AGING_TIME_CONTROL;
++
++      atc = DIV_ROUND_CLOSEST(msecs, 1000);
++
++      if (!is5325(dev) && !is5365(dev))
++              atc |= AGE_CHANGE;
++
++      b53_write32(dev, B53_MGMT_PAGE, reg, atc);
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(b53_set_ageing_time);
++
+ static const struct phylink_mac_ops b53_phylink_mac_ops = {
+       .mac_select_pcs = b53_phylink_mac_select_pcs,
+       .mac_config     = b53_phylink_mac_config,
+@@ -2490,6 +2517,7 @@ static const struct dsa_switch_ops b53_s
+       .support_eee            = b53_support_eee,
+       .get_mac_eee            = b53_get_mac_eee,
+       .set_mac_eee            = b53_set_mac_eee,
++      .set_ageing_time        = b53_set_ageing_time,
+       .port_bridge_join       = b53_br_join,
+       .port_bridge_leave      = b53_br_leave,
+       .port_pre_bridge_flags  = b53_br_flags_pre,
+--- a/drivers/net/dsa/b53/b53_priv.h
++++ b/drivers/net/dsa/b53/b53_priv.h
+@@ -343,6 +343,7 @@ void b53_get_strings(struct dsa_switch *
+ void b53_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data);
+ int b53_get_sset_count(struct dsa_switch *ds, int port, int sset);
+ void b53_get_ethtool_phy_stats(struct dsa_switch *ds, int port, uint64_t *data);
++int b53_set_ageing_time(struct dsa_switch *ds, unsigned int msecs);
+ int b53_br_join(struct dsa_switch *ds, int port, struct dsa_bridge bridge,
+               bool *tx_fwd_offload, struct netlink_ext_ack *extack);
+ void b53_br_leave(struct dsa_switch *ds, int port, struct dsa_bridge bridge);
+--- a/drivers/net/dsa/b53/b53_regs.h
++++ b/drivers/net/dsa/b53/b53_regs.h
+@@ -224,6 +224,13 @@
+ #define   BRCM_HDR_P5_EN              BIT(1) /* Enable tagging on port 5 */
+ #define   BRCM_HDR_P7_EN              BIT(2) /* Enable tagging on port 7 */
++/* Aging Time control register (32 bit) */
++#define B53_AGING_TIME_CONTROL                0x06
++#define B53_AGING_TIME_CONTROL_63XX   0x08
++#define  AGE_CHANGE                   BIT(20)
++#define  AGE_TIME_MASK                        0x7ffff
++#define  AGE_TIME_MAX                 1048575
++
+ /* Mirror capture control register (16 bit) */
+ #define B53_MIR_CAP_CTL                       0x10
+ #define  CAP_PORT_MASK                        0xf
diff --git a/target/linux/generic/backport-6.12/610-04-v6.16-net-dsa-b53-do-not-configure-bcm63xx-s-IMP-port-inte.patch b/target/linux/generic/backport-6.12/610-04-v6.16-net-dsa-b53-do-not-configure-bcm63xx-s-IMP-port-inte.patch
new file mode 100644 (file)
index 0000000..e70f09e
--- /dev/null
@@ -0,0 +1,70 @@
+From 75f4f7b2b13008803f84768ff90396f9d7553221 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Mon, 2 Jun 2025 21:39:51 +0200
+Subject: [PATCH] net: dsa: b53: do not configure bcm63xx's IMP port interface
+
+The IMP port is not a valid RGMII interface, but hard wired to internal,
+so we shouldn't touch the undefined register B53_RGMII_CTRL_IMP.
+
+While this does not seem to have any side effects, let's not touch it at
+all, so limit RGMII configuration on bcm63xx to the actual RGMII ports.
+
+Fixes: ce3bf94871f7 ("net: dsa: b53: add support for BCM63xx RGMIIs")
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250602193953.1010487-4-jonas.gorski@gmail.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/dsa/b53/b53_common.c | 22 ++++++++--------------
+ 1 file changed, 8 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -22,6 +22,7 @@
+ #include <linux/gpio.h>
+ #include <linux/kernel.h>
+ #include <linux/math.h>
++#include <linux/minmax.h>
+ #include <linux/module.h>
+ #include <linux/platform_data/b53.h>
+ #include <linux/phy.h>
+@@ -1369,24 +1370,17 @@ static void b53_adjust_63xx_rgmii(struct
+                                 phy_interface_t interface)
+ {
+       struct b53_device *dev = ds->priv;
+-      u8 rgmii_ctrl = 0, off;
+-
+-      if (port == dev->imp_port)
+-              off = B53_RGMII_CTRL_IMP;
+-      else
+-              off = B53_RGMII_CTRL_P(port);
++      u8 rgmii_ctrl = 0;
+-      b53_read8(dev, B53_CTRL_PAGE, off, &rgmii_ctrl);
++      b53_read8(dev, B53_CTRL_PAGE, B53_RGMII_CTRL_P(port), &rgmii_ctrl);
+       rgmii_ctrl &= ~(RGMII_CTRL_DLL_RXC | RGMII_CTRL_DLL_TXC);
+-      if (port != dev->imp_port) {
+-              if (is63268(dev))
+-                      rgmii_ctrl |= RGMII_CTRL_MII_OVERRIDE;
++      if (is63268(dev))
++              rgmii_ctrl |= RGMII_CTRL_MII_OVERRIDE;
+-              rgmii_ctrl |= RGMII_CTRL_ENABLE_GMII;
+-      }
++      rgmii_ctrl |= RGMII_CTRL_ENABLE_GMII;
+-      b53_write8(dev, B53_CTRL_PAGE, off, rgmii_ctrl);
++      b53_write8(dev, B53_CTRL_PAGE, B53_RGMII_CTRL_P(port), rgmii_ctrl);
+       dev_dbg(ds->dev, "Configured port %d for %s\n", port,
+               phy_modes(interface));
+@@ -1537,7 +1531,7 @@ static void b53_phylink_mac_config(struc
+       struct b53_device *dev = ds->priv;
+       int port = dp->index;
+-      if (is63xx(dev) && port >= B53_63XX_RGMII0)
++      if (is63xx(dev) && in_range(port, B53_63XX_RGMII0, 4))
+               b53_adjust_63xx_rgmii(ds, port, interface);
+       if (mode == MLO_AN_FIXED) {
diff --git a/target/linux/generic/backport-6.12/611-01-v6.17-net-dsa-tag_brcm-add-support-for-legacy-FCS-tags.patch b/target/linux/generic/backport-6.12/611-01-v6.17-net-dsa-tag_brcm-add-support-for-legacy-FCS-tags.patch
new file mode 100644 (file)
index 0000000..88f10be
--- /dev/null
@@ -0,0 +1,188 @@
+From ef07df397a621707903ef0d294a7df11f80cf206 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= <noltari@gmail.com>
+Date: Sat, 14 Jun 2025 09:59:48 +0200
+Subject: [PATCH] net: dsa: tag_brcm: add support for legacy FCS tags
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add support for legacy Broadcom FCS tags, which are similar to
+DSA_TAG_PROTO_BRCM_LEGACY.
+BCM5325 and BCM5365 switches require including the original FCS value and
+length, as opposed to BCM63xx switches.
+Adding the original FCS value and length to DSA_TAG_PROTO_BRCM_LEGACY would
+impact performance of BCM63xx switches, so it's better to create a new tag.
+
+Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250614080000.1884236-3-noltari@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ include/net/dsa.h  |  2 ++
+ net/dsa/Kconfig    | 16 ++++++++--
+ net/dsa/tag_brcm.c | 73 +++++++++++++++++++++++++++++++++++++++++++++-
+ 3 files changed, 88 insertions(+), 3 deletions(-)
+
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -54,11 +54,13 @@ struct tc_action;
+ #define DSA_TAG_PROTO_RZN1_A5PSW_VALUE                26
+ #define DSA_TAG_PROTO_LAN937X_VALUE           27
+ #define DSA_TAG_PROTO_VSC73XX_8021Q_VALUE     28
++#define DSA_TAG_PROTO_BRCM_LEGACY_FCS_VALUE   29
+ enum dsa_tag_protocol {
+       DSA_TAG_PROTO_NONE              = DSA_TAG_PROTO_NONE_VALUE,
+       DSA_TAG_PROTO_BRCM              = DSA_TAG_PROTO_BRCM_VALUE,
+       DSA_TAG_PROTO_BRCM_LEGACY       = DSA_TAG_PROTO_BRCM_LEGACY_VALUE,
++      DSA_TAG_PROTO_BRCM_LEGACY_FCS   = DSA_TAG_PROTO_BRCM_LEGACY_FCS_VALUE,
+       DSA_TAG_PROTO_BRCM_PREPEND      = DSA_TAG_PROTO_BRCM_PREPEND_VALUE,
+       DSA_TAG_PROTO_DSA               = DSA_TAG_PROTO_DSA_VALUE,
+       DSA_TAG_PROTO_EDSA              = DSA_TAG_PROTO_EDSA_VALUE,
+--- a/net/dsa/Kconfig
++++ b/net/dsa/Kconfig
+@@ -42,12 +42,24 @@ config NET_DSA_TAG_BRCM
+         Broadcom switches which place the tag after the MAC source address.
+ config NET_DSA_TAG_BRCM_LEGACY
+-      tristate "Tag driver for Broadcom legacy switches using in-frame headers"
++      tristate "Tag driver for BCM63xx legacy switches using in-frame headers"
+       select NET_DSA_TAG_BRCM_COMMON
+       help
+         Say Y if you want to enable support for tagging frames for the
+-        Broadcom legacy switches which place the tag after the MAC source
++        BCM63xx legacy switches which place the tag after the MAC source
+         address.
++        This tag is used in BCM63xx legacy switches which work without the
++        original FCS and length before the tag insertion.
++
++config NET_DSA_TAG_BRCM_LEGACY_FCS
++      tristate "Tag driver for BCM53xx legacy switches using in-frame headers"
++      select NET_DSA_TAG_BRCM_COMMON
++      help
++        Say Y if you want to enable support for tagging frames for the
++        BCM53xx legacy switches which place the tag after the MAC source
++        address.
++        This tag is used in BCM53xx legacy switches which expect original
++        FCS and length before the tag insertion to be present.
+ config NET_DSA_TAG_BRCM_PREPEND
+       tristate "Tag driver for Broadcom switches using prepended headers"
+--- a/net/dsa/tag_brcm.c
++++ b/net/dsa/tag_brcm.c
+@@ -15,6 +15,7 @@
+ #define BRCM_NAME             "brcm"
+ #define BRCM_LEGACY_NAME      "brcm-legacy"
++#define BRCM_LEGACY_FCS_NAME  "brcm-legacy-fcs"
+ #define BRCM_PREPEND_NAME     "brcm-prepend"
+ /* Legacy Broadcom tag (6 bytes) */
+@@ -32,6 +33,10 @@
+ #define BRCM_LEG_MULTICAST    (1 << 5)
+ #define BRCM_LEG_EGRESS               (2 << 5)
+ #define BRCM_LEG_INGRESS      (3 << 5)
++#define BRCM_LEG_LEN_HI(x)    (((x) >> 8) & 0x7)
++
++/* 4th byte in the tag */
++#define BRCM_LEG_LEN_LO(x)    ((x) & 0xff)
+ /* 6th byte in the tag */
+ #define BRCM_LEG_PORT_ID      (0xf)
+@@ -212,7 +217,8 @@ DSA_TAG_DRIVER(brcm_netdev_ops);
+ MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_BRCM, BRCM_NAME);
+ #endif
+-#if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_LEGACY)
++#if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_LEGACY) || \
++      IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_LEGACY_FCS)
+ static struct sk_buff *brcm_leg_tag_rcv(struct sk_buff *skb,
+                                       struct net_device *dev)
+ {
+@@ -250,7 +256,9 @@ static struct sk_buff *brcm_leg_tag_rcv(
+       return skb;
+ }
++#endif /* CONFIG_NET_DSA_TAG_BRCM_LEGACY || CONFIG_NET_DSA_TAG_BRCM_LEGACY_FCS */
++#if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_LEGACY)
+ static struct sk_buff *brcm_leg_tag_xmit(struct sk_buff *skb,
+                                        struct net_device *dev)
+ {
+@@ -300,6 +308,66 @@ DSA_TAG_DRIVER(brcm_legacy_netdev_ops);
+ MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_BRCM_LEGACY, BRCM_LEGACY_NAME);
+ #endif /* CONFIG_NET_DSA_TAG_BRCM_LEGACY */
++#if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_LEGACY_FCS)
++static struct sk_buff *brcm_leg_fcs_tag_xmit(struct sk_buff *skb,
++                                           struct net_device *dev)
++{
++      struct dsa_port *dp = dsa_user_to_port(dev);
++      unsigned int fcs_len;
++      __le32 fcs_val;
++      u8 *brcm_tag;
++
++      /* The Ethernet switch we are interfaced with needs packets to be at
++       * least 64 bytes (including FCS) otherwise they will be discarded when
++       * they enter the switch port logic. When Broadcom tags are enabled, we
++       * need to make sure that packets are at least 70 bytes (including FCS
++       * and tag) because the length verification is done after the Broadcom
++       * tag is stripped off the ingress packet.
++       *
++       * Let dsa_user_xmit() free the SKB.
++       */
++      if (__skb_put_padto(skb, ETH_ZLEN + BRCM_LEG_TAG_LEN, false))
++              return NULL;
++
++      fcs_len = skb->len;
++      fcs_val = cpu_to_le32(crc32_le(~0, skb->data, fcs_len) ^ ~0);
++
++      skb_push(skb, BRCM_LEG_TAG_LEN);
++
++      dsa_alloc_etype_header(skb, BRCM_LEG_TAG_LEN);
++
++      brcm_tag = skb->data + 2 * ETH_ALEN;
++
++      /* Broadcom tag type */
++      brcm_tag[0] = BRCM_LEG_TYPE_HI;
++      brcm_tag[1] = BRCM_LEG_TYPE_LO;
++
++      /* Broadcom tag value */
++      brcm_tag[2] = BRCM_LEG_EGRESS | BRCM_LEG_LEN_HI(fcs_len);
++      brcm_tag[3] = BRCM_LEG_LEN_LO(fcs_len);
++      brcm_tag[4] = 0;
++      brcm_tag[5] = dp->index & BRCM_LEG_PORT_ID;
++
++      /* Original FCS value */
++      if (__skb_pad(skb, ETH_FCS_LEN, false))
++              return NULL;
++      skb_put_data(skb, &fcs_val, ETH_FCS_LEN);
++
++      return skb;
++}
++
++static const struct dsa_device_ops brcm_legacy_fcs_netdev_ops = {
++      .name = BRCM_LEGACY_FCS_NAME,
++      .proto = DSA_TAG_PROTO_BRCM_LEGACY_FCS,
++      .xmit = brcm_leg_fcs_tag_xmit,
++      .rcv = brcm_leg_tag_rcv,
++      .needed_headroom = BRCM_LEG_TAG_LEN,
++};
++
++DSA_TAG_DRIVER(brcm_legacy_fcs_netdev_ops);
++MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_BRCM_LEGACY_FCS, BRCM_LEGACY_FCS_NAME);
++#endif /* CONFIG_NET_DSA_TAG_BRCM_LEGACY_FCS */
++
+ #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_PREPEND)
+ static struct sk_buff *brcm_tag_xmit_prepend(struct sk_buff *skb,
+                                            struct net_device *dev)
+@@ -334,6 +402,9 @@ static struct dsa_tag_driver *dsa_tag_dr
+ #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_LEGACY)
+       &DSA_TAG_DRIVER_NAME(brcm_legacy_netdev_ops),
+ #endif
++#if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_LEGACY_FCS)
++      &DSA_TAG_DRIVER_NAME(brcm_legacy_fcs_netdev_ops),
++#endif
+ #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_PREPEND)
+       &DSA_TAG_DRIVER_NAME(brcm_prepend_netdev_ops),
+ #endif
diff --git a/target/linux/generic/backport-6.12/611-02-v6.17-net-dsa-b53-support-legacy-FCS-tags.patch b/target/linux/generic/backport-6.12/611-02-v6.17-net-dsa-b53-support-legacy-FCS-tags.patch
new file mode 100644 (file)
index 0000000..b04c8ea
--- /dev/null
@@ -0,0 +1,48 @@
+From c3cf059a4d419b9c888ce7e9952fa13ba7569b61 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= <noltari@gmail.com>
+Date: Sat, 14 Jun 2025 09:59:49 +0200
+Subject: [PATCH] net: dsa: b53: support legacy FCS tags
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Commit 46c5176c586c ("net: dsa: b53: support legacy tags") introduced
+support for legacy tags, but it turns out that BCM5325 and BCM5365
+switches require the original FCS value and length, so they have to be
+treated differently.
+
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+Link: https://patch.msgid.link/20250614080000.1884236-4-noltari@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/Kconfig      | 1 +
+ drivers/net/dsa/b53/b53_common.c | 7 +++++--
+ 2 files changed, 6 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/b53/Kconfig
++++ b/drivers/net/dsa/b53/Kconfig
+@@ -5,6 +5,7 @@ menuconfig B53
+       select NET_DSA_TAG_NONE
+       select NET_DSA_TAG_BRCM
+       select NET_DSA_TAG_BRCM_LEGACY
++      select NET_DSA_TAG_BRCM_LEGACY_FCS
+       select NET_DSA_TAG_BRCM_PREPEND
+       help
+         This driver adds support for Broadcom managed switch chips. It supports
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -2307,8 +2307,11 @@ enum dsa_tag_protocol b53_get_tag_protoc
+               goto out;
+       }
+-      /* Older models require a different 6 byte tag */
+-      if (is5325(dev) || is5365(dev) || is63xx(dev)) {
++      /* Older models require different 6 byte tags */
++      if (is5325(dev) || is5365(dev)) {
++              dev->tag_protocol = DSA_TAG_PROTO_BRCM_LEGACY_FCS;
++              goto out;
++      } else if (is63xx(dev)) {
+               dev->tag_protocol = DSA_TAG_PROTO_BRCM_LEGACY;
+               goto out;
+       }
diff --git a/target/linux/generic/backport-6.12/611-03-v6.17-net-dsa-b53-detect-BCM5325-variants.patch b/target/linux/generic/backport-6.12/611-03-v6.17-net-dsa-b53-detect-BCM5325-variants.patch
new file mode 100644 (file)
index 0000000..3c2fa2d
--- /dev/null
@@ -0,0 +1,112 @@
+From 0cbec9aef5a86194117a956546dc1aec95031f37 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= <noltari@gmail.com>
+Date: Sat, 14 Jun 2025 09:59:50 +0200
+Subject: [PATCH] net: dsa: b53: detect BCM5325 variants
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+We need to be able to differentiate the BCM5325 variants because:
+- BCM5325M switches lack the ARLIO_PAGE->VLAN_ID_IDX register.
+- BCM5325E have less 512 ARL buckets instead of 1024.
+
+Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250614080000.1884236-5-noltari@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 24 +++++++++++++++++++++---
+ drivers/net/dsa/b53/b53_priv.h   | 19 +++++++++++++++++++
+ 2 files changed, 40 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -1835,7 +1835,8 @@ static int b53_arl_op(struct b53_device
+       /* Perform a read for the given MAC and VID */
+       b53_write48(dev, B53_ARLIO_PAGE, B53_MAC_ADDR_IDX, mac);
+-      b53_write16(dev, B53_ARLIO_PAGE, B53_VLAN_ID_IDX, vid);
++      if (!is5325m(dev))
++              b53_write16(dev, B53_ARLIO_PAGE, B53_VLAN_ID_IDX, vid);
+       /* Issue a read operation for this MAC */
+       ret = b53_arl_rw_op(dev, 1);
+@@ -2906,6 +2907,9 @@ static int b53_switch_init(struct b53_de
+               }
+       }
++      if (is5325e(dev))
++              dev->num_arl_buckets = 512;
++
+       dev->num_ports = fls(dev->enabled_ports);
+       dev->ds->num_ports = min_t(unsigned int, dev->num_ports, DSA_MAX_PORTS);
+@@ -3007,10 +3011,24 @@ int b53_switch_detect(struct b53_device
+               b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, 0xf);
+               b53_read16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, &tmp);
+-              if (tmp == 0xf)
++              if (tmp == 0xf) {
++                      u32 phy_id;
++                      int val;
++
+                       dev->chip_id = BCM5325_DEVICE_ID;
+-              else
++
++                      val = b53_phy_read16(dev->ds, 0, MII_PHYSID1);
++                      phy_id = (val & 0xffff) << 16;
++                      val = b53_phy_read16(dev->ds, 0, MII_PHYSID2);
++                      phy_id |= (val & 0xfff0);
++
++                      if (phy_id == 0x00406330)
++                              dev->variant_id = B53_VARIANT_5325M;
++                      else if (phy_id == 0x0143bc30)
++                              dev->variant_id = B53_VARIANT_5325E;
++              } else {
+                       dev->chip_id = BCM5365_DEVICE_ID;
++              }
+               break;
+       case BCM5389_DEVICE_ID:
+       case BCM5395_DEVICE_ID:
+--- a/drivers/net/dsa/b53/b53_priv.h
++++ b/drivers/net/dsa/b53/b53_priv.h
+@@ -84,6 +84,12 @@ enum {
+       BCM53134_DEVICE_ID = 0x5075,
+ };
++enum b53_variant_id {
++      B53_VARIANT_NONE = 0,
++      B53_VARIANT_5325E,
++      B53_VARIANT_5325M,
++};
++
+ struct b53_pcs {
+       struct phylink_pcs pcs;
+       struct b53_device *dev;
+@@ -118,6 +124,7 @@ struct b53_device {
+       /* chip specific data */
+       u32 chip_id;
++      enum b53_variant_id variant_id;
+       u8 core_rev;
+       u8 vta_regs[3];
+       u8 duplex_reg;
+@@ -165,6 +172,18 @@ static inline int is5325(struct b53_devi
+       return dev->chip_id == BCM5325_DEVICE_ID;
+ }
++static inline int is5325e(struct b53_device *dev)
++{
++      return is5325(dev) &&
++              dev->variant_id == B53_VARIANT_5325E;
++}
++
++static inline int is5325m(struct b53_device *dev)
++{
++      return is5325(dev) &&
++              dev->variant_id == B53_VARIANT_5325M;
++}
++
+ static inline int is5365(struct b53_device *dev)
+ {
+ #ifdef CONFIG_BCM47XX
diff --git a/target/linux/generic/backport-6.12/611-04-v6.17-net-dsa-b53-add-support-for-FDB-operations-on-5325-5365.patch b/target/linux/generic/backport-6.12/611-04-v6.17-net-dsa-b53-add-support-for-FDB-operations-on-5325-5365.patch
new file mode 100644 (file)
index 0000000..9de7381
--- /dev/null
@@ -0,0 +1,250 @@
+From c45655386e532c85ff1d679fc2aa40b3aaff9916 Mon Sep 17 00:00:00 2001
+From: Florian Fainelli <f.fainelli@gmail.com>
+Date: Sat, 14 Jun 2025 09:59:51 +0200
+Subject: [PATCH] net: dsa: b53: add support for FDB operations on 5325/5365
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+BCM5325 and BCM5365 are part of a much older generation of switches which,
+due to their limited number of ports and VLAN entries (up to 256) allowed
+a single 64-bit register to hold a full ARL entry.
+This requires a little bit of massaging when reading, writing and
+converting ARL entries in both directions.
+
+Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+Link: https://patch.msgid.link/20250614080000.1884236-6-noltari@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 101 +++++++++++++++++++++++++------
+ drivers/net/dsa/b53/b53_priv.h   |  29 +++++++++
+ drivers/net/dsa/b53/b53_regs.h   |   7 ++-
+ 3 files changed, 115 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -1821,6 +1821,45 @@ static int b53_arl_read(struct b53_devic
+       return *idx >= dev->num_arl_bins ? -ENOSPC : -ENOENT;
+ }
++static int b53_arl_read_25(struct b53_device *dev, u64 mac,
++                         u16 vid, struct b53_arl_entry *ent, u8 *idx)
++{
++      DECLARE_BITMAP(free_bins, B53_ARLTBL_MAX_BIN_ENTRIES);
++      unsigned int i;
++      int ret;
++
++      ret = b53_arl_op_wait(dev);
++      if (ret)
++              return ret;
++
++      bitmap_zero(free_bins, dev->num_arl_bins);
++
++      /* Read the bins */
++      for (i = 0; i < dev->num_arl_bins; i++) {
++              u64 mac_vid;
++
++              b53_read64(dev, B53_ARLIO_PAGE,
++                         B53_ARLTBL_MAC_VID_ENTRY(i), &mac_vid);
++
++              b53_arl_to_entry_25(ent, mac_vid);
++
++              if (!(mac_vid & ARLTBL_VALID_25)) {
++                      set_bit(i, free_bins);
++                      continue;
++              }
++              if ((mac_vid & ARLTBL_MAC_MASK) != mac)
++                      continue;
++              if (dev->vlan_enabled &&
++                  ((mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25) != vid)
++                      continue;
++              *idx = i;
++              return 0;
++      }
++
++      *idx = find_first_bit(free_bins, dev->num_arl_bins);
++      return *idx >= dev->num_arl_bins ? -ENOSPC : -ENOENT;
++}
++
+ static int b53_arl_op(struct b53_device *dev, int op, int port,
+                     const unsigned char *addr, u16 vid, bool is_valid)
+ {
+@@ -1843,7 +1882,10 @@ static int b53_arl_op(struct b53_device
+       if (ret)
+               return ret;
+-      ret = b53_arl_read(dev, mac, vid, &ent, &idx);
++      if (is5325(dev) || is5365(dev))
++              ret = b53_arl_read_25(dev, mac, vid, &ent, &idx);
++      else
++              ret = b53_arl_read(dev, mac, vid, &ent, &idx);
+       /* If this is a read, just finish now */
+       if (op)
+@@ -1887,12 +1929,17 @@ static int b53_arl_op(struct b53_device
+       ent.is_static = true;
+       ent.is_age = false;
+       memcpy(ent.mac, addr, ETH_ALEN);
+-      b53_arl_from_entry(&mac_vid, &fwd_entry, &ent);
++      if (is5325(dev) || is5365(dev))
++              b53_arl_from_entry_25(&mac_vid, &ent);
++      else
++              b53_arl_from_entry(&mac_vid, &fwd_entry, &ent);
+       b53_write64(dev, B53_ARLIO_PAGE,
+                   B53_ARLTBL_MAC_VID_ENTRY(idx), mac_vid);
+-      b53_write32(dev, B53_ARLIO_PAGE,
+-                  B53_ARLTBL_DATA_ENTRY(idx), fwd_entry);
++
++      if (!is5325(dev) && !is5365(dev))
++              b53_write32(dev, B53_ARLIO_PAGE,
++                          B53_ARLTBL_DATA_ENTRY(idx), fwd_entry);
+       return b53_arl_rw_op(dev, 0);
+ }
+@@ -1904,12 +1951,6 @@ int b53_fdb_add(struct dsa_switch *ds, i
+       struct b53_device *priv = ds->priv;
+       int ret;
+-      /* 5325 and 5365 require some more massaging, but could
+-       * be supported eventually
+-       */
+-      if (is5325(priv) || is5365(priv))
+-              return -EOPNOTSUPP;
+-
+       mutex_lock(&priv->arl_mutex);
+       ret = b53_arl_op(priv, 0, port, addr, vid, true);
+       mutex_unlock(&priv->arl_mutex);
+@@ -1936,10 +1977,15 @@ EXPORT_SYMBOL(b53_fdb_del);
+ static int b53_arl_search_wait(struct b53_device *dev)
+ {
+       unsigned int timeout = 1000;
+-      u8 reg;
++      u8 reg, offset;
++
++      if (is5325(dev) || is5365(dev))
++              offset = B53_ARL_SRCH_CTL_25;
++      else
++              offset = B53_ARL_SRCH_CTL;
+       do {
+-              b53_read8(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_CTL, &reg);
++              b53_read8(dev, B53_ARLIO_PAGE, offset, &reg);
+               if (!(reg & ARL_SRCH_STDN))
+                       return -ENOENT;
+@@ -1956,13 +2002,24 @@ static void b53_arl_search_rd(struct b53
+                             struct b53_arl_entry *ent)
+ {
+       u64 mac_vid;
+-      u32 fwd_entry;
+-      b53_read64(dev, B53_ARLIO_PAGE,
+-                 B53_ARL_SRCH_RSTL_MACVID(idx), &mac_vid);
+-      b53_read32(dev, B53_ARLIO_PAGE,
+-                 B53_ARL_SRCH_RSTL(idx), &fwd_entry);
+-      b53_arl_to_entry(ent, mac_vid, fwd_entry);
++      if (is5325(dev)) {
++              b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_25,
++                         &mac_vid);
++              b53_arl_to_entry_25(ent, mac_vid);
++      } else if (is5365(dev)) {
++              b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_65,
++                         &mac_vid);
++              b53_arl_to_entry_25(ent, mac_vid);
++      } else {
++              u32 fwd_entry;
++
++              b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_MACVID(idx),
++                         &mac_vid);
++              b53_read32(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL(idx),
++                         &fwd_entry);
++              b53_arl_to_entry(ent, mac_vid, fwd_entry);
++      }
+ }
+ static int b53_fdb_copy(int port, const struct b53_arl_entry *ent,
+@@ -1986,14 +2043,20 @@ int b53_fdb_dump(struct dsa_switch *ds,
+       struct b53_device *priv = ds->priv;
+       struct b53_arl_entry results[2];
+       unsigned int count = 0;
++      u8 offset;
+       int ret;
+       u8 reg;
+       mutex_lock(&priv->arl_mutex);
++      if (is5325(priv) || is5365(priv))
++              offset = B53_ARL_SRCH_CTL_25;
++      else
++              offset = B53_ARL_SRCH_CTL;
++
+       /* Start search operation */
+       reg = ARL_SRCH_STDN;
+-      b53_write8(priv, B53_ARLIO_PAGE, B53_ARL_SRCH_CTL, reg);
++      b53_write8(priv, offset, B53_ARL_SRCH_CTL, reg);
+       do {
+               ret = b53_arl_search_wait(priv);
+--- a/drivers/net/dsa/b53/b53_priv.h
++++ b/drivers/net/dsa/b53/b53_priv.h
+@@ -317,6 +317,19 @@ static inline void b53_arl_to_entry(stru
+       ent->vid = mac_vid >> ARLTBL_VID_S;
+ }
++static inline void b53_arl_to_entry_25(struct b53_arl_entry *ent,
++                                     u64 mac_vid)
++{
++      memset(ent, 0, sizeof(*ent));
++      ent->port = (mac_vid >> ARLTBL_DATA_PORT_ID_S_25) &
++                   ARLTBL_DATA_PORT_ID_MASK_25;
++      ent->is_valid = !!(mac_vid & ARLTBL_VALID_25);
++      ent->is_age = !!(mac_vid & ARLTBL_AGE_25);
++      ent->is_static = !!(mac_vid & ARLTBL_STATIC_25);
++      u64_to_ether_addr(mac_vid, ent->mac);
++      ent->vid = mac_vid >> ARLTBL_VID_S_65;
++}
++
+ static inline void b53_arl_from_entry(u64 *mac_vid, u32 *fwd_entry,
+                                     const struct b53_arl_entry *ent)
+ {
+@@ -331,6 +344,22 @@ static inline void b53_arl_from_entry(u6
+               *fwd_entry |= ARLTBL_AGE;
+ }
++static inline void b53_arl_from_entry_25(u64 *mac_vid,
++                                       const struct b53_arl_entry *ent)
++{
++      *mac_vid = ether_addr_to_u64(ent->mac);
++      *mac_vid |= (u64)(ent->port & ARLTBL_DATA_PORT_ID_MASK_25) <<
++                        ARLTBL_DATA_PORT_ID_S_25;
++      *mac_vid |= (u64)(ent->vid & ARLTBL_VID_MASK_25) <<
++                        ARLTBL_VID_S_65;
++      if (ent->is_valid)
++              *mac_vid |= ARLTBL_VALID_25;
++      if (ent->is_static)
++              *mac_vid |= ARLTBL_STATIC_25;
++      if (ent->is_age)
++              *mac_vid |= ARLTBL_AGE_25;
++}
++
+ #ifdef CONFIG_BCM47XX
+ #include <linux/bcm47xx_nvram.h>
+--- a/drivers/net/dsa/b53/b53_regs.h
++++ b/drivers/net/dsa/b53/b53_regs.h
+@@ -328,9 +328,10 @@
+ #define   ARLTBL_VID_MASK             0xfff
+ #define   ARLTBL_DATA_PORT_ID_S_25    48
+ #define   ARLTBL_DATA_PORT_ID_MASK_25 0xf
+-#define   ARLTBL_AGE_25                       BIT(61)
+-#define   ARLTBL_STATIC_25            BIT(62)
+-#define   ARLTBL_VALID_25             BIT(63)
++#define   ARLTBL_VID_S_65             53
++#define   ARLTBL_AGE_25                       BIT_ULL(61)
++#define   ARLTBL_STATIC_25            BIT_ULL(62)
++#define   ARLTBL_VALID_25             BIT_ULL(63)
+ /* ARL Table Data Entry N Registers (32 bit) */
+ #define B53_ARLTBL_DATA_ENTRY(n)      ((0x10 * (n)) + 0x18)
diff --git a/target/linux/generic/backport-6.12/611-05-v6.17-net-dsa-b53-prevent-FAST_AGE-access-on-BCM5325.patch b/target/linux/generic/backport-6.12/611-05-v6.17-net-dsa-b53-prevent-FAST_AGE-access-on-BCM5325.patch
new file mode 100644 (file)
index 0000000..7397e33
--- /dev/null
@@ -0,0 +1,51 @@
+From 9b6c767c312b4709e9aeb2314a6b47863e7fb72d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= <noltari@gmail.com>
+Date: Sat, 14 Jun 2025 09:59:52 +0200
+Subject: [PATCH] net: dsa: b53: prevent FAST_AGE access on BCM5325
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+BCM5325 doesn't implement FAST_AGE registers so we should avoid reading or
+writing them.
+
+Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250614080000.1884236-7-noltari@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -492,6 +492,9 @@ static int b53_flush_arl(struct b53_devi
+ {
+       unsigned int i;
++      if (is5325(dev))
++              return 0;
++
+       b53_write8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL,
+                  FAST_AGE_DONE | FAST_AGE_DYNAMIC | mask);
+@@ -516,6 +519,9 @@ out:
+ static int b53_fast_age_port(struct b53_device *dev, int port)
+ {
++      if (is5325(dev))
++              return 0;
++
+       b53_write8(dev, B53_CTRL_PAGE, B53_FAST_AGE_PORT_CTRL, port);
+       return b53_flush_arl(dev, FAST_AGE_PORT);
+@@ -523,6 +529,9 @@ static int b53_fast_age_port(struct b53_
+ static int b53_fast_age_vlan(struct b53_device *dev, u16 vid)
+ {
++      if (is5325(dev))
++              return 0;
++
+       b53_write16(dev, B53_CTRL_PAGE, B53_FAST_AGE_VID_CTRL, vid);
+       return b53_flush_arl(dev, FAST_AGE_VLAN);
diff --git a/target/linux/generic/backport-6.12/611-06-v6.17-net-dsa-b53-prevent-BRCM_HDR-access-on-older-devices.patch b/target/linux/generic/backport-6.12/611-06-v6.17-net-dsa-b53-prevent-BRCM_HDR-access-on-older-devices.patch
new file mode 100644 (file)
index 0000000..6ea8367
--- /dev/null
@@ -0,0 +1,33 @@
+From e17813968b08b1b09bf80699223dea48851cbd07 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= <noltari@gmail.com>
+Date: Sat, 14 Jun 2025 09:59:56 +0200
+Subject: [PATCH] net: dsa: b53: prevent BRCM_HDR access on older devices
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Older switches don't implement BRCM_HDR register so we should avoid
+reading or writing it.
+
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+Link: https://patch.msgid.link/20250614080000.1884236-11-noltari@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -747,6 +747,11 @@ void b53_brcm_hdr_setup(struct dsa_switc
+               hdr_ctl |= GC_FRM_MGMT_PORT_M;
+       b53_write8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, hdr_ctl);
++      /* B53_BRCM_HDR not present on devices with legacy tags */
++      if (dev->tag_protocol == DSA_TAG_PROTO_BRCM_LEGACY ||
++          dev->tag_protocol == DSA_TAG_PROTO_BRCM_LEGACY_FCS)
++              return;
++
+       /* Enable Broadcom tags for IMP port */
+       b53_read8(dev, B53_MGMT_PAGE, B53_BRCM_HDR, &hdr_ctl);
+       if (tag_en)
diff --git a/target/linux/generic/backport-6.12/611-07-v6.17-net-dsa-b53-fix-unicast-multicast-flooding-on-BCM5325.patch b/target/linux/generic/backport-6.12/611-07-v6.17-net-dsa-b53-fix-unicast-multicast-flooding-on-BCM5325.patch
new file mode 100644 (file)
index 0000000..6b9c86e
--- /dev/null
@@ -0,0 +1,128 @@
+From 651c9e71ffe44e99b5a9b011271c2117f0353b32 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= <noltari@gmail.com>
+Date: Sat, 14 Jun 2025 09:59:58 +0200
+Subject: [PATCH] net: dsa: b53: fix unicast/multicast flooding on BCM5325
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+BCM5325 doesn't implement UC_FLOOD_MASK, MC_FLOOD_MASK and IPMC_FLOOD_MASK
+registers.
+This has to be handled differently with other pages and registers.
+
+Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250614080000.1884236-13-noltari@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 60 ++++++++++++++++++++++----------
+ drivers/net/dsa/b53/b53_regs.h   | 13 +++++++
+ 2 files changed, 55 insertions(+), 18 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -564,12 +564,24 @@ static void b53_port_set_ucast_flood(str
+ {
+       u16 uc;
+-      b53_read16(dev, B53_CTRL_PAGE, B53_UC_FLOOD_MASK, &uc);
+-      if (unicast)
+-              uc |= BIT(port);
+-      else
+-              uc &= ~BIT(port);
+-      b53_write16(dev, B53_CTRL_PAGE, B53_UC_FLOOD_MASK, uc);
++      if (is5325(dev)) {
++              if (port == B53_CPU_PORT_25)
++                      port = B53_CPU_PORT;
++
++              b53_read16(dev, B53_IEEE_PAGE, B53_IEEE_UCAST_DLF, &uc);
++              if (unicast)
++                      uc |= BIT(port) | B53_IEEE_UCAST_DROP_EN;
++              else
++                      uc &= ~BIT(port);
++              b53_write16(dev, B53_IEEE_PAGE, B53_IEEE_UCAST_DLF, uc);
++      } else {
++              b53_read16(dev, B53_CTRL_PAGE, B53_UC_FLOOD_MASK, &uc);
++              if (unicast)
++                      uc |= BIT(port);
++              else
++                      uc &= ~BIT(port);
++              b53_write16(dev, B53_CTRL_PAGE, B53_UC_FLOOD_MASK, uc);
++      }
+ }
+ static void b53_port_set_mcast_flood(struct b53_device *dev, int port,
+@@ -577,19 +589,31 @@ static void b53_port_set_mcast_flood(str
+ {
+       u16 mc;
+-      b53_read16(dev, B53_CTRL_PAGE, B53_MC_FLOOD_MASK, &mc);
+-      if (multicast)
+-              mc |= BIT(port);
+-      else
+-              mc &= ~BIT(port);
+-      b53_write16(dev, B53_CTRL_PAGE, B53_MC_FLOOD_MASK, mc);
+-
+-      b53_read16(dev, B53_CTRL_PAGE, B53_IPMC_FLOOD_MASK, &mc);
+-      if (multicast)
+-              mc |= BIT(port);
+-      else
+-              mc &= ~BIT(port);
+-      b53_write16(dev, B53_CTRL_PAGE, B53_IPMC_FLOOD_MASK, mc);
++      if (is5325(dev)) {
++              if (port == B53_CPU_PORT_25)
++                      port = B53_CPU_PORT;
++
++              b53_read16(dev, B53_IEEE_PAGE, B53_IEEE_MCAST_DLF, &mc);
++              if (multicast)
++                      mc |= BIT(port) | B53_IEEE_MCAST_DROP_EN;
++              else
++                      mc &= ~BIT(port);
++              b53_write16(dev, B53_IEEE_PAGE, B53_IEEE_MCAST_DLF, mc);
++      } else {
++              b53_read16(dev, B53_CTRL_PAGE, B53_MC_FLOOD_MASK, &mc);
++              if (multicast)
++                      mc |= BIT(port);
++              else
++                      mc &= ~BIT(port);
++              b53_write16(dev, B53_CTRL_PAGE, B53_MC_FLOOD_MASK, mc);
++
++              b53_read16(dev, B53_CTRL_PAGE, B53_IPMC_FLOOD_MASK, &mc);
++              if (multicast)
++                      mc |= BIT(port);
++              else
++                      mc &= ~BIT(port);
++              b53_write16(dev, B53_CTRL_PAGE, B53_IPMC_FLOOD_MASK, mc);
++      }
+ }
+ static void b53_port_set_learning(struct b53_device *dev, int port,
+--- a/drivers/net/dsa/b53/b53_regs.h
++++ b/drivers/net/dsa/b53/b53_regs.h
+@@ -29,6 +29,7 @@
+ #define B53_ARLIO_PAGE                        0x05 /* ARL Access */
+ #define B53_FRAMEBUF_PAGE             0x06 /* Management frame access */
+ #define B53_MEM_ACCESS_PAGE           0x08 /* Memory access */
++#define B53_IEEE_PAGE                 0x0a /* IEEE 802.1X */
+ /* PHY Registers */
+ #define B53_PORT_MII_PAGE(i)          (0x10 + (i)) /* Port i MII Registers */
+@@ -371,6 +372,18 @@
+ #define B53_ARL_SRCH_RSTL(x)          (B53_ARL_SRCH_RSTL_0 + ((x) * 0x10))
+ /*************************************************************************
++ * IEEE 802.1X Registers
++ *************************************************************************/
++
++/* Multicast DLF Drop Control register (16 bit) */
++#define B53_IEEE_MCAST_DLF            0x94
++#define B53_IEEE_MCAST_DROP_EN                BIT(11)
++
++/* Unicast DLF Drop Control register (16 bit) */
++#define B53_IEEE_UCAST_DLF            0x96
++#define B53_IEEE_UCAST_DROP_EN                BIT(11)
++
++/*************************************************************************
+  * Port VLAN Registers
+  *************************************************************************/
diff --git a/target/linux/generic/backport-6.12/611-08-v6.18-net-dsa-b53-Add-phy_enable-phy_disable-methods.patch b/target/linux/generic/backport-6.12/611-08-v6.18-net-dsa-b53-Add-phy_enable-phy_disable-methods.patch
new file mode 100644 (file)
index 0000000..14106e3
--- /dev/null
@@ -0,0 +1,50 @@
+From be7a79145d85af1a9d65a45560b9243b13a67782 Mon Sep 17 00:00:00 2001
+From: Kyle Hendry <kylehendrydev@gmail.com>
+Date: Wed, 23 Jul 2025 20:52:40 -0700
+Subject: [PATCH] net: dsa: b53: Add phy_enable(), phy_disable() methods
+
+Add phy enable/disable to b53 ops to be called when
+enabling/disabling ports.
+
+Signed-off-by: Kyle Hendry <kylehendrydev@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250724035300.20497-2-kylehendrydev@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 6 ++++++
+ drivers/net/dsa/b53/b53_priv.h   | 2 ++
+ 2 files changed, 8 insertions(+)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -689,6 +689,9 @@ int b53_enable_port(struct dsa_switch *d
+       cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
++      if (dev->ops->phy_enable)
++              dev->ops->phy_enable(dev, port);
++
+       if (dev->ops->irq_enable)
+               ret = dev->ops->irq_enable(dev, port);
+       if (ret)
+@@ -727,6 +730,9 @@ void b53_disable_port(struct dsa_switch
+       reg |= PORT_CTRL_RX_DISABLE | PORT_CTRL_TX_DISABLE;
+       b53_write8(dev, B53_CTRL_PAGE, B53_PORT_CTRL(port), reg);
++      if (dev->ops->phy_disable)
++              dev->ops->phy_disable(dev, port);
++
+       if (dev->ops->irq_disable)
+               dev->ops->irq_disable(dev, port);
+ }
+--- a/drivers/net/dsa/b53/b53_priv.h
++++ b/drivers/net/dsa/b53/b53_priv.h
+@@ -45,6 +45,8 @@ struct b53_io_ops {
+       int (*phy_write16)(struct b53_device *dev, int addr, int reg, u16 value);
+       int (*irq_enable)(struct b53_device *dev, int port);
+       void (*irq_disable)(struct b53_device *dev, int port);
++      void (*phy_enable)(struct b53_device *dev, int port);
++      void (*phy_disable)(struct b53_device *dev, int port);
+       void (*phylink_get_caps)(struct b53_device *dev, int port,
+                                struct phylink_config *config);
+       struct phylink_pcs *(*phylink_mac_select_pcs)(struct b53_device *dev,
diff --git a/target/linux/generic/backport-6.12/611-09-v6.18-net-dsa-b53-Define-chip-IDs-for-more-bcm63xx-SoCs.patch b/target/linux/generic/backport-6.12/611-09-v6.18-net-dsa-b53-Define-chip-IDs-for-more-bcm63xx-SoCs.patch
new file mode 100644 (file)
index 0000000..0e03916
--- /dev/null
@@ -0,0 +1,124 @@
+From fcf02a462fab52fbfcb24e617dd940745afd0dff Mon Sep 17 00:00:00 2001
+From: Kyle Hendry <kylehendrydev@gmail.com>
+Date: Wed, 23 Jul 2025 20:52:42 -0700
+Subject: [PATCH] net: dsa: b53: Define chip IDs for more bcm63xx SoCs
+
+Add defines for bcm6318, bcm6328, bcm6362, bcm6368 chip IDs,
+update tables and switch init.
+
+Signed-off-by: Kyle Hendry <kylehendrydev@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250724035300.20497-4-kylehendrydev@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 21 ++++++---------------
+ drivers/net/dsa/b53/b53_mmap.c   |  8 ++++----
+ drivers/net/dsa/b53/b53_priv.h   | 13 +++++++++++--
+ 3 files changed, 21 insertions(+), 21 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -1419,7 +1419,7 @@ static void b53_adjust_63xx_rgmii(struct
+       b53_read8(dev, B53_CTRL_PAGE, B53_RGMII_CTRL_P(port), &rgmii_ctrl);
+       rgmii_ctrl &= ~(RGMII_CTRL_DLL_RXC | RGMII_CTRL_DLL_TXC);
+-      if (is63268(dev))
++      if (is6318_268(dev))
+               rgmii_ctrl |= RGMII_CTRL_MII_OVERRIDE;
+       rgmii_ctrl |= RGMII_CTRL_ENABLE_GMII;
+@@ -2804,19 +2804,6 @@ static const struct b53_chip_data b53_sw
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX,
+       },
+       {
+-              .chip_id = BCM63268_DEVICE_ID,
+-              .dev_name = "BCM63268",
+-              .vlans = 4096,
+-              .enabled_ports = 0, /* pdata must provide them */
+-              .arl_bins = 4,
+-              .arl_buckets = 1024,
+-              .imp_port = 8,
+-              .vta_regs = B53_VTA_REGS_63XX,
+-              .duplex_reg = B53_DUPLEX_STAT_63XX,
+-              .jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX,
+-              .jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX,
+-      },
+-      {
+               .chip_id = BCM53010_DEVICE_ID,
+               .dev_name = "BCM53010",
+               .vlans = 4096,
+@@ -2965,13 +2952,17 @@ static const struct b53_chip_data b53_sw
+ static int b53_switch_init(struct b53_device *dev)
+ {
++      u32 chip_id = dev->chip_id;
+       unsigned int i;
+       int ret;
++      if (is63xx(dev))
++              chip_id = BCM63XX_DEVICE_ID;
++
+       for (i = 0; i < ARRAY_SIZE(b53_switch_chips); i++) {
+               const struct b53_chip_data *chip = &b53_switch_chips[i];
+-              if (chip->chip_id == dev->chip_id) {
++              if (chip->chip_id == chip_id) {
+                       if (!dev->enabled_ports)
+                               dev->enabled_ports = chip->enabled_ports;
+                       dev->name = chip->dev_name;
+--- a/drivers/net/dsa/b53/b53_mmap.c
++++ b/drivers/net/dsa/b53/b53_mmap.c
+@@ -348,16 +348,16 @@ static const struct of_device_id b53_mma
+               .data = (void *)BCM63XX_DEVICE_ID,
+       }, {
+               .compatible = "brcm,bcm6318-switch",
+-              .data = (void *)BCM63268_DEVICE_ID,
++              .data = (void *)BCM6318_DEVICE_ID,
+       }, {
+               .compatible = "brcm,bcm6328-switch",
+-              .data = (void *)BCM63XX_DEVICE_ID,
++              .data = (void *)BCM6328_DEVICE_ID,
+       }, {
+               .compatible = "brcm,bcm6362-switch",
+-              .data = (void *)BCM63XX_DEVICE_ID,
++              .data = (void *)BCM6362_DEVICE_ID,
+       }, {
+               .compatible = "brcm,bcm6368-switch",
+-              .data = (void *)BCM63XX_DEVICE_ID,
++              .data = (void *)BCM6368_DEVICE_ID,
+       }, {
+               .compatible = "brcm,bcm63268-switch",
+               .data = (void *)BCM63268_DEVICE_ID,
+--- a/drivers/net/dsa/b53/b53_priv.h
++++ b/drivers/net/dsa/b53/b53_priv.h
+@@ -73,6 +73,10 @@ enum {
+       BCM53125_DEVICE_ID = 0x53125,
+       BCM53128_DEVICE_ID = 0x53128,
+       BCM63XX_DEVICE_ID = 0x6300,
++      BCM6318_DEVICE_ID = 0x6318,
++      BCM6328_DEVICE_ID = 0x6328,
++      BCM6362_DEVICE_ID = 0x6362,
++      BCM6368_DEVICE_ID = 0x6368,
+       BCM63268_DEVICE_ID = 0x63268,
+       BCM53010_DEVICE_ID = 0x53010,
+       BCM53011_DEVICE_ID = 0x53011,
+@@ -220,12 +224,17 @@ static inline int is531x5(struct b53_dev
+ static inline int is63xx(struct b53_device *dev)
+ {
+       return dev->chip_id == BCM63XX_DEVICE_ID ||
++              dev->chip_id == BCM6318_DEVICE_ID ||
++              dev->chip_id == BCM6328_DEVICE_ID ||
++              dev->chip_id == BCM6362_DEVICE_ID ||
++              dev->chip_id == BCM6368_DEVICE_ID ||
+               dev->chip_id == BCM63268_DEVICE_ID;
+ }
+-static inline int is63268(struct b53_device *dev)
++static inline int is6318_268(struct b53_device *dev)
+ {
+-      return dev->chip_id == BCM63268_DEVICE_ID;
++      return dev->chip_id == BCM6318_DEVICE_ID ||
++              dev->chip_id == BCM63268_DEVICE_ID;
+ }
+ static inline int is5301x(struct b53_device *dev)
diff --git a/target/linux/generic/backport-6.12/611-10-v6.18-net-dsa-b53-mmap-Add-syscon-reference-and-register-layout-for-bcm63268.patch b/target/linux/generic/backport-6.12/611-10-v6.18-net-dsa-b53-mmap-Add-syscon-reference-and-register-layout-for-bcm63268.patch
new file mode 100644 (file)
index 0000000..92e540d
--- /dev/null
@@ -0,0 +1,69 @@
+From aed2aaa3c963f8aabbfa061a177022fee826ebfb Mon Sep 17 00:00:00 2001
+From: Kyle Hendry <kylehendrydev@gmail.com>
+Date: Wed, 23 Jul 2025 20:52:43 -0700
+Subject: [PATCH] net: dsa: b53: mmap: Add syscon reference and register layout
+ for bcm63268
+
+On bcm63xx SoCs there are registers that control the PHYs in
+the GPIO controller. Allow the b53 driver to access them
+by passing in the syscon through the device tree.
+
+Add a structure to describe the ephy control register
+and add register info for bcm63268.
+
+Signed-off-by: Kyle Hendry <kylehendrydev@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250724035300.20497-5-kylehendrydev@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_mmap.c | 25 +++++++++++++++++++++++++
+ 1 file changed, 25 insertions(+)
+
+--- a/drivers/net/dsa/b53/b53_mmap.c
++++ b/drivers/net/dsa/b53/b53_mmap.c
+@@ -21,13 +21,32 @@
+ #include <linux/module.h>
+ #include <linux/of.h>
+ #include <linux/io.h>
++#include <linux/mfd/syscon.h>
+ #include <linux/platform_device.h>
+ #include <linux/platform_data/b53.h>
+ #include "b53_priv.h"
++struct b53_phy_info {
++      u32 ephy_enable_mask;
++      u32 ephy_port_mask;
++      u32 ephy_bias_bit;
++      const u32 *ephy_offset;
++};
++
+ struct b53_mmap_priv {
+       void __iomem *regs;
++      struct regmap *gpio_ctrl;
++      const struct b53_phy_info *phy_info;
++};
++
++static const u32 bcm63268_ephy_offsets[] = {4, 9, 14};
++
++static const struct b53_phy_info bcm63268_ephy_info = {
++      .ephy_enable_mask = GENMASK(4, 0),
++      .ephy_port_mask = GENMASK((ARRAY_SIZE(bcm63268_ephy_offsets) - 1), 0),
++      .ephy_bias_bit = 24,
++      .ephy_offset = bcm63268_ephy_offsets,
+ };
+ static int b53_mmap_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
+@@ -313,6 +332,12 @@ static int b53_mmap_probe(struct platfor
+       priv->regs = pdata->regs;
++      priv->gpio_ctrl = syscon_regmap_lookup_by_phandle(np, "brcm,gpio-ctrl");
++      if (!IS_ERR(priv->gpio_ctrl)) {
++              if (pdata->chip_id == BCM63268_DEVICE_ID)
++                      priv->phy_info = &bcm63268_ephy_info;
++      }
++
+       dev = b53_switch_alloc(&pdev->dev, &b53_mmap_ops, priv);
+       if (!dev)
+               return -ENOMEM;
diff --git a/target/linux/generic/backport-6.12/611-11-v6.18-net-dsa-b53-mmap-Add-register-layout-for-bcm6318.patch b/target/linux/generic/backport-6.12/611-11-v6.18-net-dsa-b53-mmap-Add-register-layout-for-bcm6318.patch
new file mode 100644 (file)
index 0000000..f6c1cba
--- /dev/null
@@ -0,0 +1,47 @@
+From c251304ab021ff21c77e83e0babcb9eb76f8787a Mon Sep 17 00:00:00 2001
+From: Kyle Hendry <kylehendrydev@gmail.com>
+Date: Wed, 23 Jul 2025 20:52:44 -0700
+Subject: [PATCH] net: dsa: b53: mmap: Add register layout for bcm6318
+
+Add ephy register info for bcm6318, which also applies to
+bcm6328 and bcm6362.
+
+Signed-off-by: Kyle Hendry <kylehendrydev@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250724035300.20497-6-kylehendrydev@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_mmap.c | 15 ++++++++++++++-
+ 1 file changed, 14 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/dsa/b53/b53_mmap.c
++++ b/drivers/net/dsa/b53/b53_mmap.c
+@@ -40,6 +40,15 @@ struct b53_mmap_priv {
+       const struct b53_phy_info *phy_info;
+ };
++static const u32 bcm6318_ephy_offsets[] = {4, 5, 6, 7};
++
++static const struct b53_phy_info bcm6318_ephy_info = {
++      .ephy_enable_mask = BIT(0) | BIT(4) | BIT(8) | BIT(12) | BIT(16),
++      .ephy_port_mask = GENMASK((ARRAY_SIZE(bcm6318_ephy_offsets) - 1), 0),
++      .ephy_bias_bit = 24,
++      .ephy_offset = bcm6318_ephy_offsets,
++};
++
+ static const u32 bcm63268_ephy_offsets[] = {4, 9, 14};
+ static const struct b53_phy_info bcm63268_ephy_info = {
+@@ -334,7 +343,11 @@ static int b53_mmap_probe(struct platfor
+       priv->gpio_ctrl = syscon_regmap_lookup_by_phandle(np, "brcm,gpio-ctrl");
+       if (!IS_ERR(priv->gpio_ctrl)) {
+-              if (pdata->chip_id == BCM63268_DEVICE_ID)
++              if (pdata->chip_id == BCM6318_DEVICE_ID ||
++                  pdata->chip_id == BCM6328_DEVICE_ID ||
++                  pdata->chip_id == BCM6362_DEVICE_ID)
++                      priv->phy_info = &bcm6318_ephy_info;
++              else if (pdata->chip_id == BCM63268_DEVICE_ID)
+                       priv->phy_info = &bcm63268_ephy_info;
+       }
diff --git a/target/linux/generic/backport-6.12/611-12-v6.18-net-dsa-b53-mmap-Add-register-layout-for-bcm6368.patch b/target/linux/generic/backport-6.12/611-12-v6.18-net-dsa-b53-mmap-Add-register-layout-for-bcm6368.patch
new file mode 100644 (file)
index 0000000..f703804
--- /dev/null
@@ -0,0 +1,42 @@
+From e8e13073dff7052b144d002bae2cfe9ddfa27e2a Mon Sep 17 00:00:00 2001
+From: Kyle Hendry <kylehendrydev@gmail.com>
+Date: Wed, 23 Jul 2025 20:52:45 -0700
+Subject: [PATCH] net: dsa: b53: mmap: Add register layout for bcm6368
+
+Add ephy register info for bcm6368.
+
+Signed-off-by: Kyle Hendry <kylehendrydev@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250724035300.20497-7-kylehendrydev@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_mmap.c | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+--- a/drivers/net/dsa/b53/b53_mmap.c
++++ b/drivers/net/dsa/b53/b53_mmap.c
+@@ -49,6 +49,15 @@ static const struct b53_phy_info bcm6318
+       .ephy_offset = bcm6318_ephy_offsets,
+ };
++static const u32 bcm6368_ephy_offsets[] = {2, 3, 4, 5};
++
++static const struct b53_phy_info bcm6368_ephy_info = {
++      .ephy_enable_mask = BIT(0),
++      .ephy_port_mask = GENMASK((ARRAY_SIZE(bcm6368_ephy_offsets) - 1), 0),
++      .ephy_bias_bit = 0,
++      .ephy_offset = bcm6368_ephy_offsets,
++};
++
+ static const u32 bcm63268_ephy_offsets[] = {4, 9, 14};
+ static const struct b53_phy_info bcm63268_ephy_info = {
+@@ -347,6 +356,8 @@ static int b53_mmap_probe(struct platfor
+                   pdata->chip_id == BCM6328_DEVICE_ID ||
+                   pdata->chip_id == BCM6362_DEVICE_ID)
+                       priv->phy_info = &bcm6318_ephy_info;
++              else if (pdata->chip_id == BCM6368_DEVICE_ID)
++                      priv->phy_info = &bcm6368_ephy_info;
+               else if (pdata->chip_id == BCM63268_DEVICE_ID)
+                       priv->phy_info = &bcm63268_ephy_info;
+       }
diff --git a/target/linux/generic/backport-6.12/611-13-v6.18-net-dsa-b53-mmap-Implement-bcm63xx-ephy-power-control.patch b/target/linux/generic/backport-6.12/611-13-v6.18-net-dsa-b53-mmap-Implement-bcm63xx-ephy-power-control.patch
new file mode 100644 (file)
index 0000000..bf3aeb7
--- /dev/null
@@ -0,0 +1,100 @@
+From 5ac00023852d960528a0c1d10ae6c17893fc4113 Mon Sep 17 00:00:00 2001
+From: Kyle Hendry <kylehendrydev@gmail.com>
+Date: Wed, 23 Jul 2025 20:52:46 -0700
+Subject: [PATCH] net: dsa: b53: mmap: Implement bcm63xx ephy power control
+
+Implement the phy enable/disable calls for b53 mmap, and
+set the power down registers in the ephy control register
+appropriately.
+
+Signed-off-by: Kyle Hendry <kylehendrydev@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250724035300.20497-8-kylehendrydev@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_mmap.c | 50 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 50 insertions(+)
+
+--- a/drivers/net/dsa/b53/b53_mmap.c
++++ b/drivers/net/dsa/b53/b53_mmap.c
+@@ -24,9 +24,12 @@
+ #include <linux/mfd/syscon.h>
+ #include <linux/platform_device.h>
+ #include <linux/platform_data/b53.h>
++#include <linux/regmap.h>
+ #include "b53_priv.h"
++#define BCM63XX_EPHY_REG 0x3C
++
+ struct b53_phy_info {
+       u32 ephy_enable_mask;
+       u32 ephy_port_mask;
+@@ -38,6 +41,7 @@ struct b53_mmap_priv {
+       void __iomem *regs;
+       struct regmap *gpio_ctrl;
+       const struct b53_phy_info *phy_info;
++      u32 phys_enabled;
+ };
+ static const u32 bcm6318_ephy_offsets[] = {4, 5, 6, 7};
+@@ -266,6 +270,50 @@ static int b53_mmap_phy_write16(struct b
+       return -EIO;
+ }
++static int bcm63xx_ephy_set(struct b53_device *dev, int port, bool enable)
++{
++      struct b53_mmap_priv *priv = dev->priv;
++      const struct b53_phy_info *info = priv->phy_info;
++      struct regmap *gpio_ctrl = priv->gpio_ctrl;
++      u32 mask, val;
++
++      if (enable) {
++              mask = (info->ephy_enable_mask << info->ephy_offset[port])
++                              | BIT(info->ephy_bias_bit);
++              val = 0;
++      } else {
++              mask = (info->ephy_enable_mask << info->ephy_offset[port]);
++              if (!((priv->phys_enabled & ~BIT(port)) & info->ephy_port_mask))
++                      mask |= BIT(info->ephy_bias_bit);
++              val = mask;
++      }
++      return regmap_update_bits(gpio_ctrl, BCM63XX_EPHY_REG, mask, val);
++}
++
++static void b53_mmap_phy_enable(struct b53_device *dev, int port)
++{
++      struct b53_mmap_priv *priv = dev->priv;
++      int ret = 0;
++
++      if (priv->phy_info && (BIT(port) & priv->phy_info->ephy_port_mask))
++              ret = bcm63xx_ephy_set(dev, port, true);
++
++      if (!ret)
++              priv->phys_enabled |= BIT(port);
++}
++
++static void b53_mmap_phy_disable(struct b53_device *dev, int port)
++{
++      struct b53_mmap_priv *priv = dev->priv;
++      int ret = 0;
++
++      if (priv->phy_info && (BIT(port) & priv->phy_info->ephy_port_mask))
++              ret = bcm63xx_ephy_set(dev, port, false);
++
++      if (!ret)
++              priv->phys_enabled &= ~BIT(port);
++}
++
+ static const struct b53_io_ops b53_mmap_ops = {
+       .read8 = b53_mmap_read8,
+       .read16 = b53_mmap_read16,
+@@ -279,6 +327,8 @@ static const struct b53_io_ops b53_mmap_
+       .write64 = b53_mmap_write64,
+       .phy_read16 = b53_mmap_phy_read16,
+       .phy_write16 = b53_mmap_phy_write16,
++      .phy_enable = b53_mmap_phy_enable,
++      .phy_disable = b53_mmap_phy_disable,
+ };
+ static int b53_mmap_probe_of(struct platform_device *pdev,
diff --git a/target/linux/generic/backport-6.12/611-14-v6.18-net-dsa-b53-mmap-Add-gphy-port-to-phy-info-for-bcm63268.patch b/target/linux/generic/backport-6.12/611-14-v6.18-net-dsa-b53-mmap-Add-gphy-port-to-phy-info-for-bcm63268.patch
new file mode 100644 (file)
index 0000000..9aa99d2
--- /dev/null
@@ -0,0 +1,33 @@
+From 7f95f04fe1903a31b61085e3ab1b4730f9d72941 Mon Sep 17 00:00:00 2001
+From: Kyle Hendry <kylehendrydev@gmail.com>
+Date: Wed, 13 Aug 2025 17:25:27 -0700
+Subject: [PATCH] net: dsa: b53: mmap: Add gphy port to phy info for bcm63268
+
+Add gphy mask to bcm63xx phy info struct and add data for bcm63268
+
+Signed-off-by: Kyle Hendry <kylehendrydev@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250814002530.5866-2-kylehendrydev@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_mmap.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/net/dsa/b53/b53_mmap.c
++++ b/drivers/net/dsa/b53/b53_mmap.c
+@@ -31,6 +31,7 @@
+ #define BCM63XX_EPHY_REG 0x3C
+ struct b53_phy_info {
++      u32 gphy_port_mask;
+       u32 ephy_enable_mask;
+       u32 ephy_port_mask;
+       u32 ephy_bias_bit;
+@@ -65,6 +66,7 @@ static const struct b53_phy_info bcm6368
+ static const u32 bcm63268_ephy_offsets[] = {4, 9, 14};
+ static const struct b53_phy_info bcm63268_ephy_info = {
++      .gphy_port_mask = BIT(3),
+       .ephy_enable_mask = GENMASK(4, 0),
+       .ephy_port_mask = GENMASK((ARRAY_SIZE(bcm63268_ephy_offsets) - 1), 0),
+       .ephy_bias_bit = 24,
diff --git a/target/linux/generic/backport-6.12/611-15-v6.18-net-dsa-b53-mmap-Implement-bcm63268-gphy-power-control.patch b/target/linux/generic/backport-6.12/611-15-v6.18-net-dsa-b53-mmap-Implement-bcm63268-gphy-power-control.patch
new file mode 100644 (file)
index 0000000..8dda51e
--- /dev/null
@@ -0,0 +1,77 @@
+From 61730ac10ba90c52563861a0119504f6a9be9868 Mon Sep 17 00:00:00 2001
+From: Kyle Hendry <kylehendrydev@gmail.com>
+Date: Wed, 13 Aug 2025 17:25:28 -0700
+Subject: [PATCH] net: dsa: b53: mmap: Implement bcm63268 gphy power control
+
+Add check for gphy in enable/disable phy calls and set power bits
+in gphy control register.
+
+Signed-off-by: Kyle Hendry <kylehendrydev@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250814002530.5866-3-kylehendrydev@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_mmap.c | 33 +++++++++++++++++++++++++++++----
+ 1 file changed, 29 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_mmap.c
++++ b/drivers/net/dsa/b53/b53_mmap.c
+@@ -29,6 +29,10 @@
+ #include "b53_priv.h"
+ #define BCM63XX_EPHY_REG 0x3C
++#define BCM63268_GPHY_REG 0x54
++
++#define GPHY_CTRL_LOW_PWR     BIT(3)
++#define GPHY_CTRL_IDDQ_BIAS   BIT(0)
+ struct b53_phy_info {
+       u32 gphy_port_mask;
+@@ -292,13 +296,30 @@ static int bcm63xx_ephy_set(struct b53_d
+       return regmap_update_bits(gpio_ctrl, BCM63XX_EPHY_REG, mask, val);
+ }
++static int bcm63268_gphy_set(struct b53_device *dev, bool enable)
++{
++      struct b53_mmap_priv *priv = dev->priv;
++      struct regmap *gpio_ctrl = priv->gpio_ctrl;
++      u32 mask = GPHY_CTRL_IDDQ_BIAS | GPHY_CTRL_LOW_PWR;
++      u32 val = 0;
++
++      if (!enable)
++              val = mask;
++
++      return regmap_update_bits(gpio_ctrl, BCM63268_GPHY_REG, mask, val);
++}
++
+ static void b53_mmap_phy_enable(struct b53_device *dev, int port)
+ {
+       struct b53_mmap_priv *priv = dev->priv;
+       int ret = 0;
+-      if (priv->phy_info && (BIT(port) & priv->phy_info->ephy_port_mask))
+-              ret = bcm63xx_ephy_set(dev, port, true);
++      if (priv->phy_info) {
++              if (BIT(port) & priv->phy_info->ephy_port_mask)
++                      ret = bcm63xx_ephy_set(dev, port, true);
++              else if (BIT(port) & priv->phy_info->gphy_port_mask)
++                      ret = bcm63268_gphy_set(dev, true);
++      }
+       if (!ret)
+               priv->phys_enabled |= BIT(port);
+@@ -309,8 +330,12 @@ static void b53_mmap_phy_disable(struct
+       struct b53_mmap_priv *priv = dev->priv;
+       int ret = 0;
+-      if (priv->phy_info && (BIT(port) & priv->phy_info->ephy_port_mask))
+-              ret = bcm63xx_ephy_set(dev, port, false);
++      if (priv->phy_info) {
++              if (BIT(port) & priv->phy_info->ephy_port_mask)
++                      ret = bcm63xx_ephy_set(dev, port, false);
++              else if (BIT(port) & priv->phy_info->gphy_port_mask)
++                      ret = bcm63268_gphy_set(dev, false);
++      }
+       if (!ret)
+               priv->phys_enabled &= ~BIT(port);
diff --git a/target/linux/generic/backport-6.12/611-16-v6.18-net-dsa-b53-fix-reserved-register-access-in-b53_fdb_dump.patch b/target/linux/generic/backport-6.12/611-16-v6.18-net-dsa-b53-fix-reserved-register-access-in-b53_fdb_dump.patch
new file mode 100644 (file)
index 0000000..5190cf9
--- /dev/null
@@ -0,0 +1,72 @@
+From 89eb9a62aed77b409663ba1eac152e8f758815b7 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 15 Aug 2025 22:18:09 +0200
+Subject: [PATCH] net: dsa: b53: fix reserved register access in b53_fdb_dump()
+
+When BCM5325 support was added in c45655386e53 ("net: dsa: b53: add
+support for FDB operations on 5325/5365"), the register used for ARL access
+was made conditional on the chip.
+
+But in b53_fdb_dump(), instead of the register argument the page
+argument was replaced, causing it to write to a reserved page 0x50 on
+!BCM5325*. Writing to this page seems to completely lock the switch up:
+
+[   89.680000] b53-switch spi0.1 lan2: Link is Down
+[   89.680000] WARNING: CPU: 1 PID: 26 at drivers/net/phy/phy.c:1350 _phy_state_machine+0x1bc/0x454
+[   89.720000] phy_check_link_status+0x0/0x114: returned: -5
+[   89.730000] Modules linked in: nft_fib_inet nf_flow_table_inet nft_reject_ipv6 nft_reject_ipv4 nft_reject_inet nft_reject nft_redir nft_quota nft_numgen nft_nat nft_masq nft_log nft_limit nft_hash nft_flow_offload nft_fib_ipv6 nft_fib_ipv4 nft_fib nft_ct nft_chain_nat nf_tables nf_nat nf_flow_table nf_conntrack nfnetlink nf_reject_ipv6 nf_reject_ipv4 nf_log_syslog nf_defrag_ipv6 nf_defrag_ipv4 cls_flower sch_tbf sch_ingress sch_htb sch_hfsc em_u32 cls_u32 cls_route cls_matchall cls_fw cls_flow cls_basic act_skbedit act_mirred act_gact vrf md5 crc32c_cryptoapi
+[   89.780000] CPU: 1 UID: 0 PID: 26 Comm: kworker/u10:0 Tainted: G        W           6.16.0-rc1+ #0 NONE
+[   89.780000] Tainted: [W]=WARN
+[   89.780000] Hardware name: Netgear DGND3700 v1
+[   89.780000] Workqueue: events_power_efficient phy_state_machine
+[   89.780000] Stack : 809c762c 8006b050 00000001 820a9ce3 0000114c 000affff 805d22d0 8200ba00
+[   89.780000]         82005000 6576656e 74735f70 6f776572 5f656666 10008b00 820a9cb8 82088700
+[   89.780000]         00000000 00000000 809c762c 820a9a98 00000000 00000000 ffffefff 80a7a76c
+[   89.780000]         80a70000 820a9af8 80a70000 80a70000 80a70000 00000000 809c762c 820a9dd4
+[   89.780000]         00000000 805d1494 80a029e4 80a70000 00000003 00000000 00000004 81a60004
+[   89.780000]         ...
+[   89.780000] Call Trace:
+[   89.780000] [<800228b8>] show_stack+0x38/0x118
+[   89.780000] [<8001afc4>] dump_stack_lvl+0x6c/0xac
+[   89.780000] [<80046b90>] __warn+0x9c/0x114
+[   89.780000] [<80046da8>] warn_slowpath_fmt+0x1a0/0x1b0
+[   89.780000] [<805d1494>] _phy_state_machine+0x1bc/0x454
+[   89.780000] [<805d22fc>] phy_state_machine+0x2c/0x70
+[   89.780000] [<80066b08>] process_one_work+0x1e8/0x3e0
+[   89.780000] [<80067a1c>] worker_thread+0x354/0x4e4
+[   89.780000] [<800706cc>] kthread+0x130/0x274
+[   89.780000] [<8001d808>] ret_from_kernel_thread+0x14/0x1c
+
+And any further accesses fail:
+
+[  120.790000] b53-switch spi0.1: timeout waiting for ARL to finish: 0x81
+[  120.800000] b53-switch spi0.1: port 2 failed to add 2c:b0:5d:27:9a:bd vid 3 to fdb: -145
+[  121.010000] b53-switch spi0.1: timeout waiting for ARL to finish: 0xbf
+[  121.020000] b53-switch spi0.1: port 3 failed to add 2c:b0:5d:27:9a:bd vid 3 to fdb: -145
+
+Restore the correct page B53_ARLIO_PAGE again, and move the offset
+argument to the correct place.
+
+*On BCM5325, this became a write to the MIB page of Port 1. Still
+a reserved offset, but likely less brokenness from that write.
+
+Fixes: c45655386e53 ("net: dsa: b53: add support for FDB operations on 5325/5365")
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250815201809.549195-1-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -2100,7 +2100,7 @@ int b53_fdb_dump(struct dsa_switch *ds,
+       /* Start search operation */
+       reg = ARL_SRCH_STDN;
+-      b53_write8(priv, offset, B53_ARL_SRCH_CTL, reg);
++      b53_write8(priv, B53_ARLIO_PAGE, offset, reg);
+       do {
+               ret = b53_arl_search_wait(priv);
diff --git a/target/linux/generic/backport-6.12/611-17-v6.18-net-dsa-b53-fix-ageing-time-for-BCM53101.patch b/target/linux/generic/backport-6.12/611-17-v6.18-net-dsa-b53-fix-ageing-time-for-BCM53101.patch
new file mode 100644 (file)
index 0000000..0eb0870
--- /dev/null
@@ -0,0 +1,77 @@
+From 674b34c4c770551e916ae707829c7faea4782d3a Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 5 Sep 2025 14:45:07 +0200
+Subject: [PATCH] net: dsa: b53: fix ageing time for BCM53101
+
+For some reason Broadcom decided that BCM53101 uses 0.5s increments for
+the ageing time register, but kept the field width the same [1]. Due to
+this, the actual ageing time was always half of what was configured.
+
+Fix this by adapting the limits and value calculation for BCM53101.
+
+So far it looks like this is the only chip with the increased tick
+speed:
+
+$ grep -l -r "Specifies the aging time in 0.5 seconds" cdk/PKG/chip | sort
+cdk/PKG/chip/bcm53101/bcm53101_a0_defs.h
+
+$ grep -l -r "Specifies the aging time in seconds" cdk/PKG/chip | sort
+cdk/PKG/chip/bcm53010/bcm53010_a0_defs.h
+cdk/PKG/chip/bcm53020/bcm53020_a0_defs.h
+cdk/PKG/chip/bcm53084/bcm53084_a0_defs.h
+cdk/PKG/chip/bcm53115/bcm53115_a0_defs.h
+cdk/PKG/chip/bcm53118/bcm53118_a0_defs.h
+cdk/PKG/chip/bcm53125/bcm53125_a0_defs.h
+cdk/PKG/chip/bcm53128/bcm53128_a0_defs.h
+cdk/PKG/chip/bcm53134/bcm53134_a0_defs.h
+cdk/PKG/chip/bcm53242/bcm53242_a0_defs.h
+cdk/PKG/chip/bcm53262/bcm53262_a0_defs.h
+cdk/PKG/chip/bcm53280/bcm53280_a0_defs.h
+cdk/PKG/chip/bcm53280/bcm53280_b0_defs.h
+cdk/PKG/chip/bcm53600/bcm53600_a0_defs.h
+cdk/PKG/chip/bcm89500/bcm89500_a0_defs.h
+
+[1] https://github.com/Broadcom/OpenMDK/blob/a5d3fc9b12af3eeb68f2ca0ce7ec4056cd14d6c2/cdk/PKG/chip/bcm53101/bcm53101_a0_defs.h#L28966
+
+Fixes: e39d14a760c0 ("net: dsa: b53: implement setting ageing time")
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20250905124507.59186-1-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 17 +++++++++++++----
+ 1 file changed, 13 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -1273,9 +1273,15 @@ static int b53_setup(struct dsa_switch *
+        */
+       ds->untag_vlan_aware_bridge_pvid = true;
+-      /* Ageing time is set in seconds */
+-      ds->ageing_time_min = 1 * 1000;
+-      ds->ageing_time_max = AGE_TIME_MAX * 1000;
++      if (dev->chip_id == BCM53101_DEVICE_ID) {
++              /* BCM53101 uses 0.5 second increments */
++              ds->ageing_time_min = 1 * 500;
++              ds->ageing_time_max = AGE_TIME_MAX * 500;
++      } else {
++              /* Everything else uses 1 second increments */
++              ds->ageing_time_min = 1 * 1000;
++              ds->ageing_time_max = AGE_TIME_MAX * 1000;
++      }
+       ret = b53_reset_switch(dev);
+       if (ret) {
+@@ -2587,7 +2593,10 @@ int b53_set_ageing_time(struct dsa_switc
+       else
+               reg = B53_AGING_TIME_CONTROL;
+-      atc = DIV_ROUND_CLOSEST(msecs, 1000);
++      if (dev->chip_id == BCM53101_DEVICE_ID)
++              atc = DIV_ROUND_CLOSEST(msecs, 500);
++      else
++              atc = DIV_ROUND_CLOSEST(msecs, 1000);
+       if (!is5325(dev) && !is5365(dev))
+               atc |= AGE_CHANGE;
diff --git a/target/linux/generic/backport-6.12/611-18-v6.18-net-dsa-b53-properly-bound-ARL-searches-for-4-ARL-bin-chips.patch b/target/linux/generic/backport-6.12/611-18-v6.18-net-dsa-b53-properly-bound-ARL-searches-for-4-ARL-bin-chips.patch
new file mode 100644 (file)
index 0000000..50ddcf9
--- /dev/null
@@ -0,0 +1,61 @@
+From e57723fe536f040cc2635ec1545dd0a7919a321e Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Sun, 2 Nov 2025 11:07:58 +0100
+Subject: [PATCH] net: dsa: b53: properly bound ARL searches for < 4 ARL bin
+ chips
+
+When iterating over the ARL table we stop at max ARL entries / 2, but
+this is only valid if the chip actually returns 2 results at once. For
+chips with only one result register we will stop before reaching the end
+of the table if it is more than half full.
+
+Fix this by only dividing the maximum results by two if we have a chip
+with more than one result register (i.e. those with 4 ARL bins).
+
+Fixes: cd169d799bee ("net: dsa: b53: Bound check ARL searches")
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20251102100758.28352-4-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -2090,13 +2090,16 @@ static int b53_fdb_copy(int port, const
+ int b53_fdb_dump(struct dsa_switch *ds, int port,
+                dsa_fdb_dump_cb_t *cb, void *data)
+ {
++      unsigned int count = 0, results_per_hit = 1;
+       struct b53_device *priv = ds->priv;
+       struct b53_arl_entry results[2];
+-      unsigned int count = 0;
+       u8 offset;
+       int ret;
+       u8 reg;
++      if (priv->num_arl_bins > 2)
++              results_per_hit = 2;
++
+       mutex_lock(&priv->arl_mutex);
+       if (is5325(priv) || is5365(priv))
+@@ -2118,7 +2121,7 @@ int b53_fdb_dump(struct dsa_switch *ds,
+               if (ret)
+                       break;
+-              if (priv->num_arl_bins > 2) {
++              if (results_per_hit == 2) {
+                       b53_arl_search_rd(priv, 1, &results[1]);
+                       ret = b53_fdb_copy(port, &results[1], cb, data);
+                       if (ret)
+@@ -2128,7 +2131,7 @@ int b53_fdb_dump(struct dsa_switch *ds,
+                               break;
+               }
+-      } while (count++ < b53_max_arl_entries(priv) / 2);
++      } while (count++ < b53_max_arl_entries(priv) / results_per_hit);
+       mutex_unlock(&priv->arl_mutex);
diff --git a/target/linux/generic/backport-6.12/611-19-v6.18-net-dsa-tag_brcm-do-not-mark-link-local-traffic-as-offloaded.patch b/target/linux/generic/backport-6.12/611-19-v6.18-net-dsa-tag_brcm-do-not-mark-link-local-traffic-as-offloaded.patch
new file mode 100644 (file)
index 0000000..7c3d062
--- /dev/null
@@ -0,0 +1,59 @@
+From 762e7e174da91cf4babfe77e45bc6b67334b1503 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Sun, 9 Nov 2025 14:46:35 +0100
+Subject: [PATCH] net: dsa: tag_brcm: do not mark link local traffic as
+ offloaded
+
+Broadcom switches locally terminate link local traffic and do not
+forward it, so we should not mark it as offloaded.
+
+In some situations we still want/need to flood this traffic, e.g. if STP
+is disabled, or it is explicitly enabled via the group_fwd_mask. But if
+the skb is marked as offloaded, the kernel will assume this was already
+done in hardware, and the packets never reach other bridge ports.
+
+So ensure that link local traffic is never marked as offloaded, so that
+the kernel can forward/flood these packets in software if needed.
+
+Since the local termination in not configurable, check the destination
+MAC, and never mark packets as offloaded if it is a link local ether
+address.
+
+While modern switches set the tag reason code to BRCM_EG_RC_PROT_TERM
+for trapped link local traffic, they also set it for link local traffic
+that is flooded (01:80:c2:00:00:10 to 01:80:c2:00:00:2f), so we cannot
+use it and need to look at the destination address for them as well.
+
+Fixes: 964dbf186eaa ("net: dsa: tag_brcm: add support for legacy tags")
+Fixes: 0e62f543bed0 ("net: dsa: Fix duplicate frames flooded by learning")
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20251109134635.243951-1-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/dsa/tag_brcm.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+--- a/net/dsa/tag_brcm.c
++++ b/net/dsa/tag_brcm.c
+@@ -176,7 +176,8 @@ static struct sk_buff *brcm_tag_rcv_ll(s
+       /* Remove Broadcom tag and update checksum */
+       skb_pull_rcsum(skb, BRCM_TAG_LEN);
+-      dsa_default_offload_fwd_mark(skb);
++      if (likely(!is_link_local_ether_addr(eth_hdr(skb)->h_dest)))
++              dsa_default_offload_fwd_mark(skb);
+       return skb;
+ }
+@@ -250,7 +251,8 @@ static struct sk_buff *brcm_leg_tag_rcv(
+       /* Remove Broadcom tag and update checksum */
+       skb_pull_rcsum(skb, len);
+-      dsa_default_offload_fwd_mark(skb);
++      if (likely(!is_link_local_ether_addr(eth_hdr(skb)->h_dest)))
++              dsa_default_offload_fwd_mark(skb);
+       dsa_strip_etype_header(skb, len);
diff --git a/target/linux/generic/backport-6.12/612-01-v6.19-net-dsa-b53-b53_arl_read-25-use-the-entry-for-comparision.patch b/target/linux/generic/backport-6.12/612-01-v6.19-net-dsa-b53-b53_arl_read-25-use-the-entry-for-comparision.patch
new file mode 100644 (file)
index 0000000..2b6cff7
--- /dev/null
@@ -0,0 +1,85 @@
+From a6e4fd38bf2f2e2363b61c27f4e6c49b14e4bb07 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 7 Nov 2025 09:07:42 +0100
+Subject: [PATCH] net: dsa: b53: b53_arl_read{,25}(): use the entry for
+ comparision
+
+Align the b53_arl_read{,25}() functions by consistently using the
+parsed arl entry instead of parsing the raw registers again.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20251107080749.26936-2-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 22 ++++++++++------------
+ 1 file changed, 10 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -1830,7 +1830,7 @@ static int b53_arl_rw_op(struct b53_devi
+       return b53_arl_op_wait(dev);
+ }
+-static int b53_arl_read(struct b53_device *dev, u64 mac,
++static int b53_arl_read(struct b53_device *dev, const u8 *mac,
+                       u16 vid, struct b53_arl_entry *ent, u8 *idx)
+ {
+       DECLARE_BITMAP(free_bins, B53_ARLTBL_MAX_BIN_ENTRIES);
+@@ -1854,14 +1854,13 @@ static int b53_arl_read(struct b53_devic
+                          B53_ARLTBL_DATA_ENTRY(i), &fwd_entry);
+               b53_arl_to_entry(ent, mac_vid, fwd_entry);
+-              if (!(fwd_entry & ARLTBL_VALID)) {
++              if (!ent->is_valid) {
+                       set_bit(i, free_bins);
+                       continue;
+               }
+-              if ((mac_vid & ARLTBL_MAC_MASK) != mac)
++              if (!ether_addr_equal(ent->mac, mac))
+                       continue;
+-              if (dev->vlan_enabled &&
+-                  ((mac_vid >> ARLTBL_VID_S) & ARLTBL_VID_MASK) != vid)
++              if (dev->vlan_enabled && ent->vid != vid)
+                       continue;
+               *idx = i;
+               return 0;
+@@ -1871,7 +1870,7 @@ static int b53_arl_read(struct b53_devic
+       return *idx >= dev->num_arl_bins ? -ENOSPC : -ENOENT;
+ }
+-static int b53_arl_read_25(struct b53_device *dev, u64 mac,
++static int b53_arl_read_25(struct b53_device *dev, const u8 *mac,
+                          u16 vid, struct b53_arl_entry *ent, u8 *idx)
+ {
+       DECLARE_BITMAP(free_bins, B53_ARLTBL_MAX_BIN_ENTRIES);
+@@ -1893,14 +1892,13 @@ static int b53_arl_read_25(struct b53_de
+               b53_arl_to_entry_25(ent, mac_vid);
+-              if (!(mac_vid & ARLTBL_VALID_25)) {
++              if (!ent->is_valid) {
+                       set_bit(i, free_bins);
+                       continue;
+               }
+-              if ((mac_vid & ARLTBL_MAC_MASK) != mac)
++              if (!ether_addr_equal(ent->mac, mac))
+                       continue;
+-              if (dev->vlan_enabled &&
+-                  ((mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25) != vid)
++              if (dev->vlan_enabled && ent->vid != vid)
+                       continue;
+               *idx = i;
+               return 0;
+@@ -1933,9 +1931,9 @@ static int b53_arl_op(struct b53_device
+               return ret;
+       if (is5325(dev) || is5365(dev))
+-              ret = b53_arl_read_25(dev, mac, vid, &ent, &idx);
++              ret = b53_arl_read_25(dev, addr, vid, &ent, &idx);
+       else
+-              ret = b53_arl_read(dev, mac, vid, &ent, &idx);
++              ret = b53_arl_read(dev, addr, vid, &ent, &idx);
+       /* If this is a read, just finish now */
+       if (op)
diff --git a/target/linux/generic/backport-6.12/612-02-v6.19-net-dsa-b53-move-reading-ARL-entries-into-their-own-function.patch b/target/linux/generic/backport-6.12/612-02-v6.19-net-dsa-b53-move-reading-ARL-entries-into-their-own-function.patch
new file mode 100644 (file)
index 0000000..8cf85ce
--- /dev/null
@@ -0,0 +1,117 @@
+From 4a291fe7226736a465ddb3fa93c21fcef7162ec7 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 7 Nov 2025 09:07:43 +0100
+Subject: [PATCH] net: dsa: b53: move reading ARL entries into their own
+ function
+
+Instead of duplicating the whole code iterating over all bins for
+BCM5325, factor out reading and parsing the entry into its own
+functions, and name it the modern one after the first chip with that ARL
+format, (BCM53)95.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20251107080749.26936-3-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 69 +++++++++++---------------------
+ 1 file changed, 23 insertions(+), 46 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -1830,48 +1830,30 @@ static int b53_arl_rw_op(struct b53_devi
+       return b53_arl_op_wait(dev);
+ }
+-static int b53_arl_read(struct b53_device *dev, const u8 *mac,
+-                      u16 vid, struct b53_arl_entry *ent, u8 *idx)
++static void b53_arl_read_entry_25(struct b53_device *dev,
++                                struct b53_arl_entry *ent, u8 idx)
+ {
+-      DECLARE_BITMAP(free_bins, B53_ARLTBL_MAX_BIN_ENTRIES);
+-      unsigned int i;
+-      int ret;
+-
+-      ret = b53_arl_op_wait(dev);
+-      if (ret)
+-              return ret;
++      u64 mac_vid;
+-      bitmap_zero(free_bins, dev->num_arl_bins);
+-
+-      /* Read the bins */
+-      for (i = 0; i < dev->num_arl_bins; i++) {
+-              u64 mac_vid;
+-              u32 fwd_entry;
+-
+-              b53_read64(dev, B53_ARLIO_PAGE,
+-                         B53_ARLTBL_MAC_VID_ENTRY(i), &mac_vid);
+-              b53_read32(dev, B53_ARLIO_PAGE,
+-                         B53_ARLTBL_DATA_ENTRY(i), &fwd_entry);
+-              b53_arl_to_entry(ent, mac_vid, fwd_entry);
++      b53_read64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx),
++                 &mac_vid);
++      b53_arl_to_entry_25(ent, mac_vid);
++}
+-              if (!ent->is_valid) {
+-                      set_bit(i, free_bins);
+-                      continue;
+-              }
+-              if (!ether_addr_equal(ent->mac, mac))
+-                      continue;
+-              if (dev->vlan_enabled && ent->vid != vid)
+-                      continue;
+-              *idx = i;
+-              return 0;
+-      }
++static void b53_arl_read_entry_95(struct b53_device *dev,
++                                struct b53_arl_entry *ent, u8 idx)
++{
++      u32 fwd_entry;
++      u64 mac_vid;
+-      *idx = find_first_bit(free_bins, dev->num_arl_bins);
+-      return *idx >= dev->num_arl_bins ? -ENOSPC : -ENOENT;
++      b53_read64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx),
++                 &mac_vid);
++      b53_read32(dev, B53_ARLIO_PAGE, B53_ARLTBL_DATA_ENTRY(idx), &fwd_entry);
++      b53_arl_to_entry(ent, mac_vid, fwd_entry);
+ }
+-static int b53_arl_read_25(struct b53_device *dev, const u8 *mac,
+-                         u16 vid, struct b53_arl_entry *ent, u8 *idx)
++static int b53_arl_read(struct b53_device *dev, const u8 *mac,
++                      u16 vid, struct b53_arl_entry *ent, u8 *idx)
+ {
+       DECLARE_BITMAP(free_bins, B53_ARLTBL_MAX_BIN_ENTRIES);
+       unsigned int i;
+@@ -1885,12 +1867,10 @@ static int b53_arl_read_25(struct b53_de
+       /* Read the bins */
+       for (i = 0; i < dev->num_arl_bins; i++) {
+-              u64 mac_vid;
+-
+-              b53_read64(dev, B53_ARLIO_PAGE,
+-                         B53_ARLTBL_MAC_VID_ENTRY(i), &mac_vid);
+-
+-              b53_arl_to_entry_25(ent, mac_vid);
++              if (is5325(dev) || is5365(dev))
++                      b53_arl_read_entry_25(dev, ent, i);
++              else
++                      b53_arl_read_entry_95(dev, ent, i);
+               if (!ent->is_valid) {
+                       set_bit(i, free_bins);
+@@ -1930,10 +1910,7 @@ static int b53_arl_op(struct b53_device
+       if (ret)
+               return ret;
+-      if (is5325(dev) || is5365(dev))
+-              ret = b53_arl_read_25(dev, addr, vid, &ent, &idx);
+-      else
+-              ret = b53_arl_read(dev, addr, vid, &ent, &idx);
++      ret = b53_arl_read(dev, addr, vid, &ent, &idx);
+       /* If this is a read, just finish now */
+       if (op)
diff --git a/target/linux/generic/backport-6.12/612-03-v6.19-net-dsa-b53-move-writing-ARL-entries-into-their-own-functions.patch b/target/linux/generic/backport-6.12/612-03-v6.19-net-dsa-b53-move-writing-ARL-entries-into-their-own-functions.patch
new file mode 100644 (file)
index 0000000..a171232
--- /dev/null
@@ -0,0 +1,93 @@
+From bf6e9d2ae1dbafee53ec4ccd126595172e1e5278 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 7 Nov 2025 09:07:44 +0100
+Subject: [PATCH] net: dsa: b53: move writing ARL entries into their own
+ functions
+
+Move writing ARL entries into individual functions for each format.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20251107080749.26936-4-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 38 ++++++++++++++++++++++----------
+ 1 file changed, 26 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -1840,6 +1840,16 @@ static void b53_arl_read_entry_25(struct
+       b53_arl_to_entry_25(ent, mac_vid);
+ }
++static void b53_arl_write_entry_25(struct b53_device *dev,
++                                 const struct b53_arl_entry *ent, u8 idx)
++{
++      u64 mac_vid;
++
++      b53_arl_from_entry_25(&mac_vid, ent);
++      b53_write64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx),
++                  mac_vid);
++}
++
+ static void b53_arl_read_entry_95(struct b53_device *dev,
+                                 struct b53_arl_entry *ent, u8 idx)
+ {
+@@ -1852,6 +1862,19 @@ static void b53_arl_read_entry_95(struct
+       b53_arl_to_entry(ent, mac_vid, fwd_entry);
+ }
++static void b53_arl_write_entry_95(struct b53_device *dev,
++                                 const struct b53_arl_entry *ent, u8 idx)
++{
++      u32 fwd_entry;
++      u64 mac_vid;
++
++      b53_arl_from_entry(&mac_vid, &fwd_entry, ent);
++      b53_write64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx),
++                  mac_vid);
++      b53_write32(dev, B53_ARLIO_PAGE, B53_ARLTBL_DATA_ENTRY(idx),
++                  fwd_entry);
++}
++
+ static int b53_arl_read(struct b53_device *dev, const u8 *mac,
+                       u16 vid, struct b53_arl_entry *ent, u8 *idx)
+ {
+@@ -1892,9 +1915,8 @@ static int b53_arl_op(struct b53_device
+                     const unsigned char *addr, u16 vid, bool is_valid)
+ {
+       struct b53_arl_entry ent;
+-      u32 fwd_entry;
+-      u64 mac, mac_vid = 0;
+       u8 idx = 0;
++      u64 mac;
+       int ret;
+       /* Convert the array into a 64-bit MAC */
+@@ -1927,7 +1949,6 @@ static int b53_arl_op(struct b53_device
+               /* We could not find a matching MAC, so reset to a new entry */
+               dev_dbg(dev->dev, "{%pM,%.4d} not found, using idx: %d\n",
+                       addr, vid, idx);
+-              fwd_entry = 0;
+               break;
+       default:
+               dev_dbg(dev->dev, "{%pM,%.4d} found, using idx: %d\n",
+@@ -1955,16 +1976,9 @@ static int b53_arl_op(struct b53_device
+       ent.is_age = false;
+       memcpy(ent.mac, addr, ETH_ALEN);
+       if (is5325(dev) || is5365(dev))
+-              b53_arl_from_entry_25(&mac_vid, &ent);
++              b53_arl_write_entry_25(dev, &ent, idx);
+       else
+-              b53_arl_from_entry(&mac_vid, &fwd_entry, &ent);
+-
+-      b53_write64(dev, B53_ARLIO_PAGE,
+-                  B53_ARLTBL_MAC_VID_ENTRY(idx), mac_vid);
+-
+-      if (!is5325(dev) && !is5365(dev))
+-              b53_write32(dev, B53_ARLIO_PAGE,
+-                          B53_ARLTBL_DATA_ENTRY(idx), fwd_entry);
++              b53_arl_write_entry_95(dev, &ent, idx);
+       return b53_arl_rw_op(dev, 0);
+ }
diff --git a/target/linux/generic/backport-6.12/612-04-v6.19-net-dsa-b53-provide-accessors-for-accessing-ARL_SRCH_CTL.patch b/target/linux/generic/backport-6.12/612-04-v6.19-net-dsa-b53-provide-accessors-for-accessing-ARL_SRCH_CTL.patch
new file mode 100644 (file)
index 0000000..2922427
--- /dev/null
@@ -0,0 +1,85 @@
+From 1716be6db04af53bac9b869f01156a460595cf41 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 7 Nov 2025 09:07:45 +0100
+Subject: [PATCH] net: dsa: b53: provide accessors for accessing ARL_SRCH_CTL
+
+In order to more easily support more formats, move accessing
+ARL_SRCH_CTL into helper functions to contain the differences.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20251107080749.26936-5-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 37 +++++++++++++++++++++-----------
+ 1 file changed, 24 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -2013,18 +2013,37 @@ int b53_fdb_del(struct dsa_switch *ds, i
+ }
+ EXPORT_SYMBOL(b53_fdb_del);
+-static int b53_arl_search_wait(struct b53_device *dev)
++static void b53_read_arl_srch_ctl(struct b53_device *dev, u8 *val)
+ {
+-      unsigned int timeout = 1000;
+-      u8 reg, offset;
++      u8 offset;
++
++      if (is5325(dev) || is5365(dev))
++              offset = B53_ARL_SRCH_CTL_25;
++      else
++              offset = B53_ARL_SRCH_CTL;
++
++      b53_read8(dev, B53_ARLIO_PAGE, offset, val);
++}
++
++static void b53_write_arl_srch_ctl(struct b53_device *dev, u8 val)
++{
++      u8 offset;
+       if (is5325(dev) || is5365(dev))
+               offset = B53_ARL_SRCH_CTL_25;
+       else
+               offset = B53_ARL_SRCH_CTL;
++      b53_write8(dev, B53_ARLIO_PAGE, offset, val);
++}
++
++static int b53_arl_search_wait(struct b53_device *dev)
++{
++      unsigned int timeout = 1000;
++      u8 reg;
++
+       do {
+-              b53_read8(dev, B53_ARLIO_PAGE, offset, &reg);
++              b53_read_arl_srch_ctl(dev, &reg);
+               if (!(reg & ARL_SRCH_STDN))
+                       return -ENOENT;
+@@ -2082,23 +2101,15 @@ int b53_fdb_dump(struct dsa_switch *ds,
+       unsigned int count = 0, results_per_hit = 1;
+       struct b53_device *priv = ds->priv;
+       struct b53_arl_entry results[2];
+-      u8 offset;
+       int ret;
+-      u8 reg;
+       if (priv->num_arl_bins > 2)
+               results_per_hit = 2;
+       mutex_lock(&priv->arl_mutex);
+-      if (is5325(priv) || is5365(priv))
+-              offset = B53_ARL_SRCH_CTL_25;
+-      else
+-              offset = B53_ARL_SRCH_CTL;
+-
+       /* Start search operation */
+-      reg = ARL_SRCH_STDN;
+-      b53_write8(priv, B53_ARLIO_PAGE, offset, reg);
++      b53_write_arl_srch_ctl(priv, ARL_SRCH_STDN);
+       do {
+               ret = b53_arl_search_wait(priv);
diff --git a/target/linux/generic/backport-6.12/612-05-v6.19-net-dsa-b53-split-reading-search-entry-into-their-own-functions.patch b/target/linux/generic/backport-6.12/612-05-v6.19-net-dsa-b53-split-reading-search-entry-into-their-own-functions.patch
new file mode 100644 (file)
index 0000000..3e263f8
--- /dev/null
@@ -0,0 +1,86 @@
+From e0c476f325a8c9b961a3d446c24d3c8ecae7d186 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 7 Nov 2025 09:07:46 +0100
+Subject: [PATCH] net: dsa: b53: split reading search entry into their own
+ functions
+
+Split reading search entries into a function for each format.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20251107080749.26936-6-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 56 ++++++++++++++++++++++----------
+ 1 file changed, 38 insertions(+), 18 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -2056,28 +2056,48 @@ static int b53_arl_search_wait(struct b5
+       return -ETIMEDOUT;
+ }
+-static void b53_arl_search_rd(struct b53_device *dev, u8 idx,
+-                            struct b53_arl_entry *ent)
++static void b53_arl_search_read_25(struct b53_device *dev, u8 idx,
++                                 struct b53_arl_entry *ent)
+ {
+       u64 mac_vid;
+-      if (is5325(dev)) {
+-              b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_25,
+-                         &mac_vid);
+-              b53_arl_to_entry_25(ent, mac_vid);
+-      } else if (is5365(dev)) {
+-              b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_65,
+-                         &mac_vid);
+-              b53_arl_to_entry_25(ent, mac_vid);
+-      } else {
+-              u32 fwd_entry;
+-
+-              b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_MACVID(idx),
+-                         &mac_vid);
+-              b53_read32(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL(idx),
+-                         &fwd_entry);
+-              b53_arl_to_entry(ent, mac_vid, fwd_entry);
+-      }
++      b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_25,
++                 &mac_vid);
++      b53_arl_to_entry_25(ent, mac_vid);
++}
++
++static void b53_arl_search_read_65(struct b53_device *dev, u8 idx,
++                                 struct b53_arl_entry *ent)
++{
++      u64 mac_vid;
++
++      b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_65,
++                 &mac_vid);
++      b53_arl_to_entry_25(ent, mac_vid);
++}
++
++static void b53_arl_search_read_95(struct b53_device *dev, u8 idx,
++                                 struct b53_arl_entry *ent)
++{
++      u32 fwd_entry;
++      u64 mac_vid;
++
++      b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_MACVID(idx),
++                 &mac_vid);
++      b53_read32(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL(idx),
++                 &fwd_entry);
++      b53_arl_to_entry(ent, mac_vid, fwd_entry);
++}
++
++static void b53_arl_search_rd(struct b53_device *dev, u8 idx,
++                            struct b53_arl_entry *ent)
++{
++      if (is5325(dev))
++              b53_arl_search_read_25(dev, idx, ent);
++      else if (is5365(dev))
++              b53_arl_search_read_65(dev, idx, ent);
++      else
++              b53_arl_search_read_95(dev, idx, ent);
+ }
+ static int b53_fdb_copy(int port, const struct b53_arl_entry *ent,
diff --git a/target/linux/generic/backport-6.12/612-06-v6.19-net-dsa-b53-move-ARL-entry-functions-into-ops-struct.patch b/target/linux/generic/backport-6.12/612-06-v6.19-net-dsa-b53-move-ARL-entry-functions-into-ops-struct.patch
new file mode 100644 (file)
index 0000000..110e160
--- /dev/null
@@ -0,0 +1,348 @@
+From a7e73339ad46ade76d29fb6cc7d7854222608c26 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 7 Nov 2025 09:07:47 +0100
+Subject: [PATCH] net: dsa: b53: move ARL entry functions into ops struct
+
+Now that the differences in ARL entry formats are neatly contained into
+functions per chip family, wrap them into an ops struct and add wrapper
+functions to access them.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20251107080749.26936-7-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 67 ++++++++++++++++++++++----------
+ drivers/net/dsa/b53/b53_priv.h   | 30 ++++++++++++++
+ 2 files changed, 76 insertions(+), 21 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -1890,10 +1890,7 @@ static int b53_arl_read(struct b53_devic
+       /* Read the bins */
+       for (i = 0; i < dev->num_arl_bins; i++) {
+-              if (is5325(dev) || is5365(dev))
+-                      b53_arl_read_entry_25(dev, ent, i);
+-              else
+-                      b53_arl_read_entry_95(dev, ent, i);
++              b53_arl_read_entry(dev, ent, i);
+               if (!ent->is_valid) {
+                       set_bit(i, free_bins);
+@@ -1975,10 +1972,7 @@ static int b53_arl_op(struct b53_device
+       ent.is_static = true;
+       ent.is_age = false;
+       memcpy(ent.mac, addr, ETH_ALEN);
+-      if (is5325(dev) || is5365(dev))
+-              b53_arl_write_entry_25(dev, &ent, idx);
+-      else
+-              b53_arl_write_entry_95(dev, &ent, idx);
++      b53_arl_write_entry(dev, &ent, idx);
+       return b53_arl_rw_op(dev, 0);
+ }
+@@ -2089,17 +2083,6 @@ static void b53_arl_search_read_95(struc
+       b53_arl_to_entry(ent, mac_vid, fwd_entry);
+ }
+-static void b53_arl_search_rd(struct b53_device *dev, u8 idx,
+-                            struct b53_arl_entry *ent)
+-{
+-      if (is5325(dev))
+-              b53_arl_search_read_25(dev, idx, ent);
+-      else if (is5365(dev))
+-              b53_arl_search_read_65(dev, idx, ent);
+-      else
+-              b53_arl_search_read_95(dev, idx, ent);
+-}
+-
+ static int b53_fdb_copy(int port, const struct b53_arl_entry *ent,
+                       dsa_fdb_dump_cb_t *cb, void *data)
+ {
+@@ -2136,13 +2119,13 @@ int b53_fdb_dump(struct dsa_switch *ds,
+               if (ret)
+                       break;
+-              b53_arl_search_rd(priv, 0, &results[0]);
++              b53_arl_search_read(priv, 0, &results[0]);
+               ret = b53_fdb_copy(port, &results[0], cb, data);
+               if (ret)
+                       break;
+               if (results_per_hit == 2) {
+-                      b53_arl_search_rd(priv, 1, &results[1]);
++                      b53_arl_search_read(priv, 1, &results[1]);
+                       ret = b53_fdb_copy(port, &results[1], cb, data);
+                       if (ret)
+                               break;
+@@ -2675,6 +2658,24 @@ static const struct dsa_switch_ops b53_s
+       .port_change_mtu        = b53_change_mtu,
+ };
++static const struct b53_arl_ops b53_arl_ops_25 = {
++      .arl_read_entry = b53_arl_read_entry_25,
++      .arl_write_entry = b53_arl_write_entry_25,
++      .arl_search_read = b53_arl_search_read_25,
++};
++
++static const struct b53_arl_ops b53_arl_ops_65 = {
++      .arl_read_entry = b53_arl_read_entry_25,
++      .arl_write_entry = b53_arl_write_entry_25,
++      .arl_search_read = b53_arl_search_read_65,
++};
++
++static const struct b53_arl_ops b53_arl_ops_95 = {
++      .arl_read_entry = b53_arl_read_entry_95,
++      .arl_write_entry = b53_arl_write_entry_95,
++      .arl_search_read = b53_arl_search_read_95,
++};
++
+ struct b53_chip_data {
+       u32 chip_id;
+       const char *dev_name;
+@@ -2688,6 +2689,7 @@ struct b53_chip_data {
+       u8 duplex_reg;
+       u8 jumbo_pm_reg;
+       u8 jumbo_size_reg;
++      const struct b53_arl_ops *arl_ops;
+ };
+ #define B53_VTA_REGS  \
+@@ -2707,6 +2709,7 @@ static const struct b53_chip_data b53_sw
+               .arl_buckets = 1024,
+               .imp_port = 5,
+               .duplex_reg = B53_DUPLEX_STAT_FE,
++              .arl_ops = &b53_arl_ops_25,
+       },
+       {
+               .chip_id = BCM5365_DEVICE_ID,
+@@ -2717,6 +2720,7 @@ static const struct b53_chip_data b53_sw
+               .arl_buckets = 1024,
+               .imp_port = 5,
+               .duplex_reg = B53_DUPLEX_STAT_FE,
++              .arl_ops = &b53_arl_ops_65,
+       },
+       {
+               .chip_id = BCM5389_DEVICE_ID,
+@@ -2730,6 +2734,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM5395_DEVICE_ID,
+@@ -2743,6 +2748,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM5397_DEVICE_ID,
+@@ -2756,6 +2762,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM5398_DEVICE_ID,
+@@ -2769,6 +2776,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM53101_DEVICE_ID,
+@@ -2782,6 +2790,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM53115_DEVICE_ID,
+@@ -2795,6 +2804,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM53125_DEVICE_ID,
+@@ -2808,6 +2818,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM53128_DEVICE_ID,
+@@ -2821,6 +2832,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM63XX_DEVICE_ID,
+@@ -2834,6 +2846,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_63XX,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM53010_DEVICE_ID,
+@@ -2847,6 +2860,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM53011_DEVICE_ID,
+@@ -2860,6 +2874,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM53012_DEVICE_ID,
+@@ -2873,6 +2888,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM53018_DEVICE_ID,
+@@ -2886,6 +2902,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM53019_DEVICE_ID,
+@@ -2899,6 +2916,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM58XX_DEVICE_ID,
+@@ -2912,6 +2930,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM583XX_DEVICE_ID,
+@@ -2925,6 +2944,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       /* Starfighter 2 */
+       {
+@@ -2939,6 +2959,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM7445_DEVICE_ID,
+@@ -2952,6 +2973,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM7278_DEVICE_ID,
+@@ -2965,6 +2987,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+       {
+               .chip_id = BCM53134_DEVICE_ID,
+@@ -2979,6 +3002,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
++              .arl_ops = &b53_arl_ops_95,
+       },
+ };
+@@ -3007,6 +3031,7 @@ static int b53_switch_init(struct b53_de
+                       dev->num_vlans = chip->vlans;
+                       dev->num_arl_bins = chip->arl_bins;
+                       dev->num_arl_buckets = chip->arl_buckets;
++                      dev->arl_ops = chip->arl_ops;
+                       break;
+               }
+       }
+--- a/drivers/net/dsa/b53/b53_priv.h
++++ b/drivers/net/dsa/b53/b53_priv.h
+@@ -58,6 +58,17 @@ struct b53_io_ops {
+                               bool link_up);
+ };
++struct b53_arl_entry;
++
++struct b53_arl_ops {
++      void (*arl_read_entry)(struct b53_device *dev,
++                             struct b53_arl_entry *ent, u8 idx);
++      void (*arl_write_entry)(struct b53_device *dev,
++                              const struct b53_arl_entry *ent, u8 idx);
++      void (*arl_search_read)(struct b53_device *dev, u8 idx,
++                              struct b53_arl_entry *ent);
++};
++
+ #define B53_INVALID_LANE      0xff
+ enum {
+@@ -127,6 +138,7 @@ struct b53_device {
+       struct mutex stats_mutex;
+       struct mutex arl_mutex;
+       const struct b53_io_ops *ops;
++      const struct b53_arl_ops *arl_ops;
+       /* chip specific data */
+       u32 chip_id;
+@@ -371,6 +383,24 @@ static inline void b53_arl_from_entry_25
+               *mac_vid |= ARLTBL_AGE_25;
+ }
++static inline void b53_arl_read_entry(struct b53_device *dev,
++                                    struct b53_arl_entry *ent, u8 idx)
++{
++      dev->arl_ops->arl_read_entry(dev, ent, idx);
++}
++
++static inline void b53_arl_write_entry(struct b53_device *dev,
++                                     const struct b53_arl_entry *ent, u8 idx)
++{
++      dev->arl_ops->arl_write_entry(dev, ent, idx);
++}
++
++static inline void b53_arl_search_read(struct b53_device *dev, u8 idx,
++                                     struct b53_arl_entry *ent)
++{
++      dev->arl_ops->arl_search_read(dev, idx, ent);
++}
++
+ #ifdef CONFIG_BCM47XX
+ #include <linux/bcm47xx_nvram.h>
diff --git a/target/linux/generic/backport-6.12/612-07-v6.19-net-dsa-b53-add-support-for-5389-5397-5398-ARL-entry-format.patch b/target/linux/generic/backport-6.12/612-07-v6.19-net-dsa-b53-add-support-for-5389-5397-5398-ARL-entry-format.patch
new file mode 100644 (file)
index 0000000..948b4ab
--- /dev/null
@@ -0,0 +1,221 @@
+From 300f78e8b6b7be17c2c78afeded75be68acb1aa7 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 7 Nov 2025 09:07:48 +0100
+Subject: [PATCH] net: dsa: b53: add support for 5389/5397/5398 ARL entry
+ format
+
+BCM5389, BCM5397 and BCM5398 use a different ARL entry format with just
+a 16 bit fwdentry register, as well as different search control and data
+offsets.
+
+So add appropriate ops for them and switch those chips to use them.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20251107080749.26936-8-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 53 ++++++++++++++++++++++++++++++--
+ drivers/net/dsa/b53/b53_priv.h   | 26 ++++++++++++++++
+ drivers/net/dsa/b53/b53_regs.h   | 13 ++++++++
+ 3 files changed, 89 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -1850,6 +1850,31 @@ static void b53_arl_write_entry_25(struc
+                   mac_vid);
+ }
++static void b53_arl_read_entry_89(struct b53_device *dev,
++                                struct b53_arl_entry *ent, u8 idx)
++{
++      u64 mac_vid;
++      u16 fwd_entry;
++
++      b53_read64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx),
++                 &mac_vid);
++      b53_read16(dev, B53_ARLIO_PAGE, B53_ARLTBL_DATA_ENTRY(idx), &fwd_entry);
++      b53_arl_to_entry_89(ent, mac_vid, fwd_entry);
++}
++
++static void b53_arl_write_entry_89(struct b53_device *dev,
++                                 const struct b53_arl_entry *ent, u8 idx)
++{
++      u32 fwd_entry;
++      u64 mac_vid;
++
++      b53_arl_from_entry_89(&mac_vid, &fwd_entry, ent);
++      b53_write64(dev, B53_ARLIO_PAGE,
++                  B53_ARLTBL_MAC_VID_ENTRY(idx), mac_vid);
++      b53_write16(dev, B53_ARLIO_PAGE,
++                  B53_ARLTBL_DATA_ENTRY(idx), fwd_entry);
++}
++
+ static void b53_arl_read_entry_95(struct b53_device *dev,
+                                 struct b53_arl_entry *ent, u8 idx)
+ {
+@@ -2013,6 +2038,8 @@ static void b53_read_arl_srch_ctl(struct
+       if (is5325(dev) || is5365(dev))
+               offset = B53_ARL_SRCH_CTL_25;
++      else if (dev->chip_id == BCM5389_DEVICE_ID || is5397_98(dev))
++              offset = B53_ARL_SRCH_CTL_89;
+       else
+               offset = B53_ARL_SRCH_CTL;
+@@ -2025,6 +2052,8 @@ static void b53_write_arl_srch_ctl(struc
+       if (is5325(dev) || is5365(dev))
+               offset = B53_ARL_SRCH_CTL_25;
++      else if (dev->chip_id == BCM5389_DEVICE_ID || is5397_98(dev))
++              offset = B53_ARL_SRCH_CTL_89;
+       else
+               offset = B53_ARL_SRCH_CTL;
+@@ -2070,6 +2099,18 @@ static void b53_arl_search_read_65(struc
+       b53_arl_to_entry_25(ent, mac_vid);
+ }
++static void b53_arl_search_read_89(struct b53_device *dev, u8 idx,
++                                 struct b53_arl_entry *ent)
++{
++      u16 fwd_entry;
++      u64 mac_vid;
++
++      b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSLT_MACVID_89,
++                 &mac_vid);
++      b53_read16(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSLT_89, &fwd_entry);
++      b53_arl_to_entry_89(ent, mac_vid, fwd_entry);
++}
++
+ static void b53_arl_search_read_95(struct b53_device *dev, u8 idx,
+                                  struct b53_arl_entry *ent)
+ {
+@@ -2670,6 +2711,12 @@ static const struct b53_arl_ops b53_arl_
+       .arl_search_read = b53_arl_search_read_65,
+ };
++static const struct b53_arl_ops b53_arl_ops_89 = {
++      .arl_read_entry = b53_arl_read_entry_89,
++      .arl_write_entry = b53_arl_write_entry_89,
++      .arl_search_read = b53_arl_search_read_89,
++};
++
+ static const struct b53_arl_ops b53_arl_ops_95 = {
+       .arl_read_entry = b53_arl_read_entry_95,
+       .arl_write_entry = b53_arl_write_entry_95,
+@@ -2734,7 +2781,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+-              .arl_ops = &b53_arl_ops_95,
++              .arl_ops = &b53_arl_ops_89,
+       },
+       {
+               .chip_id = BCM5395_DEVICE_ID,
+@@ -2762,7 +2809,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+-              .arl_ops = &b53_arl_ops_95,
++              .arl_ops = &b53_arl_ops_89,
+       },
+       {
+               .chip_id = BCM5398_DEVICE_ID,
+@@ -2776,7 +2823,7 @@ static const struct b53_chip_data b53_sw
+               .duplex_reg = B53_DUPLEX_STAT_GE,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+-              .arl_ops = &b53_arl_ops_95,
++              .arl_ops = &b53_arl_ops_89,
+       },
+       {
+               .chip_id = BCM53101_DEVICE_ID,
+--- a/drivers/net/dsa/b53/b53_priv.h
++++ b/drivers/net/dsa/b53/b53_priv.h
+@@ -353,6 +353,18 @@ static inline void b53_arl_to_entry_25(s
+       ent->vid = mac_vid >> ARLTBL_VID_S_65;
+ }
++static inline void b53_arl_to_entry_89(struct b53_arl_entry *ent,
++                                     u64 mac_vid, u16 fwd_entry)
++{
++      memset(ent, 0, sizeof(*ent));
++      ent->port = fwd_entry & ARLTBL_DATA_PORT_ID_MASK_89;
++      ent->is_valid = !!(fwd_entry & ARLTBL_VALID_89);
++      ent->is_age = !!(fwd_entry & ARLTBL_AGE_89);
++      ent->is_static = !!(fwd_entry & ARLTBL_STATIC_89);
++      u64_to_ether_addr(mac_vid, ent->mac);
++      ent->vid = mac_vid >> ARLTBL_VID_S;
++}
++
+ static inline void b53_arl_from_entry(u64 *mac_vid, u32 *fwd_entry,
+                                     const struct b53_arl_entry *ent)
+ {
+@@ -383,6 +395,20 @@ static inline void b53_arl_from_entry_25
+               *mac_vid |= ARLTBL_AGE_25;
+ }
++static inline void b53_arl_from_entry_89(u64 *mac_vid, u32 *fwd_entry,
++                                       const struct b53_arl_entry *ent)
++{
++      *mac_vid = ether_addr_to_u64(ent->mac);
++      *mac_vid |= (u64)(ent->vid & ARLTBL_VID_MASK) << ARLTBL_VID_S;
++      *fwd_entry = ent->port & ARLTBL_DATA_PORT_ID_MASK_89;
++      if (ent->is_valid)
++              *fwd_entry |= ARLTBL_VALID_89;
++      if (ent->is_static)
++              *fwd_entry |= ARLTBL_STATIC_89;
++      if (ent->is_age)
++              *fwd_entry |= ARLTBL_AGE_89;
++}
++
+ static inline void b53_arl_read_entry(struct b53_device *dev,
+                                     struct b53_arl_entry *ent, u8 idx)
+ {
+--- a/drivers/net/dsa/b53/b53_regs.h
++++ b/drivers/net/dsa/b53/b53_regs.h
+@@ -342,12 +342,20 @@
+ #define   ARLTBL_STATIC                       BIT(15)
+ #define   ARLTBL_VALID                        BIT(16)
++/* BCM5389 ARL Table Data Entry N Register format (16 bit) */
++#define   ARLTBL_DATA_PORT_ID_MASK_89 GENMASK(8, 0)
++#define   ARLTBL_TC_MASK_89           GENMASK(12, 10)
++#define   ARLTBL_AGE_89                       BIT(13)
++#define   ARLTBL_STATIC_89            BIT(14)
++#define   ARLTBL_VALID_89             BIT(15)
++
+ /* Maximum number of bin entries in the ARL for all switches */
+ #define B53_ARLTBL_MAX_BIN_ENTRIES    4
+ /* ARL Search Control Register (8 bit) */
+ #define B53_ARL_SRCH_CTL              0x50
+ #define B53_ARL_SRCH_CTL_25           0x20
++#define B53_ARL_SRCH_CTL_89           0x30
+ #define   ARL_SRCH_VLID                       BIT(0)
+ #define   ARL_SRCH_STDN                       BIT(7)
+@@ -355,10 +363,12 @@
+ #define B53_ARL_SRCH_ADDR             0x51
+ #define B53_ARL_SRCH_ADDR_25          0x22
+ #define B53_ARL_SRCH_ADDR_65          0x24
++#define B53_ARL_SRCH_ADDR_89          0x31
+ #define  ARL_ADDR_MASK                        GENMASK(14, 0)
+ /* ARL Search MAC/VID Result (64 bit) */
+ #define B53_ARL_SRCH_RSTL_0_MACVID    0x60
++#define B53_ARL_SRCH_RSLT_MACVID_89   0x33
+ /* Single register search result on 5325 */
+ #define B53_ARL_SRCH_RSTL_0_MACVID_25 0x24
+@@ -368,6 +378,9 @@
+ /* ARL Search Data Result (32 bit) */
+ #define B53_ARL_SRCH_RSTL_0           0x68
++/* BCM5389 ARL Search Data Result (16 bit) */
++#define B53_ARL_SRCH_RSLT_89          0x3b
++
+ #define B53_ARL_SRCH_RSTL_MACVID(x)   (B53_ARL_SRCH_RSTL_0_MACVID + ((x) * 0x10))
+ #define B53_ARL_SRCH_RSTL(x)          (B53_ARL_SRCH_RSTL_0 + ((x) * 0x10))
diff --git a/target/linux/generic/backport-6.12/612-08-v6.19-net-dsa-b53-add-support-for-bcm63xx-ARL-entry-format.patch b/target/linux/generic/backport-6.12/612-08-v6.19-net-dsa-b53-add-support-for-bcm63xx-ARL-entry-format.patch
new file mode 100644 (file)
index 0000000..ec0e85e
--- /dev/null
@@ -0,0 +1,177 @@
+From 2b3013ac03028a2364d8779719bb6bfbc0212435 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 7 Nov 2025 09:07:49 +0100
+Subject: [PATCH] net: dsa: b53: add support for bcm63xx ARL entry format
+
+The ARL registers of BCM63XX embedded switches are somewhat unique. The
+normal ARL table access registers have the same format as BCM5389, but
+the ARL search registers differ:
+
+* SRCH_CTL is at the same offset of BCM5389, but 16 bits wide. It does
+  not have more fields, just needs to be accessed by a 16 bit read.
+* SRCH_RSLT_MACVID and SRCH_RSLT are aligned to 32 bit, and have shifted
+  offsets.
+* SRCH_RSLT has a different format than the normal ARL data entry
+  register.
+* There is only one set of ENTRY_N registers, implying a 1 bin layout.
+
+So add appropriate ops for bcm63xx and let it use it.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20251107080749.26936-9-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 44 +++++++++++++++++++++++++++-----
+ drivers/net/dsa/b53/b53_priv.h   | 15 +++++++++++
+ drivers/net/dsa/b53/b53_regs.h   |  9 +++++++
+ 3 files changed, 61 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -2038,12 +2038,20 @@ static void b53_read_arl_srch_ctl(struct
+       if (is5325(dev) || is5365(dev))
+               offset = B53_ARL_SRCH_CTL_25;
+-      else if (dev->chip_id == BCM5389_DEVICE_ID || is5397_98(dev))
++      else if (dev->chip_id == BCM5389_DEVICE_ID || is5397_98(dev) ||
++               is63xx(dev))
+               offset = B53_ARL_SRCH_CTL_89;
+       else
+               offset = B53_ARL_SRCH_CTL;
+-      b53_read8(dev, B53_ARLIO_PAGE, offset, val);
++      if (is63xx(dev)) {
++              u16 val16;
++
++              b53_read16(dev, B53_ARLIO_PAGE, offset, &val16);
++              *val = val16 & 0xff;
++      } else {
++              b53_read8(dev, B53_ARLIO_PAGE, offset, val);
++      }
+ }
+ static void b53_write_arl_srch_ctl(struct b53_device *dev, u8 val)
+@@ -2052,12 +2060,16 @@ static void b53_write_arl_srch_ctl(struc
+       if (is5325(dev) || is5365(dev))
+               offset = B53_ARL_SRCH_CTL_25;
+-      else if (dev->chip_id == BCM5389_DEVICE_ID || is5397_98(dev))
++      else if (dev->chip_id == BCM5389_DEVICE_ID || is5397_98(dev) ||
++               is63xx(dev))
+               offset = B53_ARL_SRCH_CTL_89;
+       else
+               offset = B53_ARL_SRCH_CTL;
+-      b53_write8(dev, B53_ARLIO_PAGE, offset, val);
++      if (is63xx(dev))
++              b53_write16(dev, B53_ARLIO_PAGE, offset, val);
++      else
++              b53_write8(dev, B53_ARLIO_PAGE, offset, val);
+ }
+ static int b53_arl_search_wait(struct b53_device *dev)
+@@ -2111,6 +2123,18 @@ static void b53_arl_search_read_89(struc
+       b53_arl_to_entry_89(ent, mac_vid, fwd_entry);
+ }
++static void b53_arl_search_read_63xx(struct b53_device *dev, u8 idx,
++                                   struct b53_arl_entry *ent)
++{
++      u16 fwd_entry;
++      u64 mac_vid;
++
++      b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSLT_MACVID_63XX,
++                 &mac_vid);
++      b53_read16(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSLT_63XX, &fwd_entry);
++      b53_arl_search_to_entry_63xx(ent, mac_vid, fwd_entry);
++}
++
+ static void b53_arl_search_read_95(struct b53_device *dev, u8 idx,
+                                  struct b53_arl_entry *ent)
+ {
+@@ -2717,6 +2741,12 @@ static const struct b53_arl_ops b53_arl_
+       .arl_search_read = b53_arl_search_read_89,
+ };
++static const struct b53_arl_ops b53_arl_ops_63xx = {
++      .arl_read_entry = b53_arl_read_entry_89,
++      .arl_write_entry = b53_arl_write_entry_89,
++      .arl_search_read = b53_arl_search_read_63xx,
++};
++
+ static const struct b53_arl_ops b53_arl_ops_95 = {
+       .arl_read_entry = b53_arl_read_entry_95,
+       .arl_write_entry = b53_arl_write_entry_95,
+@@ -2886,14 +2916,14 @@ static const struct b53_chip_data b53_sw
+               .dev_name = "BCM63xx",
+               .vlans = 4096,
+               .enabled_ports = 0, /* pdata must provide them */
+-              .arl_bins = 4,
+-              .arl_buckets = 1024,
++              .arl_bins = 1,
++              .arl_buckets = 4096,
+               .imp_port = 8,
+               .vta_regs = B53_VTA_REGS_63XX,
+               .duplex_reg = B53_DUPLEX_STAT_63XX,
+               .jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX,
+               .jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX,
+-              .arl_ops = &b53_arl_ops_95,
++              .arl_ops = &b53_arl_ops_63xx,
+       },
+       {
+               .chip_id = BCM53010_DEVICE_ID,
+--- a/drivers/net/dsa/b53/b53_priv.h
++++ b/drivers/net/dsa/b53/b53_priv.h
+@@ -409,6 +409,21 @@ static inline void b53_arl_from_entry_89
+               *fwd_entry |= ARLTBL_AGE_89;
+ }
++static inline void b53_arl_search_to_entry_63xx(struct b53_arl_entry *ent,
++                                              u64 mac_vid, u16 fwd_entry)
++{
++      memset(ent, 0, sizeof(*ent));
++      u64_to_ether_addr(mac_vid, ent->mac);
++      ent->vid = mac_vid >> ARLTBL_VID_S;
++
++      ent->port = fwd_entry & ARL_SRST_PORT_ID_MASK_63XX;
++      ent->port >>= 1;
++
++      ent->is_age = !!(fwd_entry & ARL_SRST_AGE_63XX);
++      ent->is_static = !!(fwd_entry & ARL_SRST_STATIC_63XX);
++      ent->is_valid = 1;
++}
++
+ static inline void b53_arl_read_entry(struct b53_device *dev,
+                                     struct b53_arl_entry *ent, u8 idx)
+ {
+--- a/drivers/net/dsa/b53/b53_regs.h
++++ b/drivers/net/dsa/b53/b53_regs.h
+@@ -364,11 +364,13 @@
+ #define B53_ARL_SRCH_ADDR_25          0x22
+ #define B53_ARL_SRCH_ADDR_65          0x24
+ #define B53_ARL_SRCH_ADDR_89          0x31
++#define B53_ARL_SRCH_ADDR_63XX                0x32
+ #define  ARL_ADDR_MASK                        GENMASK(14, 0)
+ /* ARL Search MAC/VID Result (64 bit) */
+ #define B53_ARL_SRCH_RSTL_0_MACVID    0x60
+ #define B53_ARL_SRCH_RSLT_MACVID_89   0x33
++#define B53_ARL_SRCH_RSLT_MACVID_63XX 0x34
+ /* Single register search result on 5325 */
+ #define B53_ARL_SRCH_RSTL_0_MACVID_25 0x24
+@@ -384,6 +386,13 @@
+ #define B53_ARL_SRCH_RSTL_MACVID(x)   (B53_ARL_SRCH_RSTL_0_MACVID + ((x) * 0x10))
+ #define B53_ARL_SRCH_RSTL(x)          (B53_ARL_SRCH_RSTL_0 + ((x) * 0x10))
++/* 63XX ARL Search Data Result (16 bit) */
++#define B53_ARL_SRCH_RSLT_63XX                0x3c
++#define   ARL_SRST_PORT_ID_MASK_63XX  GENMASK(9, 1)
++#define   ARL_SRST_TC_MASK_63XX               GENMASK(13, 11)
++#define   ARL_SRST_AGE_63XX           BIT(14)
++#define   ARL_SRST_STATIC_63XX                BIT(15)
++
+ /*************************************************************************
+  * IEEE 802.1X Registers
+  *************************************************************************/
diff --git a/target/linux/generic/backport-6.12/613-01-v6.19-net-dsa-b53-fix-VLAN_ID_IDX-write-size-for-BCM5325-65.patch b/target/linux/generic/backport-6.12/613-01-v6.19-net-dsa-b53-fix-VLAN_ID_IDX-write-size-for-BCM5325-65.patch
new file mode 100644 (file)
index 0000000..d953ea7
--- /dev/null
@@ -0,0 +1,38 @@
+From 6f268e275c74dae0536e0b61982a8db25bcf4f16 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 28 Nov 2025 09:06:19 +0100
+Subject: [PATCH] net: dsa: b53: fix VLAN_ID_IDX write size for BCM5325/65
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Since BCM5325 and BCM5365 only support up to 256 VLANs, the VLAN_ID_IDX
+register is only 8 bit wide, not 16 bit, so use an appropriate accessor.
+
+Fixes: c45655386e53 ("net: dsa: b53: add support for FDB operations on 5325/5365")
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Tested-by: Álvaro Fernández Rojas <noltari@gmail.com>
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Link: https://patch.msgid.link/20251128080625.27181-2-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 8 ++++++--
+ 1 file changed, 6 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -1946,8 +1946,12 @@ static int b53_arl_op(struct b53_device
+       /* Perform a read for the given MAC and VID */
+       b53_write48(dev, B53_ARLIO_PAGE, B53_MAC_ADDR_IDX, mac);
+-      if (!is5325m(dev))
+-              b53_write16(dev, B53_ARLIO_PAGE, B53_VLAN_ID_IDX, vid);
++      if (!is5325m(dev)) {
++              if (is5325(dev) || is5365(dev))
++                      b53_write8(dev, B53_ARLIO_PAGE, B53_VLAN_ID_IDX, vid);
++              else
++                      b53_write16(dev, B53_ARLIO_PAGE, B53_VLAN_ID_IDX, vid);
++      }
+       /* Issue a read operation for this MAC */
+       ret = b53_arl_rw_op(dev, 1);
diff --git a/target/linux/generic/backport-6.12/613-02-v6.19-net-dsa-b53-fix-extracting-VID-from-entry-for-BCM5325-65.patch b/target/linux/generic/backport-6.12/613-02-v6.19-net-dsa-b53-fix-extracting-VID-from-entry-for-BCM5325-65.patch
new file mode 100644 (file)
index 0000000..72f81d0
--- /dev/null
@@ -0,0 +1,35 @@
+From 9316012dd01952f75e37035360138ccc786ef727 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 28 Nov 2025 09:06:20 +0100
+Subject: [PATCH] net: dsa: b53: fix extracting VID from entry for BCM5325/65
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+BCM5325/65's Entry register uses the highest three bits for
+VALID/STATIC/AGE, so shifting by 53 only will add these to
+b53_arl_entry::vid.
+
+So make sure to mask the vid value as well, to not get invalid VIDs.
+
+Fixes: c45655386e53 ("net: dsa: b53: add support for FDB operations on 5325/5365")
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Tested-by: Álvaro Fernández Rojas <noltari@gmail.com>
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Link: https://patch.msgid.link/20251128080625.27181-3-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_priv.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/dsa/b53/b53_priv.h
++++ b/drivers/net/dsa/b53/b53_priv.h
+@@ -350,7 +350,7 @@ static inline void b53_arl_to_entry_25(s
+       ent->is_age = !!(mac_vid & ARLTBL_AGE_25);
+       ent->is_static = !!(mac_vid & ARLTBL_STATIC_25);
+       u64_to_ether_addr(mac_vid, ent->mac);
+-      ent->vid = mac_vid >> ARLTBL_VID_S_65;
++      ent->vid = (mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25;
+ }
+ static inline void b53_arl_to_entry_89(struct b53_arl_entry *ent,
diff --git a/target/linux/generic/backport-6.12/613-03-v6.19-net-dsa-b53-use-same-ARL-search-result-offset-for-BCM5325-65.patch b/target/linux/generic/backport-6.12/613-03-v6.19-net-dsa-b53-use-same-ARL-search-result-offset-for-BCM5325-65.patch
new file mode 100644 (file)
index 0000000..ddb95df
--- /dev/null
@@ -0,0 +1,78 @@
+From 8e46aacea4264bcb8d4265fb07577afff58ae78d Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 28 Nov 2025 09:06:21 +0100
+Subject: [PATCH] net: dsa: b53: use same ARL search result offset for BCM5325/65
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+BCM5365's search result is at the same offset as BCM5325's search
+result, and they (mostly) share the same format, so switch BCM5365 to
+BCM5325's arl ops.
+
+Fixes: c45655386e53 ("net: dsa: b53: add support for FDB operations on 5325/5365")
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Tested-by: Álvaro Fernández Rojas <noltari@gmail.com>
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Link: https://patch.msgid.link/20251128080625.27181-4-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 18 +-----------------
+ drivers/net/dsa/b53/b53_regs.h   |  4 +---
+ 2 files changed, 2 insertions(+), 20 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -2105,16 +2105,6 @@ static void b53_arl_search_read_25(struc
+       b53_arl_to_entry_25(ent, mac_vid);
+ }
+-static void b53_arl_search_read_65(struct b53_device *dev, u8 idx,
+-                                 struct b53_arl_entry *ent)
+-{
+-      u64 mac_vid;
+-
+-      b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_65,
+-                 &mac_vid);
+-      b53_arl_to_entry_25(ent, mac_vid);
+-}
+-
+ static void b53_arl_search_read_89(struct b53_device *dev, u8 idx,
+                                  struct b53_arl_entry *ent)
+ {
+@@ -2733,12 +2723,6 @@ static const struct b53_arl_ops b53_arl_
+       .arl_search_read = b53_arl_search_read_25,
+ };
+-static const struct b53_arl_ops b53_arl_ops_65 = {
+-      .arl_read_entry = b53_arl_read_entry_25,
+-      .arl_write_entry = b53_arl_write_entry_25,
+-      .arl_search_read = b53_arl_search_read_65,
+-};
+-
+ static const struct b53_arl_ops b53_arl_ops_89 = {
+       .arl_read_entry = b53_arl_read_entry_89,
+       .arl_write_entry = b53_arl_write_entry_89,
+@@ -2801,7 +2785,7 @@ static const struct b53_chip_data b53_sw
+               .arl_buckets = 1024,
+               .imp_port = 5,
+               .duplex_reg = B53_DUPLEX_STAT_FE,
+-              .arl_ops = &b53_arl_ops_65,
++              .arl_ops = &b53_arl_ops_25,
+       },
+       {
+               .chip_id = BCM5389_DEVICE_ID,
+--- a/drivers/net/dsa/b53/b53_regs.h
++++ b/drivers/net/dsa/b53/b53_regs.h
+@@ -372,10 +372,8 @@
+ #define B53_ARL_SRCH_RSLT_MACVID_89   0x33
+ #define B53_ARL_SRCH_RSLT_MACVID_63XX 0x34
+-/* Single register search result on 5325 */
++/* Single register search result on 5325/5365 */
+ #define B53_ARL_SRCH_RSTL_0_MACVID_25 0x24
+-/* Single register search result on 5365 */
+-#define B53_ARL_SRCH_RSTL_0_MACVID_65 0x30
+ /* ARL Search Data Result (32 bit) */
+ #define B53_ARL_SRCH_RSTL_0           0x68
diff --git a/target/linux/generic/backport-6.12/613-04-v6.19-net-dsa-b53-fix-CPU-port-unicast-ARL-entries-for-BCM5325-65.patch b/target/linux/generic/backport-6.12/613-04-v6.19-net-dsa-b53-fix-CPU-port-unicast-ARL-entries-for-BCM5325-65.patch
new file mode 100644 (file)
index 0000000..3748892
--- /dev/null
@@ -0,0 +1,51 @@
+From 85132103f700b1340fc17df8a981509d17bf4872 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 28 Nov 2025 09:06:22 +0100
+Subject: [PATCH] net: dsa: b53: fix CPU port unicast ARL entries for BCM5325/65
+
+On BCM5325 and BCM5365, unicast ARL entries use 8 as the value for the
+CPU port, so we need to translate it to/from 5 as used for the CPU port
+at most other places.
+
+Fixes: c45655386e53 ("net: dsa: b53: add support for FDB operations on 5325/5365")
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20251128080625.27181-5-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_priv.h | 13 +++++++++----
+ 1 file changed, 9 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_priv.h
++++ b/drivers/net/dsa/b53/b53_priv.h
+@@ -344,12 +344,14 @@ static inline void b53_arl_to_entry_25(s
+                                      u64 mac_vid)
+ {
+       memset(ent, 0, sizeof(*ent));
+-      ent->port = (mac_vid >> ARLTBL_DATA_PORT_ID_S_25) &
+-                   ARLTBL_DATA_PORT_ID_MASK_25;
+       ent->is_valid = !!(mac_vid & ARLTBL_VALID_25);
+       ent->is_age = !!(mac_vid & ARLTBL_AGE_25);
+       ent->is_static = !!(mac_vid & ARLTBL_STATIC_25);
+       u64_to_ether_addr(mac_vid, ent->mac);
++      ent->port = (mac_vid >> ARLTBL_DATA_PORT_ID_S_25) &
++                   ARLTBL_DATA_PORT_ID_MASK_25;
++      if (is_unicast_ether_addr(ent->mac) && ent->port == B53_CPU_PORT)
++              ent->port = B53_CPU_PORT_25;
+       ent->vid = (mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25;
+ }
+@@ -383,8 +385,11 @@ static inline void b53_arl_from_entry_25
+                                        const struct b53_arl_entry *ent)
+ {
+       *mac_vid = ether_addr_to_u64(ent->mac);
+-      *mac_vid |= (u64)(ent->port & ARLTBL_DATA_PORT_ID_MASK_25) <<
+-                        ARLTBL_DATA_PORT_ID_S_25;
++      if (is_unicast_ether_addr(ent->mac) && ent->port == B53_CPU_PORT_25)
++              *mac_vid |= (u64)B53_CPU_PORT << ARLTBL_DATA_PORT_ID_S_25;
++      else
++              *mac_vid |= (u64)(ent->port & ARLTBL_DATA_PORT_ID_MASK_25) <<
++                                ARLTBL_DATA_PORT_ID_S_25;
+       *mac_vid |= (u64)(ent->vid & ARLTBL_VID_MASK_25) <<
+                         ARLTBL_VID_S_65;
+       if (ent->is_valid)
diff --git a/target/linux/generic/backport-6.12/613-05-v6.19-net-dsa-b53-fix-BCM5325-65-ARL-entry-multicast-port-masks.patch b/target/linux/generic/backport-6.12/613-05-v6.19-net-dsa-b53-fix-BCM5325-65-ARL-entry-multicast-port-masks.patch
new file mode 100644 (file)
index 0000000..c0f5b5d
--- /dev/null
@@ -0,0 +1,120 @@
+From 3b08863469aa6028ac7c3120966f4e2f6051cf6b Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 28 Nov 2025 09:06:23 +0100
+Subject: [PATCH] net: dsa: b53: fix BCM5325/65 ARL entry multicast port masks
+
+We currently use the mask 0xf for writing and reading b53_entry::port,
+but this is only correct for unicast ARL entries. Multicast ARL entries
+use a bitmask, and 0xf is not enough space for ports > 3, which includes
+the CPU port.
+
+So extend the mask accordingly to also fit port 4 (bit 4) and MII (bit
+5). According to the datasheet the multicast port mask is [60:48],
+making it 12 bit wide, but bits 60-55 are reserved anyway, and collide
+with the priority field at [60:59], so I am not sure if this is valid.
+Therefore leave it at the actual used range, [53:48].
+
+The ARL search result register differs a bit, and there the mask is only
+[52:48], so only spanning the user ports. The MII port bit is
+contained in the Search Result Extension register. So create a separate
+search result parse function that properly handles this.
+
+Fixes: c45655386e53 ("net: dsa: b53: add support for FDB operations on 5325/5365")
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Link: https://patch.msgid.link/20251128080625.27181-6-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c |  4 +++-
+ drivers/net/dsa/b53/b53_priv.h   | 25 +++++++++++++++++++++----
+ drivers/net/dsa/b53/b53_regs.h   |  8 +++++++-
+ 3 files changed, 31 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -2099,10 +2099,12 @@ static void b53_arl_search_read_25(struc
+                                  struct b53_arl_entry *ent)
+ {
+       u64 mac_vid;
++      u8 ext;
++      b53_read8(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSLT_EXT_25, &ext);
+       b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_25,
+                  &mac_vid);
+-      b53_arl_to_entry_25(ent, mac_vid);
++      b53_arl_search_to_entry_25(ent, mac_vid, ext);
+ }
+ static void b53_arl_search_read_89(struct b53_device *dev, u8 idx,
+--- a/drivers/net/dsa/b53/b53_priv.h
++++ b/drivers/net/dsa/b53/b53_priv.h
+@@ -348,8 +348,8 @@ static inline void b53_arl_to_entry_25(s
+       ent->is_age = !!(mac_vid & ARLTBL_AGE_25);
+       ent->is_static = !!(mac_vid & ARLTBL_STATIC_25);
+       u64_to_ether_addr(mac_vid, ent->mac);
+-      ent->port = (mac_vid >> ARLTBL_DATA_PORT_ID_S_25) &
+-                   ARLTBL_DATA_PORT_ID_MASK_25;
++      ent->port = (mac_vid & ARLTBL_DATA_PORT_ID_MASK_25) >>
++                   ARLTBL_DATA_PORT_ID_S_25;
+       if (is_unicast_ether_addr(ent->mac) && ent->port == B53_CPU_PORT)
+               ent->port = B53_CPU_PORT_25;
+       ent->vid = (mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25;
+@@ -388,8 +388,8 @@ static inline void b53_arl_from_entry_25
+       if (is_unicast_ether_addr(ent->mac) && ent->port == B53_CPU_PORT_25)
+               *mac_vid |= (u64)B53_CPU_PORT << ARLTBL_DATA_PORT_ID_S_25;
+       else
+-              *mac_vid |= (u64)(ent->port & ARLTBL_DATA_PORT_ID_MASK_25) <<
+-                                ARLTBL_DATA_PORT_ID_S_25;
++              *mac_vid |= ((u64)ent->port << ARLTBL_DATA_PORT_ID_S_25) &
++                          ARLTBL_DATA_PORT_ID_MASK_25;
+       *mac_vid |= (u64)(ent->vid & ARLTBL_VID_MASK_25) <<
+                         ARLTBL_VID_S_65;
+       if (ent->is_valid)
+@@ -414,6 +414,23 @@ static inline void b53_arl_from_entry_89
+               *fwd_entry |= ARLTBL_AGE_89;
+ }
++static inline void b53_arl_search_to_entry_25(struct b53_arl_entry *ent,
++                                            u64 mac_vid, u8 ext)
++{
++      memset(ent, 0, sizeof(*ent));
++      ent->is_valid = !!(mac_vid & ARLTBL_VALID_25);
++      ent->is_age = !!(mac_vid & ARLTBL_AGE_25);
++      ent->is_static = !!(mac_vid & ARLTBL_STATIC_25);
++      u64_to_ether_addr(mac_vid, ent->mac);
++      ent->vid = (mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25;
++      ent->port = (mac_vid & ARL_SRCH_RSLT_PORT_ID_MASK_25) >>
++                  ARL_SRCH_RSLT_PORT_ID_S_25;
++      if (is_multicast_ether_addr(ent->mac) && (ext & ARL_SRCH_RSLT_EXT_MC_MII))
++              ent->port |= BIT(B53_CPU_PORT_25);
++      else if (!is_multicast_ether_addr(ent->mac) && ent->port == B53_CPU_PORT)
++              ent->port = B53_CPU_PORT_25;
++}
++
+ static inline void b53_arl_search_to_entry_63xx(struct b53_arl_entry *ent,
+                                               u64 mac_vid, u16 fwd_entry)
+ {
+--- a/drivers/net/dsa/b53/b53_regs.h
++++ b/drivers/net/dsa/b53/b53_regs.h
+@@ -328,7 +328,7 @@
+ #define   ARLTBL_VID_MASK_25          0xff
+ #define   ARLTBL_VID_MASK             0xfff
+ #define   ARLTBL_DATA_PORT_ID_S_25    48
+-#define   ARLTBL_DATA_PORT_ID_MASK_25 0xf
++#define   ARLTBL_DATA_PORT_ID_MASK_25 GENMASK_ULL(53, 48)
+ #define   ARLTBL_VID_S_65             53
+ #define   ARLTBL_AGE_25                       BIT_ULL(61)
+ #define   ARLTBL_STATIC_25            BIT_ULL(62)
+@@ -374,6 +374,12 @@
+ /* Single register search result on 5325/5365 */
+ #define B53_ARL_SRCH_RSTL_0_MACVID_25 0x24
++#define   ARL_SRCH_RSLT_PORT_ID_S_25  48
++#define   ARL_SRCH_RSLT_PORT_ID_MASK_25       GENMASK_ULL(52, 48)
++
++/* BCM5325/5365 Search result extend register (8 bit) */
++#define B53_ARL_SRCH_RSLT_EXT_25      0x2c
++#define   ARL_SRCH_RSLT_EXT_MC_MII    BIT(2)
+ /* ARL Search Data Result (32 bit) */
+ #define B53_ARL_SRCH_RSTL_0           0x68
diff --git a/target/linux/generic/backport-6.12/613-06-v6.19-net-dsa-b53-fix-BCM5325-65-ARL-entry-VIDs.patch b/target/linux/generic/backport-6.12/613-06-v6.19-net-dsa-b53-fix-BCM5325-65-ARL-entry-VIDs.patch
new file mode 100644 (file)
index 0000000..a2025f9
--- /dev/null
@@ -0,0 +1,140 @@
+From d39514e6a2d14f57830d649e2bf03b49612c2f73 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 28 Nov 2025 09:06:24 +0100
+Subject: [PATCH] net: dsa: b53: fix BCM5325/65 ARL entry VIDs
+
+BCM5325/65's ARL entry registers do not contain the VID, only the search
+result register does. ARL entries have a separate VID entry register for
+the index into the VLAN table.
+
+So make ARL entry accessors use the VID entry registers instead, and
+move the VLAN ID field definition to the search register definition.
+
+Fixes: c45655386e53 ("net: dsa: b53: add support for FDB operations on 5325/5365")
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20251128080625.27181-7-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c |  9 +++++++--
+ drivers/net/dsa/b53/b53_priv.h   | 12 ++++++------
+ drivers/net/dsa/b53/b53_regs.h   |  7 +++++--
+ 3 files changed, 18 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -1833,19 +1833,24 @@ static int b53_arl_rw_op(struct b53_devi
+ static void b53_arl_read_entry_25(struct b53_device *dev,
+                                 struct b53_arl_entry *ent, u8 idx)
+ {
++      u8 vid_entry;
+       u64 mac_vid;
++      b53_read8(dev, B53_ARLIO_PAGE, B53_ARLTBL_VID_ENTRY_25(idx),
++                &vid_entry);
+       b53_read64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx),
+                  &mac_vid);
+-      b53_arl_to_entry_25(ent, mac_vid);
++      b53_arl_to_entry_25(ent, mac_vid, vid_entry);
+ }
+ static void b53_arl_write_entry_25(struct b53_device *dev,
+                                  const struct b53_arl_entry *ent, u8 idx)
+ {
++      u8 vid_entry;
+       u64 mac_vid;
+-      b53_arl_from_entry_25(&mac_vid, ent);
++      b53_arl_from_entry_25(&mac_vid, &vid_entry, ent);
++      b53_write8(dev, B53_ARLIO_PAGE, B53_ARLTBL_VID_ENTRY_25(idx), vid_entry);
+       b53_write64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx),
+                   mac_vid);
+ }
+--- a/drivers/net/dsa/b53/b53_priv.h
++++ b/drivers/net/dsa/b53/b53_priv.h
+@@ -341,7 +341,7 @@ static inline void b53_arl_to_entry(stru
+ }
+ static inline void b53_arl_to_entry_25(struct b53_arl_entry *ent,
+-                                     u64 mac_vid)
++                                     u64 mac_vid, u8 vid_entry)
+ {
+       memset(ent, 0, sizeof(*ent));
+       ent->is_valid = !!(mac_vid & ARLTBL_VALID_25);
+@@ -352,7 +352,7 @@ static inline void b53_arl_to_entry_25(s
+                    ARLTBL_DATA_PORT_ID_S_25;
+       if (is_unicast_ether_addr(ent->mac) && ent->port == B53_CPU_PORT)
+               ent->port = B53_CPU_PORT_25;
+-      ent->vid = (mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25;
++      ent->vid = vid_entry;
+ }
+ static inline void b53_arl_to_entry_89(struct b53_arl_entry *ent,
+@@ -381,7 +381,7 @@ static inline void b53_arl_from_entry(u6
+               *fwd_entry |= ARLTBL_AGE;
+ }
+-static inline void b53_arl_from_entry_25(u64 *mac_vid,
++static inline void b53_arl_from_entry_25(u64 *mac_vid, u8 *vid_entry,
+                                        const struct b53_arl_entry *ent)
+ {
+       *mac_vid = ether_addr_to_u64(ent->mac);
+@@ -390,14 +390,13 @@ static inline void b53_arl_from_entry_25
+       else
+               *mac_vid |= ((u64)ent->port << ARLTBL_DATA_PORT_ID_S_25) &
+                           ARLTBL_DATA_PORT_ID_MASK_25;
+-      *mac_vid |= (u64)(ent->vid & ARLTBL_VID_MASK_25) <<
+-                        ARLTBL_VID_S_65;
+       if (ent->is_valid)
+               *mac_vid |= ARLTBL_VALID_25;
+       if (ent->is_static)
+               *mac_vid |= ARLTBL_STATIC_25;
+       if (ent->is_age)
+               *mac_vid |= ARLTBL_AGE_25;
++      *vid_entry = ent->vid;
+ }
+ static inline void b53_arl_from_entry_89(u64 *mac_vid, u32 *fwd_entry,
+@@ -422,7 +421,8 @@ static inline void b53_arl_search_to_ent
+       ent->is_age = !!(mac_vid & ARLTBL_AGE_25);
+       ent->is_static = !!(mac_vid & ARLTBL_STATIC_25);
+       u64_to_ether_addr(mac_vid, ent->mac);
+-      ent->vid = (mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25;
++      ent->vid = (mac_vid & ARL_SRCH_RSLT_VID_MASK_25) >>
++                 ARL_SRCH_RSLT_VID_S_25;
+       ent->port = (mac_vid & ARL_SRCH_RSLT_PORT_ID_MASK_25) >>
+                   ARL_SRCH_RSLT_PORT_ID_S_25;
+       if (is_multicast_ether_addr(ent->mac) && (ext & ARL_SRCH_RSLT_EXT_MC_MII))
+--- a/drivers/net/dsa/b53/b53_regs.h
++++ b/drivers/net/dsa/b53/b53_regs.h
+@@ -325,11 +325,9 @@
+ #define B53_ARLTBL_MAC_VID_ENTRY(n)   ((0x10 * (n)) + 0x10)
+ #define   ARLTBL_MAC_MASK             0xffffffffffffULL
+ #define   ARLTBL_VID_S                        48
+-#define   ARLTBL_VID_MASK_25          0xff
+ #define   ARLTBL_VID_MASK             0xfff
+ #define   ARLTBL_DATA_PORT_ID_S_25    48
+ #define   ARLTBL_DATA_PORT_ID_MASK_25 GENMASK_ULL(53, 48)
+-#define   ARLTBL_VID_S_65             53
+ #define   ARLTBL_AGE_25                       BIT_ULL(61)
+ #define   ARLTBL_STATIC_25            BIT_ULL(62)
+ #define   ARLTBL_VALID_25             BIT_ULL(63)
+@@ -349,6 +347,9 @@
+ #define   ARLTBL_STATIC_89            BIT(14)
+ #define   ARLTBL_VALID_89             BIT(15)
++/* BCM5325/BCM565 ARL Table VID Entry N Registers (8 bit) */
++#define B53_ARLTBL_VID_ENTRY_25(n)    ((0x2 * (n)) + 0x30)
++
+ /* Maximum number of bin entries in the ARL for all switches */
+ #define B53_ARLTBL_MAX_BIN_ENTRIES    4
+@@ -376,6 +377,8 @@
+ #define B53_ARL_SRCH_RSTL_0_MACVID_25 0x24
+ #define   ARL_SRCH_RSLT_PORT_ID_S_25  48
+ #define   ARL_SRCH_RSLT_PORT_ID_MASK_25       GENMASK_ULL(52, 48)
++#define   ARL_SRCH_RSLT_VID_S_25      53
++#define   ARL_SRCH_RSLT_VID_MASK_25   GENMASK_ULL(60, 53)
+ /* BCM5325/5365 Search result extend register (8 bit) */
+ #define B53_ARL_SRCH_RSLT_EXT_25      0x2c
diff --git a/target/linux/generic/backport-6.12/613-07-v6.19-net-dsa-b53-allow-VID-0-for-BCM5325-65.patch b/target/linux/generic/backport-6.12/613-07-v6.19-net-dsa-b53-allow-VID-0-for-BCM5325-65.patch
new file mode 100644 (file)
index 0000000..7c6a63f
--- /dev/null
@@ -0,0 +1,44 @@
+From 0b2b27058692d437b12d3f2a3bf0fa699af7376e Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Fri, 28 Nov 2025 09:06:25 +0100
+Subject: [PATCH] net: dsa: b53: allow VID 0 for BCM5325/65
+
+Now that writing ARL entries works properly, we can actually use VID 0
+as the default untagged VLAN for BCM5325 and BCM5365 as well.
+
+So use 0 as default PVID for all chips and do not reject VLAN 0 anymore,
+which we ignored since commit 45e9d59d3950 ("net: dsa: b53: do not allow
+to configure VLAN 0") anyway.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/20251128080625.27181-8-jonas.gorski@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/b53/b53_common.c | 8 +-------
+ 1 file changed, 1 insertion(+), 7 deletions(-)
+
+--- a/drivers/net/dsa/b53/b53_common.c
++++ b/drivers/net/dsa/b53/b53_common.c
+@@ -852,10 +852,7 @@ static void b53_enable_stp(struct b53_de
+ static u16 b53_default_pvid(struct b53_device *dev)
+ {
+-      if (is5325(dev) || is5365(dev))
+-              return 1;
+-      else
+-              return 0;
++      return 0;
+ }
+ static bool b53_vlan_port_needs_forced_tagged(struct dsa_switch *ds, int port)
+@@ -1679,9 +1676,6 @@ static int b53_vlan_prepare(struct dsa_s
+ {
+       struct b53_device *dev = ds->priv;
+-      if ((is5325(dev) || is5365(dev)) && vlan->vid == 0)
+-              return -EOPNOTSUPP;
+-
+       /* Port 7 on 7278 connects to the ASP's UniMAC which is not capable of
+        * receiving VLAN tagged frames at all, we can still allow the port to
+        * be configured for egress untagged.
diff --git a/target/linux/generic/backport-6.12/620-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch b/target/linux/generic/backport-6.12/620-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch
new file mode 100644 (file)
index 0000000..333627a
--- /dev/null
@@ -0,0 +1,79 @@
+From: Qingfang Deng <dqfext@gmail.com>
+Date: Sat, 1 Mar 2025 21:55:16 +0800
+Subject: [PATCH] ppp: use IFF_NO_QUEUE in virtual interfaces
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+For PPPoE, PPTP, and PPPoL2TP, the start_xmit() function directly
+forwards packets to the underlying network stack and never returns
+anything other than 1. So these interfaces do not require a qdisc,
+and the IFF_NO_QUEUE flag should be set.
+
+Introduces a direct_xmit flag in struct ppp_channel to indicate when
+IFF_NO_QUEUE should be applied. The flag is set in ppp_connect_channel()
+for relevant protocols.
+
+While at it, remove the usused latency member from struct ppp_channel.
+
+Signed-off-by: Qingfang Deng <dqfext@gmail.com>
+Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
+Link: https://patch.msgid.link/20250301135517.695809-1-dqfext@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+
+--- a/drivers/net/ppp/ppp_generic.c
++++ b/drivers/net/ppp/ppp_generic.c
+@@ -3504,6 +3504,10 @@ ppp_connect_channel(struct channel *pch,
+               ret = -ENOTCONN;
+               goto outl;
+       }
++      if (pch->chan->direct_xmit)
++              ppp->dev->priv_flags |= IFF_NO_QUEUE;
++      else
++              ppp->dev->priv_flags &= ~IFF_NO_QUEUE;
+       spin_unlock_bh(&pch->downl);
+       if (pch->file.hdrlen > ppp->file.hdrlen)
+               ppp->file.hdrlen = pch->file.hdrlen;
+--- a/drivers/net/ppp/pppoe.c
++++ b/drivers/net/ppp/pppoe.c
+@@ -693,6 +693,7 @@ static int pppoe_connect(struct socket *
+               po->chan.mtu = dev->mtu - sizeof(struct pppoe_hdr) - 2;
+               po->chan.private = sk;
+               po->chan.ops = &pppoe_chan_ops;
++              po->chan.direct_xmit = true;
+               error = ppp_register_net_channel(dev_net(dev), &po->chan);
+               if (error) {
+--- a/drivers/net/ppp/pptp.c
++++ b/drivers/net/ppp/pptp.c
+@@ -469,6 +469,7 @@ static int pptp_connect(struct socket *s
+       po->chan.mtu -= PPTP_HEADER_OVERHEAD;
+       po->chan.hdrlen = 2 + sizeof(struct pptp_gre_header);
++      po->chan.direct_xmit = true;
+       error = ppp_register_channel(&po->chan);
+       if (error) {
+               pr_err("PPTP: failed to register PPP channel (%d)\n", error);
+--- a/include/linux/ppp_channel.h
++++ b/include/linux/ppp_channel.h
+@@ -42,8 +42,7 @@ struct ppp_channel {
+       int             hdrlen;         /* amount of headroom channel needs */
+       void            *ppp;           /* opaque to channel */
+       int             speed;          /* transfer rate (bytes/second) */
+-      /* the following is not used at present */
+-      int             latency;        /* overhead time in milliseconds */
++      bool            direct_xmit;    /* no qdisc, xmit directly */
+ };
+ #ifdef __KERNEL__
+--- a/net/l2tp/l2tp_ppp.c
++++ b/net/l2tp/l2tp_ppp.c
+@@ -796,6 +796,7 @@ static int pppol2tp_connect(struct socke
+       po->chan.private = sk;
+       po->chan.ops     = &pppol2tp_chan_ops;
+       po->chan.mtu     = pppol2tp_tunnel_mtu(tunnel);
++      po->chan.direct_xmit    = true;
+       error = ppp_register_net_channel(sock_net(sk), &po->chan);
+       if (error) {
diff --git a/target/linux/generic/backport-6.12/621-v6.17-ppp-convert-to-percpu-netstats.patch b/target/linux/generic/backport-6.12/621-v6.17-ppp-convert-to-percpu-netstats.patch
new file mode 100644 (file)
index 0000000..874d2bf
--- /dev/null
@@ -0,0 +1,126 @@
+From 1a3e9b7a6b09e8ab3d2af019e4a392622685855e Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <dqfext@gmail.com>
+Date: Tue, 10 Jun 2025 16:32:10 +0800
+Subject: [PATCH] ppp: convert to percpu netstats
+
+Convert to percpu netstats to avoid lock contention when reading them.
+
+Signed-off-by: Qingfang Deng <dqfext@gmail.com>
+Link: https://patch.msgid.link/20250610083211.909015-1-dqfext@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ppp/ppp_generic.c | 52 +++++++++++++----------------------
+ 1 file changed, 19 insertions(+), 33 deletions(-)
+
+--- a/drivers/net/ppp/ppp_generic.c
++++ b/drivers/net/ppp/ppp_generic.c
+@@ -108,18 +108,6 @@ struct ppp_file {
+ #define PF_TO_CHANNEL(pf)     PF_TO_X(pf, struct channel)
+ /*
+- * Data structure to hold primary network stats for which
+- * we want to use 64 bit storage.  Other network stats
+- * are stored in dev->stats of the ppp strucute.
+- */
+-struct ppp_link_stats {
+-      u64 rx_packets;
+-      u64 tx_packets;
+-      u64 rx_bytes;
+-      u64 tx_bytes;
+-};
+-
+-/*
+  * Data structure describing one ppp unit.
+  * A ppp unit corresponds to a ppp network interface device
+  * and represents a multilink bundle.
+@@ -162,7 +150,6 @@ struct ppp {
+       struct bpf_prog *active_filter; /* filter for pkts to reset idle */
+ #endif /* CONFIG_PPP_FILTER */
+       struct net      *ppp_net;       /* the net we belong to */
+-      struct ppp_link_stats stats64;  /* 64 bit network stats */
+ };
+ /*
+@@ -1541,23 +1528,12 @@ ppp_net_siocdevprivate(struct net_device
+ static void
+ ppp_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats64)
+ {
+-      struct ppp *ppp = netdev_priv(dev);
+-
+-      ppp_recv_lock(ppp);
+-      stats64->rx_packets = ppp->stats64.rx_packets;
+-      stats64->rx_bytes   = ppp->stats64.rx_bytes;
+-      ppp_recv_unlock(ppp);
+-
+-      ppp_xmit_lock(ppp);
+-      stats64->tx_packets = ppp->stats64.tx_packets;
+-      stats64->tx_bytes   = ppp->stats64.tx_bytes;
+-      ppp_xmit_unlock(ppp);
+-
+       stats64->rx_errors        = dev->stats.rx_errors;
+       stats64->tx_errors        = dev->stats.tx_errors;
+       stats64->rx_dropped       = dev->stats.rx_dropped;
+       stats64->tx_dropped       = dev->stats.tx_dropped;
+       stats64->rx_length_errors = dev->stats.rx_length_errors;
++      dev_fetch_sw_netstats(stats64, dev->tstats);
+ }
+ static int ppp_dev_init(struct net_device *dev)
+@@ -1655,6 +1631,7 @@ static void ppp_setup(struct net_device
+       dev->type = ARPHRD_PPP;
+       dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+       dev->priv_destructor = ppp_dev_priv_destructor;
++      dev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS;
+       netif_keep_dst(dev);
+ }
+@@ -1800,8 +1777,7 @@ ppp_send_frame(struct ppp *ppp, struct s
+ #endif /* CONFIG_PPP_FILTER */
+       }
+-      ++ppp->stats64.tx_packets;
+-      ppp->stats64.tx_bytes += skb->len - PPP_PROTO_LEN;
++      dev_sw_netstats_tx_add(ppp->dev, 1, skb->len - PPP_PROTO_LEN);
+       switch (proto) {
+       case PPP_IP:
+@@ -2479,8 +2455,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp,
+               break;
+       }
+-      ++ppp->stats64.rx_packets;
+-      ppp->stats64.rx_bytes += skb->len - 2;
++      dev_sw_netstats_rx_add(ppp->dev, skb->len - PPP_PROTO_LEN);
+       npi = proto_to_npindex(proto);
+       if (npi < 0) {
+@@ -3308,14 +3283,25 @@ static void
+ ppp_get_stats(struct ppp *ppp, struct ppp_stats *st)
+ {
+       struct slcompress *vj = ppp->vj;
++      int cpu;
+       memset(st, 0, sizeof(*st));
+-      st->p.ppp_ipackets = ppp->stats64.rx_packets;
++      for_each_possible_cpu(cpu) {
++              struct pcpu_sw_netstats *p = per_cpu_ptr(ppp->dev->tstats, cpu);
++              u64 rx_packets, rx_bytes, tx_packets, tx_bytes;
++
++              rx_packets = u64_stats_read(&p->rx_packets);
++              rx_bytes = u64_stats_read(&p->rx_bytes);
++              tx_packets = u64_stats_read(&p->tx_packets);
++              tx_bytes = u64_stats_read(&p->tx_bytes);
++
++              st->p.ppp_ipackets += rx_packets;
++              st->p.ppp_ibytes += rx_bytes;
++              st->p.ppp_opackets += tx_packets;
++              st->p.ppp_obytes += tx_bytes;
++      }
+       st->p.ppp_ierrors = ppp->dev->stats.rx_errors;
+-      st->p.ppp_ibytes = ppp->stats64.rx_bytes;
+-      st->p.ppp_opackets = ppp->stats64.tx_packets;
+       st->p.ppp_oerrors = ppp->dev->stats.tx_errors;
+-      st->p.ppp_obytes = ppp->stats64.tx_bytes;
+       if (!vj)
+               return;
+       st->vj.vjs_packets = vj->sls_o_compressed + vj->sls_o_uncompressed;
diff --git a/target/linux/generic/backport-6.12/622-v6.18-ppp-remove-rwlock-usage.patch b/target/linux/generic/backport-6.12/622-v6.18-ppp-remove-rwlock-usage.patch
new file mode 100644 (file)
index 0000000..f66a522
--- /dev/null
@@ -0,0 +1,334 @@
+From b8844aab519a154808dbce15a132f3e8f1c34af6 Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <dqfext@gmail.com>
+Date: Fri, 22 Aug 2025 09:25:47 +0800
+Subject: [PATCH] ppp: remove rwlock usage
+
+In struct channel, the upl lock is implemented using rwlock_t,
+protecting access to pch->ppp and pch->bridge.
+
+As previously discussed on the list, using rwlock in the network fast
+path is not recommended.
+This patch replaces the rwlock with a spinlock for writers, and uses RCU
+for readers.
+
+- pch->ppp and pch->bridge are now declared as __rcu pointers.
+- Readers use rcu_dereference_bh() under rcu_read_lock_bh().
+- Writers use spin_lock() to update.
+
+Signed-off-by: Qingfang Deng <dqfext@gmail.com>
+Link: https://patch.msgid.link/20250822012548.6232-1-dqfext@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ppp/ppp_generic.c | 120 ++++++++++++++++++----------------
+ 1 file changed, 63 insertions(+), 57 deletions(-)
+
+--- a/drivers/net/ppp/ppp_generic.c
++++ b/drivers/net/ppp/ppp_generic.c
+@@ -173,11 +173,11 @@ struct channel {
+       struct ppp_channel *chan;       /* public channel data structure */
+       struct rw_semaphore chan_sem;   /* protects `chan' during chan ioctl */
+       spinlock_t      downl;          /* protects `chan', file.xq dequeue */
+-      struct ppp      *ppp;           /* ppp unit we're connected to */
++      struct ppp __rcu *ppp;          /* ppp unit we're connected to */
+       struct net      *chan_net;      /* the net channel belongs to */
+       netns_tracker   ns_tracker;
+       struct list_head clist;         /* link in list of channels per unit */
+-      rwlock_t        upl;            /* protects `ppp' and 'bridge' */
++      spinlock_t      upl;            /* protects `ppp' and 'bridge' */
+       struct channel __rcu *bridge;   /* "bridged" ppp channel */
+ #ifdef CONFIG_PPP_MULTILINK
+       u8              avail;          /* flag used in multilink stuff */
+@@ -639,34 +639,34 @@ static struct bpf_prog *compat_ppp_get_f
+  */
+ static int ppp_bridge_channels(struct channel *pch, struct channel *pchb)
+ {
+-      write_lock_bh(&pch->upl);
+-      if (pch->ppp ||
++      spin_lock(&pch->upl);
++      if (rcu_dereference_protected(pch->ppp, lockdep_is_held(&pch->upl)) ||
+           rcu_dereference_protected(pch->bridge, lockdep_is_held(&pch->upl))) {
+-              write_unlock_bh(&pch->upl);
++              spin_unlock(&pch->upl);
+               return -EALREADY;
+       }
+       refcount_inc(&pchb->file.refcnt);
+       rcu_assign_pointer(pch->bridge, pchb);
+-      write_unlock_bh(&pch->upl);
++      spin_unlock(&pch->upl);
+-      write_lock_bh(&pchb->upl);
+-      if (pchb->ppp ||
++      spin_lock(&pchb->upl);
++      if (rcu_dereference_protected(pchb->ppp, lockdep_is_held(&pchb->upl)) ||
+           rcu_dereference_protected(pchb->bridge, lockdep_is_held(&pchb->upl))) {
+-              write_unlock_bh(&pchb->upl);
++              spin_unlock(&pchb->upl);
+               goto err_unset;
+       }
+       refcount_inc(&pch->file.refcnt);
+       rcu_assign_pointer(pchb->bridge, pch);
+-      write_unlock_bh(&pchb->upl);
++      spin_unlock(&pchb->upl);
+       return 0;
+ err_unset:
+-      write_lock_bh(&pch->upl);
++      spin_lock(&pch->upl);
+       /* Re-read pch->bridge with upl held in case it was modified concurrently */
+       pchb = rcu_dereference_protected(pch->bridge, lockdep_is_held(&pch->upl));
+       RCU_INIT_POINTER(pch->bridge, NULL);
+-      write_unlock_bh(&pch->upl);
++      spin_unlock(&pch->upl);
+       synchronize_rcu();
+       if (pchb)
+@@ -680,25 +680,25 @@ static int ppp_unbridge_channels(struct
+ {
+       struct channel *pchb, *pchbb;
+-      write_lock_bh(&pch->upl);
++      spin_lock(&pch->upl);
+       pchb = rcu_dereference_protected(pch->bridge, lockdep_is_held(&pch->upl));
+       if (!pchb) {
+-              write_unlock_bh(&pch->upl);
++              spin_unlock(&pch->upl);
+               return -EINVAL;
+       }
+       RCU_INIT_POINTER(pch->bridge, NULL);
+-      write_unlock_bh(&pch->upl);
++      spin_unlock(&pch->upl);
+       /* Only modify pchb if phcb->bridge points back to pch.
+        * If not, it implies that there has been a race unbridging (and possibly
+        * even rebridging) pchb.  We should leave pchb alone to avoid either a
+        * refcount underflow, or breaking another established bridge instance.
+        */
+-      write_lock_bh(&pchb->upl);
++      spin_lock(&pchb->upl);
+       pchbb = rcu_dereference_protected(pchb->bridge, lockdep_is_held(&pchb->upl));
+       if (pchbb == pch)
+               RCU_INIT_POINTER(pchb->bridge, NULL);
+-      write_unlock_bh(&pchb->upl);
++      spin_unlock(&pchb->upl);
+       synchronize_rcu();
+@@ -2144,10 +2144,9 @@ static int ppp_mp_explode(struct ppp *pp
+ #endif /* CONFIG_PPP_MULTILINK */
+ /* Try to send data out on a channel */
+-static void __ppp_channel_push(struct channel *pch)
++static void __ppp_channel_push(struct channel *pch, struct ppp *ppp)
+ {
+       struct sk_buff *skb;
+-      struct ppp *ppp;
+       spin_lock(&pch->downl);
+       if (pch->chan) {
+@@ -2166,7 +2165,6 @@ static void __ppp_channel_push(struct ch
+       spin_unlock(&pch->downl);
+       /* see if there is anything from the attached unit to be sent */
+       if (skb_queue_empty(&pch->file.xq)) {
+-              ppp = pch->ppp;
+               if (ppp)
+                       __ppp_xmit_process(ppp, NULL);
+       }
+@@ -2174,15 +2172,18 @@ static void __ppp_channel_push(struct ch
+ static void ppp_channel_push(struct channel *pch)
+ {
+-      read_lock_bh(&pch->upl);
+-      if (pch->ppp) {
+-              (*this_cpu_ptr(pch->ppp->xmit_recursion))++;
+-              __ppp_channel_push(pch);
+-              (*this_cpu_ptr(pch->ppp->xmit_recursion))--;
++      struct ppp *ppp;
++
++      rcu_read_lock_bh();
++      ppp = rcu_dereference_bh(pch->ppp);
++      if (ppp) {
++              (*this_cpu_ptr(ppp->xmit_recursion))++;
++              __ppp_channel_push(pch, ppp);
++              (*this_cpu_ptr(ppp->xmit_recursion))--;
+       } else {
+-              __ppp_channel_push(pch);
++              __ppp_channel_push(pch, NULL);
+       }
+-      read_unlock_bh(&pch->upl);
++      rcu_read_unlock_bh();
+ }
+ /*
+@@ -2284,6 +2285,7 @@ void
+ ppp_input(struct ppp_channel *chan, struct sk_buff *skb)
+ {
+       struct channel *pch = chan->ppp;
++      struct ppp *ppp;
+       int proto;
+       if (!pch) {
+@@ -2295,18 +2297,19 @@ ppp_input(struct ppp_channel *chan, stru
+       if (ppp_channel_bridge_input(pch, skb))
+               return;
+-      read_lock_bh(&pch->upl);
++      rcu_read_lock_bh();
++      ppp = rcu_dereference_bh(pch->ppp);
+       if (!ppp_decompress_proto(skb)) {
+               kfree_skb(skb);
+-              if (pch->ppp) {
+-                      ++pch->ppp->dev->stats.rx_length_errors;
+-                      ppp_receive_error(pch->ppp);
++              if (ppp) {
++                      ++ppp->dev->stats.rx_length_errors;
++                      ppp_receive_error(ppp);
+               }
+               goto done;
+       }
+       proto = PPP_PROTO(skb);
+-      if (!pch->ppp || proto >= 0xc000 || proto == PPP_CCPFRAG) {
++      if (!ppp || proto >= 0xc000 || proto == PPP_CCPFRAG) {
+               /* put it on the channel queue */
+               skb_queue_tail(&pch->file.rq, skb);
+               /* drop old frames if queue too long */
+@@ -2315,11 +2318,11 @@ ppp_input(struct ppp_channel *chan, stru
+                       kfree_skb(skb);
+               wake_up_interruptible(&pch->file.rwait);
+       } else {
+-              ppp_do_recv(pch->ppp, skb, pch);
++              ppp_do_recv(ppp, skb, pch);
+       }
+ done:
+-      read_unlock_bh(&pch->upl);
++      rcu_read_unlock_bh();
+ }
+ /* Put a 0-length skb in the receive queue as an error indication */
+@@ -2328,20 +2331,22 @@ ppp_input_error(struct ppp_channel *chan
+ {
+       struct channel *pch = chan->ppp;
+       struct sk_buff *skb;
++      struct ppp *ppp;
+       if (!pch)
+               return;
+-      read_lock_bh(&pch->upl);
+-      if (pch->ppp) {
++      rcu_read_lock_bh();
++      ppp = rcu_dereference_bh(pch->ppp);
++      if (ppp) {
+               skb = alloc_skb(0, GFP_ATOMIC);
+               if (skb) {
+                       skb->len = 0;           /* probably unnecessary */
+                       skb->cb[0] = code;
+-                      ppp_do_recv(pch->ppp, skb, pch);
++                      ppp_do_recv(ppp, skb, pch);
+               }
+       }
+-      read_unlock_bh(&pch->upl);
++      rcu_read_unlock_bh();
+ }
+ /*
+@@ -2889,7 +2894,6 @@ int ppp_register_net_channel(struct net
+       pn = ppp_pernet(net);
+-      pch->ppp = NULL;
+       pch->chan = chan;
+       pch->chan_net = get_net_track(net, &pch->ns_tracker, GFP_KERNEL);
+       chan->ppp = pch;
+@@ -2900,7 +2904,7 @@ int ppp_register_net_channel(struct net
+ #endif /* CONFIG_PPP_MULTILINK */
+       init_rwsem(&pch->chan_sem);
+       spin_lock_init(&pch->downl);
+-      rwlock_init(&pch->upl);
++      spin_lock_init(&pch->upl);
+       spin_lock_bh(&pn->all_channels_lock);
+       pch->file.index = ++pn->last_channel_index;
+@@ -2929,13 +2933,15 @@ int ppp_channel_index(struct ppp_channel
+ int ppp_unit_number(struct ppp_channel *chan)
+ {
+       struct channel *pch = chan->ppp;
++      struct ppp *ppp;
+       int unit = -1;
+       if (pch) {
+-              read_lock_bh(&pch->upl);
+-              if (pch->ppp)
+-                      unit = pch->ppp->file.index;
+-              read_unlock_bh(&pch->upl);
++              rcu_read_lock();
++              ppp = rcu_dereference(pch->ppp);
++              if (ppp)
++                      unit = ppp->file.index;
++              rcu_read_unlock();
+       }
+       return unit;
+ }
+@@ -2947,12 +2953,14 @@ char *ppp_dev_name(struct ppp_channel *c
+ {
+       struct channel *pch = chan->ppp;
+       char *name = NULL;
++      struct ppp *ppp;
+       if (pch) {
+-              read_lock_bh(&pch->upl);
+-              if (pch->ppp && pch->ppp->dev)
+-                      name = pch->ppp->dev->name;
+-              read_unlock_bh(&pch->upl);
++              rcu_read_lock();
++              ppp = rcu_dereference(pch->ppp);
++              if (ppp && ppp->dev)
++                      name = ppp->dev->name;
++              rcu_read_unlock();
+       }
+       return name;
+ }
+@@ -3475,9 +3483,9 @@ ppp_connect_channel(struct channel *pch,
+       ppp = ppp_find_unit(pn, unit);
+       if (!ppp)
+               goto out;
+-      write_lock_bh(&pch->upl);
++      spin_lock(&pch->upl);
+       ret = -EINVAL;
+-      if (pch->ppp ||
++      if (rcu_dereference_protected(pch->ppp, lockdep_is_held(&pch->upl)) ||
+           rcu_dereference_protected(pch->bridge, lockdep_is_held(&pch->upl)))
+               goto outl;
+@@ -3502,13 +3510,13 @@ ppp_connect_channel(struct channel *pch,
+               ppp->dev->hard_header_len = hdrlen;
+       list_add_tail_rcu(&pch->clist, &ppp->channels);
+       ++ppp->n_channels;
+-      pch->ppp = ppp;
++      rcu_assign_pointer(pch->ppp, ppp);
+       refcount_inc(&ppp->file.refcnt);
+       ppp_unlock(ppp);
+       ret = 0;
+  outl:
+-      write_unlock_bh(&pch->upl);
++      spin_unlock(&pch->upl);
+  out:
+       mutex_unlock(&pn->all_ppp_mutex);
+       return ret;
+@@ -3523,10 +3531,9 @@ ppp_disconnect_channel(struct channel *p
+       struct ppp *ppp;
+       int err = -EINVAL;
+-      write_lock_bh(&pch->upl);
+-      ppp = pch->ppp;
+-      pch->ppp = NULL;
+-      write_unlock_bh(&pch->upl);
++      spin_lock(&pch->upl);
++      ppp = rcu_replace_pointer(pch->ppp, NULL, lockdep_is_held(&pch->upl));
++      spin_unlock(&pch->upl);
+       if (ppp) {
+               /* remove it from the ppp unit's list */
+               ppp_lock(ppp);
diff --git a/target/linux/generic/backport-6.12/623-v6.18-pppoe-remove-rwlock-usage.patch b/target/linux/generic/backport-6.12/623-v6.18-pppoe-remove-rwlock-usage.patch
new file mode 100644 (file)
index 0000000..619f1cb
--- /dev/null
@@ -0,0 +1,321 @@
+From 72cdc67e7fa74931b055df3a76852bab551f1a04 Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <dqfext@gmail.com>
+Date: Thu, 28 Aug 2025 09:20:16 +0800
+Subject: [PATCH] pppoe: remove rwlock usage
+
+Like ppp_generic.c, convert the PPPoE socket hash table to use RCU for
+lookups and a spinlock for updates. This removes rwlock usage and allows
+lockless readers on the fast path.
+
+- Mark hash table and list pointers as __rcu.
+- Use spin_lock() to protect writers.
+- Readers use rcu_dereference() under rcu_read_lock(). All known callers
+  of get_item() already hold the RCU read lock, so no additional locking
+  is needed.
+- get_item() now uses refcount_inc_not_zero() instead of sock_hold() to
+  safely take a reference. This prevents crashes if a socket is already
+  in the process of being freed (sk_refcnt == 0).
+- Set SOCK_RCU_FREE to defer socket freeing until after an RCU grace
+  period.
+- Move skb_queue_purge() into sk_destruct callback to ensure purge
+  happens after an RCU grace period.
+
+Signed-off-by: Qingfang Deng <dqfext@gmail.com>
+Reviewed-by: Eric Dumazet <edumazet@google.com>
+Link: https://patch.msgid.link/20250828012018.15922-1-dqfext@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ppp/pppoe.c  | 94 ++++++++++++++++++++++------------------
+ include/linux/if_pppox.h |  2 +-
+ 2 files changed, 54 insertions(+), 42 deletions(-)
+
+--- a/drivers/net/ppp/pppoe.c
++++ b/drivers/net/ppp/pppoe.c
+@@ -100,8 +100,8 @@ struct pppoe_net {
+        * as well, moreover in case of SMP less locking
+        * controversy here
+        */
+-      struct pppox_sock *hash_table[PPPOE_HASH_SIZE];
+-      rwlock_t hash_lock;
++      struct pppox_sock __rcu *hash_table[PPPOE_HASH_SIZE];
++      spinlock_t hash_lock;
+ };
+ /*
+@@ -162,13 +162,13 @@ static struct pppox_sock *__get_item(str
+       int hash = hash_item(sid, addr);
+       struct pppox_sock *ret;
+-      ret = pn->hash_table[hash];
++      ret = rcu_dereference(pn->hash_table[hash]);
+       while (ret) {
+               if (cmp_addr(&ret->pppoe_pa, sid, addr) &&
+                   ret->pppoe_ifindex == ifindex)
+                       return ret;
+-              ret = ret->next;
++              ret = rcu_dereference(ret->next);
+       }
+       return NULL;
+@@ -177,19 +177,20 @@ static struct pppox_sock *__get_item(str
+ static int __set_item(struct pppoe_net *pn, struct pppox_sock *po)
+ {
+       int hash = hash_item(po->pppoe_pa.sid, po->pppoe_pa.remote);
+-      struct pppox_sock *ret;
++      struct pppox_sock *ret, *first;
+-      ret = pn->hash_table[hash];
++      first = rcu_dereference_protected(pn->hash_table[hash], lockdep_is_held(&pn->hash_lock));
++      ret = first;
+       while (ret) {
+               if (cmp_2_addr(&ret->pppoe_pa, &po->pppoe_pa) &&
+                   ret->pppoe_ifindex == po->pppoe_ifindex)
+                       return -EALREADY;
+-              ret = ret->next;
++              ret = rcu_dereference_protected(ret->next, lockdep_is_held(&pn->hash_lock));
+       }
+-      po->next = pn->hash_table[hash];
+-      pn->hash_table[hash] = po;
++      RCU_INIT_POINTER(po->next, first);
++      rcu_assign_pointer(pn->hash_table[hash], po);
+       return 0;
+ }
+@@ -198,20 +199,24 @@ static void __delete_item(struct pppoe_n
+                                       char *addr, int ifindex)
+ {
+       int hash = hash_item(sid, addr);
+-      struct pppox_sock *ret, **src;
++      struct pppox_sock *ret, __rcu **src;
+-      ret = pn->hash_table[hash];
++      ret = rcu_dereference_protected(pn->hash_table[hash], lockdep_is_held(&pn->hash_lock));
+       src = &pn->hash_table[hash];
+       while (ret) {
+               if (cmp_addr(&ret->pppoe_pa, sid, addr) &&
+                   ret->pppoe_ifindex == ifindex) {
+-                      *src = ret->next;
++                      struct pppox_sock *next;
++
++                      next = rcu_dereference_protected(ret->next,
++                                                       lockdep_is_held(&pn->hash_lock));
++                      rcu_assign_pointer(*src, next);
+                       break;
+               }
+               src = &ret->next;
+-              ret = ret->next;
++              ret = rcu_dereference_protected(ret->next, lockdep_is_held(&pn->hash_lock));
+       }
+ }
+@@ -225,11 +230,9 @@ static inline struct pppox_sock *get_ite
+ {
+       struct pppox_sock *po;
+-      read_lock_bh(&pn->hash_lock);
+       po = __get_item(pn, sid, addr, ifindex);
+-      if (po)
+-              sock_hold(sk_pppox(po));
+-      read_unlock_bh(&pn->hash_lock);
++      if (po && !refcount_inc_not_zero(&sk_pppox(po)->sk_refcnt))
++              po = NULL;
+       return po;
+ }
+@@ -258,9 +261,9 @@ static inline struct pppox_sock *get_ite
+ static inline void delete_item(struct pppoe_net *pn, __be16 sid,
+                                       char *addr, int ifindex)
+ {
+-      write_lock_bh(&pn->hash_lock);
++      spin_lock(&pn->hash_lock);
+       __delete_item(pn, sid, addr, ifindex);
+-      write_unlock_bh(&pn->hash_lock);
++      spin_unlock(&pn->hash_lock);
+ }
+ /***************************************************************************
+@@ -276,14 +279,16 @@ static void pppoe_flush_dev(struct net_d
+       int i;
+       pn = pppoe_pernet(dev_net(dev));
+-      write_lock_bh(&pn->hash_lock);
++      spin_lock(&pn->hash_lock);
+       for (i = 0; i < PPPOE_HASH_SIZE; i++) {
+-              struct pppox_sock *po = pn->hash_table[i];
++              struct pppox_sock *po = rcu_dereference_protected(pn->hash_table[i],
++                                                                lockdep_is_held(&pn->hash_lock));
+               struct sock *sk;
+               while (po) {
+                       while (po && po->pppoe_dev != dev) {
+-                              po = po->next;
++                              po = rcu_dereference_protected(po->next,
++                                                             lockdep_is_held(&pn->hash_lock));
+                       }
+                       if (!po)
+@@ -300,7 +305,7 @@ static void pppoe_flush_dev(struct net_d
+                        */
+                       sock_hold(sk);
+-                      write_unlock_bh(&pn->hash_lock);
++                      spin_unlock(&pn->hash_lock);
+                       lock_sock(sk);
+                       if (po->pppoe_dev == dev &&
+@@ -320,11 +325,12 @@ static void pppoe_flush_dev(struct net_d
+                        */
+                       BUG_ON(pppoe_pernet(dev_net(dev)) == NULL);
+-                      write_lock_bh(&pn->hash_lock);
+-                      po = pn->hash_table[i];
++                      spin_lock(&pn->hash_lock);
++                      po = rcu_dereference_protected(pn->hash_table[i],
++                                                     lockdep_is_held(&pn->hash_lock));
+               }
+       }
+-      write_unlock_bh(&pn->hash_lock);
++      spin_unlock(&pn->hash_lock);
+ }
+ static int pppoe_device_event(struct notifier_block *this,
+@@ -528,6 +534,11 @@ static struct proto pppoe_sk_proto __rea
+       .obj_size = sizeof(struct pppox_sock),
+ };
++static void pppoe_destruct(struct sock *sk)
++{
++      skb_queue_purge(&sk->sk_receive_queue);
++}
++
+ /***********************************************************************
+  *
+  * Initialize a new struct sock.
+@@ -542,11 +553,13 @@ static int pppoe_create(struct net *net,
+               return -ENOMEM;
+       sock_init_data(sock, sk);
++      sock_set_flag(sk, SOCK_RCU_FREE);
+       sock->state     = SS_UNCONNECTED;
+       sock->ops       = &pppoe_ops;
+       sk->sk_backlog_rcv      = pppoe_rcv_core;
++      sk->sk_destruct         = pppoe_destruct;
+       sk->sk_state            = PPPOX_NONE;
+       sk->sk_type             = SOCK_STREAM;
+       sk->sk_family           = PF_PPPOX;
+@@ -599,7 +612,6 @@ static int pppoe_release(struct socket *
+       sock_orphan(sk);
+       sock->sk = NULL;
+-      skb_queue_purge(&sk->sk_receive_queue);
+       release_sock(sk);
+       sock_put(sk);
+@@ -681,9 +693,9 @@ static int pppoe_connect(struct socket *
+                      &sp->sa_addr.pppoe,
+                      sizeof(struct pppoe_addr));
+-              write_lock_bh(&pn->hash_lock);
++              spin_lock(&pn->hash_lock);
+               error = __set_item(pn, po);
+-              write_unlock_bh(&pn->hash_lock);
++              spin_unlock(&pn->hash_lock);
+               if (error < 0)
+                       goto err_put;
+@@ -1052,11 +1064,11 @@ static inline struct pppox_sock *pppoe_g
+       int i;
+       for (i = 0; i < PPPOE_HASH_SIZE; i++) {
+-              po = pn->hash_table[i];
++              po = rcu_dereference(pn->hash_table[i]);
+               while (po) {
+                       if (!pos--)
+                               goto out;
+-                      po = po->next;
++                      po = rcu_dereference(po->next);
+               }
+       }
+@@ -1065,19 +1077,19 @@ out:
+ }
+ static void *pppoe_seq_start(struct seq_file *seq, loff_t *pos)
+-      __acquires(pn->hash_lock)
++      __acquires(RCU)
+ {
+       struct pppoe_net *pn = pppoe_pernet(seq_file_net(seq));
+       loff_t l = *pos;
+-      read_lock_bh(&pn->hash_lock);
++      rcu_read_lock();
+       return l ? pppoe_get_idx(pn, --l) : SEQ_START_TOKEN;
+ }
+ static void *pppoe_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+ {
+       struct pppoe_net *pn = pppoe_pernet(seq_file_net(seq));
+-      struct pppox_sock *po;
++      struct pppox_sock *po, *next;
+       ++*pos;
+       if (v == SEQ_START_TOKEN) {
+@@ -1085,14 +1097,15 @@ static void *pppoe_seq_next(struct seq_f
+               goto out;
+       }
+       po = v;
+-      if (po->next)
+-              po = po->next;
++      next = rcu_dereference(po->next);
++      if (next)
++              po = next;
+       else {
+               int hash = hash_item(po->pppoe_pa.sid, po->pppoe_pa.remote);
+               po = NULL;
+               while (++hash < PPPOE_HASH_SIZE) {
+-                      po = pn->hash_table[hash];
++                      po = rcu_dereference(pn->hash_table[hash]);
+                       if (po)
+                               break;
+               }
+@@ -1103,10 +1116,9 @@ out:
+ }
+ static void pppoe_seq_stop(struct seq_file *seq, void *v)
+-      __releases(pn->hash_lock)
++      __releases(RCU)
+ {
+-      struct pppoe_net *pn = pppoe_pernet(seq_file_net(seq));
+-      read_unlock_bh(&pn->hash_lock);
++      rcu_read_unlock();
+ }
+ static const struct seq_operations pppoe_seq_ops = {
+@@ -1149,7 +1161,7 @@ static __net_init int pppoe_init_net(str
+       struct pppoe_net *pn = pppoe_pernet(net);
+       struct proc_dir_entry *pde;
+-      rwlock_init(&pn->hash_lock);
++      spin_lock_init(&pn->hash_lock);
+       pde = proc_create_net("pppoe", 0444, net->proc_net,
+                       &pppoe_seq_ops, sizeof(struct seq_net_private));
+--- a/include/linux/if_pppox.h
++++ b/include/linux/if_pppox.h
+@@ -43,7 +43,7 @@ struct pppox_sock {
+       /* struct sock must be the first member of pppox_sock */
+       struct sock sk;
+       struct ppp_channel chan;
+-      struct pppox_sock       *next;    /* for hash table */
++      struct pppox_sock __rcu *next;    /* for hash table */
+       union {
+               struct pppoe_opt pppoe;
+               struct pptp_opt  pptp;
diff --git a/target/linux/generic/backport-6.12/624-v6.18-pppoe-drop-sock-reference-counting-on-fast-path.patch b/target/linux/generic/backport-6.12/624-v6.18-pppoe-drop-sock-reference-counting-on-fast-path.patch
new file mode 100644 (file)
index 0000000..2637621
--- /dev/null
@@ -0,0 +1,124 @@
+From 4f54dff818d7b5b1d84becd5d90bc46e6233c0d7 Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <dqfext@gmail.com>
+Date: Thu, 28 Aug 2025 09:20:17 +0800
+Subject: [PATCH] pppoe: drop sock reference counting on fast path
+
+Now that PPPoE sockets are freed via RCU (SOCK_RCU_FREE), it is no longer
+necessary to take a reference count when looking up sockets on the receive
+path. Readers are protected by RCU, so the socket memory remains valid
+until after a grace period.
+
+Convert fast-path lookups to avoid refcounting:
+ - Replace get_item() and sk_receive_skb() in pppoe_rcv() with
+   __get_item() and __sk_receive_skb().
+ - Rework get_item_by_addr() into __get_item_by_addr() (no refcount and
+   move RCU lock into pppoe_ioctl)
+ - Remove unnecessary sock_put() calls.
+
+This avoids cacheline bouncing from atomic reference counting and improves
+performance on the receive fast path.
+
+Signed-off-by: Qingfang Deng <dqfext@gmail.com>
+Reviewed-by: Eric Dumazet <edumazet@google.com>
+Link: https://patch.msgid.link/20250828012018.15922-2-dqfext@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ppp/pppoe.c | 35 +++++++++++++----------------------
+ 1 file changed, 13 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/ppp/pppoe.c
++++ b/drivers/net/ppp/pppoe.c
+@@ -237,8 +237,8 @@ static inline struct pppox_sock *get_ite
+       return po;
+ }
+-static inline struct pppox_sock *get_item_by_addr(struct net *net,
+-                                              struct sockaddr_pppox *sp)
++static inline struct pppox_sock *__get_item_by_addr(struct net *net,
++                                                  struct sockaddr_pppox *sp)
+ {
+       struct net_device *dev;
+       struct pppoe_net *pn;
+@@ -246,15 +246,13 @@ static inline struct pppox_sock *get_ite
+       int ifindex;
+-      rcu_read_lock();
+       dev = dev_get_by_name_rcu(net, sp->sa_addr.pppoe.dev);
+       if (dev) {
+               ifindex = dev->ifindex;
+               pn = pppoe_pernet(net);
+-              pppox_sock = get_item(pn, sp->sa_addr.pppoe.sid,
+-                              sp->sa_addr.pppoe.remote, ifindex);
++              pppox_sock = __get_item(pn, sp->sa_addr.pppoe.sid,
++                                      sp->sa_addr.pppoe.remote, ifindex);
+       }
+-      rcu_read_unlock();
+       return pppox_sock;
+ }
+@@ -384,18 +382,16 @@ static int pppoe_rcv_core(struct sock *s
+       if (sk->sk_state & PPPOX_BOUND) {
+               ppp_input(&po->chan, skb);
+       } else if (sk->sk_state & PPPOX_RELAY) {
+-              relay_po = get_item_by_addr(sock_net(sk),
+-                                          &po->pppoe_relay);
++              relay_po = __get_item_by_addr(sock_net(sk),
++                                            &po->pppoe_relay);
+               if (relay_po == NULL)
+                       goto abort_kfree;
+               if ((sk_pppox(relay_po)->sk_state & PPPOX_CONNECTED) == 0)
+-                      goto abort_put;
++                      goto abort_kfree;
+               if (!__pppoe_xmit(sk_pppox(relay_po), skb))
+-                      goto abort_put;
+-
+-              sock_put(sk_pppox(relay_po));
++                      goto abort_kfree;
+       } else {
+               if (sock_queue_rcv_skb(sk, skb))
+                       goto abort_kfree;
+@@ -403,9 +399,6 @@ static int pppoe_rcv_core(struct sock *s
+       return NET_RX_SUCCESS;
+-abort_put:
+-      sock_put(sk_pppox(relay_po));
+-
+ abort_kfree:
+       kfree_skb(skb);
+       return NET_RX_DROP;
+@@ -447,14 +440,11 @@ static int pppoe_rcv(struct sk_buff *skb
+       ph = pppoe_hdr(skb);
+       pn = pppoe_pernet(dev_net(dev));
+-      /* Note that get_item does a sock_hold(), so sk_pppox(po)
+-       * is known to be safe.
+-       */
+-      po = get_item(pn, ph->sid, eth_hdr(skb)->h_source, dev->ifindex);
++      po = __get_item(pn, ph->sid, eth_hdr(skb)->h_source, dev->ifindex);
+       if (!po)
+               goto drop;
+-      return sk_receive_skb(sk_pppox(po), skb, 0);
++      return __sk_receive_skb(sk_pppox(po), skb, 0, 1, false);
+ drop:
+       kfree_skb(skb);
+@@ -820,11 +810,12 @@ static int pppoe_ioctl(struct socket *so
+               /* Check that the socket referenced by the address
+                  actually exists. */
+-              relay_po = get_item_by_addr(sock_net(sk), &po->pppoe_relay);
++              rcu_read_lock();
++              relay_po = __get_item_by_addr(sock_net(sk), &po->pppoe_relay);
++              rcu_read_unlock();
+               if (!relay_po)
+                       break;
+-              sock_put(sk_pppox(relay_po));
+               sk->sk_state |= PPPOX_RELAY;
+               err = 0;
+               break;
diff --git a/target/linux/generic/backport-6.12/625-v7.0-ppp-enable-TX-scatter-gather.patch b/target/linux/generic/backport-6.12/625-v7.0-ppp-enable-TX-scatter-gather.patch
new file mode 100644 (file)
index 0000000..5ea56f5
--- /dev/null
@@ -0,0 +1,104 @@
+From 42fcb213e58a7da33d5d2d7517b4e521025c68c3 Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <dqfext@gmail.com>
+Date: Thu, 29 Jan 2026 09:29:02 +0800
+Subject: [PATCH] ppp: enable TX scatter-gather
+
+PPP channels using chan->direct_xmit prepend the PPP header to a skb and
+call dev_queue_xmit() directly. In this mode the skb does not need to be
+linear, but the PPP netdevice currently does not advertise
+scatter-gather features, causing unnecessary linearization and
+preventing GSO.
+
+Enable NETIF_F_SG and NETIF_F_FRAGLIST on PPP devices. In case a linear
+buffer is required (PPP compression, multilink, and channels without
+direct_xmit), call skb_linearize() explicitly.
+
+Signed-off-by: Qingfang Deng <dqfext@gmail.com>
+Link: https://patch.msgid.link/20260129012902.941-1-dqfext@gmail.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ppp/ppp_generic.c | 30 +++++++++++++++++++++++++-----
+ 1 file changed, 25 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ppp/ppp_generic.c
++++ b/drivers/net/ppp/ppp_generic.c
+@@ -1632,6 +1632,8 @@ static void ppp_setup(struct net_device
+       dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+       dev->priv_destructor = ppp_dev_priv_destructor;
+       dev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS;
++      dev->features = NETIF_F_SG | NETIF_F_FRAGLIST;
++      dev->hw_features = dev->features;
+       netif_keep_dst(dev);
+ }
+@@ -1696,6 +1698,10 @@ pad_compress_skb(struct ppp *ppp, struct
+               ppp->xcomp->comp_extra + ppp->dev->hard_header_len;
+       int compressor_skb_size = ppp->dev->mtu +
+               ppp->xcomp->comp_extra + PPP_HDRLEN;
++
++      if (skb_linearize(skb))
++              return NULL;
++
+       new_skb = alloc_skb(new_skb_size, GFP_ATOMIC);
+       if (!new_skb) {
+               if (net_ratelimit())
+@@ -1783,6 +1789,10 @@ ppp_send_frame(struct ppp *ppp, struct s
+       case PPP_IP:
+               if (!ppp->vj || (ppp->flags & SC_COMP_TCP) == 0)
+                       break;
++
++              if (skb_linearize(skb))
++                      goto drop;
++
+               /* try to do VJ TCP header compression */
+               new_skb = alloc_skb(skb->len + ppp->dev->hard_header_len - 2,
+                                   GFP_ATOMIC);
+@@ -1880,19 +1890,26 @@ ppp_push(struct ppp *ppp)
+       }
+       if ((ppp->flags & SC_MULTILINK) == 0) {
++              struct ppp_channel *chan;
+               /* not doing multilink: send it down the first channel */
+               list = list->next;
+               pch = list_entry(list, struct channel, clist);
+               spin_lock(&pch->downl);
+-              if (pch->chan) {
+-                      if (pch->chan->ops->start_xmit(pch->chan, skb))
+-                              ppp->xmit_pending = NULL;
+-              } else {
+-                      /* channel got unregistered */
++              chan = pch->chan;
++              if (unlikely(!chan || (!chan->direct_xmit && skb_linearize(skb)))) {
++                      /* channel got unregistered, or it requires a linear
++                       * skb but linearization failed
++                       */
+                       kfree_skb(skb);
+                       ppp->xmit_pending = NULL;
++                      goto out;
+               }
++
++              if (chan->ops->start_xmit(chan, skb))
++                      ppp->xmit_pending = NULL;
++
++out:
+               spin_unlock(&pch->downl);
+               return;
+       }
+@@ -1977,6 +1994,8 @@ static int ppp_mp_explode(struct ppp *pp
+               return 0; /* can't take now, leave it in xmit_pending */
+       /* Do protocol field compression */
++      if (skb_linearize(skb))
++              goto err_linearize;
+       p = skb->data;
+       len = skb->len;
+       if (*p == 0 && mp_protocol_compress) {
+@@ -2135,6 +2154,7 @@ static int ppp_mp_explode(struct ppp *pp
+  noskb:
+       spin_unlock(&pch->downl);
++ err_linearize:
+       if (ppp->debug & 1)
+               netdev_err(ppp->dev, "PPP: no memory (fragment)\n");
+       ++ppp->dev->stats.tx_errors;
diff --git a/target/linux/generic/backport-6.12/626-01-v6.13-net-pse-pd-Add-power-limit-check.patch b/target/linux/generic/backport-6.12/626-01-v6.13-net-pse-pd-Add-power-limit-check.patch
new file mode 100644 (file)
index 0000000..fbcc3ec
--- /dev/null
@@ -0,0 +1,41 @@
+From 6e56a6d47a7fad705a1a1d088237b0858c01a770 Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:22 +0100
+Subject: [PATCH] net: pse-pd: Add power limit check
+
+Checking only the current limit is not sufficient. According to the
+standard, voltage can reach up to 57V and current up to 1.92A, which
+exceeds the power limit described in the standard (99.9W). Add a power
+limit check to prevent this.
+
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pse_core.c | 3 +++
+ include/linux/pse-pd/pse.h    | 2 ++
+ 2 files changed, 5 insertions(+)
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -868,6 +868,9 @@ int pse_ethtool_set_pw_limit(struct pse_
+       int uV, uA, ret;
+       s64 tmp_64;
++      if (pw_limit > MAX_PI_PW)
++              return -ERANGE;
++
+       ret = regulator_get_voltage(psec->ps);
+       if (!ret) {
+               NL_SET_ERR_MSG(extack,
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -11,6 +11,8 @@
+ /* Maximum current in uA according to IEEE 802.3-2022 Table 145-1 */
+ #define MAX_PI_CURRENT 1920000
++/* Maximum power in mW according to IEEE 802.3-2022 Table 145-16 */
++#define MAX_PI_PW 99900
+ struct phy_device;
+ struct pse_controller_dev;
diff --git a/target/linux/generic/backport-6.12/626-02-v6.13-net-pse-pd-tps23881-Simplify-function-returns.patch b/target/linux/generic/backport-6.12/626-02-v6.13-net-pse-pd-tps23881-Simplify-function-returns.patch
new file mode 100644 (file)
index 0000000..fc6d532
--- /dev/null
@@ -0,0 +1,112 @@
+From 0b567519d1152de52b29b2da2c47aa0f39a46266 Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:23 +0100
+Subject: [PATCH] net: pse-pd: tps23881: Simplify function returns by removing
+ redundant checks
+
+Cleaned up several functions in tps23881 by removing redundant checks on
+return values at the end of functions. These check has been removed, and
+the return statement now directly returns the function result, reducing
+the code's complexity and making it more concise.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Reviewed-by: Kyle Swenson <kyle.swenson@est.tech>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 34 ++++++----------------------------
+ 1 file changed, 6 insertions(+), 28 deletions(-)
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -59,7 +59,6 @@ static int tps23881_pi_enable(struct pse
+       struct i2c_client *client = priv->client;
+       u8 chan;
+       u16 val;
+-      int ret;
+       if (id >= TPS23881_MAX_CHANS)
+               return -ERANGE;
+@@ -78,11 +77,7 @@ static int tps23881_pi_enable(struct pse
+                       val |= BIT(chan + 4);
+       }
+-      ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+-      if (ret)
+-              return ret;
+-
+-      return 0;
++      return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+ }
+ static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id)
+@@ -91,7 +86,6 @@ static int tps23881_pi_disable(struct ps
+       struct i2c_client *client = priv->client;
+       u8 chan;
+       u16 val;
+-      int ret;
+       if (id >= TPS23881_MAX_CHANS)
+               return -ERANGE;
+@@ -110,11 +104,7 @@ static int tps23881_pi_disable(struct ps
+                       val |= BIT(chan + 8);
+       }
+-      ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+-      if (ret)
+-              return ret;
+-
+-      return 0;
++      return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+ }
+ static int tps23881_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
+@@ -480,7 +470,7 @@ tps23881_write_port_matrix(struct tps238
+       struct i2c_client *client = priv->client;
+       u8 pi_id, lgcl_chan, hw_chan;
+       u16 val = 0;
+-      int i, ret;
++      int i;
+       for (i = 0; i < port_cnt; i++) {
+               pi_id = port_matrix[i].pi_id;
+@@ -511,11 +501,7 @@ tps23881_write_port_matrix(struct tps238
+       }
+       /* Write hardware ports matrix */
+-      ret = i2c_smbus_write_word_data(client, TPS23881_REG_PORT_MAP, val);
+-      if (ret)
+-              return ret;
+-
+-      return 0;
++      return i2c_smbus_write_word_data(client, TPS23881_REG_PORT_MAP, val);
+ }
+ static int
+@@ -564,11 +550,7 @@ tps23881_set_ports_conf(struct tps23881_
+                       val |= BIT(port_matrix[i].lgcl_chan[1]) |
+                              BIT(port_matrix[i].lgcl_chan[1] + 4);
+       }
+-      ret = i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
+-      if (ret)
+-              return ret;
+-
+-      return 0;
++      return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
+ }
+ static int
+@@ -594,11 +576,7 @@ tps23881_set_ports_matrix(struct tps2388
+       if (ret)
+               return ret;
+-      ret = tps23881_set_ports_conf(priv, port_matrix);
+-      if (ret)
+-              return ret;
+-
+-      return 0;
++      return tps23881_set_ports_conf(priv, port_matrix);
+ }
+ static int tps23881_setup_pi_matrix(struct pse_controller_dev *pcdev)
diff --git a/target/linux/generic/backport-6.12/626-03-v6.13-net-pse-pd-tps23881-Use-helpers-to-calculate-bit-offset.patch b/target/linux/generic/backport-6.12/626-03-v6.13-net-pse-pd-tps23881-Use-helpers-to-calculate-bit-offset.patch
new file mode 100644 (file)
index 0000000..3b390ea
--- /dev/null
@@ -0,0 +1,190 @@
+From 4c2bab507eb7edc8e497e91b9b7f05d76d7e32bb Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:24 +0100
+Subject: [PATCH] net: pse-pd: tps23881: Use helpers to calculate bit offset
+ for a channel
+
+This driver frequently follows a pattern where two registers are read or
+written in a single operation, followed by calculating the bit offset for
+a specific channel.
+
+Introduce helpers to streamline this process and reduce code redundancy,
+making the codebase cleaner and more maintainable.
+
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 107 ++++++++++++++++++++++------------
+ 1 file changed, 69 insertions(+), 38 deletions(-)
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -53,6 +53,55 @@ static struct tps23881_priv *to_tps23881
+       return container_of(pcdev, struct tps23881_priv, pcdev);
+ }
++/*
++ * Helper to extract a value from a u16 register value, which is made of two
++ * u8 registers. The function calculates the bit offset based on the channel
++ * and extracts the relevant bits using a provided field mask.
++ *
++ * @param reg_val: The u16 register value (composed of two u8 registers).
++ * @param chan: The channel number (0-7).
++ * @param field_offset: The base bit offset to apply (e.g., 0 or 4).
++ * @param field_mask: The mask to apply to extract the required bits.
++ * @return: The extracted value for the specific channel.
++ */
++static u16 tps23881_calc_val(u16 reg_val, u8 chan, u8 field_offset,
++                           u16 field_mask)
++{
++      if (chan >= 4)
++              reg_val >>= 8;
++
++      return (reg_val >> field_offset) & field_mask;
++}
++
++/*
++ * Helper to combine individual channel values into a u16 register value.
++ * The function sets the value for a specific channel in the appropriate
++ * position.
++ *
++ * @param reg_val: The current u16 register value.
++ * @param chan: The channel number (0-7).
++ * @param field_offset: The base bit offset to apply (e.g., 0 or 4).
++ * @param field_mask: The mask to apply for the field (e.g., 0x0F).
++ * @param field_val: The value to set for the specific channel (masked by
++ *                   field_mask).
++ * @return: The updated u16 register value with the channel value set.
++ */
++static u16 tps23881_set_val(u16 reg_val, u8 chan, u8 field_offset,
++                          u16 field_mask, u16 field_val)
++{
++      field_val &= field_mask;
++
++      if (chan < 4) {
++              reg_val &= ~(field_mask << field_offset);
++              reg_val |= (field_val << field_offset);
++      } else {
++              reg_val &= ~(field_mask << (field_offset + 8));
++              reg_val |= (field_val << (field_offset + 8));
++      }
++
++      return reg_val;
++}
++
+ static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id)
+ {
+       struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+@@ -64,17 +113,12 @@ static int tps23881_pi_enable(struct pse
+               return -ERANGE;
+       chan = priv->port[id].chan[0];
+-      if (chan < 4)
+-              val = BIT(chan);
+-      else
+-              val = BIT(chan + 4);
++      val = tps23881_set_val(0, chan, 0, BIT(chan % 4), BIT(chan % 4));
+       if (priv->port[id].is_4p) {
+               chan = priv->port[id].chan[1];
+-              if (chan < 4)
+-                      val |= BIT(chan);
+-              else
+-                      val |= BIT(chan + 4);
++              val = tps23881_set_val(val, chan, 0, BIT(chan % 4),
++                                     BIT(chan % 4));
+       }
+       return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+@@ -91,17 +135,12 @@ static int tps23881_pi_disable(struct ps
+               return -ERANGE;
+       chan = priv->port[id].chan[0];
+-      if (chan < 4)
+-              val = BIT(chan + 4);
+-      else
+-              val = BIT(chan + 8);
++      val = tps23881_set_val(0, chan, 4, BIT(chan % 4), BIT(chan % 4));
+       if (priv->port[id].is_4p) {
+               chan = priv->port[id].chan[1];
+-              if (chan < 4)
+-                      val |= BIT(chan + 4);
+-              else
+-                      val |= BIT(chan + 8);
++              val = tps23881_set_val(val, chan, 4, BIT(chan % 4),
++                                     BIT(chan % 4));
+       }
+       return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+@@ -113,6 +152,7 @@ static int tps23881_pi_is_enabled(struct
+       struct i2c_client *client = priv->client;
+       bool enabled;
+       u8 chan;
++      u16 val;
+       int ret;
+       ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS);
+@@ -120,17 +160,13 @@ static int tps23881_pi_is_enabled(struct
+               return ret;
+       chan = priv->port[id].chan[0];
+-      if (chan < 4)
+-              enabled = ret & BIT(chan);
+-      else
+-              enabled = ret & BIT(chan + 4);
++      val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
++      enabled = !!(val);
+       if (priv->port[id].is_4p) {
+               chan = priv->port[id].chan[1];
+-              if (chan < 4)
+-                      enabled &= !!(ret & BIT(chan));
+-              else
+-                      enabled &= !!(ret & BIT(chan + 4));
++              val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
++              enabled &= !!(val);
+       }
+       /* Return enabled status only if both channel are on this state */
+@@ -146,6 +182,7 @@ static int tps23881_ethtool_get_status(s
+       struct i2c_client *client = priv->client;
+       bool enabled, delivering;
+       u8 chan;
++      u16 val;
+       int ret;
+       ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS);
+@@ -153,23 +190,17 @@ static int tps23881_ethtool_get_status(s
+               return ret;
+       chan = priv->port[id].chan[0];
+-      if (chan < 4) {
+-              enabled = ret & BIT(chan);
+-              delivering = ret & BIT(chan + 4);
+-      } else {
+-              enabled = ret & BIT(chan + 4);
+-              delivering = ret & BIT(chan + 8);
+-      }
++      val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
++      enabled = !!(val);
++      val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4));
++      delivering = !!(val);
+       if (priv->port[id].is_4p) {
+               chan = priv->port[id].chan[1];
+-              if (chan < 4) {
+-                      enabled &= !!(ret & BIT(chan));
+-                      delivering &= !!(ret & BIT(chan + 4));
+-              } else {
+-                      enabled &= !!(ret & BIT(chan + 4));
+-                      delivering &= !!(ret & BIT(chan + 8));
+-              }
++              val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
++              enabled &= !!(val);
++              val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4));
++              delivering &= !!(val);
+       }
+       /* Return delivering status only if both channel are on this state */
diff --git a/target/linux/generic/backport-6.12/626-04-v6.13-net-pse-pd-tps23881-Add-missing-configuration-register.patch b/target/linux/generic/backport-6.12/626-04-v6.13-net-pse-pd-tps23881-Add-missing-configuration-register.patch
new file mode 100644 (file)
index 0000000..c00dd90
--- /dev/null
@@ -0,0 +1,63 @@
+From f3cb3c7bea0c08e821d8e9dfd2f96acd1db7c24e Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:25 +0100
+Subject: [PATCH] net: pse-pd: tps23881: Add missing configuration register
+ after disable
+
+When setting the PWOFF register, the controller resets multiple
+configuration registers. This patch ensures these registers are
+reconfigured as needed following a disable operation.
+
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 30 +++++++++++++++++++++++++++++-
+ 1 file changed, 29 insertions(+), 1 deletion(-)
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -130,6 +130,7 @@ static int tps23881_pi_disable(struct ps
+       struct i2c_client *client = priv->client;
+       u8 chan;
+       u16 val;
++      int ret;
+       if (id >= TPS23881_MAX_CHANS)
+               return -ERANGE;
+@@ -143,7 +144,34 @@ static int tps23881_pi_disable(struct ps
+                                      BIT(chan % 4));
+       }
+-      return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
++      ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
++      if (ret)
++              return ret;
++
++      /* PWOFF command resets lots of register which need to be
++       * configured again. According to the datasheet "It may take upwards
++       * of 5ms after PWOFFn command for all register values to be updated"
++       */
++      mdelay(5);
++
++      /* Enable detection and classification */
++      ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_CLA_EN);
++      if (ret < 0)
++              return ret;
++
++      chan = priv->port[id].chan[0];
++      val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4));
++      val = tps23881_set_val(val, chan, 4, BIT(chan % 4), BIT(chan % 4));
++
++      if (priv->port[id].is_4p) {
++              chan = priv->port[id].chan[1];
++              val = tps23881_set_val(ret, chan, 0, BIT(chan % 4),
++                                     BIT(chan % 4));
++              val = tps23881_set_val(val, chan, 4, BIT(chan % 4),
++                                     BIT(chan % 4));
++      }
++
++      return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
+ }
+ static int tps23881_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
diff --git a/target/linux/generic/backport-6.12/626-05-v6.13-net-pse-pd-Split-ethtool_get_status-into-multiple-callbacks.patch b/target/linux/generic/backport-6.12/626-05-v6.13-net-pse-pd-Split-ethtool_get_status-into-multiple-callbacks.patch
new file mode 100644 (file)
index 0000000..71a2379
--- /dev/null
@@ -0,0 +1,714 @@
+From 3e9dbfec499807767d03592ebdf19d9c15fd495b Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:27 +0100
+Subject: [PATCH] net: pse-pd: Split ethtool_get_status into multiple callbacks
+
+The ethtool_get_status callback currently handles all status and PSE
+information within a single function. This approach has two key
+drawbacks:
+
+1. If the core requires some information for purposes other than
+   ethtool_get_status, redundant code will be needed to fetch the same
+   data from the driver (like is_enabled).
+
+2. Drivers currently have access to all information passed to ethtool.
+   New variables will soon be added to ethtool status, such as PSE ID,
+   power domain IDs, and budget evaluation strategies, which are meant
+   to be managed solely by the core. Drivers should not have the ability
+   to modify these variables.
+
+To resolve these issues, ethtool_get_status has been split into multiple
+callbacks, with each handling a specific piece of information required
+by ethtool or the core.
+
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c       | 153 ++++++++++++++++++++---------
+ drivers/net/pse-pd/pse_core.c      |  83 ++++++++++++++--
+ drivers/net/pse-pd/pse_regulator.c |  26 +++--
+ drivers/net/pse-pd/tps23881.c      |  60 ++++++++---
+ include/linux/pse-pd/pse.h         |  87 +++++++++++++---
+ net/ethtool/pse-pd.c               |   8 +-
+ 6 files changed, 323 insertions(+), 94 deletions(-)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -517,21 +517,38 @@ pd692x0_pse_ext_state_map[] = {
+       { /* sentinel */ }
+ };
+-static void
+-pd692x0_get_ext_state(struct ethtool_c33_pse_ext_state_info *c33_ext_state_info,
+-                    u32 status_code)
++static int
++pd692x0_pi_get_ext_state(struct pse_controller_dev *pcdev, int id,
++                       struct pse_ext_state_info *ext_state_info)
+ {
++      struct ethtool_c33_pse_ext_state_info *c33_ext_state_info;
+       const struct pd692x0_pse_ext_state_mapping *ext_state_map;
++      struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
++      struct pd692x0_msg msg, buf = {0};
++      int ret;
++
++      ret = pd692x0_fw_unavailable(priv);
++      if (ret)
++              return ret;
++      msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
++      msg.sub[2] = id;
++      ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
++      if (ret < 0)
++              return ret;
++
++      c33_ext_state_info = &ext_state_info->c33_ext_state_info;
+       ext_state_map = pd692x0_pse_ext_state_map;
+       while (ext_state_map->status_code) {
+-              if (ext_state_map->status_code == status_code) {
++              if (ext_state_map->status_code == buf.sub[0]) {
+                       c33_ext_state_info->c33_pse_ext_state = ext_state_map->pse_ext_state;
+                       c33_ext_state_info->__c33_pse_ext_substate = ext_state_map->pse_ext_substate;
+-                      return;
++                      return  0;
+               }
+               ext_state_map++;
+       }
++
++      return 0;
+ }
+ struct pd692x0_class_pw {
+@@ -613,35 +630,36 @@ static int pd692x0_pi_set_pw_from_table(
+ }
+ static int
+-pd692x0_pi_get_pw_ranges(struct pse_control_status *st)
++pd692x0_pi_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id,
++                             struct pse_pw_limit_ranges *pw_limit_ranges)
+ {
++      struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges;
+       const struct pd692x0_class_pw *pw_table;
+       int i;
+       pw_table = pd692x0_class_pw_table;
+-      st->c33_pw_limit_ranges = kcalloc(PD692X0_CLASS_PW_TABLE_SIZE,
+-                                        sizeof(struct ethtool_c33_pse_pw_limit_range),
+-                                        GFP_KERNEL);
+-      if (!st->c33_pw_limit_ranges)
++      c33_pw_limit_ranges = kcalloc(PD692X0_CLASS_PW_TABLE_SIZE,
++                                    sizeof(*c33_pw_limit_ranges),
++                                    GFP_KERNEL);
++      if (!c33_pw_limit_ranges)
+               return -ENOMEM;
+       for (i = 0; i < PD692X0_CLASS_PW_TABLE_SIZE; i++, pw_table++) {
+-              st->c33_pw_limit_ranges[i].min = pw_table->class_pw;
+-              st->c33_pw_limit_ranges[i].max = pw_table->class_pw + pw_table->max_added_class_pw;
++              c33_pw_limit_ranges[i].min = pw_table->class_pw;
++              c33_pw_limit_ranges[i].max = pw_table->class_pw +
++                                           pw_table->max_added_class_pw;
+       }
+-      st->c33_pw_limit_nb_ranges = i;
+-      return 0;
++      pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges;
++      return i;
+ }
+-static int pd692x0_ethtool_get_status(struct pse_controller_dev *pcdev,
+-                                    unsigned long id,
+-                                    struct netlink_ext_ack *extack,
+-                                    struct pse_control_status *status)
++static int
++pd692x0_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
++                         struct pse_admin_state *admin_state)
+ {
+       struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+       struct pd692x0_msg msg, buf = {0};
+-      u32 class;
+       int ret;
+       ret = pd692x0_fw_unavailable(priv);
+@@ -654,39 +672,65 @@ static int pd692x0_ethtool_get_status(st
+       if (ret < 0)
+               return ret;
+-      /* Compare Port Status (Communication Protocol Document par. 7.1) */
+-      if ((buf.sub[0] & 0xf0) == 0x80 || (buf.sub[0] & 0xf0) == 0x90)
+-              status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+-      else if (buf.sub[0] == 0x1b || buf.sub[0] == 0x22)
+-              status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;
+-      else if (buf.sub[0] == 0x12)
+-              status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_FAULT;
+-      else
+-              status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
+-
+       if (buf.sub[1])
+-              status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
++              admin_state->c33_admin_state =
++                      ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
+       else
+-              status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
++              admin_state->c33_admin_state =
++                      ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
+-      priv->admin_state[id] = status->c33_admin_state;
++      priv->admin_state[id] = admin_state->c33_admin_state;
+-      pd692x0_get_ext_state(&status->c33_ext_state_info, buf.sub[0]);
+-      status->c33_actual_pw = (buf.data[0] << 4 | buf.data[1]) * 100;
++      return 0;
++}
+-      msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM];
++static int
++pd692x0_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
++                       struct pse_pw_status *pw_status)
++{
++      struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
++      struct pd692x0_msg msg, buf = {0};
++      int ret;
++
++      ret = pd692x0_fw_unavailable(priv);
++      if (ret)
++              return ret;
++
++      msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
+       msg.sub[2] = id;
+-      memset(&buf, 0, sizeof(buf));
+       ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+       if (ret < 0)
+               return ret;
+-      ret = pd692x0_pi_get_pw_from_table(buf.data[0], buf.data[1]);
+-      if (ret < 0)
++      /* Compare Port Status (Communication Protocol Document par. 7.1) */
++      if ((buf.sub[0] & 0xf0) == 0x80 || (buf.sub[0] & 0xf0) == 0x90)
++              pw_status->c33_pw_status =
++                      ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
++      else if (buf.sub[0] == 0x1b || buf.sub[0] == 0x22)
++              pw_status->c33_pw_status =
++                      ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;
++      else if (buf.sub[0] == 0x12)
++              pw_status->c33_pw_status =
++                      ETHTOOL_C33_PSE_PW_D_STATUS_FAULT;
++      else
++              pw_status->c33_pw_status =
++                      ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
++
++      return 0;
++}
++
++static int
++pd692x0_pi_get_pw_class(struct pse_controller_dev *pcdev, int id)
++{
++      struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
++      struct pd692x0_msg msg, buf = {0};
++      u32 class;
++      int ret;
++
++      ret = pd692x0_fw_unavailable(priv);
++      if (ret)
+               return ret;
+-      status->c33_avail_pw_limit = ret;
+-      memset(&buf, 0, sizeof(buf));
+       msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_CLASS];
+       msg.sub[2] = id;
+       ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+@@ -695,13 +739,29 @@ static int pd692x0_ethtool_get_status(st
+       class = buf.data[3] >> 4;
+       if (class <= 8)
+-              status->c33_pw_class = class;
++              return class;
++
++      return 0;
++}
++
++static int
++pd692x0_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
++{
++      struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
++      struct pd692x0_msg msg, buf = {0};
++      int ret;
++
++      ret = pd692x0_fw_unavailable(priv);
++      if (ret)
++              return ret;
+-      ret = pd692x0_pi_get_pw_ranges(status);
++      msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
++      msg.sub[2] = id;
++      ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+       if (ret < 0)
+               return ret;
+-      return 0;
++      return (buf.data[0] << 4 | buf.data[1]) * 100;
+ }
+ static struct pd692x0_msg_ver pd692x0_get_sw_version(struct pd692x0_priv *priv)
+@@ -1038,13 +1098,18 @@ static int pd692x0_pi_set_pw_limit(struc
+ static const struct pse_controller_ops pd692x0_ops = {
+       .setup_pi_matrix = pd692x0_setup_pi_matrix,
+-      .ethtool_get_status = pd692x0_ethtool_get_status,
++      .pi_get_admin_state = pd692x0_pi_get_admin_state,
++      .pi_get_pw_status = pd692x0_pi_get_pw_status,
++      .pi_get_ext_state = pd692x0_pi_get_ext_state,
++      .pi_get_pw_class = pd692x0_pi_get_pw_class,
++      .pi_get_actual_pw = pd692x0_pi_get_actual_pw,
+       .pi_enable = pd692x0_pi_enable,
+       .pi_disable = pd692x0_pi_disable,
+       .pi_is_enabled = pd692x0_pi_is_enabled,
+       .pi_get_voltage = pd692x0_pi_get_voltage,
+       .pi_get_pw_limit = pd692x0_pi_get_pw_limit,
+       .pi_set_pw_limit = pd692x0_pi_set_pw_limit,
++      .pi_get_pw_limit_ranges = pd692x0_pi_get_pw_limit_ranges,
+ };
+ #define PD692X0_FW_LINE_MAX_SZ 0xff
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -443,6 +443,13 @@ int pse_controller_register(struct pse_c
+       if (!pcdev->nr_lines)
+               pcdev->nr_lines = 1;
++      if (!pcdev->ops->pi_get_admin_state ||
++          !pcdev->ops->pi_get_pw_status) {
++              dev_err(pcdev->dev,
++                      "Mandatory status report callbacks are missing");
++              return -EINVAL;
++      }
++
+       ret = of_load_pse_pis(pcdev);
+       if (ret)
+               return ret;
+@@ -745,25 +752,81 @@ EXPORT_SYMBOL_GPL(of_pse_control_get);
+  */
+ int pse_ethtool_get_status(struct pse_control *psec,
+                          struct netlink_ext_ack *extack,
+-                         struct pse_control_status *status)
++                         struct ethtool_pse_control_status *status)
+ {
++      struct pse_admin_state admin_state = {0};
++      struct pse_pw_status pw_status = {0};
+       const struct pse_controller_ops *ops;
+       struct pse_controller_dev *pcdev;
+-      int err;
++      int ret;
+       pcdev = psec->pcdev;
+       ops = pcdev->ops;
+-      if (!ops->ethtool_get_status) {
+-              NL_SET_ERR_MSG(extack,
+-                             "PSE driver does not support status report");
+-              return -EOPNOTSUPP;
++      mutex_lock(&pcdev->lock);
++      ret = ops->pi_get_admin_state(pcdev, psec->id, &admin_state);
++      if (ret)
++              goto out;
++      status->podl_admin_state = admin_state.podl_admin_state;
++      status->c33_admin_state = admin_state.c33_admin_state;
++
++      ret = ops->pi_get_pw_status(pcdev, psec->id, &pw_status);
++      if (ret)
++              goto out;
++      status->podl_pw_status = pw_status.podl_pw_status;
++      status->c33_pw_status = pw_status.c33_pw_status;
++
++      if (ops->pi_get_ext_state) {
++              struct pse_ext_state_info ext_state_info = {0};
++
++              ret = ops->pi_get_ext_state(pcdev, psec->id,
++                                          &ext_state_info);
++              if (ret)
++                      goto out;
++
++              memcpy(&status->c33_ext_state_info,
++                     &ext_state_info.c33_ext_state_info,
++                     sizeof(status->c33_ext_state_info));
+       }
+-      mutex_lock(&pcdev->lock);
+-      err = ops->ethtool_get_status(pcdev, psec->id, extack, status);
+-      mutex_unlock(&pcdev->lock);
++      if (ops->pi_get_pw_class) {
++              ret = ops->pi_get_pw_class(pcdev, psec->id);
++              if (ret < 0)
++                      goto out;
++
++              status->c33_pw_class = ret;
++      }
++
++      if (ops->pi_get_actual_pw) {
++              ret = ops->pi_get_actual_pw(pcdev, psec->id);
++              if (ret < 0)
++                      goto out;
++
++              status->c33_actual_pw = ret;
++      }
++
++      if (ops->pi_get_pw_limit) {
++              ret = ops->pi_get_pw_limit(pcdev, psec->id);
++              if (ret < 0)
++                      goto out;
++
++              status->c33_avail_pw_limit = ret;
++      }
++
++      if (ops->pi_get_pw_limit_ranges) {
++              struct pse_pw_limit_ranges pw_limit_ranges = {0};
+-      return err;
++              ret = ops->pi_get_pw_limit_ranges(pcdev, psec->id,
++                                                &pw_limit_ranges);
++              if (ret < 0)
++                      goto out;
++
++              status->c33_pw_limit_ranges =
++                      pw_limit_ranges.c33_pw_limit_ranges;
++              status->c33_pw_limit_nb_ranges = ret;
++      }
++out:
++      mutex_unlock(&psec->pcdev->lock);
++      return ret;
+ }
+ EXPORT_SYMBOL_GPL(pse_ethtool_get_status);
+--- a/drivers/net/pse-pd/pse_regulator.c
++++ b/drivers/net/pse-pd/pse_regulator.c
+@@ -60,9 +60,19 @@ pse_reg_pi_is_enabled(struct pse_control
+ }
+ static int
+-pse_reg_ethtool_get_status(struct pse_controller_dev *pcdev, unsigned long id,
+-                         struct netlink_ext_ack *extack,
+-                         struct pse_control_status *status)
++pse_reg_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
++                         struct pse_admin_state *admin_state)
++{
++      struct pse_reg_priv *priv = to_pse_reg(pcdev);
++
++      admin_state->podl_admin_state = priv->admin_state;
++
++      return 0;
++}
++
++static int
++pse_reg_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
++                       struct pse_pw_status *pw_status)
+ {
+       struct pse_reg_priv *priv = to_pse_reg(pcdev);
+       int ret;
+@@ -72,18 +82,18 @@ pse_reg_ethtool_get_status(struct pse_co
+               return ret;
+       if (!ret)
+-              status->podl_pw_status = ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED;
++              pw_status->podl_pw_status =
++                      ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED;
+       else
+-              status->podl_pw_status =
++              pw_status->podl_pw_status =
+                       ETHTOOL_PODL_PSE_PW_D_STATUS_DELIVERING;
+-      status->podl_admin_state = priv->admin_state;
+-
+       return 0;
+ }
+ static const struct pse_controller_ops pse_reg_ops = {
+-      .ethtool_get_status = pse_reg_ethtool_get_status,
++      .pi_get_admin_state = pse_reg_pi_get_admin_state,
++      .pi_get_pw_status = pse_reg_pi_get_pw_status,
+       .pi_enable = pse_reg_pi_enable,
+       .pi_is_enabled = pse_reg_pi_is_enabled,
+       .pi_disable = pse_reg_pi_disable,
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -201,14 +201,13 @@ static int tps23881_pi_is_enabled(struct
+       return enabled;
+ }
+-static int tps23881_ethtool_get_status(struct pse_controller_dev *pcdev,
+-                                     unsigned long id,
+-                                     struct netlink_ext_ack *extack,
+-                                     struct pse_control_status *status)
++static int
++tps23881_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
++                          struct pse_admin_state *admin_state)
+ {
+       struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+       struct i2c_client *client = priv->client;
+-      bool enabled, delivering;
++      bool enabled;
+       u8 chan;
+       u16 val;
+       int ret;
+@@ -220,28 +219,56 @@ static int tps23881_ethtool_get_status(s
+       chan = priv->port[id].chan[0];
+       val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
+       enabled = !!(val);
+-      val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4));
+-      delivering = !!(val);
+       if (priv->port[id].is_4p) {
+               chan = priv->port[id].chan[1];
+               val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
+               enabled &= !!(val);
++      }
++
++      /* Return enabled status only if both channel are on this state */
++      if (enabled)
++              admin_state->c33_admin_state =
++                      ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
++      else
++              admin_state->c33_admin_state =
++                      ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
++
++      return 0;
++}
++
++static int
++tps23881_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
++                        struct pse_pw_status *pw_status)
++{
++      struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++      struct i2c_client *client = priv->client;
++      bool delivering;
++      u8 chan;
++      u16 val;
++      int ret;
++
++      ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS);
++      if (ret < 0)
++              return ret;
++
++      chan = priv->port[id].chan[0];
++      val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4));
++      delivering = !!(val);
++
++      if (priv->port[id].is_4p) {
++              chan = priv->port[id].chan[1];
+               val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4));
+               delivering &= !!(val);
+       }
+       /* Return delivering status only if both channel are on this state */
+       if (delivering)
+-              status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+-      else
+-              status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
+-
+-      /* Return enabled status only if both channel are on this state */
+-      if (enabled)
+-              status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
++              pw_status->c33_pw_status =
++                      ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+       else
+-              status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
++              pw_status->c33_pw_status =
++                      ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
+       return 0;
+ }
+@@ -664,7 +691,8 @@ static const struct pse_controller_ops t
+       .pi_enable = tps23881_pi_enable,
+       .pi_disable = tps23881_pi_disable,
+       .pi_is_enabled = tps23881_pi_is_enabled,
+-      .ethtool_get_status = tps23881_ethtool_get_status,
++      .pi_get_admin_state = tps23881_pi_get_admin_state,
++      .pi_get_pw_status = tps23881_pi_get_pw_status,
+ };
+ static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin";
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -31,7 +31,52 @@ struct pse_control_config {
+ };
+ /**
+- * struct pse_control_status - PSE control/channel status.
++ * struct pse_admin_state - PSE operational state
++ *
++ * @podl_admin_state: operational state of the PoDL PSE
++ *    functions. IEEE 802.3-2018 30.15.1.1.2 aPoDLPSEAdminState
++ * @c33_admin_state: operational state of the PSE
++ *    functions. IEEE 802.3-2022 30.9.1.1.2 aPSEAdminState
++ */
++struct pse_admin_state {
++      enum ethtool_podl_pse_admin_state podl_admin_state;
++      enum ethtool_c33_pse_admin_state c33_admin_state;
++};
++
++/**
++ * struct pse_pw_status - PSE power detection status
++ *
++ * @podl_pw_status: power detection status of the PoDL PSE.
++ *    IEEE 802.3-2018 30.15.1.1.3 aPoDLPSEPowerDetectionStatus:
++ * @c33_pw_status: power detection status of the PSE.
++ *    IEEE 802.3-2022 30.9.1.1.5 aPSEPowerDetectionStatus:
++ */
++struct pse_pw_status {
++      enum ethtool_podl_pse_pw_d_status podl_pw_status;
++      enum ethtool_c33_pse_pw_d_status c33_pw_status;
++};
++
++/**
++ * struct pse_ext_state_info - PSE extended state information
++ *
++ * @c33_ext_state_info: extended state information of the PSE
++ */
++struct pse_ext_state_info {
++      struct ethtool_c33_pse_ext_state_info c33_ext_state_info;
++};
++
++/**
++ * struct pse_pw_limit_ranges - PSE power limit configuration range
++ *
++ * @c33_pw_limit_ranges: supported power limit configuration range. The driver
++ *                     is in charge of the memory allocation.
++ */
++struct pse_pw_limit_ranges {
++      struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges;
++};
++
++/**
++ * struct ethtool_pse_control_status - PSE control/channel status.
+  *
+  * @podl_admin_state: operational state of the PoDL PSE
+  *    functions. IEEE 802.3-2018 30.15.1.1.2 aPoDLPSEAdminState
+@@ -49,11 +94,11 @@ struct pse_control_config {
+  * @c33_avail_pw_limit: available power limit of the PSE in mW
+  *    IEEE 802.3-2022 145.2.5.4 pse_avail_pwr
+  * @c33_pw_limit_ranges: supported power limit configuration range. The driver
+- *    is in charge of the memory allocation.
++ *    is in charge of the memory allocation
+  * @c33_pw_limit_nb_ranges: number of supported power limit configuration
+  *    ranges
+  */
+-struct pse_control_status {
++struct ethtool_pse_control_status {
+       enum ethtool_podl_pse_admin_state podl_admin_state;
+       enum ethtool_podl_pse_pw_d_status podl_pw_status;
+       enum ethtool_c33_pse_admin_state c33_admin_state;
+@@ -69,22 +114,37 @@ struct pse_control_status {
+ /**
+  * struct pse_controller_ops - PSE controller driver callbacks
+  *
+- * @ethtool_get_status: get PSE control status for ethtool interface
+  * @setup_pi_matrix: setup PI matrix of the PSE controller
++ * @pi_get_admin_state: Get the operational state of the PSE PI. This ops
++ *                    is mandatory.
++ * @pi_get_pw_status: Get the power detection status of the PSE PI. This
++ *                  ops is mandatory.
++ * @pi_get_ext_state: Get the extended state of the PSE PI.
++ * @pi_get_pw_class: Get the power class of the PSE PI.
++ * @pi_get_actual_pw: Get actual power of the PSE PI in mW.
+  * @pi_is_enabled: Return 1 if the PSE PI is enabled, 0 if not.
+  *               May also return negative errno.
+  * @pi_enable: Configure the PSE PI as enabled.
+  * @pi_disable: Configure the PSE PI as disabled.
+  * @pi_get_voltage: Return voltage similarly to get_voltage regulator
+- *                callback.
+- * @pi_get_pw_limit: Get the configured power limit of the PSE PI.
+- * @pi_set_pw_limit: Configure the power limit of the PSE PI.
++ *                callback in uV.
++ * @pi_get_pw_limit: Get the configured power limit of the PSE PI in mW.
++ * @pi_set_pw_limit: Configure the power limit of the PSE PI in mW.
++ * @pi_get_pw_limit_ranges: Get the supported power limit configuration
++ *                        range. The driver is in charge of the memory
++ *                        allocation and should return the number of
++ *                        ranges.
+  */
+ struct pse_controller_ops {
+-      int (*ethtool_get_status)(struct pse_controller_dev *pcdev,
+-              unsigned long id, struct netlink_ext_ack *extack,
+-              struct pse_control_status *status);
+       int (*setup_pi_matrix)(struct pse_controller_dev *pcdev);
++      int (*pi_get_admin_state)(struct pse_controller_dev *pcdev, int id,
++                                struct pse_admin_state *admin_state);
++      int (*pi_get_pw_status)(struct pse_controller_dev *pcdev, int id,
++                              struct pse_pw_status *pw_status);
++      int (*pi_get_ext_state)(struct pse_controller_dev *pcdev, int id,
++                              struct pse_ext_state_info *ext_state_info);
++      int (*pi_get_pw_class)(struct pse_controller_dev *pcdev, int id);
++      int (*pi_get_actual_pw)(struct pse_controller_dev *pcdev, int id);
+       int (*pi_is_enabled)(struct pse_controller_dev *pcdev, int id);
+       int (*pi_enable)(struct pse_controller_dev *pcdev, int id);
+       int (*pi_disable)(struct pse_controller_dev *pcdev, int id);
+@@ -93,12 +153,15 @@ struct pse_controller_ops {
+                              int id);
+       int (*pi_set_pw_limit)(struct pse_controller_dev *pcdev,
+                              int id, int max_mW);
++      int (*pi_get_pw_limit_ranges)(struct pse_controller_dev *pcdev, int id,
++                                    struct pse_pw_limit_ranges *pw_limit_ranges);
+ };
+ struct module;
+ struct device_node;
+ struct of_phandle_args;
+ struct pse_control;
++struct ethtool_pse_control_status;
+ /* PSE PI pairset pinout can either be Alternative A or Alternative B */
+ enum pse_pi_pairset_pinout {
+@@ -175,7 +238,7 @@ void pse_control_put(struct pse_control
+ int pse_ethtool_get_status(struct pse_control *psec,
+                          struct netlink_ext_ack *extack,
+-                         struct pse_control_status *status);
++                         struct ethtool_pse_control_status *status);
+ int pse_ethtool_set_config(struct pse_control *psec,
+                          struct netlink_ext_ack *extack,
+                          const struct pse_control_config *config);
+@@ -201,7 +264,7 @@ static inline void pse_control_put(struc
+ static inline int pse_ethtool_get_status(struct pse_control *psec,
+                                        struct netlink_ext_ack *extack,
+-                                       struct pse_control_status *status)
++                                       struct ethtool_pse_control_status *status)
+ {
+       return -EOPNOTSUPP;
+ }
+--- a/net/ethtool/pse-pd.c
++++ b/net/ethtool/pse-pd.c
+@@ -19,7 +19,7 @@ struct pse_req_info {
+ struct pse_reply_data {
+       struct ethnl_reply_data base;
+-      struct pse_control_status status;
++      struct ethtool_pse_control_status status;
+ };
+ #define PSE_REPDATA(__reply_base) \
+@@ -80,7 +80,7 @@ static int pse_reply_size(const struct e
+                         const struct ethnl_reply_data *reply_base)
+ {
+       const struct pse_reply_data *data = PSE_REPDATA(reply_base);
+-      const struct pse_control_status *st = &data->status;
++      const struct ethtool_pse_control_status *st = &data->status;
+       int len = 0;
+       if (st->podl_admin_state > 0)
+@@ -114,7 +114,7 @@ static int pse_reply_size(const struct e
+ }
+ static int pse_put_pw_limit_ranges(struct sk_buff *skb,
+-                                 const struct pse_control_status *st)
++                                 const struct ethtool_pse_control_status *st)
+ {
+       const struct ethtool_c33_pse_pw_limit_range *pw_limit_ranges;
+       int i;
+@@ -146,7 +146,7 @@ static int pse_fill_reply(struct sk_buff
+                         const struct ethnl_reply_data *reply_base)
+ {
+       const struct pse_reply_data *data = PSE_REPDATA(reply_base);
+-      const struct pse_control_status *st = &data->status;
++      const struct ethtool_pse_control_status *st = &data->status;
+       if (st->podl_admin_state > 0 &&
+           nla_put_u32(skb, ETHTOOL_A_PODL_PSE_ADMIN_STATE,
diff --git a/target/linux/generic/backport-6.12/626-06-v6.13-net-pse-pd-Remove-is_enabled-callback-from-drivers.patch b/target/linux/generic/backport-6.12/626-06-v6.13-net-pse-pd-Remove-is_enabled-callback-from-drivers.patch
new file mode 100644 (file)
index 0000000..dd7a20e
--- /dev/null
@@ -0,0 +1,184 @@
+From 4640a1f0d8f2246f34d6e74330d7e7d2cf75605b Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:28 +0100
+Subject: [PATCH] net: pse-pd: Remove is_enabled callback from drivers
+
+The is_enabled callback is now redundant as the admin_state can be obtained
+directly from the driver and provides the same information.
+
+To simplify functionality, the core will handle this internally, making
+the is_enabled callback unnecessary at the driver level. Remove the
+callback from all drivers.
+
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c       | 26 --------------------------
+ drivers/net/pse-pd/pse_core.c      | 13 +++++++++++--
+ drivers/net/pse-pd/pse_regulator.c |  9 ---------
+ drivers/net/pse-pd/tps23881.c      | 28 ----------------------------
+ include/linux/pse-pd/pse.h         |  3 ---
+ 5 files changed, 11 insertions(+), 68 deletions(-)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -431,31 +431,6 @@ static int pd692x0_pi_disable(struct pse
+       return 0;
+ }
+-static int pd692x0_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
+-{
+-      struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+-      struct pd692x0_msg msg, buf = {0};
+-      int ret;
+-
+-      ret = pd692x0_fw_unavailable(priv);
+-      if (ret)
+-              return ret;
+-
+-      msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
+-      msg.sub[2] = id;
+-      ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+-      if (ret < 0)
+-              return ret;
+-
+-      if (buf.sub[1]) {
+-              priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
+-              return 1;
+-      } else {
+-              priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
+-              return 0;
+-      }
+-}
+-
+ struct pd692x0_pse_ext_state_mapping {
+       u32 status_code;
+       enum ethtool_c33_pse_ext_state pse_ext_state;
+@@ -1105,7 +1080,6 @@ static const struct pse_controller_ops p
+       .pi_get_actual_pw = pd692x0_pi_get_actual_pw,
+       .pi_enable = pd692x0_pi_enable,
+       .pi_disable = pd692x0_pi_disable,
+-      .pi_is_enabled = pd692x0_pi_is_enabled,
+       .pi_get_voltage = pd692x0_pi_get_voltage,
+       .pi_get_pw_limit = pd692x0_pi_get_pw_limit,
+       .pi_set_pw_limit = pd692x0_pi_set_pw_limit,
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -210,16 +210,25 @@ out:
+ static int pse_pi_is_enabled(struct regulator_dev *rdev)
+ {
+       struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
++      struct pse_admin_state admin_state = {0};
+       const struct pse_controller_ops *ops;
+       int id, ret;
+       ops = pcdev->ops;
+-      if (!ops->pi_is_enabled)
++      if (!ops->pi_get_admin_state)
+               return -EOPNOTSUPP;
+       id = rdev_get_id(rdev);
+       mutex_lock(&pcdev->lock);
+-      ret = ops->pi_is_enabled(pcdev, id);
++      ret = ops->pi_get_admin_state(pcdev, id, &admin_state);
++      if (ret)
++              goto out;
++
++      if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED ||
++          admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED)
++              ret = 1;
++
++out:
+       mutex_unlock(&pcdev->lock);
+       return ret;
+--- a/drivers/net/pse-pd/pse_regulator.c
++++ b/drivers/net/pse-pd/pse_regulator.c
+@@ -52,14 +52,6 @@ pse_reg_pi_disable(struct pse_controller
+ }
+ static int
+-pse_reg_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
+-{
+-      struct pse_reg_priv *priv = to_pse_reg(pcdev);
+-
+-      return regulator_is_enabled(priv->ps);
+-}
+-
+-static int
+ pse_reg_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
+                          struct pse_admin_state *admin_state)
+ {
+@@ -95,7 +87,6 @@ static const struct pse_controller_ops p
+       .pi_get_admin_state = pse_reg_pi_get_admin_state,
+       .pi_get_pw_status = pse_reg_pi_get_pw_status,
+       .pi_enable = pse_reg_pi_enable,
+-      .pi_is_enabled = pse_reg_pi_is_enabled,
+       .pi_disable = pse_reg_pi_disable,
+ };
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -174,33 +174,6 @@ static int tps23881_pi_disable(struct ps
+       return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
+ }
+-static int tps23881_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
+-{
+-      struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+-      struct i2c_client *client = priv->client;
+-      bool enabled;
+-      u8 chan;
+-      u16 val;
+-      int ret;
+-
+-      ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS);
+-      if (ret < 0)
+-              return ret;
+-
+-      chan = priv->port[id].chan[0];
+-      val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
+-      enabled = !!(val);
+-
+-      if (priv->port[id].is_4p) {
+-              chan = priv->port[id].chan[1];
+-              val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
+-              enabled &= !!(val);
+-      }
+-
+-      /* Return enabled status only if both channel are on this state */
+-      return enabled;
+-}
+-
+ static int
+ tps23881_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
+                           struct pse_admin_state *admin_state)
+@@ -690,7 +663,6 @@ static const struct pse_controller_ops t
+       .setup_pi_matrix = tps23881_setup_pi_matrix,
+       .pi_enable = tps23881_pi_enable,
+       .pi_disable = tps23881_pi_disable,
+-      .pi_is_enabled = tps23881_pi_is_enabled,
+       .pi_get_admin_state = tps23881_pi_get_admin_state,
+       .pi_get_pw_status = tps23881_pi_get_pw_status,
+ };
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -122,8 +122,6 @@ struct ethtool_pse_control_status {
+  * @pi_get_ext_state: Get the extended state of the PSE PI.
+  * @pi_get_pw_class: Get the power class of the PSE PI.
+  * @pi_get_actual_pw: Get actual power of the PSE PI in mW.
+- * @pi_is_enabled: Return 1 if the PSE PI is enabled, 0 if not.
+- *               May also return negative errno.
+  * @pi_enable: Configure the PSE PI as enabled.
+  * @pi_disable: Configure the PSE PI as disabled.
+  * @pi_get_voltage: Return voltage similarly to get_voltage regulator
+@@ -145,7 +143,6 @@ struct pse_controller_ops {
+                               struct pse_ext_state_info *ext_state_info);
+       int (*pi_get_pw_class)(struct pse_controller_dev *pcdev, int id);
+       int (*pi_get_actual_pw)(struct pse_controller_dev *pcdev, int id);
+-      int (*pi_is_enabled)(struct pse_controller_dev *pcdev, int id);
+       int (*pi_enable)(struct pse_controller_dev *pcdev, int id);
+       int (*pi_disable)(struct pse_controller_dev *pcdev, int id);
+       int (*pi_get_voltage)(struct pse_controller_dev *pcdev, int id);
diff --git a/target/linux/generic/backport-6.12/626-07-v6.13-net-pse-pd-tps23881-Add-power-limit-and-measurement-features.patch b/target/linux/generic/backport-6.12/626-07-v6.13-net-pse-pd-tps23881-Add-power-limit-and-measurement-features.patch
new file mode 100644 (file)
index 0000000..de3cc76
--- /dev/null
@@ -0,0 +1,337 @@
+From 7f076ce3f17334964590c2cce49a02c0851c099a Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:29 +0100
+Subject: [PATCH] net: pse-pd: tps23881: Add support for power limit and
+ measurement features
+
+Expand PSE callbacks to support the newly introduced
+pi_get/set_pw_limit() and pi_get_voltage() functions. These callbacks
+allow for power limit configuration in the TPS23881 controller.
+
+Additionally, the patch includes the pi_get_pw_class() the
+pi_get_actual_pw(), and the pi_get_pw_limit_ranges') callbacks providing
+more comprehensive PoE status reporting.
+
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 258 +++++++++++++++++++++++++++++++++-
+ 1 file changed, 256 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -25,20 +25,32 @@
+ #define TPS23881_REG_GEN_MASK 0x17
+ #define TPS23881_REG_NBITACC  BIT(5)
+ #define TPS23881_REG_PW_EN    0x19
++#define TPS23881_REG_2PAIR_POL1       0x1e
+ #define TPS23881_REG_PORT_MAP 0x26
+ #define TPS23881_REG_PORT_POWER       0x29
+-#define TPS23881_REG_POEPLUS  0x40
++#define TPS23881_REG_4PAIR_POL1       0x2a
++#define TPS23881_REG_INPUT_V  0x2e
++#define TPS23881_REG_CHAN1_A  0x30
++#define TPS23881_REG_CHAN1_V  0x32
++#define TPS23881_REG_FOLDBACK 0x40
+ #define TPS23881_REG_TPON     BIT(0)
+ #define TPS23881_REG_FWREV    0x41
+ #define TPS23881_REG_DEVID    0x43
+ #define TPS23881_REG_DEVID_MASK       0xF0
+ #define TPS23881_DEVICE_ID    0x02
++#define TPS23881_REG_CHAN1_CLASS      0x4c
+ #define TPS23881_REG_SRAM_CTRL        0x60
+ #define TPS23881_REG_SRAM_DATA        0x61
++#define TPS23881_UV_STEP      3662
++#define TPS23881_NA_STEP      70190
++#define TPS23881_MW_STEP      500
++#define TPS23881_MIN_PI_PW_LIMIT_MW   2000
++
+ struct tps23881_port_desc {
+       u8 chan[2];
+       bool is_4p;
++      int pw_pol;
+ };
+ struct tps23881_priv {
+@@ -102,6 +114,54 @@ static u16 tps23881_set_val(u16 reg_val,
+       return reg_val;
+ }
++static int
++tps23881_pi_set_pw_pol_limit(struct tps23881_priv *priv, int id, u8 pw_pol,
++                           bool is_4p)
++{
++      struct i2c_client *client = priv->client;
++      int ret, reg;
++      u16 val;
++      u8 chan;
++
++      chan = priv->port[id].chan[0];
++      if (!is_4p) {
++              reg = TPS23881_REG_2PAIR_POL1 + (chan % 4);
++      } else {
++              /* One chan is enough to configure the 4p PI power limit */
++              if ((chan % 4) < 2)
++                      reg = TPS23881_REG_4PAIR_POL1;
++              else
++                      reg = TPS23881_REG_4PAIR_POL1 + 1;
++      }
++
++      ret = i2c_smbus_read_word_data(client, reg);
++      if (ret < 0)
++              return ret;
++
++      val = tps23881_set_val(ret, chan, 0, 0xff, pw_pol);
++      return i2c_smbus_write_word_data(client, reg, val);
++}
++
++static int tps23881_pi_enable_manual_pol(struct tps23881_priv *priv, int id)
++{
++      struct i2c_client *client = priv->client;
++      int ret;
++      u8 chan;
++      u16 val;
++
++      ret = i2c_smbus_read_byte_data(client, TPS23881_REG_FOLDBACK);
++      if (ret < 0)
++              return ret;
++
++      /* No need to test if the chan is PoE4 as setting either bit for a
++       * 4P configured port disables the automatic configuration on both
++       * channels.
++       */
++      chan = priv->port[id].chan[0];
++      val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4));
++      return i2c_smbus_write_byte_data(client, TPS23881_REG_FOLDBACK, val);
++}
++
+ static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id)
+ {
+       struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+@@ -171,7 +231,21 @@ static int tps23881_pi_disable(struct ps
+                                      BIT(chan % 4));
+       }
+-      return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
++      ret = i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
++      if (ret)
++              return ret;
++
++      /* No power policy */
++      if (priv->port[id].pw_pol < 0)
++              return 0;
++
++      ret = tps23881_pi_enable_manual_pol(priv, id);
++      if (ret < 0)
++              return ret;
++
++      /* Set power policy */
++      return tps23881_pi_set_pw_pol_limit(priv, id, priv->port[id].pw_pol,
++                                          priv->port[id].is_4p);
+ }
+ static int
+@@ -246,6 +320,177 @@ tps23881_pi_get_pw_status(struct pse_con
+       return 0;
+ }
++static int tps23881_pi_get_voltage(struct pse_controller_dev *pcdev, int id)
++{
++      struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++      struct i2c_client *client = priv->client;
++      int ret;
++      u64 uV;
++
++      ret = i2c_smbus_read_word_data(client, TPS23881_REG_INPUT_V);
++      if (ret < 0)
++              return ret;
++
++      uV = ret & 0x3fff;
++      uV *= TPS23881_UV_STEP;
++
++      return (int)uV;
++}
++
++static int
++tps23881_pi_get_chan_current(struct tps23881_priv *priv, u8 chan)
++{
++      struct i2c_client *client = priv->client;
++      int reg, ret;
++      u64 tmp_64;
++
++      /* Registers 0x30 to 0x3d */
++      reg = TPS23881_REG_CHAN1_A + (chan % 4) * 4 + (chan >= 4);
++      ret = i2c_smbus_read_word_data(client, reg);
++      if (ret < 0)
++              return ret;
++
++      tmp_64 = ret & 0x3fff;
++      tmp_64 *= TPS23881_NA_STEP;
++      /* uA = nA / 1000 */
++      tmp_64 = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000);
++      return (int)tmp_64;
++}
++
++static int tps23881_pi_get_pw_class(struct pse_controller_dev *pcdev,
++                                  int id)
++{
++      struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++      struct i2c_client *client = priv->client;
++      int ret, reg;
++      u8 chan;
++
++      chan = priv->port[id].chan[0];
++      reg = TPS23881_REG_CHAN1_CLASS + (chan % 4);
++      ret = i2c_smbus_read_word_data(client, reg);
++      if (ret < 0)
++              return ret;
++
++      return tps23881_calc_val(ret, chan, 4, 0x0f);
++}
++
++static int
++tps23881_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
++{
++      struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++      int ret, uV, uA;
++      u64 tmp_64;
++      u8 chan;
++
++      ret = tps23881_pi_get_voltage(&priv->pcdev, id);
++      if (ret < 0)
++              return ret;
++      uV = ret;
++
++      chan = priv->port[id].chan[0];
++      ret = tps23881_pi_get_chan_current(priv, chan);
++      if (ret < 0)
++              return ret;
++      uA = ret;
++
++      if (priv->port[id].is_4p) {
++              chan = priv->port[id].chan[1];
++              ret = tps23881_pi_get_chan_current(priv, chan);
++              if (ret < 0)
++                      return ret;
++              uA += ret;
++      }
++
++      tmp_64 = uV;
++      tmp_64 *= uA;
++      /* mW = uV * uA / 1000000000 */
++      return DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000);
++}
++
++static int
++tps23881_pi_get_pw_limit_chan(struct tps23881_priv *priv, u8 chan)
++{
++      struct i2c_client *client = priv->client;
++      int ret, reg;
++      u16 val;
++
++      reg = TPS23881_REG_2PAIR_POL1 + (chan % 4);
++      ret = i2c_smbus_read_word_data(client, reg);
++      if (ret < 0)
++              return ret;
++
++      val = tps23881_calc_val(ret, chan, 0, 0xff);
++      return val * TPS23881_MW_STEP;
++}
++
++static int tps23881_pi_get_pw_limit(struct pse_controller_dev *pcdev, int id)
++{
++      struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++      int ret, mW;
++      u8 chan;
++
++      chan = priv->port[id].chan[0];
++      ret = tps23881_pi_get_pw_limit_chan(priv, chan);
++      if (ret < 0)
++              return ret;
++
++      mW = ret;
++      if (priv->port[id].is_4p) {
++              chan = priv->port[id].chan[1];
++              ret = tps23881_pi_get_pw_limit_chan(priv, chan);
++              if (ret < 0)
++                      return ret;
++              mW += ret;
++      }
++
++      return mW;
++}
++
++static int tps23881_pi_set_pw_limit(struct pse_controller_dev *pcdev,
++                                  int id, int max_mW)
++{
++      struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++      u8 pw_pol;
++      int ret;
++
++      if (max_mW < TPS23881_MIN_PI_PW_LIMIT_MW || MAX_PI_PW < max_mW) {
++              dev_err(&priv->client->dev,
++                      "power limit %d out of ranges [%d,%d]",
++                      max_mW, TPS23881_MIN_PI_PW_LIMIT_MW, MAX_PI_PW);
++              return -ERANGE;
++      }
++
++      ret = tps23881_pi_enable_manual_pol(priv, id);
++      if (ret < 0)
++              return ret;
++
++      pw_pol = DIV_ROUND_CLOSEST_ULL(max_mW, TPS23881_MW_STEP);
++
++      /* Save power policy to reconfigure it after a disabled call */
++      priv->port[id].pw_pol = pw_pol;
++      return tps23881_pi_set_pw_pol_limit(priv, id, pw_pol,
++                                          priv->port[id].is_4p);
++}
++
++static int
++tps23881_pi_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id,
++                              struct pse_pw_limit_ranges *pw_limit_ranges)
++{
++      struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges;
++
++      c33_pw_limit_ranges = kzalloc(sizeof(*c33_pw_limit_ranges),
++                                    GFP_KERNEL);
++      if (!c33_pw_limit_ranges)
++              return -ENOMEM;
++
++      c33_pw_limit_ranges->min = TPS23881_MIN_PI_PW_LIMIT_MW;
++      c33_pw_limit_ranges->max = MAX_PI_PW;
++      pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges;
++
++      /* Return the number of ranges */
++      return 1;
++}
++
+ /* Parse managers subnode into a array of device node */
+ static int
+ tps23881_get_of_channels(struct tps23881_priv *priv,
+@@ -540,6 +785,9 @@ tps23881_write_port_matrix(struct tps238
+               if (port_matrix[i].exist)
+                       priv->port[pi_id].chan[0] = lgcl_chan;
++              /* Initialize power policy internal value */
++              priv->port[pi_id].pw_pol = -1;
++
+               /* Set hardware port matrix for all ports */
+               val |= hw_chan << (lgcl_chan * 2);
+@@ -665,6 +913,12 @@ static const struct pse_controller_ops t
+       .pi_disable = tps23881_pi_disable,
+       .pi_get_admin_state = tps23881_pi_get_admin_state,
+       .pi_get_pw_status = tps23881_pi_get_pw_status,
++      .pi_get_pw_class = tps23881_pi_get_pw_class,
++      .pi_get_actual_pw = tps23881_pi_get_actual_pw,
++      .pi_get_voltage = tps23881_pi_get_voltage,
++      .pi_get_pw_limit = tps23881_pi_get_pw_limit,
++      .pi_set_pw_limit = tps23881_pi_set_pw_limit,
++      .pi_get_pw_limit_ranges = tps23881_pi_get_pw_limit_ranges,
+ };
+ static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin";
diff --git a/target/linux/generic/backport-6.12/626-08-v6.13-net-pse-pd-Fix-missing-PI-of_node-description.patch b/target/linux/generic/backport-6.12/626-08-v6.13-net-pse-pd-Fix-missing-PI-of_node-description.patch
new file mode 100644 (file)
index 0000000..3aa46cd
--- /dev/null
@@ -0,0 +1,28 @@
+From 10276f3e1c7e7f5de9f0bba58f8a849cb195253d Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:30 +0100
+Subject: [PATCH] net: pse-pd: Fix missing PI of_node description
+
+The PI of_node was not assigned in the regulator_config structure, leading
+to failures in resolving the correct supply when different power supplies
+are assigned to multiple PIs of a PSE controller. This fix ensures that the
+of_node is properly set in the regulator_config, allowing accurate supply
+resolution for each PI.
+
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pse_core.c | 1 +
+ 1 file changed, 1 insertion(+)
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -422,6 +422,7 @@ devm_pse_pi_regulator_register(struct ps
+       rconfig.dev = pcdev->dev;
+       rconfig.driver_data = pcdev;
+       rconfig.init_data = rinit_data;
++      rconfig.of_node = pcdev->pi[id].np;
+       rdev = devm_regulator_register(pcdev->dev, rdesc, &rconfig);
+       if (IS_ERR(rdev)) {
diff --git a/target/linux/generic/backport-6.12/626-09-v6.13-net-pse-pd-Clean-ethtool-header-of-PSE-structures.patch b/target/linux/generic/backport-6.12/626-09-v6.13-net-pse-pd-Clean-ethtool-header-of-PSE-structures.patch
new file mode 100644 (file)
index 0000000..7d33812
--- /dev/null
@@ -0,0 +1,92 @@
+From 5385f1e1923ca8131eb143567d509b101a344e06 Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:31 +0100
+Subject: [PATCH] net: pse-pd: Clean ethtool header of PSE structures
+
+Remove PSE-specific structures from the ethtool header to improve code
+modularity, maintain independent headers, and reduce incremental build
+time.
+
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pse_core.c |  1 +
+ include/linux/ethtool.h       | 20 --------------------
+ include/linux/pse-pd/pse.h    | 22 +++++++++++++++++++++-
+ 3 files changed, 22 insertions(+), 21 deletions(-)
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -6,6 +6,7 @@
+ //
+ #include <linux/device.h>
++#include <linux/ethtool.h>
+ #include <linux/of.h>
+ #include <linux/pse-pd/pse.h>
+ #include <linux/regulator/driver.h>
+--- a/include/linux/ethtool.h
++++ b/include/linux/ethtool.h
+@@ -1322,24 +1322,4 @@ struct ethtool_forced_speed_map {
+ void
+ ethtool_forced_speed_maps_init(struct ethtool_forced_speed_map *maps, u32 size);
+-
+-/* C33 PSE extended state and substate. */
+-struct ethtool_c33_pse_ext_state_info {
+-      enum ethtool_c33_pse_ext_state c33_pse_ext_state;
+-      union {
+-              enum ethtool_c33_pse_ext_substate_error_condition error_condition;
+-              enum ethtool_c33_pse_ext_substate_mr_pse_enable mr_pse_enable;
+-              enum ethtool_c33_pse_ext_substate_option_detect_ted option_detect_ted;
+-              enum ethtool_c33_pse_ext_substate_option_vport_lim option_vport_lim;
+-              enum ethtool_c33_pse_ext_substate_ovld_detected ovld_detected;
+-              enum ethtool_c33_pse_ext_substate_power_not_available power_not_available;
+-              enum ethtool_c33_pse_ext_substate_short_detected short_detected;
+-              u32 __c33_pse_ext_substate;
+-      };
+-};
+-
+-struct ethtool_c33_pse_pw_limit_range {
+-      u32 min;
+-      u32 max;
+-};
+ #endif /* _LINUX_ETHTOOL_H */
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -5,7 +5,6 @@
+ #ifndef _LINUX_PSE_CONTROLLER_H
+ #define _LINUX_PSE_CONTROLLER_H
+-#include <linux/ethtool.h>
+ #include <linux/list.h>
+ #include <uapi/linux/ethtool.h>
+@@ -16,6 +15,27 @@
+ struct phy_device;
+ struct pse_controller_dev;
++struct netlink_ext_ack;
++
++/* C33 PSE extended state and substate. */
++struct ethtool_c33_pse_ext_state_info {
++      enum ethtool_c33_pse_ext_state c33_pse_ext_state;
++      union {
++              enum ethtool_c33_pse_ext_substate_error_condition error_condition;
++              enum ethtool_c33_pse_ext_substate_mr_pse_enable mr_pse_enable;
++              enum ethtool_c33_pse_ext_substate_option_detect_ted option_detect_ted;
++              enum ethtool_c33_pse_ext_substate_option_vport_lim option_vport_lim;
++              enum ethtool_c33_pse_ext_substate_ovld_detected ovld_detected;
++              enum ethtool_c33_pse_ext_substate_power_not_available power_not_available;
++              enum ethtool_c33_pse_ext_substate_short_detected short_detected;
++              u32 __c33_pse_ext_substate;
++      };
++};
++
++struct ethtool_c33_pse_pw_limit_range {
++      u32 min;
++      u32 max;
++};
+ /**
+  * struct pse_control_config - PSE control/channel configuration.
diff --git a/target/linux/generic/backport-6.12/626-10-v6.17-net-pse-pd-Introduce-attached_phydev-to-pse-control.patch b/target/linux/generic/backport-6.12/626-10-v6.17-net-pse-pd-Introduce-attached_phydev-to-pse-control.patch
new file mode 100644 (file)
index 0000000..b00c0e1
--- /dev/null
@@ -0,0 +1,171 @@
+From fa2f0454174c2f33005f5a6e6f70c7160a15b2a1 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:00 +0200
+Subject: [PATCH] net: pse-pd: Introduce attached_phydev to pse control
+
+In preparation for reporting PSE events via ethtool notifications,
+introduce an attached_phydev field in the pse_control structure.
+This field stores the phy_device associated with the PSE PI,
+ensuring that notifications are sent to the correct network
+interface.
+
+The attached_phydev pointer is directly tied to the PHY lifecycle. It
+is set when the PHY is registered and cleared when the PHY is removed.
+There is no need to use a refcount, as doing so could interfere with
+the PHY removal process.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-1-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/mdio/fwnode_mdio.c | 26 ++++++++++++++------------
+ drivers/net/pse-pd/pse_core.c  | 11 ++++++++---
+ include/linux/pse-pd/pse.h     |  6 ++++--
+ 3 files changed, 26 insertions(+), 17 deletions(-)
+--- a/drivers/net/mdio/fwnode_mdio.c
++++ b/drivers/net/mdio/fwnode_mdio.c
+@@ -18,7 +18,8 @@ MODULE_LICENSE("GPL");
+ MODULE_DESCRIPTION("FWNODE MDIO bus (Ethernet PHY) accessors");
+ static struct pse_control *
+-fwnode_find_pse_control(struct fwnode_handle *fwnode)
++fwnode_find_pse_control(struct fwnode_handle *fwnode,
++                      struct phy_device *phydev)
+ {
+       struct pse_control *psec;
+       struct device_node *np;
+@@ -30,7 +31,7 @@ fwnode_find_pse_control(struct fwnode_ha
+       if (!np)
+               return NULL;
+-      psec = of_pse_control_get(np);
++      psec = of_pse_control_get(np, phydev);
+       if (PTR_ERR(psec) == -ENOENT)
+               return NULL;
+@@ -128,15 +129,9 @@ int fwnode_mdiobus_register_phy(struct m
+       u32 phy_id;
+       int rc;
+-      psec = fwnode_find_pse_control(child);
+-      if (IS_ERR(psec))
+-              return PTR_ERR(psec);
+-
+       mii_ts = fwnode_find_mii_timestamper(child);
+-      if (IS_ERR(mii_ts)) {
+-              rc = PTR_ERR(mii_ts);
+-              goto clean_pse;
+-      }
++      if (IS_ERR(mii_ts))
++              return PTR_ERR(mii_ts);
+       is_c45 = fwnode_device_is_compatible(child, "ethernet-phy-ieee802.3-c45");
+       if (is_c45 || fwnode_get_phy_id(child, &phy_id))
+@@ -169,6 +164,12 @@ int fwnode_mdiobus_register_phy(struct m
+                       goto clean_phy;
+       }
++      psec = fwnode_find_pse_control(child, phy);
++      if (IS_ERR(psec)) {
++              rc = PTR_ERR(psec);
++              goto unregister_phy;
++      }
++
+       phy->psec = psec;
+       /* phy->mii_ts may already be defined by the PHY driver. A
+@@ -180,12 +181,13 @@ int fwnode_mdiobus_register_phy(struct m
+       return 0;
++unregister_phy:
++      if (is_acpi_node(child) || is_of_node(child))
++              phy_device_remove(phy);
+ clean_phy:
+       phy_device_free(phy);
+ clean_mii_ts:
+       unregister_mii_timestamper(mii_ts);
+-clean_pse:
+-      pse_control_put(psec);
+       return rc;
+ }
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -23,6 +23,7 @@ static LIST_HEAD(pse_controller_list);
+  * @list: list entry for the pcdev's PSE controller list
+  * @id: ID of the PSE line in the PSE controller device
+  * @refcnt: Number of gets of this pse_control
++ * @attached_phydev: PHY device pointer attached by the PSE control
+  */
+ struct pse_control {
+       struct pse_controller_dev *pcdev;
+@@ -30,6 +31,7 @@ struct pse_control {
+       struct list_head list;
+       unsigned int id;
+       struct kref refcnt;
++      struct phy_device *attached_phydev;
+ };
+ static int of_load_single_pse_pi_pairset(struct device_node *node,
+@@ -599,7 +601,8 @@ void pse_control_put(struct pse_control
+ EXPORT_SYMBOL_GPL(pse_control_put);
+ static struct pse_control *
+-pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index)
++pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index,
++                       struct phy_device *phydev)
+ {
+       struct pse_control *psec;
+       int ret;
+@@ -638,6 +641,7 @@ pse_control_get_internal(struct pse_cont
+       psec->pcdev = pcdev;
+       list_add(&psec->list, &pcdev->pse_control_head);
+       psec->id = index;
++      psec->attached_phydev = phydev;
+       kref_init(&psec->refcnt);
+       return psec;
+@@ -693,7 +697,8 @@ static int psec_id_xlate(struct pse_cont
+       return pse_spec->args[0];
+ }
+-struct pse_control *of_pse_control_get(struct device_node *node)
++struct pse_control *of_pse_control_get(struct device_node *node,
++                                     struct phy_device *phydev)
+ {
+       struct pse_controller_dev *r, *pcdev;
+       struct of_phandle_args args;
+@@ -743,7 +748,7 @@ struct pse_control *of_pse_control_get(s
+       }
+       /* pse_list_mutex also protects the pcdev's pse_control list */
+-      psec = pse_control_get_internal(pcdev, psec_id);
++      psec = pse_control_get_internal(pcdev, psec_id, phydev);
+ out:
+       mutex_unlock(&pse_list_mutex);
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -250,7 +250,8 @@ struct device;
+ int devm_pse_controller_register(struct device *dev,
+                                struct pse_controller_dev *pcdev);
+-struct pse_control *of_pse_control_get(struct device_node *node);
++struct pse_control *of_pse_control_get(struct device_node *node,
++                                     struct phy_device *phydev);
+ void pse_control_put(struct pse_control *psec);
+ int pse_ethtool_get_status(struct pse_control *psec,
+@@ -270,7 +271,8 @@ bool pse_has_c33(struct pse_control *pse
+ #else
+-static inline struct pse_control *of_pse_control_get(struct device_node *node)
++static inline struct pse_control *of_pse_control_get(struct device_node *node,
++                                                   struct phy_device *phydev)
+ {
+       return ERR_PTR(-ENOENT);
+ }
diff --git a/target/linux/generic/backport-6.12/626-11-v6.17-net-pse-pd-Add-support-for-reporting-events.patch b/target/linux/generic/backport-6.12/626-11-v6.17-net-pse-pd-Add-support-for-reporting-events.patch
new file mode 100644 (file)
index 0000000..ec99fca
--- /dev/null
@@ -0,0 +1,363 @@
+# ADAPTED FOR OPENWRT 6.12.67 - Documentation changes removed
+# Original commit: fc0e6db30941
+From fc0e6db30941a66e284b8516b82356f97f31061d Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:01 +0200
+Subject: [PATCH] net: pse-pd: Add support for reporting events
+
+Add support for devm_pse_irq_helper() to register PSE interrupts and report
+events such as over-current or over-temperature conditions. This follows a
+similar approach to the regulator API but also sends notifications using a
+dedicated PSE ethtool netlink socket.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent@bootlin.com>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-2-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ Documentation/netlink/specs/ethtool.yaml      |  34 ++++
+ Documentation/networking/ethtool-netlink.rst  |  19 ++
+ drivers/net/pse-pd/pse_core.c                 | 179 ++++++++++++++++++
+ include/linux/ethtool_netlink.h               |   7 +
+ include/linux/pse-pd/pse.h                    |  20 ++
+ .../uapi/linux/ethtool_netlink_generated.h    |  19 ++
+ net/ethtool/pse-pd.c                          |  39 ++++
+ 7 files changed, 317 insertions(+)
+
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -7,10 +7,14 @@
+ #include <linux/device.h>
+ #include <linux/ethtool.h>
++#include <linux/ethtool_netlink.h>
+ #include <linux/of.h>
++#include <linux/phy.h>
+ #include <linux/pse-pd/pse.h>
+ #include <linux/regulator/driver.h>
+ #include <linux/regulator/machine.h>
++#include <linux/rtnetlink.h>
++#include <net/net_trackers.h>
+ static DEFINE_MUTEX(pse_list_mutex);
+ static LIST_HEAD(pse_controller_list);
+@@ -210,6 +214,48 @@ out:
+       return ret;
+ }
++/**
++ * pse_control_find_net_by_id - Find net attached to the pse control id
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE control
++ *
++ * Return: pse_control pointer or NULL. The device returned has had a
++ *       reference added and the pointer is safe until the user calls
++ *       pse_control_put() to indicate they have finished with it.
++ */
++static struct pse_control *
++pse_control_find_by_id(struct pse_controller_dev *pcdev, int id)
++{
++      struct pse_control *psec;
++
++      mutex_lock(&pse_list_mutex);
++      list_for_each_entry(psec, &pcdev->pse_control_head, list) {
++              if (psec->id == id) {
++                      kref_get(&psec->refcnt);
++                      mutex_unlock(&pse_list_mutex);
++                      return psec;
++              }
++      }
++      mutex_unlock(&pse_list_mutex);
++      return NULL;
++}
++
++/**
++ * pse_control_get_netdev - Return netdev associated to a PSE control
++ * @psec: PSE control pointer
++ *
++ * Return: netdev pointer or NULL
++ */
++static struct net_device *pse_control_get_netdev(struct pse_control *psec)
++{
++      ASSERT_RTNL();
++
++      if (!psec || !psec->attached_phydev)
++              return NULL;
++
++      return psec->attached_phydev->attached_dev;
++}
++
+ static int pse_pi_is_enabled(struct regulator_dev *rdev)
+ {
+       struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+@@ -559,6 +605,139 @@ int devm_pse_controller_register(struct
+ }
+ EXPORT_SYMBOL_GPL(devm_pse_controller_register);
++struct pse_irq {
++      struct pse_controller_dev *pcdev;
++      struct pse_irq_desc desc;
++      unsigned long *notifs;
++};
++
++/**
++ * pse_to_regulator_notifs - Convert PSE notifications to Regulator
++ *                         notifications
++ * @notifs: PSE notifications
++ *
++ * Return: Regulator notifications
++ */
++static unsigned long pse_to_regulator_notifs(unsigned long notifs)
++{
++      unsigned long rnotifs = 0;
++
++      if (notifs & ETHTOOL_PSE_EVENT_OVER_CURRENT)
++              rnotifs |= REGULATOR_EVENT_OVER_CURRENT;
++      if (notifs & ETHTOOL_PSE_EVENT_OVER_TEMP)
++              rnotifs |= REGULATOR_EVENT_OVER_TEMP;
++
++      return rnotifs;
++}
++
++/**
++ * pse_isr - IRQ handler for PSE
++ * @irq: irq number
++ * @data: pointer to user interrupt structure
++ *
++ * Return: irqreturn_t - status of IRQ
++ */
++static irqreturn_t pse_isr(int irq, void *data)
++{
++      struct pse_controller_dev *pcdev;
++      unsigned long notifs_mask = 0;
++      struct pse_irq_desc *desc;
++      struct pse_irq *h = data;
++      int ret, i;
++
++      desc = &h->desc;
++      pcdev = h->pcdev;
++
++      /* Clear notifs mask */
++      memset(h->notifs, 0, pcdev->nr_lines * sizeof(*h->notifs));
++      mutex_lock(&pcdev->lock);
++      ret = desc->map_event(irq, pcdev, h->notifs, &notifs_mask);
++      mutex_unlock(&pcdev->lock);
++      if (ret || !notifs_mask)
++              return IRQ_NONE;
++
++      for_each_set_bit(i, &notifs_mask, pcdev->nr_lines) {
++              unsigned long notifs, rnotifs;
++              struct net_device *netdev;
++              struct pse_control *psec;
++
++              /* Do nothing PI not described */
++              if (!pcdev->pi[i].rdev)
++                      continue;
++
++              notifs = h->notifs[i];
++              dev_dbg(h->pcdev->dev,
++                      "Sending PSE notification EVT 0x%lx\n", notifs);
++
++              psec = pse_control_find_by_id(pcdev, i);
++              rtnl_lock();
++              netdev = pse_control_get_netdev(psec);
++              if (netdev)
++                      ethnl_pse_send_ntf(netdev, notifs);
++              rtnl_unlock();
++              pse_control_put(psec);
++
++              rnotifs = pse_to_regulator_notifs(notifs);
++              regulator_notifier_call_chain(pcdev->pi[i].rdev, rnotifs,
++                                            NULL);
++      }
++
++      return IRQ_HANDLED;
++}
++
++/**
++ * devm_pse_irq_helper - Register IRQ based PSE event notifier
++ * @pcdev: a pointer to the PSE
++ * @irq: the irq value to be passed to request_irq
++ * @irq_flags: the flags to be passed to request_irq
++ * @d: PSE interrupt description
++ *
++ * Return: 0 on success and errno on failure
++ */
++int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq,
++                      int irq_flags, const struct pse_irq_desc *d)
++{
++      struct device *dev = pcdev->dev;
++      size_t irq_name_len;
++      struct pse_irq *h;
++      char *irq_name;
++      int ret;
++
++      if (!d || !d->map_event || !d->name)
++              return -EINVAL;
++
++      h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL);
++      if (!h)
++              return -ENOMEM;
++
++      h->pcdev = pcdev;
++      h->desc = *d;
++
++      /* IRQ name len is pcdev dev name + 5 char + irq desc name + 1 */
++      irq_name_len = strlen(dev_name(pcdev->dev)) + 5 + strlen(d->name) + 1;
++      irq_name = devm_kzalloc(dev, irq_name_len, GFP_KERNEL);
++      if (!irq_name)
++              return -ENOMEM;
++
++      snprintf(irq_name, irq_name_len, "pse-%s:%s", dev_name(pcdev->dev),
++               d->name);
++
++      h->notifs = devm_kcalloc(dev, pcdev->nr_lines,
++                               sizeof(*h->notifs), GFP_KERNEL);
++      if (!h->notifs)
++              return -ENOMEM;
++
++      ret = devm_request_threaded_irq(dev, irq, NULL, pse_isr,
++                                      IRQF_ONESHOT | irq_flags,
++                                      irq_name, h);
++      if (ret)
++              dev_err(pcdev->dev, "Failed to request IRQ %d\n", irq);
++
++      pcdev->irq = irq;
++      return ret;
++}
++EXPORT_SYMBOL_GPL(devm_pse_irq_helper);
++
+ /* PSE control section */
+ static void __pse_control_release(struct kref *kref)
+--- a/include/linux/ethtool_netlink.h
++++ b/include/linux/ethtool_netlink.h
+@@ -43,6 +43,8 @@ void ethtool_aggregate_rmon_stats(struct
+                                 struct ethtool_rmon_stats *rmon_stats);
+ bool ethtool_dev_mm_supported(struct net_device *dev);
++void ethnl_pse_send_ntf(struct net_device *netdev, unsigned long notif);
++
+ #else
+ static inline int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd)
+ {
+@@ -120,6 +122,11 @@ static inline bool ethtool_dev_mm_suppor
+       return false;
+ }
++static inline void ethnl_pse_send_ntf(struct phy_device *phydev,
++                                    unsigned long notif)
++{
++}
++
+ #endif /* IS_ENABLED(CONFIG_ETHTOOL_NETLINK) */
+ static inline int ethnl_cable_test_result(struct phy_device *phydev, u8 pair,
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -7,12 +7,15 @@
+ #include <linux/list.h>
+ #include <uapi/linux/ethtool.h>
++#include <uapi/linux/ethtool_netlink_generated.h>
++#include <linux/regulator/driver.h>
+ /* Maximum current in uA according to IEEE 802.3-2022 Table 145-1 */
+ #define MAX_PI_CURRENT 1920000
+ /* Maximum power in mW according to IEEE 802.3-2022 Table 145-16 */
+ #define MAX_PI_PW 99900
++struct net_device;
+ struct phy_device;
+ struct pse_controller_dev;
+ struct netlink_ext_ack;
+@@ -38,6 +41,19 @@ struct ethtool_c33_pse_pw_limit_range {
+ };
+ /**
++ * struct pse_irq_desc - notification sender description for IRQ based events.
++ *
++ * @name: the visible name for the IRQ
++ * @map_event: driver callback to map IRQ status into PSE devices with events.
++ */
++struct pse_irq_desc {
++      const char *name;
++      int (*map_event)(int irq, struct pse_controller_dev *pcdev,
++                       unsigned long *notifs,
++                       unsigned long *notifs_mask);
++};
++
++/**
+  * struct pse_control_config - PSE control/channel configuration.
+  *
+  * @podl_admin_control: set PoDL PSE admin control as described in
+@@ -228,6 +244,7 @@ struct pse_pi {
+  * @types: types of the PSE controller
+  * @pi: table of PSE PIs described in this controller device
+  * @no_of_pse_pi: flag set if the pse_pis devicetree node is not used
++ * @irq: PSE interrupt
+  */
+ struct pse_controller_dev {
+       const struct pse_controller_ops *ops;
+@@ -241,6 +258,7 @@ struct pse_controller_dev {
+       enum ethtool_pse_types types;
+       struct pse_pi *pi;
+       bool no_of_pse_pi;
++      int irq;
+ };
+ #if IS_ENABLED(CONFIG_PSE_CONTROLLER)
+@@ -249,6 +267,8 @@ void pse_controller_unregister(struct ps
+ struct device;
+ int devm_pse_controller_register(struct device *dev,
+                                struct pse_controller_dev *pcdev);
++int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq,
++                      int irq_flags, const struct pse_irq_desc *d);
+ struct pse_control *of_pse_control_get(struct device_node *node,
+                                      struct phy_device *phydev);
+--- a/net/ethtool/pse-pd.c
++++ b/net/ethtool/pse-pd.c
+@@ -315,3 +315,42 @@ const struct ethnl_request_ops ethnl_pse
+       .set                    = ethnl_set_pse,
+       /* PSE has no notification */
+ };
++
++void ethnl_pse_send_ntf(struct net_device *netdev, unsigned long notifs)
++{
++      void *reply_payload;
++      struct sk_buff *skb;
++      int reply_len;
++      int ret;
++
++      ASSERT_RTNL();
++
++      if (!netdev || !notifs)
++              return;
++
++      reply_len = ethnl_reply_header_size() +
++                  nla_total_size(sizeof(u32)); /* _PSE_NTF_EVENTS */
++
++      skb = genlmsg_new(reply_len, GFP_KERNEL);
++      if (!skb)
++              return;
++
++      reply_payload = ethnl_bcastmsg_put(skb, ETHTOOL_MSG_PSE_NTF);
++      if (!reply_payload)
++              goto err_skb;
++
++      ret = ethnl_fill_reply_header(skb, netdev, ETHTOOL_A_PSE_NTF_HEADER);
++      if (ret < 0)
++              goto err_skb;
++
++      if (nla_put_uint(skb, ETHTOOL_A_PSE_NTF_EVENTS, notifs))
++              goto err_skb;
++
++      genlmsg_end(skb, reply_payload);
++      ethnl_multicast(skb, netdev);
++      return;
++
++err_skb:
++      nlmsg_free(skb);
++}
++EXPORT_SYMBOL_GPL(ethnl_pse_send_ntf);
diff --git a/target/linux/generic/backport-6.12/626-12-v6.17-net-pse-pd-tps23881-Add-support-for-PSE-events.patch b/target/linux/generic/backport-6.12/626-12-v6.17-net-pse-pd-tps23881-Add-support-for-PSE-events.patch
new file mode 100644 (file)
index 0000000..8afcb3f
--- /dev/null
@@ -0,0 +1,252 @@
+From f5e7aecaa4efcd4c85477b6a62f94fea668031db Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:02 +0200
+Subject: [PATCH] net: pse-pd: tps23881: Add support for PSE events and
+ interrupts
+
+Add support for PSE event reporting through interrupts. Set up the newly
+introduced devm_pse_irq_helper helper to register the interrupt. Events are
+reported for over-current and over-temperature conditions.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-3-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 189 +++++++++++++++++++++++++++++++++-
+ 1 file changed, 187 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -16,7 +16,15 @@
+ #include <linux/pse-pd/pse.h>
+ #define TPS23881_MAX_CHANS 8
++#define TPS23881_MAX_IRQ_RETRIES 10
++#define TPS23881_REG_IT               0x0
++#define TPS23881_REG_IT_MASK  0x1
++#define TPS23881_REG_IT_IFAULT        BIT(5)
++#define TPS23881_REG_IT_SUPF  BIT(7)
++#define TPS23881_REG_FAULT    0x7
++#define TPS23881_REG_SUPF_EVENT       0xb
++#define TPS23881_REG_TSD      BIT(7)
+ #define TPS23881_REG_PW_STATUS        0x10
+ #define TPS23881_REG_OP_MODE  0x12
+ #define TPS23881_OP_MODE_SEMIAUTO     0xaaaa
+@@ -24,6 +32,7 @@
+ #define TPS23881_REG_DET_CLA_EN       0x14
+ #define TPS23881_REG_GEN_MASK 0x17
+ #define TPS23881_REG_NBITACC  BIT(5)
++#define TPS23881_REG_INTEN    BIT(7)
+ #define TPS23881_REG_PW_EN    0x19
+ #define TPS23881_REG_2PAIR_POL1       0x1e
+ #define TPS23881_REG_PORT_MAP 0x26
+@@ -51,6 +60,7 @@ struct tps23881_port_desc {
+       u8 chan[2];
+       bool is_4p;
+       int pw_pol;
++      bool exist;
+ };
+ struct tps23881_priv {
+@@ -782,8 +792,10 @@ tps23881_write_port_matrix(struct tps238
+               hw_chan = port_matrix[i].hw_chan[0] % 4;
+               /* Set software port matrix for existing ports */
+-              if (port_matrix[i].exist)
++              if (port_matrix[i].exist) {
+                       priv->port[pi_id].chan[0] = lgcl_chan;
++                      priv->port[pi_id].exist = true;
++              }
+               /* Initialize power policy internal value */
+               priv->port[pi_id].pw_pol = -1;
+@@ -1017,6 +1029,173 @@ static int tps23881_flash_sram_fw(struct
+       return 0;
+ }
++/* Convert interrupt events to 0xff to be aligned with the chan
++ * number.
++ */
++static u8 tps23881_irq_export_chans_helper(u16 reg_val, u8 field_offset)
++{
++      u8 val;
++
++      val = (reg_val >> (4 + field_offset) & 0xf0) |
++            (reg_val >> field_offset & 0x0f);
++
++      return val;
++}
++
++/* Convert chan number to port number */
++static void tps23881_set_notifs_helper(struct tps23881_priv *priv,
++                                     u8 chans,
++                                     unsigned long *notifs,
++                                     unsigned long *notifs_mask,
++                                     enum ethtool_pse_event event)
++{
++      u8 chan;
++      int i;
++
++      if (!chans)
++              return;
++
++      for (i = 0; i < TPS23881_MAX_CHANS; i++) {
++              if (!priv->port[i].exist)
++                      continue;
++              /* No need to look at the 2nd channel in case of PoE4 as
++               * both registers are set.
++               */
++              chan = priv->port[i].chan[0];
++
++              if (BIT(chan) & chans) {
++                      *notifs_mask |= BIT(i);
++                      notifs[i] |= event;
++              }
++      }
++}
++
++static void tps23881_irq_event_over_temp(struct tps23881_priv *priv,
++                                       u16 reg_val,
++                                       unsigned long *notifs,
++                                       unsigned long *notifs_mask)
++{
++      int i;
++
++      if (reg_val & TPS23881_REG_TSD) {
++              for (i = 0; i < TPS23881_MAX_CHANS; i++) {
++                      if (!priv->port[i].exist)
++                              continue;
++
++                      *notifs_mask |= BIT(i);
++                      notifs[i] |= ETHTOOL_PSE_EVENT_OVER_TEMP;
++              }
++      }
++}
++
++static void tps23881_irq_event_over_current(struct tps23881_priv *priv,
++                                          u16 reg_val,
++                                          unsigned long *notifs,
++                                          unsigned long *notifs_mask)
++{
++      u8 chans;
++
++      chans = tps23881_irq_export_chans_helper(reg_val, 0);
++      if (chans)
++              tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask,
++                                         ETHTOOL_PSE_EVENT_OVER_CURRENT);
++}
++
++static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg,
++                                    unsigned long *notifs,
++                                    unsigned long *notifs_mask)
++{
++      struct i2c_client *client = priv->client;
++      int ret;
++
++      /* The Supply event bit is repeated twice so we only need to read
++       * the one from the first byte.
++       */
++      if (reg & TPS23881_REG_IT_SUPF) {
++              ret = i2c_smbus_read_word_data(client, TPS23881_REG_SUPF_EVENT);
++              if (ret < 0)
++                      return ret;
++              tps23881_irq_event_over_temp(priv, ret, notifs, notifs_mask);
++      }
++
++      if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8)) {
++              ret = i2c_smbus_read_word_data(client, TPS23881_REG_FAULT);
++              if (ret < 0)
++                      return ret;
++              tps23881_irq_event_over_current(priv, ret, notifs, notifs_mask);
++      }
++
++      return 0;
++}
++
++static int tps23881_irq_handler(int irq, struct pse_controller_dev *pcdev,
++                              unsigned long *notifs,
++                              unsigned long *notifs_mask)
++{
++      struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++      struct i2c_client *client = priv->client;
++      int ret, it_mask, retry;
++
++      /* Get interruption mask */
++      ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT_MASK);
++      if (ret < 0)
++              return ret;
++      it_mask = ret;
++
++      /* Read interrupt register until it frees the interruption pin. */
++      retry = 0;
++      while (true) {
++              if (retry > TPS23881_MAX_IRQ_RETRIES) {
++                      dev_err(&client->dev, "interrupt never freed");
++                      return -ETIMEDOUT;
++              }
++
++              ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT);
++              if (ret < 0)
++                      return ret;
++
++              /* No more relevant interruption */
++              if (!(ret & it_mask))
++                      return 0;
++
++              ret = tps23881_irq_event_handler(priv, (u16)ret, notifs,
++                                               notifs_mask);
++              if (ret)
++                      return ret;
++
++              retry++;
++      }
++      return 0;
++}
++
++static int tps23881_setup_irq(struct tps23881_priv *priv, int irq)
++{
++      struct i2c_client *client = priv->client;
++      struct pse_irq_desc irq_desc = {
++              .name = "tps23881-irq",
++              .map_event = tps23881_irq_handler,
++      };
++      int ret;
++      u16 val;
++
++      val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF;
++      val |= val << 8;
++      ret = i2c_smbus_write_word_data(client, TPS23881_REG_IT_MASK, val);
++      if (ret)
++              return ret;
++
++      ret = i2c_smbus_read_word_data(client, TPS23881_REG_GEN_MASK);
++      if (ret < 0)
++              return ret;
++
++      val = (u16)(ret | TPS23881_REG_INTEN | TPS23881_REG_INTEN << 8);
++      ret = i2c_smbus_write_word_data(client, TPS23881_REG_GEN_MASK, val);
++      if (ret < 0)
++              return ret;
++
++      return devm_pse_irq_helper(&priv->pcdev, irq, 0, &irq_desc);
++}
++
+ static int tps23881_i2c_probe(struct i2c_client *client)
+ {
+       struct device *dev = &client->dev;
+@@ -1097,6 +1276,12 @@ static int tps23881_i2c_probe(struct i2c
+                                    "failed to register PSE controller\n");
+       }
++      if (client->irq) {
++              ret = tps23881_setup_irq(priv, client->irq);
++              if (ret)
++                      return ret;
++      }
++
+       return ret;
+ }
diff --git a/target/linux/generic/backport-6.12/626-13-v6.17-net-pse-pd-Add-support-for-PSE-power-domains.patch b/target/linux/generic/backport-6.12/626-13-v6.17-net-pse-pd-Add-support-for-PSE-power-domains.patch
new file mode 100644 (file)
index 0000000..a02bbbc
--- /dev/null
@@ -0,0 +1,218 @@
+From 50f8b341d26826aa5fdccb8f497fbff2500934b3 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:03 +0200
+Subject: [PATCH] net: pse-pd: Add support for PSE power domains
+
+Introduce PSE power domain support as groundwork for upcoming port
+priority features. Multiple PSE PIs can now be grouped under a single
+PSE power domain, enabling future enhancements like defining available
+power budgets, port priority modes, and disconnection policies. This
+setup will allow the system to assess whether activating a port would
+exceed the available power budget, preventing over-budget states
+proactively.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-4-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pse_core.c | 140 ++++++++++++++++++++++++++++++++++
+ include/linux/pse-pd/pse.h    |   2 +
+ 2 files changed, 142 insertions(+)
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -16,8 +16,12 @@
+ #include <linux/rtnetlink.h>
+ #include <net/net_trackers.h>
++#define PSE_PW_D_LIMIT INT_MAX
++
+ static DEFINE_MUTEX(pse_list_mutex);
+ static LIST_HEAD(pse_controller_list);
++static DEFINE_XARRAY_ALLOC(pse_pw_d_map);
++static DEFINE_MUTEX(pse_pw_d_mutex);
+ /**
+  * struct pse_control - a PSE control
+@@ -38,6 +42,18 @@ struct pse_control {
+       struct phy_device *attached_phydev;
+ };
++/**
++ * struct pse_power_domain - a PSE power domain
++ * @id: ID of the power domain
++ * @supply: Power supply the Power Domain
++ * @refcnt: Number of gets of this pse_power_domain
++ */
++struct pse_power_domain {
++      int id;
++      struct regulator *supply;
++      struct kref refcnt;
++};
++
+ static int of_load_single_pse_pi_pairset(struct device_node *node,
+                                        struct pse_pi *pi,
+                                        int pairset_num)
+@@ -485,6 +501,125 @@ devm_pse_pi_regulator_register(struct ps
+       return 0;
+ }
++static void __pse_pw_d_release(struct kref *kref)
++{
++      struct pse_power_domain *pw_d = container_of(kref,
++                                                   struct pse_power_domain,
++                                                   refcnt);
++
++      regulator_put(pw_d->supply);
++      xa_erase(&pse_pw_d_map, pw_d->id);
++      mutex_unlock(&pse_pw_d_mutex);
++}
++
++/**
++ * pse_flush_pw_ds - flush all PSE power domains of a PSE
++ * @pcdev: a pointer to the initialized PSE controller device
++ */
++static void pse_flush_pw_ds(struct pse_controller_dev *pcdev)
++{
++      struct pse_power_domain *pw_d;
++      int i;
++
++      for (i = 0; i < pcdev->nr_lines; i++) {
++              if (!pcdev->pi[i].pw_d)
++                      continue;
++
++              pw_d = xa_load(&pse_pw_d_map, pcdev->pi[i].pw_d->id);
++              if (!pw_d)
++                      continue;
++
++              kref_put_mutex(&pw_d->refcnt, __pse_pw_d_release,
++                             &pse_pw_d_mutex);
++      }
++}
++
++/**
++ * devm_pse_alloc_pw_d - allocate a new PSE power domain for a device
++ * @dev: device that is registering this PSE power domain
++ *
++ * Return: Pointer to the newly allocated PSE power domain or error pointers
++ */
++static struct pse_power_domain *devm_pse_alloc_pw_d(struct device *dev)
++{
++      struct pse_power_domain *pw_d;
++      int index, ret;
++
++      pw_d = devm_kzalloc(dev, sizeof(*pw_d), GFP_KERNEL);
++      if (!pw_d)
++              return ERR_PTR(-ENOMEM);
++
++      ret = xa_alloc(&pse_pw_d_map, &index, pw_d, XA_LIMIT(1, PSE_PW_D_LIMIT),
++                     GFP_KERNEL);
++      if (ret)
++              return ERR_PTR(ret);
++
++      kref_init(&pw_d->refcnt);
++      pw_d->id = index;
++      return pw_d;
++}
++
++/**
++ * pse_register_pw_ds - register the PSE power domains for a PSE
++ * @pcdev: a pointer to the PSE controller device
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int pse_register_pw_ds(struct pse_controller_dev *pcdev)
++{
++      int i, ret = 0;
++
++      mutex_lock(&pse_pw_d_mutex);
++      for (i = 0; i < pcdev->nr_lines; i++) {
++              struct regulator_dev *rdev = pcdev->pi[i].rdev;
++              struct pse_power_domain *pw_d;
++              struct regulator *supply;
++              bool present = false;
++              unsigned long index;
++
++              /* No regulator or regulator parent supply registered.
++               * We need a regulator parent to register a PSE power domain
++               */
++              if (!rdev || !rdev->supply)
++                      continue;
++
++              xa_for_each(&pse_pw_d_map, index, pw_d) {
++                      /* Power supply already registered as a PSE power
++                       * domain.
++                       */
++                      if (regulator_is_equal(pw_d->supply, rdev->supply)) {
++                              present = true;
++                              pcdev->pi[i].pw_d = pw_d;
++                              break;
++                      }
++              }
++              if (present) {
++                      kref_get(&pw_d->refcnt);
++                      continue;
++              }
++
++              pw_d = devm_pse_alloc_pw_d(pcdev->dev);
++              if (IS_ERR(pw_d)) {
++                      ret = PTR_ERR(pw_d);
++                      goto out;
++              }
++
++              supply = regulator_get(&rdev->dev, rdev->supply_name);
++              if (IS_ERR(supply)) {
++                      xa_erase(&pse_pw_d_map, pw_d->id);
++                      ret = PTR_ERR(supply);
++                      goto out;
++              }
++
++              pw_d->supply = supply;
++              pcdev->pi[i].pw_d = pw_d;
++      }
++
++out:
++      mutex_unlock(&pse_pw_d_mutex);
++      return ret;
++}
++
+ /**
+  * pse_controller_register - register a PSE controller device
+  * @pcdev: a pointer to the initialized PSE controller device
+@@ -544,6 +679,10 @@ int pse_controller_register(struct pse_c
+                       return ret;
+       }
++      ret = pse_register_pw_ds(pcdev);
++      if (ret)
++              return ret;
++
+       mutex_lock(&pse_list_mutex);
+       list_add(&pcdev->list, &pse_controller_list);
+       mutex_unlock(&pse_list_mutex);
+@@ -558,6 +697,7 @@ EXPORT_SYMBOL_GPL(pse_controller_registe
+  */
+ void pse_controller_unregister(struct pse_controller_dev *pcdev)
+ {
++      pse_flush_pw_ds(pcdev);
+       pse_release_pis(pcdev);
+       mutex_lock(&pse_list_mutex);
+       list_del(&pcdev->list);
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -222,12 +222,14 @@ struct pse_pi_pairset {
+  * @np: device node pointer of the PSE PI node
+  * @rdev: regulator represented by the PSE PI
+  * @admin_state_enabled: PI enabled state
++ * @pw_d: Power domain of the PSE PI
+  */
+ struct pse_pi {
+       struct pse_pi_pairset pairset[2];
+       struct device_node *np;
+       struct regulator_dev *rdev;
+       bool admin_state_enabled;
++      struct pse_power_domain *pw_d;
+ };
+ /**
diff --git a/target/linux/generic/backport-6.12/626-14-v6.17-net-pse-pd-ethtool-Add-support-for-power-domains-index.patch b/target/linux/generic/backport-6.12/626-14-v6.17-net-pse-pd-ethtool-Add-support-for-power-domains-index.patch
new file mode 100644 (file)
index 0000000..1124882
--- /dev/null
@@ -0,0 +1,78 @@
+# ADAPTED FOR OPENWRT 6.12.67 - Documentation changes removed
+# Original commit: 1176978ed851
+From 1176978ed851952652ddea3685e2f71a0e5d61ff Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:04 +0200
+Subject: [PATCH] net: ethtool: Add support for new power domains index
+ description
+
+Report the index of the newly introduced PSE power domain to the user,
+enabling improved management of the power budget for PSE devices.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-5-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ Documentation/netlink/specs/ethtool.yaml       | 5 +++++
+ Documentation/networking/ethtool-netlink.rst   | 4 ++++
+ drivers/net/pse-pd/pse_core.c                  | 3 +++
+ include/linux/pse-pd/pse.h                     | 2 ++
+ include/uapi/linux/ethtool_netlink_generated.h | 1 +
+ net/ethtool/pse-pd.c                           | 7 +++++++
+ 6 files changed, 22 insertions(+)
+
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -1098,6 +1098,9 @@ int pse_ethtool_get_status(struct pse_co
+       pcdev = psec->pcdev;
+       ops = pcdev->ops;
+       mutex_lock(&pcdev->lock);
++      if (pcdev->pi[psec->id].pw_d)
++              status->pw_d_id = pcdev->pi[psec->id].pw_d->id;
++
+       ret = ops->pi_get_admin_state(pcdev, psec->id, &admin_state);
+       if (ret)
+               goto out;
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -114,6 +114,7 @@ struct pse_pw_limit_ranges {
+ /**
+  * struct ethtool_pse_control_status - PSE control/channel status.
+  *
++ * @pw_d_id: PSE power domain index.
+  * @podl_admin_state: operational state of the PoDL PSE
+  *    functions. IEEE 802.3-2018 30.15.1.1.2 aPoDLPSEAdminState
+  * @podl_pw_status: power detection status of the PoDL PSE.
+@@ -135,6 +136,7 @@ struct pse_pw_limit_ranges {
+  *    ranges
+  */
+ struct ethtool_pse_control_status {
++      u32 pw_d_id;
+       enum ethtool_podl_pse_admin_state podl_admin_state;
+       enum ethtool_podl_pse_pw_d_status podl_pw_status;
+       enum ethtool_c33_pse_admin_state c33_admin_state;
+--- a/net/ethtool/pse-pd.c
++++ b/net/ethtool/pse-pd.c
+@@ -83,6 +83,8 @@ static int pse_reply_size(const struct e
+       const struct ethtool_pse_control_status *st = &data->status;
+       int len = 0;
++      if (st->pw_d_id)
++              len += nla_total_size(sizeof(u32)); /* _PSE_PW_D_ID */
+       if (st->podl_admin_state > 0)
+               len += nla_total_size(sizeof(u32)); /* _PODL_PSE_ADMIN_STATE */
+       if (st->podl_pw_status > 0)
+@@ -148,6 +150,11 @@ static int pse_fill_reply(struct sk_buff
+       const struct pse_reply_data *data = PSE_REPDATA(reply_base);
+       const struct ethtool_pse_control_status *st = &data->status;
++      if (st->pw_d_id &&
++          nla_put_u32(skb, ETHTOOL_A_PSE_PW_D_ID,
++                      st->pw_d_id))
++              return -EMSGSIZE;
++
+       if (st->podl_admin_state > 0 &&
+           nla_put_u32(skb, ETHTOOL_A_PODL_PSE_ADMIN_STATE,
+                       st->podl_admin_state))
diff --git a/target/linux/generic/backport-6.12/626-15-v6.17-net-pse-pd-Add-helper-to-report-hw-enable-status.patch b/target/linux/generic/backport-6.12/626-15-v6.17-net-pse-pd-Add-helper-to-report-hw-enable-status.patch
new file mode 100644 (file)
index 0000000..ed51289
--- /dev/null
@@ -0,0 +1,74 @@
+From c394e757dedd9cf947f9ac470d615d28fd2b07d1 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:05 +0200
+Subject: [PATCH] net: pse-pd: Add helper to report hardware enable status of
+ the PI
+
+Refactor code by introducing a helper function to retrieve the hardware
+enabled state of the PI, avoiding redundant implementations in the
+future.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-6-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pse_core.c | 36 +++++++++++++++++++++++++----------
+ 1 file changed, 26 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -272,10 +272,34 @@ static struct net_device *pse_control_ge
+       return psec->attached_phydev->attached_dev;
+ }
++/**
++ * pse_pi_is_hw_enabled - Is PI enabled at the hardware level
++ * @pcdev: a pointer to the PSE controller device
++ * @id: Index of the PI
++ *
++ * Return: 1 if the PI is enabled at the hardware level, 0 if not, and
++ *       a failure value on error
++ */
++static int pse_pi_is_hw_enabled(struct pse_controller_dev *pcdev, int id)
++{
++      struct pse_admin_state admin_state = {0};
++      int ret;
++
++      ret = pcdev->ops->pi_get_admin_state(pcdev, id, &admin_state);
++      if (ret < 0)
++              return ret;
++
++      /* PI is well enabled at the hardware level */
++      if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED ||
++          admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED)
++              return 1;
++
++      return 0;
++}
++
+ static int pse_pi_is_enabled(struct regulator_dev *rdev)
+ {
+       struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+-      struct pse_admin_state admin_state = {0};
+       const struct pse_controller_ops *ops;
+       int id, ret;
+@@ -285,15 +309,7 @@ static int pse_pi_is_enabled(struct regu
+       id = rdev_get_id(rdev);
+       mutex_lock(&pcdev->lock);
+-      ret = ops->pi_get_admin_state(pcdev, id, &admin_state);
+-      if (ret)
+-              goto out;
+-
+-      if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED ||
+-          admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED)
+-              ret = 1;
+-
+-out:
++      ret = pse_pi_is_hw_enabled(pcdev, id);
+       mutex_unlock(&pcdev->lock);
+       return ret;
diff --git a/target/linux/generic/backport-6.12/626-16-v6.17-net-pse-pd-Add-support-for-budget-evaluation-strategies.patch b/target/linux/generic/backport-6.12/626-16-v6.17-net-pse-pd-Add-support-for-budget-evaluation-strategies.patch
new file mode 100644 (file)
index 0000000..c95d8f0
--- /dev/null
@@ -0,0 +1,1104 @@
+From ffef61d6d27374542f1bce4452200d9bdd2e1edd Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:06 +0200
+Subject: [PATCH] net: pse-pd: Add support for budget evaluation strategies
+
+ADAPTED FOR OPENWRT 6.12.67 - Documentation and ethtool_netlink_generated.h changes removed
+
+This patch introduces the ability to configure the PSE PI budget evaluation
+strategies. Budget evaluation strategies is utilized by PSE controllers to
+determine which ports to turn off first in scenarios such as power budget
+exceedance.
+
+The pis_prio_max value is used to define the maximum priority level
+supported by the controller. Both the current priority and the maximum
+priority are exposed to the user through the pse_ethtool_get_status call.
+
+This patch add support for two mode of budget evaluation strategies.
+1. Static Method:
+   This method involves distributing power based on PD classification.
+
+2. Dynamic Method:
+   Budget evaluation strategy based on the current consumption per ports
+   compared to the total power budget.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent@bootlin.com>
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-7-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pse_core.c | 729 +++++++++++++++++++++++++++++++++-
+ include/linux/pse-pd/pse.h    |  76 ++++
+ 2 files changed, 791 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -47,11 +47,14 @@ struct pse_control {
+  * @id: ID of the power domain
+  * @supply: Power supply the Power Domain
+  * @refcnt: Number of gets of this pse_power_domain
++ * @budget_eval_strategy: Current power budget evaluation strategy of the
++ *                      power domain
+  */
+ struct pse_power_domain {
+       int id;
+       struct regulator *supply;
+       struct kref refcnt;
++      u32 budget_eval_strategy;
+ };
+ static int of_load_single_pse_pi_pairset(struct device_node *node,
+@@ -297,6 +300,115 @@ static int pse_pi_is_hw_enabled(struct p
+       return 0;
+ }
++/**
++ * pse_pi_is_admin_enable_pending - Check if PI is in admin enable pending state
++ *                                which mean the power is not yet being
++ *                                delivered
++ * @pcdev: a pointer to the PSE controller device
++ * @id: Index of the PI
++ *
++ * Detects if a PI is enabled in software with a PD detected, but the hardware
++ * admin state hasn't been applied yet.
++ *
++ * This function is used in the power delivery and retry mechanisms to determine
++ * which PIs need to have power delivery attempted again.
++ *
++ * Return: true if the PI has admin enable flag set in software but not yet
++ *       reflected in the hardware admin state, false otherwise.
++ */
++static bool
++pse_pi_is_admin_enable_pending(struct pse_controller_dev *pcdev, int id)
++{
++      int ret;
++
++      /* PI not enabled or nothing is plugged */
++      if (!pcdev->pi[id].admin_state_enabled ||
++          !pcdev->pi[id].isr_pd_detected)
++              return false;
++
++      ret = pse_pi_is_hw_enabled(pcdev, id);
++      /* PSE PI is already enabled at hardware level */
++      if (ret == 1)
++              return false;
++
++      return true;
++}
++
++static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev,
++                                           int id,
++                                           struct netlink_ext_ack *extack);
++
++/**
++ * pse_pw_d_retry_power_delivery - Retry power delivery for pending ports in a
++ *                               PSE power domain
++ * @pcdev: a pointer to the PSE controller device
++ * @pw_d: a pointer to the PSE power domain
++ *
++ * Scans all ports in the specified power domain and attempts to enable power
++ * delivery to any ports that have admin enable state set but don't yet have
++ * hardware power enabled. Used when there are changes in connection status,
++ * admin state, or priority that might allow previously unpowered ports to
++ * receive power, especially in over-budget conditions.
++ */
++static void pse_pw_d_retry_power_delivery(struct pse_controller_dev *pcdev,
++                                        struct pse_power_domain *pw_d)
++{
++      int i, ret = 0;
++
++      for (i = 0; i < pcdev->nr_lines; i++) {
++              int prio_max = pcdev->nr_lines;
++              struct netlink_ext_ack extack;
++
++              if (pcdev->pi[i].pw_d != pw_d)
++                      continue;
++
++              if (!pse_pi_is_admin_enable_pending(pcdev, i))
++                      continue;
++
++              /* Do not try to enable PI with a lower prio (higher value)
++               * than one which already can't be enabled.
++               */
++              if (pcdev->pi[i].prio > prio_max)
++                      continue;
++
++              ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, i, &extack);
++              if (ret == -ERANGE)
++                      prio_max = pcdev->pi[i].prio;
++      }
++}
++
++/**
++ * pse_pw_d_is_sw_pw_control - Determine if power control is software managed
++ * @pcdev: a pointer to the PSE controller device
++ * @pw_d: a pointer to the PSE power domain
++ *
++ * This function determines whether the power control for a specific power
++ * domain is managed by software in the interrupt handler rather than directly
++ * by hardware.
++ *
++ * Software power control is active in the following cases:
++ * - When the budget evaluation strategy is set to static
++ * - When the budget evaluation strategy is disabled but the PSE controller
++ *   has an interrupt handler that can report if a Powered Device is connected
++ *
++ * Return: true if the power control of the power domain is managed by software,
++ *         false otherwise
++ */
++static bool pse_pw_d_is_sw_pw_control(struct pse_controller_dev *pcdev,
++                                    struct pse_power_domain *pw_d)
++{
++      if (!pw_d)
++              return false;
++
++      if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC)
++              return true;
++      if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_DISABLED &&
++          pcdev->ops->pi_enable && pcdev->irq)
++              return true;
++
++      return false;
++}
++
+ static int pse_pi_is_enabled(struct regulator_dev *rdev)
+ {
+       struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+@@ -309,17 +421,252 @@ static int pse_pi_is_enabled(struct regu
+       id = rdev_get_id(rdev);
+       mutex_lock(&pcdev->lock);
++      if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
++              ret = pcdev->pi[id].admin_state_enabled;
++              goto out;
++      }
++
+       ret = pse_pi_is_hw_enabled(pcdev, id);
++
++out:
+       mutex_unlock(&pcdev->lock);
+       return ret;
+ }
++/**
++ * pse_pi_deallocate_pw_budget - Deallocate power budget of the PI
++ * @pi: a pointer to the PSE PI
++ */
++static void pse_pi_deallocate_pw_budget(struct pse_pi *pi)
++{
++      if (!pi->pw_d || !pi->pw_allocated_mW)
++              return;
++
++      regulator_free_power_budget(pi->pw_d->supply, pi->pw_allocated_mW);
++      pi->pw_allocated_mW = 0;
++}
++
++/**
++ * _pse_pi_disable - Call disable operation. Assumes the PSE lock has been
++ *                 acquired.
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE control
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int _pse_pi_disable(struct pse_controller_dev *pcdev, int id)
++{
++      const struct pse_controller_ops *ops = pcdev->ops;
++      int ret;
++
++      if (!ops->pi_disable)
++              return -EOPNOTSUPP;
++
++      ret = ops->pi_disable(pcdev, id);
++      if (ret)
++              return ret;
++
++      pse_pi_deallocate_pw_budget(&pcdev->pi[id]);
++
++      if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d))
++              pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[id].pw_d);
++
++      return 0;
++}
++
++/**
++ * pse_disable_pi_pol - Disable a PI on a power budget policy
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE PI
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int pse_disable_pi_pol(struct pse_controller_dev *pcdev, int id)
++{
++      unsigned long notifs = ETHTOOL_PSE_EVENT_OVER_BUDGET;
++      struct pse_ntf ntf = {};
++      int ret;
++
++      dev_dbg(pcdev->dev, "Disabling PI %d to free power budget\n", id);
++
++      ret = _pse_pi_disable(pcdev, id);
++      if (ret)
++              notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR;
++
++      ntf.notifs = notifs;
++      ntf.id = id;
++      kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1, &pcdev->ntf_fifo_lock);
++      schedule_work(&pcdev->ntf_work);
++
++      return ret;
++}
++
++/**
++ * pse_disable_pi_prio - Disable all PIs of a given priority inside a PSE
++ *                     power domain
++ * @pcdev: a pointer to the PSE
++ * @pw_d: a pointer to the PSE power domain
++ * @prio: priority
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int pse_disable_pi_prio(struct pse_controller_dev *pcdev,
++                             struct pse_power_domain *pw_d,
++                             int prio)
++{
++      int i;
++
++      for (i = 0; i < pcdev->nr_lines; i++) {
++              int ret;
++
++              if (pcdev->pi[i].prio != prio ||
++                  pcdev->pi[i].pw_d != pw_d ||
++                  pse_pi_is_hw_enabled(pcdev, i) <= 0)
++                      continue;
++
++              ret = pse_disable_pi_pol(pcdev, i);
++              if (ret)
++                      return ret;
++      }
++
++      return 0;
++}
++
++/**
++ * pse_pi_allocate_pw_budget_static_prio - Allocate power budget for the PI
++ *                                       when the budget eval strategy is
++ *                                       static
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE control
++ * @pw_req: power requested in mW
++ * @extack: extack for error reporting
++ *
++ * Allocates power using static budget evaluation strategy, where allocation
++ * is based on PD classification. When insufficient budget is available,
++ * lower-priority ports (higher priority numbers) are turned off first.
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int
++pse_pi_allocate_pw_budget_static_prio(struct pse_controller_dev *pcdev, int id,
++                                    int pw_req, struct netlink_ext_ack *extack)
++{
++      struct pse_pi *pi = &pcdev->pi[id];
++      int ret, _prio;
++
++      _prio = pcdev->nr_lines;
++      while (regulator_request_power_budget(pi->pw_d->supply, pw_req) == -ERANGE) {
++              if (_prio <= pi->prio) {
++                      NL_SET_ERR_MSG_FMT(extack,
++                                         "PI %d: not enough power budget available",
++                                         id);
++                      return -ERANGE;
++              }
++
++              ret = pse_disable_pi_prio(pcdev, pi->pw_d, _prio);
++              if (ret < 0)
++                      return ret;
++
++              _prio--;
++      }
++
++      pi->pw_allocated_mW = pw_req;
++      return 0;
++}
++
++/**
++ * pse_pi_allocate_pw_budget - Allocate power budget for the PI
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE control
++ * @pw_req: power requested in mW
++ * @extack: extack for error reporting
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int pse_pi_allocate_pw_budget(struct pse_controller_dev *pcdev, int id,
++                                   int pw_req, struct netlink_ext_ack *extack)
++{
++      struct pse_pi *pi = &pcdev->pi[id];
++
++      if (!pi->pw_d)
++              return 0;
++
++      /* PSE_BUDGET_EVAL_STRAT_STATIC */
++      if (pi->pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC)
++              return pse_pi_allocate_pw_budget_static_prio(pcdev, id, pw_req,
++                                                           extack);
++
++      return 0;
++}
++
++/**
++ * _pse_pi_delivery_power_sw_pw_ctrl - Enable PSE PI in case of software power
++ *                                   control. Assumes the PSE lock has been
++ *                                   acquired.
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE control
++ * @extack: extack for error reporting
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev,
++                                           int id,
++                                           struct netlink_ext_ack *extack)
++{
++      const struct pse_controller_ops *ops = pcdev->ops;
++      struct pse_pi *pi = &pcdev->pi[id];
++      int ret, pw_req;
++
++      if (!ops->pi_get_pw_req) {
++              /* No power allocation management */
++              ret = ops->pi_enable(pcdev, id);
++              if (ret)
++                      NL_SET_ERR_MSG_FMT(extack,
++                                         "PI %d: enable error %d",
++                                         id, ret);
++              return ret;
++      }
++
++      ret = ops->pi_get_pw_req(pcdev, id);
++      if (ret < 0)
++              return ret;
++
++      pw_req = ret;
++
++      /* Compare requested power with port power limit and use the lowest
++       * one.
++       */
++      if (ops->pi_get_pw_limit) {
++              ret = ops->pi_get_pw_limit(pcdev, id);
++              if (ret < 0)
++                      return ret;
++
++              if (ret < pw_req)
++                      pw_req = ret;
++      }
++
++      ret = pse_pi_allocate_pw_budget(pcdev, id, pw_req, extack);
++      if (ret)
++              return ret;
++
++      ret = ops->pi_enable(pcdev, id);
++      if (ret) {
++              pse_pi_deallocate_pw_budget(pi);
++              NL_SET_ERR_MSG_FMT(extack,
++                                 "PI %d: enable error %d",
++                                 id, ret);
++              return ret;
++      }
++
++      return 0;
++}
++
+ static int pse_pi_enable(struct regulator_dev *rdev)
+ {
+       struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+       const struct pse_controller_ops *ops;
+-      int id, ret;
++      int id, ret = 0;
+       ops = pcdev->ops;
+       if (!ops->pi_enable)
+@@ -327,6 +674,23 @@ static int pse_pi_enable(struct regulato
+       id = rdev_get_id(rdev);
+       mutex_lock(&pcdev->lock);
++      if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
++              /* Manage enabled status by software.
++               * Real enable process will happen if a port is connected.
++               */
++              if (pcdev->pi[id].isr_pd_detected) {
++                      struct netlink_ext_ack extack;
++
++                      ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id, &extack);
++              }
++              if (!ret || ret == -ERANGE) {
++                      pcdev->pi[id].admin_state_enabled = 1;
++                      ret = 0;
++              }
++              mutex_unlock(&pcdev->lock);
++              return ret;
++      }
++
+       ret = ops->pi_enable(pcdev, id);
+       if (!ret)
+               pcdev->pi[id].admin_state_enabled = 1;
+@@ -338,21 +702,18 @@ static int pse_pi_enable(struct regulato
+ static int pse_pi_disable(struct regulator_dev *rdev)
+ {
+       struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+-      const struct pse_controller_ops *ops;
++      struct pse_pi *pi;
+       int id, ret;
+-      ops = pcdev->ops;
+-      if (!ops->pi_disable)
+-              return -EOPNOTSUPP;
+-
+       id = rdev_get_id(rdev);
++      pi = &pcdev->pi[id];
+       mutex_lock(&pcdev->lock);
+-      ret = ops->pi_disable(pcdev, id);
++      ret = _pse_pi_disable(pcdev, id);
+       if (!ret)
+-              pcdev->pi[id].admin_state_enabled = 0;
+-      mutex_unlock(&pcdev->lock);
++              pi->admin_state_enabled = 0;
+-      return ret;
++      mutex_unlock(&pcdev->lock);
++      return 0;
+ }
+ static int _pse_pi_get_voltage(struct regulator_dev *rdev)
+@@ -628,6 +989,11 @@ static int pse_register_pw_ds(struct pse
+               }
+               pw_d->supply = supply;
++              if (pcdev->supp_budget_eval_strategies)
++                      pw_d->budget_eval_strategy = pcdev->supp_budget_eval_strategies;
++              else
++                      pw_d->budget_eval_strategy = PSE_BUDGET_EVAL_STRAT_DISABLED;
++              kref_init(&pw_d->refcnt);
+               pcdev->pi[i].pw_d = pw_d;
+       }
+@@ -637,6 +1003,34 @@ out:
+ }
+ /**
++ * pse_send_ntf_worker - Worker to send PSE notifications
++ * @work: work object
++ *
++ * Manage and send PSE netlink notifications using a workqueue to avoid
++ * deadlock between pcdev_lock and pse_list_mutex.
++ */
++static void pse_send_ntf_worker(struct work_struct *work)
++{
++      struct pse_controller_dev *pcdev;
++      struct pse_ntf ntf;
++
++      pcdev = container_of(work, struct pse_controller_dev, ntf_work);
++
++      while (kfifo_out(&pcdev->ntf_fifo, &ntf, 1)) {
++              struct net_device *netdev;
++              struct pse_control *psec;
++
++              psec = pse_control_find_by_id(pcdev, ntf.id);
++              rtnl_lock();
++              netdev = pse_control_get_netdev(psec);
++              if (netdev)
++                      ethnl_pse_send_ntf(netdev, ntf.notifs);
++              rtnl_unlock();
++              pse_control_put(psec);
++      }
++}
++
++/**
+  * pse_controller_register - register a PSE controller device
+  * @pcdev: a pointer to the initialized PSE controller device
+  *
+@@ -649,6 +1043,13 @@ int pse_controller_register(struct pse_c
+       mutex_init(&pcdev->lock);
+       INIT_LIST_HEAD(&pcdev->pse_control_head);
++      spin_lock_init(&pcdev->ntf_fifo_lock);
++      ret = kfifo_alloc(&pcdev->ntf_fifo, pcdev->nr_lines, GFP_KERNEL);
++      if (ret) {
++              dev_err(pcdev->dev, "failed to allocate kfifo notifications\n");
++              return ret;
++      }
++      INIT_WORK(&pcdev->ntf_work, pse_send_ntf_worker);
+       if (!pcdev->nr_lines)
+               pcdev->nr_lines = 1;
+@@ -715,6 +1116,10 @@ void pse_controller_unregister(struct ps
+ {
+       pse_flush_pw_ds(pcdev);
+       pse_release_pis(pcdev);
++      if (pcdev->irq)
++              disable_irq(pcdev->irq);
++      cancel_work_sync(&pcdev->ntf_work);
++      kfifo_free(&pcdev->ntf_fifo);
+       mutex_lock(&pse_list_mutex);
+       list_del(&pcdev->list);
+       mutex_unlock(&pse_list_mutex);
+@@ -787,6 +1192,52 @@ static unsigned long pse_to_regulator_no
+ }
+ /**
++ * pse_set_config_isr - Set PSE control config according to the PSE
++ *                    notifications
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE control
++ * @notifs: PSE event notifications
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int pse_set_config_isr(struct pse_controller_dev *pcdev, int id,
++                            unsigned long notifs)
++{
++      int ret = 0;
++
++      if (notifs & PSE_BUDGET_EVAL_STRAT_DYNAMIC)
++              return 0;
++
++      if ((notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) &&
++          ((notifs & ETHTOOL_C33_PSE_EVENT_DETECTION) ||
++           (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION))) {
++              dev_dbg(pcdev->dev,
++                      "PI %d: error, connection and disconnection reported simultaneously",
++                      id);
++              return -EINVAL;
++      }
++
++      if (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION) {
++              struct netlink_ext_ack extack;
++
++              pcdev->pi[id].isr_pd_detected = true;
++              if (pcdev->pi[id].admin_state_enabled) {
++                      ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id,
++                                                              &extack);
++                      if (ret == -ERANGE)
++                              ret = 0;
++              }
++      } else if (notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) {
++              if (pcdev->pi[id].admin_state_enabled &&
++                  pcdev->pi[id].isr_pd_detected)
++                      ret = _pse_pi_disable(pcdev, id);
++              pcdev->pi[id].isr_pd_detected = false;
++      }
++
++      return ret;
++}
++
++/**
+  * pse_isr - IRQ handler for PSE
+  * @irq: irq number
+  * @data: pointer to user interrupt structure
+@@ -808,36 +1259,42 @@ static irqreturn_t pse_isr(int irq, void
+       memset(h->notifs, 0, pcdev->nr_lines * sizeof(*h->notifs));
+       mutex_lock(&pcdev->lock);
+       ret = desc->map_event(irq, pcdev, h->notifs, &notifs_mask);
+-      mutex_unlock(&pcdev->lock);
+-      if (ret || !notifs_mask)
++      if (ret || !notifs_mask) {
++              mutex_unlock(&pcdev->lock);
+               return IRQ_NONE;
++      }
+       for_each_set_bit(i, &notifs_mask, pcdev->nr_lines) {
+               unsigned long notifs, rnotifs;
+-              struct net_device *netdev;
+-              struct pse_control *psec;
++              struct pse_ntf ntf = {};
+               /* Do nothing PI not described */
+               if (!pcdev->pi[i].rdev)
+                       continue;
+               notifs = h->notifs[i];
++              if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[i].pw_d)) {
++                      ret = pse_set_config_isr(pcdev, i, notifs);
++                      if (ret)
++                              notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR;
++              }
++
+               dev_dbg(h->pcdev->dev,
+                       "Sending PSE notification EVT 0x%lx\n", notifs);
+-              psec = pse_control_find_by_id(pcdev, i);
+-              rtnl_lock();
+-              netdev = pse_control_get_netdev(psec);
+-              if (netdev)
+-                      ethnl_pse_send_ntf(netdev, notifs);
+-              rtnl_unlock();
+-              pse_control_put(psec);
++              ntf.notifs = notifs;
++              ntf.id = i;
++              kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1,
++                                  &pcdev->ntf_fifo_lock);
++              schedule_work(&pcdev->ntf_work);
+               rnotifs = pse_to_regulator_notifs(notifs);
+               regulator_notifier_call_chain(pcdev->pi[i].rdev, rnotifs,
+                                             NULL);
+       }
++      mutex_unlock(&pcdev->lock);
++
+       return IRQ_HANDLED;
+ }
+@@ -960,6 +1417,20 @@ pse_control_get_internal(struct pse_cont
+               goto free_psec;
+       }
++      if (!pcdev->ops->pi_get_admin_state) {
++              ret = -EOPNOTSUPP;
++              goto free_psec;
++      }
++
++      /* Initialize admin_state_enabled before the regulator_get. This
++       * aims to have the right value reported in the first is_enabled
++       * call in case of control managed by software.
++       */
++      ret = pse_pi_is_hw_enabled(pcdev, index);
++      if (ret < 0)
++              goto free_psec;
++
++      pcdev->pi[index].admin_state_enabled = ret;
+       psec->ps = devm_regulator_get_exclusive(pcdev->dev,
+                                               rdev_get_name(pcdev->pi[index].rdev));
+       if (IS_ERR(psec->ps)) {
+@@ -967,12 +1438,6 @@ pse_control_get_internal(struct pse_cont
+               goto put_module;
+       }
+-      ret = regulator_is_enabled(psec->ps);
+-      if (ret < 0)
+-              goto regulator_put;
+-
+-      pcdev->pi[index].admin_state_enabled = ret;
+-
+       psec->pcdev = pcdev;
+       list_add(&psec->list, &pcdev->pse_control_head);
+       psec->id = index;
+@@ -981,8 +1446,6 @@ pse_control_get_internal(struct pse_cont
+       return psec;
+-regulator_put:
+-      devm_regulator_put(psec->ps);
+ put_module:
+       module_put(pcdev->owner);
+ free_psec:
+@@ -1094,6 +1557,35 @@ out:
+ EXPORT_SYMBOL_GPL(of_pse_control_get);
+ /**
++ * pse_get_sw_admin_state - Convert the software admin state to c33 or podl
++ *                        admin state value used in the standard
++ * @psec: PSE control pointer
++ * @admin_state: a pointer to the admin_state structure
++ */
++static void pse_get_sw_admin_state(struct pse_control *psec,
++                                 struct pse_admin_state *admin_state)
++{
++      struct pse_pi *pi = &psec->pcdev->pi[psec->id];
++
++      if (pse_has_podl(psec)) {
++              if (pi->admin_state_enabled)
++                      admin_state->podl_admin_state =
++                              ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED;
++              else
++                      admin_state->podl_admin_state =
++                              ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED;
++      }
++      if (pse_has_c33(psec)) {
++              if (pi->admin_state_enabled)
++                      admin_state->c33_admin_state =
++                              ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
++              else
++                      admin_state->c33_admin_state =
++                              ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
++      }
++}
++
++/**
+  * pse_ethtool_get_status - get status of PSE control
+  * @psec: PSE control pointer
+  * @extack: extack for reporting useful error messages
+@@ -1109,19 +1601,46 @@ int pse_ethtool_get_status(struct pse_co
+       struct pse_pw_status pw_status = {0};
+       const struct pse_controller_ops *ops;
+       struct pse_controller_dev *pcdev;
++      struct pse_pi *pi;
+       int ret;
+       pcdev = psec->pcdev;
+       ops = pcdev->ops;
++
++      pi = &pcdev->pi[psec->id];
+       mutex_lock(&pcdev->lock);
+-      if (pcdev->pi[psec->id].pw_d)
+-              status->pw_d_id = pcdev->pi[psec->id].pw_d->id;
++      if (pi->pw_d) {
++              status->pw_d_id = pi->pw_d->id;
++              if (pse_pw_d_is_sw_pw_control(pcdev, pi->pw_d)) {
++                      pse_get_sw_admin_state(psec, &admin_state);
++              } else {
++                      ret = ops->pi_get_admin_state(pcdev, psec->id,
++                                                    &admin_state);
++                      if (ret)
++                              goto out;
++              }
++              status->podl_admin_state = admin_state.podl_admin_state;
++              status->c33_admin_state = admin_state.c33_admin_state;
+-      ret = ops->pi_get_admin_state(pcdev, psec->id, &admin_state);
+-      if (ret)
+-              goto out;
+-      status->podl_admin_state = admin_state.podl_admin_state;
+-      status->c33_admin_state = admin_state.c33_admin_state;
++              switch (pi->pw_d->budget_eval_strategy) {
++              case PSE_BUDGET_EVAL_STRAT_STATIC:
++                      status->prio_max = pcdev->nr_lines - 1;
++                      status->prio = pi->prio;
++                      break;
++              case PSE_BUDGET_EVAL_STRAT_DYNAMIC:
++                      status->prio_max = pcdev->pis_prio_max;
++                      if (ops->pi_get_prio) {
++                              ret = ops->pi_get_prio(pcdev, psec->id);
++                              if (ret < 0)
++                                      goto out;
++
++                              status->prio = ret;
++                      }
++                      break;
++              default:
++                      break;
++              }
++      }
+       ret = ops->pi_get_pw_status(pcdev, psec->id, &pw_status);
+       if (ret)
+@@ -1271,6 +1790,52 @@ int pse_ethtool_set_config(struct pse_co
+ EXPORT_SYMBOL_GPL(pse_ethtool_set_config);
+ /**
++ * pse_pi_update_pw_budget - Update PSE power budget allocated with new
++ *                         power in mW
++ * @pcdev: a pointer to the PSE controller device
++ * @id: index of the PSE PI
++ * @pw_req: power requested
++ * @extack: extack for reporting useful error messages
++ *
++ * Return: Previous power allocated on success and failure value on error
++ */
++static int pse_pi_update_pw_budget(struct pse_controller_dev *pcdev, int id,
++                                 const unsigned int pw_req,
++                                 struct netlink_ext_ack *extack)
++{
++      struct pse_pi *pi = &pcdev->pi[id];
++      int previous_pw_allocated;
++      int pw_diff, ret = 0;
++
++      /* We don't want pw_allocated_mW value change in the middle of an
++       * power budget update
++       */
++      mutex_lock(&pcdev->lock);
++      previous_pw_allocated = pi->pw_allocated_mW;
++      pw_diff = pw_req - previous_pw_allocated;
++      if (!pw_diff) {
++              goto out;
++      } else if (pw_diff > 0) {
++              ret = regulator_request_power_budget(pi->pw_d->supply, pw_diff);
++              if (ret) {
++                      NL_SET_ERR_MSG_FMT(extack,
++                                         "PI %d: not enough power budget available",
++                                         id);
++                      goto out;
++              }
++
++      } else {
++              regulator_free_power_budget(pi->pw_d->supply, -pw_diff);
++      }
++      pi->pw_allocated_mW = pw_req;
++      ret = previous_pw_allocated;
++
++out:
++      mutex_unlock(&pcdev->lock);
++      return ret;
++}
++
++/**
+  * pse_ethtool_set_pw_limit - set PSE control power limit
+  * @psec: PSE control pointer
+  * @extack: extack for reporting useful error messages
+@@ -1282,7 +1847,7 @@ int pse_ethtool_set_pw_limit(struct pse_
+                            struct netlink_ext_ack *extack,
+                            const unsigned int pw_limit)
+ {
+-      int uV, uA, ret;
++      int uV, uA, ret, previous_pw_allocated = 0;
+       s64 tmp_64;
+       if (pw_limit > MAX_PI_PW)
+@@ -1306,10 +1871,100 @@ int pse_ethtool_set_pw_limit(struct pse_
+       /* uA = mW * 1000000000 / uV */
+       uA = DIV_ROUND_CLOSEST_ULL(tmp_64, uV);
+-      return regulator_set_current_limit(psec->ps, 0, uA);
++      /* Update power budget only in software power control case and
++       * if a Power Device is powered.
++       */
++      if (pse_pw_d_is_sw_pw_control(psec->pcdev,
++                                    psec->pcdev->pi[psec->id].pw_d) &&
++          psec->pcdev->pi[psec->id].admin_state_enabled &&
++          psec->pcdev->pi[psec->id].isr_pd_detected) {
++              ret = pse_pi_update_pw_budget(psec->pcdev, psec->id,
++                                            pw_limit, extack);
++              if (ret < 0)
++                      return ret;
++              previous_pw_allocated = ret;
++      }
++
++      ret = regulator_set_current_limit(psec->ps, 0, uA);
++      if (ret < 0 && previous_pw_allocated) {
++              pse_pi_update_pw_budget(psec->pcdev, psec->id,
++                                      previous_pw_allocated, extack);
++      }
++
++      return ret;
+ }
+ EXPORT_SYMBOL_GPL(pse_ethtool_set_pw_limit);
++/**
++ * pse_ethtool_set_prio - Set PSE PI priority according to the budget
++ *                      evaluation strategy
++ * @psec: PSE control pointer
++ * @extack: extack for reporting useful error messages
++ * @prio: priovity value
++ *
++ * Return: 0 on success and failure value on error
++ */
++int pse_ethtool_set_prio(struct pse_control *psec,
++                       struct netlink_ext_ack *extack,
++                       unsigned int prio)
++{
++      struct pse_controller_dev *pcdev = psec->pcdev;
++      const struct pse_controller_ops *ops;
++      int ret = 0;
++
++      if (!pcdev->pi[psec->id].pw_d) {
++              NL_SET_ERR_MSG(extack, "no power domain attached");
++              return -EOPNOTSUPP;
++      }
++
++      /* We don't want priority change in the middle of an
++       * enable/disable call or a priority mode change
++       */
++      mutex_lock(&pcdev->lock);
++      switch (pcdev->pi[psec->id].pw_d->budget_eval_strategy) {
++      case PSE_BUDGET_EVAL_STRAT_STATIC:
++              if (prio >= pcdev->nr_lines) {
++                      NL_SET_ERR_MSG_FMT(extack,
++                                         "priority %d exceed priority max %d",
++                                         prio, pcdev->nr_lines);
++                      ret = -ERANGE;
++                      goto out;
++              }
++
++              pcdev->pi[psec->id].prio = prio;
++              pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[psec->id].pw_d);
++              break;
++
++      case PSE_BUDGET_EVAL_STRAT_DYNAMIC:
++              ops = psec->pcdev->ops;
++              if (!ops->pi_set_prio) {
++                      NL_SET_ERR_MSG(extack,
++                                     "pse driver does not support setting port priority");
++                      ret = -EOPNOTSUPP;
++                      goto out;
++              }
++
++              if (prio > pcdev->pis_prio_max) {
++                      NL_SET_ERR_MSG_FMT(extack,
++                                         "priority %d exceed priority max %d",
++                                         prio, pcdev->pis_prio_max);
++                      ret = -ERANGE;
++                      goto out;
++              }
++
++              ret = ops->pi_set_prio(pcdev, psec->id, prio);
++              break;
++
++      default:
++              ret = -EOPNOTSUPP;
++      }
++
++out:
++      mutex_unlock(&pcdev->lock);
++      return ret;
++}
++EXPORT_SYMBOL_GPL(pse_ethtool_set_prio);
++
+ bool pse_has_podl(struct pse_control *psec)
+ {
+       return psec->pcdev->types & ETHTOOL_PSE_PODL;
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -6,6 +6,8 @@
+ #define _LINUX_PSE_CONTROLLER_H
+ #include <linux/list.h>
++#include <linux/netlink.h>
++#include <linux/kfifo.h>
+ #include <uapi/linux/ethtool.h>
+ #include <uapi/linux/ethtool_netlink_generated.h>
+ #include <linux/regulator/driver.h>
+@@ -134,6 +136,9 @@ struct pse_pw_limit_ranges {
+  *    is in charge of the memory allocation
+  * @c33_pw_limit_nb_ranges: number of supported power limit configuration
+  *    ranges
++ * @prio_max: max priority allowed for the c33_prio variable value.
++ * @prio: priority of the PSE. Managed by PSE core in case of static budget
++ *    evaluation strategy.
+  */
+ struct ethtool_pse_control_status {
+       u32 pw_d_id;
+@@ -147,6 +152,8 @@ struct ethtool_pse_control_status {
+       u32 c33_avail_pw_limit;
+       struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges;
+       u32 c33_pw_limit_nb_ranges;
++      u32 prio_max;
++      u32 prio;
+ };
+ /**
+@@ -170,6 +177,11 @@ struct ethtool_pse_control_status {
+  *                        range. The driver is in charge of the memory
+  *                        allocation and should return the number of
+  *                        ranges.
++ * @pi_get_prio: Get the PSE PI priority.
++ * @pi_set_prio: Configure the PSE PI priority.
++ * @pi_get_pw_req: Get the power requested by a PD before enabling the PSE PI.
++ *               This is only relevant when an interrupt is registered using
++ *               devm_pse_irq_helper helper.
+  */
+ struct pse_controller_ops {
+       int (*setup_pi_matrix)(struct pse_controller_dev *pcdev);
+@@ -190,6 +202,10 @@ struct pse_controller_ops {
+                              int id, int max_mW);
+       int (*pi_get_pw_limit_ranges)(struct pse_controller_dev *pcdev, int id,
+                                     struct pse_pw_limit_ranges *pw_limit_ranges);
++      int (*pi_get_prio)(struct pse_controller_dev *pcdev, int id);
++      int (*pi_set_prio)(struct pse_controller_dev *pcdev, int id,
++                         unsigned int prio);
++      int (*pi_get_pw_req)(struct pse_controller_dev *pcdev, int id);
+ };
+ struct module;
+@@ -225,6 +241,13 @@ struct pse_pi_pairset {
+  * @rdev: regulator represented by the PSE PI
+  * @admin_state_enabled: PI enabled state
+  * @pw_d: Power domain of the PSE PI
++ * @prio: Priority of the PSE PI. Used in static budget evaluation strategy
++ * @isr_pd_detected: PSE PI detection status managed by the interruption
++ *                 handler. This variable is relevant when the power enabled
++ *                 management is managed in software like the static
++ *                 budget evaluation strategy.
++ * @pw_allocated_mW: Power allocated to a PSE PI to manage power budget in
++ *                 static budget evaluation strategy.
+  */
+ struct pse_pi {
+       struct pse_pi_pairset pairset[2];
+@@ -232,6 +255,20 @@ struct pse_pi {
+       struct regulator_dev *rdev;
+       bool admin_state_enabled;
+       struct pse_power_domain *pw_d;
++      int prio;
++      bool isr_pd_detected;
++      int pw_allocated_mW;
++};
++
++/**
++ * struct pse_ntf - PSE notification element
++ *
++ * @id: ID of the PSE control
++ * @notifs: PSE notifications to be reported
++ */
++struct pse_ntf {
++      int id;
++      unsigned long notifs;
+ };
+ /**
+@@ -249,6 +286,12 @@ struct pse_pi {
+  * @pi: table of PSE PIs described in this controller device
+  * @no_of_pse_pi: flag set if the pse_pis devicetree node is not used
+  * @irq: PSE interrupt
++ * @pis_prio_max: Maximum value allowed for the PSE PIs priority
++ * @supp_budget_eval_strategies: budget evaluation strategies supported
++ *                             by the PSE
++ * @ntf_work: workqueue for PSE notification management
++ * @ntf_fifo: PSE notifications FIFO
++ * @ntf_fifo_lock: protect @ntf_fifo writer
+  */
+ struct pse_controller_dev {
+       const struct pse_controller_ops *ops;
+@@ -263,6 +306,29 @@ struct pse_controller_dev {
+       struct pse_pi *pi;
+       bool no_of_pse_pi;
+       int irq;
++      unsigned int pis_prio_max;
++      u32 supp_budget_eval_strategies;
++      struct work_struct ntf_work;
++      DECLARE_KFIFO_PTR(ntf_fifo, struct pse_ntf);
++      spinlock_t ntf_fifo_lock; /* Protect @ntf_fifo writer */
++};
++
++/**
++ * enum pse_budget_eval_strategies - PSE budget evaluation strategies.
++ * @PSE_BUDGET_EVAL_STRAT_DISABLED: Budget evaluation strategy disabled.
++ * @PSE_BUDGET_EVAL_STRAT_STATIC: PSE static budget evaluation strategy.
++ *    Budget evaluation strategy based on the power requested during PD
++ *    classification. This strategy is managed by the PSE core.
++ * @PSE_BUDGET_EVAL_STRAT_DYNAMIC: PSE dynamic budget evaluation
++ *    strategy. Budget evaluation strategy based on the current consumption
++ *    per ports compared to the total power budget. This mode is managed by
++ *    the PSE controller.
++ */
++
++enum pse_budget_eval_strategies {
++      PSE_BUDGET_EVAL_STRAT_DISABLED  = 1 << 0,
++      PSE_BUDGET_EVAL_STRAT_STATIC    = 1 << 1,
++      PSE_BUDGET_EVAL_STRAT_DYNAMIC   = 1 << 2,
+ };
+ #if IS_ENABLED(CONFIG_PSE_CONTROLLER)
+@@ -287,6 +353,9 @@ int pse_ethtool_set_config(struct pse_co
+ int pse_ethtool_set_pw_limit(struct pse_control *psec,
+                            struct netlink_ext_ack *extack,
+                            const unsigned int pw_limit);
++int pse_ethtool_set_prio(struct pse_control *psec,
++                       struct netlink_ext_ack *extack,
++                       unsigned int prio);
+ int pse_ethtool_get_pw_limit(struct pse_control *psec,
+                            struct netlink_ext_ack *extack);
+@@ -331,6 +400,13 @@ static inline int pse_ethtool_get_pw_lim
+ {
+       return -EOPNOTSUPP;
+ }
++
++static inline int pse_ethtool_set_prio(struct pse_control *psec,
++                                     struct netlink_ext_ack *extack,
++                                     unsigned int prio)
++{
++      return -EOPNOTSUPP;
++}
+ static inline bool pse_has_podl(struct pse_control *psec)
+ {
diff --git a/target/linux/generic/backport-6.12/626-16b-v6.17-regulator-Add-support-for-power-budget.patch b/target/linux/generic/backport-6.12/626-16b-v6.17-regulator-Add-support-for-power-budget.patch
new file mode 100644 (file)
index 0000000..b77f96d
--- /dev/null
@@ -0,0 +1,257 @@
+From 42d7c87b4e1251f36eceac987e74623e7cda8577 Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@bootlin.com>
+Date: Wed, 15 Jan 2025 15:41:57 +0100
+Subject: [PATCH] regulator: Add support for power budget
+
+Introduce power budget management for the regulator device. Enable tracking
+of available power capacity by providing helpers to request and release
+power budget allocations.
+
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Link: https://patch.msgid.link/20250115-feature_regulator_pw_budget-v2-1-0a44b949e6bc@bootlin.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Signed-off-by: Bevan Weiss <bevan.weiss@gmail.com>
+---
+ drivers/regulator/core.c           | 114 +++++++++++++++++++++++++++++
+ drivers/regulator/of_regulator.c   |   3 +
+ include/linux/regulator/consumer.h |  21 ++++++
+ include/linux/regulator/driver.h   |   2 +
+ include/linux/regulator/machine.h  |   2 +
+ 5 files changed, 142 insertions(+)
+
+--- a/drivers/regulator/core.c
++++ b/drivers/regulator/core.c
+@@ -916,6 +916,26 @@ static ssize_t bypass_show(struct device
+ }
+ static DEVICE_ATTR_RO(bypass);
++static ssize_t power_budget_milliwatt_show(struct device *dev,
++                                         struct device_attribute *attr,
++                                         char *buf)
++{
++      struct regulator_dev *rdev = dev_get_drvdata(dev);
++
++      return sprintf(buf, "%d\n", rdev->constraints->pw_budget_mW);
++}
++static DEVICE_ATTR_RO(power_budget_milliwatt);
++
++static ssize_t power_requested_milliwatt_show(struct device *dev,
++                                            struct device_attribute *attr,
++                                            char *buf)
++{
++      struct regulator_dev *rdev = dev_get_drvdata(dev);
++
++      return sprintf(buf, "%d\n", rdev->pw_requested_mW);
++}
++static DEVICE_ATTR_RO(power_requested_milliwatt);
++
+ #define REGULATOR_ERROR_ATTR(name, bit)                                                       \
+       static ssize_t name##_show(struct device *dev, struct device_attribute *attr,   \
+                                  char *buf)                                           \
+@@ -1148,6 +1168,10 @@ static void print_constraints_debug(stru
+       if (constraints->valid_modes_mask & REGULATOR_MODE_STANDBY)
+               count += scnprintf(buf + count, len - count, "standby ");
++      if (constraints->pw_budget_mW)
++              count += scnprintf(buf + count, len - count, "%d mW budget",
++                                 constraints->pw_budget_mW);
++
+       if (!count)
+               count = scnprintf(buf, len, "no parameters");
+       else
+@@ -1636,6 +1660,9 @@ static int set_machine_constraints(struc
+               rdev->last_off = ktime_get();
+       }
++      if (!rdev->constraints->pw_budget_mW)
++              rdev->constraints->pw_budget_mW = INT_MAX;
++
+       print_constraints(rdev);
+       return 0;
+ }
+@@ -4651,6 +4678,87 @@ int regulator_get_current_limit(struct r
+ EXPORT_SYMBOL_GPL(regulator_get_current_limit);
+ /**
++ * regulator_get_unclaimed_power_budget - get regulator unclaimed power budget
++ * @regulator: regulator source
++ *
++ * Return: Unclaimed power budget of the regulator in mW.
++ */
++int regulator_get_unclaimed_power_budget(struct regulator *regulator)
++{
++      return regulator->rdev->constraints->pw_budget_mW -
++             regulator->rdev->pw_requested_mW;
++}
++EXPORT_SYMBOL_GPL(regulator_get_unclaimed_power_budget);
++
++/**
++ * regulator_request_power_budget - request power budget on a regulator
++ * @regulator: regulator source
++ * @pw_req: Power requested
++ *
++ * Return: 0 on success or a negative error number on failure.
++ */
++int regulator_request_power_budget(struct regulator *regulator,
++                                 unsigned int pw_req)
++{
++      struct regulator_dev *rdev = regulator->rdev;
++      int ret = 0, pw_tot_req;
++
++      regulator_lock(rdev);
++      if (rdev->supply) {
++              ret = regulator_request_power_budget(rdev->supply, pw_req);
++              if (ret < 0)
++                      goto out;
++      }
++
++      pw_tot_req = rdev->pw_requested_mW + pw_req;
++      if (pw_tot_req > rdev->constraints->pw_budget_mW) {
++              rdev_warn(rdev, "power requested %d mW out of budget %d mW",
++                        pw_req,
++                        rdev->constraints->pw_budget_mW - rdev->pw_requested_mW);
++              regulator_notifier_call_chain(rdev,
++                                            REGULATOR_EVENT_OVER_CURRENT_WARN,
++                                            NULL);
++              ret = -ERANGE;
++              goto out;
++      }
++
++      rdev->pw_requested_mW = pw_tot_req;
++out:
++      regulator_unlock(rdev);
++      return ret;
++}
++EXPORT_SYMBOL_GPL(regulator_request_power_budget);
++
++/**
++ * regulator_free_power_budget - free power budget on a regulator
++ * @regulator: regulator source
++ * @pw: Power to be released.
++ *
++ * Return: Power budget of the regulator in mW.
++ */
++void regulator_free_power_budget(struct regulator *regulator,
++                               unsigned int pw)
++{
++      struct regulator_dev *rdev = regulator->rdev;
++      int pw_tot_req;
++
++      regulator_lock(rdev);
++      if (rdev->supply)
++              regulator_free_power_budget(rdev->supply, pw);
++
++      pw_tot_req = rdev->pw_requested_mW - pw;
++      if (pw_tot_req >= 0)
++              rdev->pw_requested_mW = pw_tot_req;
++      else
++              rdev_warn(rdev,
++                        "too much power freed %d mW (already requested %d mW)",
++                        pw, rdev->pw_requested_mW);
++
++      regulator_unlock(rdev);
++}
++EXPORT_SYMBOL_GPL(regulator_free_power_budget);
++
++/**
+  * regulator_set_mode - set regulator operating mode
+  * @regulator: regulator source
+  * @mode: operating mode - one of the REGULATOR_MODE constants
+@@ -5288,6 +5396,8 @@ static struct attribute *regulator_dev_a
+       &dev_attr_suspend_standby_mode.attr,
+       &dev_attr_suspend_mem_mode.attr,
+       &dev_attr_suspend_disk_mode.attr,
++      &dev_attr_power_budget_milliwatt.attr,
++      &dev_attr_power_requested_milliwatt.attr,
+       NULL
+ };
+@@ -5369,6 +5479,10 @@ static umode_t regulator_attr_is_visible
+           attr == &dev_attr_suspend_disk_mode.attr)
+               return ops->set_suspend_mode ? mode : 0;
++      if (attr == &dev_attr_power_budget_milliwatt.attr ||
++          attr == &dev_attr_power_requested_milliwatt.attr)
++              return rdev->constraints->pw_budget_mW != INT_MAX ? mode : 0;
++
+       return mode;
+ }
+--- a/drivers/regulator/of_regulator.c
++++ b/drivers/regulator/of_regulator.c
+@@ -125,6 +125,9 @@ static int of_get_regulation_constraints
+       if (constraints->min_uA != constraints->max_uA)
+               constraints->valid_ops_mask |= REGULATOR_CHANGE_CURRENT;
++      if (!of_property_read_u32(np, "regulator-power-budget-milliwatt", &pval))
++              constraints->pw_budget_mW = pval;
++
+       constraints->boot_on = of_property_read_bool(np, "regulator-boot-on");
+       constraints->always_on = of_property_read_bool(np, "regulator-always-on");
+       if (!constraints->always_on) /* status change should be possible. */
+--- a/include/linux/regulator/consumer.h
++++ b/include/linux/regulator/consumer.h
+@@ -235,6 +235,11 @@ int regulator_sync_voltage(struct regula
+ int regulator_set_current_limit(struct regulator *regulator,
+                              int min_uA, int max_uA);
+ int regulator_get_current_limit(struct regulator *regulator);
++int regulator_get_unclaimed_power_budget(struct regulator *regulator);
++int regulator_request_power_budget(struct regulator *regulator,
++                                 unsigned int pw_req);
++void regulator_free_power_budget(struct regulator *regulator,
++                               unsigned int pw);
+ int regulator_set_mode(struct regulator *regulator, unsigned int mode);
+ unsigned int regulator_get_mode(struct regulator *regulator);
+@@ -534,6 +539,22 @@ static inline int regulator_get_current_
+       return 0;
+ }
++static inline int regulator_get_unclaimed_power_budget(struct regulator *regulator)
++{
++      return INT_MAX;
++}
++
++static inline int regulator_request_power_budget(struct regulator *regulator,
++                                               unsigned int pw_req)
++{
++      return -EOPNOTSUPP;
++}
++
++static inline void regulator_free_power_budget(struct regulator *regulator,
++                                             unsigned int pw)
++{
++}
++
+ static inline int regulator_set_mode(struct regulator *regulator,
+       unsigned int mode)
+ {
+--- a/include/linux/regulator/driver.h
++++ b/include/linux/regulator/driver.h
+@@ -649,6 +649,8 @@ struct regulator_dev {
+       int cached_err;
+       bool use_cached_err;
+       spinlock_t err_lock;
++
++      int pw_requested_mW;
+ };
+ /*
+--- a/include/linux/regulator/machine.h
++++ b/include/linux/regulator/machine.h
+@@ -113,6 +113,7 @@ struct notification_limit {
+  * @min_uA: Smallest current consumers may set.
+  * @max_uA: Largest current consumers may set.
+  * @ilim_uA: Maximum input current.
++ * @pw_budget_mW: Power budget for the regulator in mW.
+  * @system_load: Load that isn't captured by any consumer requests.
+  *
+  * @over_curr_limits:         Limits for acting on over current.
+@@ -185,6 +186,7 @@ struct regulation_constraints {
+       int max_uA;
+       int ilim_uA;
++      int pw_budget_mW;
+       int system_load;
+       /* used for coupled regulators */
diff --git a/target/linux/generic/backport-6.12/626-17-v6.17-net-pse-pd-pd692x0-Add-PSE-PI-priority-feature.patch b/target/linux/generic/backport-6.12/626-17-v6.17-net-pse-pd-pd692x0-Add-PSE-PI-priority-feature.patch
new file mode 100644 (file)
index 0000000..6e1f2d6
--- /dev/null
@@ -0,0 +1,326 @@
+From 359754013e6a7fc81af6735ebbfedd4a01999f68 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:08 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Add support for PSE PI priority feature
+
+This patch extends the PSE callbacks by adding support for the newly
+introduced pi_set_prio() callback, enabling the configuration of PSE PI
+priorities. The current port priority is now also included in the status
+information returned to users.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-9-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 205 +++++++++++++++++++++++++++++++++++
+ 1 file changed, 205 insertions(+)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -12,6 +12,8 @@
+ #include <linux/of.h>
+ #include <linux/platform_device.h>
+ #include <linux/pse-pd/pse.h>
++#include <linux/regulator/driver.h>
++#include <linux/regulator/machine.h>
+ #define PD692X0_PSE_NAME "pd692x0_pse"
+@@ -76,6 +78,8 @@ enum {
+       PD692X0_MSG_GET_PORT_CLASS,
+       PD692X0_MSG_GET_PORT_MEAS,
+       PD692X0_MSG_GET_PORT_PARAM,
++      PD692X0_MSG_GET_POWER_BANK,
++      PD692X0_MSG_SET_POWER_BANK,
+       /* add new message above here */
+       PD692X0_MSG_CNT
+@@ -95,6 +99,8 @@ struct pd692x0_priv {
+       unsigned long last_cmd_key_time;
+       enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS];
++      struct regulator_dev *manager_reg[PD692X0_MAX_MANAGERS];
++      int manager_pw_budget[PD692X0_MAX_MANAGERS];
+ };
+ /* Template list of communication messages. The non-null bytes defined here
+@@ -170,6 +176,16 @@ static const struct pd692x0_msg pd692x0_
+               .data = {0x4e, 0x4e, 0x4e, 0x4e,
+                        0x4e, 0x4e, 0x4e, 0x4e},
+       },
++      [PD692X0_MSG_GET_POWER_BANK] = {
++              .key = PD692X0_KEY_REQ,
++              .sub = {0x07, 0x0b, 0x57},
++              .data = {   0, 0x4e, 0x4e, 0x4e,
++                       0x4e, 0x4e, 0x4e, 0x4e},
++      },
++      [PD692X0_MSG_SET_POWER_BANK] = {
++              .key = PD692X0_KEY_CMD,
++              .sub = {0x07, 0x0b, 0x57},
++      },
+ };
+ static u8 pd692x0_build_msg(struct pd692x0_msg *msg, u8 echo)
+@@ -739,6 +755,29 @@ pd692x0_pi_get_actual_pw(struct pse_cont
+       return (buf.data[0] << 4 | buf.data[1]) * 100;
+ }
++static int
++pd692x0_pi_get_prio(struct pse_controller_dev *pcdev, int id)
++{
++      struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
++      struct pd692x0_msg msg, buf = {0};
++      int ret;
++
++      ret = pd692x0_fw_unavailable(priv);
++      if (ret)
++              return ret;
++
++      msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM];
++      msg.sub[2] = id;
++      ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
++      if (ret < 0)
++              return ret;
++      if (!buf.data[2] || buf.data[2] > pcdev->pis_prio_max + 1)
++              return -ERANGE;
++
++      /* PSE core priority start at 0 */
++      return buf.data[2] - 1;
++}
++
+ static struct pd692x0_msg_ver pd692x0_get_sw_version(struct pd692x0_priv *priv)
+ {
+       struct device *dev = &priv->client->dev;
+@@ -766,6 +805,7 @@ static struct pd692x0_msg_ver pd692x0_ge
+ struct pd692x0_manager {
+       struct device_node *port_node[PD692X0_MAX_MANAGER_PORTS];
++      struct device_node *node;
+       int nports;
+ };
+@@ -857,6 +897,8 @@ pd692x0_of_get_managers(struct pd692x0_p
+               if (ret)
+                       goto out;
++              of_node_get(node);
++              manager[manager_id].node = node;
+               nmanagers++;
+       }
+@@ -869,6 +911,8 @@ out:
+                       of_node_put(manager[i].port_node[j]);
+                       manager[i].port_node[j] = NULL;
+               }
++              of_node_put(manager[i].node);
++              manager[i].node = NULL;
+       }
+       of_node_put(node);
+@@ -876,6 +920,130 @@ out:
+       return ret;
+ }
++static const struct regulator_ops dummy_ops;
++
++static struct regulator_dev *
++pd692x0_register_manager_regulator(struct device *dev, char *reg_name,
++                                 struct device_node *node)
++{
++      struct regulator_init_data *rinit_data;
++      struct regulator_config rconfig = {0};
++      struct regulator_desc *rdesc;
++      struct regulator_dev *rdev;
++
++      rinit_data = devm_kzalloc(dev, sizeof(*rinit_data),
++                                GFP_KERNEL);
++      if (!rinit_data)
++              return ERR_PTR(-ENOMEM);
++
++      rdesc = devm_kzalloc(dev, sizeof(*rdesc), GFP_KERNEL);
++      if (!rdesc)
++              return ERR_PTR(-ENOMEM);
++
++      rdesc->name = reg_name;
++      rdesc->type = REGULATOR_VOLTAGE;
++      rdesc->ops = &dummy_ops;
++      rdesc->owner = THIS_MODULE;
++
++      rinit_data->supply_regulator = "vmain";
++
++      rconfig.dev = dev;
++      rconfig.init_data = rinit_data;
++      rconfig.of_node = node;
++
++      rdev = devm_regulator_register(dev, rdesc, &rconfig);
++      if (IS_ERR(rdev)) {
++              dev_err_probe(dev, PTR_ERR(rdev),
++                            "Failed to register regulator\n");
++              return rdev;
++      }
++
++      return rdev;
++}
++
++static int
++pd692x0_register_managers_regulator(struct pd692x0_priv *priv,
++                                  const struct pd692x0_manager *manager,
++                                  int nmanagers)
++{
++      struct device *dev = &priv->client->dev;
++      size_t reg_name_len;
++      int i;
++
++      /* Each regulator name len is dev name + 12 char +
++       * int max digit number (10) + 1
++       */
++      reg_name_len = strlen(dev_name(dev)) + 23;
++
++      for (i = 0; i < nmanagers; i++) {
++              struct regulator_dev *rdev;
++              char *reg_name;
++
++              reg_name = devm_kzalloc(dev, reg_name_len, GFP_KERNEL);
++              if (!reg_name)
++                      return -ENOMEM;
++              snprintf(reg_name, 26, "pse-%s-manager%d", dev_name(dev), i);
++              rdev = pd692x0_register_manager_regulator(dev, reg_name,
++                                                        manager[i].node);
++              if (IS_ERR(rdev))
++                      return PTR_ERR(rdev);
++
++              priv->manager_reg[i] = rdev;
++      }
++
++      return 0;
++}
++
++static int
++pd692x0_conf_manager_power_budget(struct pd692x0_priv *priv, int id, int pw)
++{
++      struct pd692x0_msg msg, buf;
++      int ret, pw_mW = pw / 1000;
++
++      msg = pd692x0_msg_template_list[PD692X0_MSG_GET_POWER_BANK];
++      msg.data[0] = id;
++      ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
++      if (ret < 0)
++              return ret;
++
++      msg = pd692x0_msg_template_list[PD692X0_MSG_SET_POWER_BANK];
++      msg.data[0] = id;
++      msg.data[1] = pw_mW >> 8;
++      msg.data[2] = pw_mW & 0xff;
++      msg.data[3] = buf.sub[2];
++      msg.data[4] = buf.data[0];
++      msg.data[5] = buf.data[1];
++      msg.data[6] = buf.data[2];
++      msg.data[7] = buf.data[3];
++      return pd692x0_sendrecv_msg(priv, &msg, &buf);
++}
++
++static int
++pd692x0_configure_managers(struct pd692x0_priv *priv, int nmanagers)
++{
++      int i, ret;
++
++      for (i = 0; i < nmanagers; i++) {
++              struct regulator *supply = priv->manager_reg[i]->supply;
++              int pw_budget;
++
++              pw_budget = regulator_get_unclaimed_power_budget(supply);
++              /* Max power budget per manager */
++              if (pw_budget > 6000000)
++                      pw_budget = 6000000;
++              ret = regulator_request_power_budget(supply, pw_budget);
++              if (ret < 0)
++                      return ret;
++
++              priv->manager_pw_budget[i] = pw_budget;
++              ret = pd692x0_conf_manager_power_budget(priv, i, pw_budget);
++              if (ret < 0)
++                      return ret;
++      }
++
++      return 0;
++}
++
+ static int
+ pd692x0_set_port_matrix(const struct pse_pi_pairset *pairset,
+                       const struct pd692x0_manager *manager,
+@@ -998,6 +1166,14 @@ static int pd692x0_setup_pi_matrix(struc
+               return ret;
+       nmanagers = ret;
++      ret = pd692x0_register_managers_regulator(priv, manager, nmanagers);
++      if (ret)
++              goto out;
++
++      ret = pd692x0_configure_managers(priv, nmanagers);
++      if (ret)
++              goto out;
++
+       ret = pd692x0_set_ports_matrix(priv, manager, nmanagers, port_matrix);
+       if (ret)
+               goto out;
+@@ -1008,8 +1184,14 @@ static int pd692x0_setup_pi_matrix(struc
+ out:
+       for (i = 0; i < nmanagers; i++) {
++              struct regulator *supply = priv->manager_reg[i]->supply;
++
++              regulator_free_power_budget(supply,
++                                          priv->manager_pw_budget[i]);
++
+               for (j = 0; j < manager[i].nports; j++)
+                       of_node_put(manager[i].port_node[j]);
++              of_node_put(manager[i].node);
+       }
+       return ret;
+ }
+@@ -1071,6 +1253,25 @@ static int pd692x0_pi_set_pw_limit(struc
+       return pd692x0_sendrecv_msg(priv, &msg, &buf);
+ }
++static int pd692x0_pi_set_prio(struct pse_controller_dev *pcdev, int id,
++                             unsigned int prio)
++{
++      struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
++      struct pd692x0_msg msg, buf = {0};
++      int ret;
++
++      ret = pd692x0_fw_unavailable(priv);
++      if (ret)
++              return ret;
++
++      msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM];
++      msg.sub[2] = id;
++      /* Controller priority from 1 to 3 */
++      msg.data[4] = prio + 1;
++
++      return pd692x0_sendrecv_msg(priv, &msg, &buf);
++}
++
+ static const struct pse_controller_ops pd692x0_ops = {
+       .setup_pi_matrix = pd692x0_setup_pi_matrix,
+       .pi_get_admin_state = pd692x0_pi_get_admin_state,
+@@ -1084,6 +1285,8 @@ static const struct pse_controller_ops p
+       .pi_get_pw_limit = pd692x0_pi_get_pw_limit,
+       .pi_set_pw_limit = pd692x0_pi_set_pw_limit,
+       .pi_get_pw_limit_ranges = pd692x0_pi_get_pw_limit_ranges,
++      .pi_get_prio = pd692x0_pi_get_prio,
++      .pi_set_prio = pd692x0_pi_set_prio,
+ };
+ #define PD692X0_FW_LINE_MAX_SZ 0xff
+@@ -1500,6 +1703,8 @@ static int pd692x0_i2c_probe(struct i2c_
+       priv->pcdev.ops = &pd692x0_ops;
+       priv->pcdev.dev = dev;
+       priv->pcdev.types = ETHTOOL_PSE_C33;
++      priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC;
++      priv->pcdev.pis_prio_max = 2;
+       ret = devm_pse_controller_register(dev, &priv->pcdev);
+       if (ret)
+               return dev_err_probe(dev, ret,
diff --git a/target/linux/generic/backport-6.12/626-18-v6.17-net-pse-pd-pd692x0-Add-controller-and-manager-power.patch b/target/linux/generic/backport-6.12/626-18-v6.17-net-pse-pd-pd692x0-Add-controller-and-manager-power.patch
new file mode 100644 (file)
index 0000000..a93dca0
--- /dev/null
@@ -0,0 +1,71 @@
+From 24a4e3a05dd0eadd0c9585c411880e5dcb6be97f Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:09 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Add support for controller and manager
+ power supplies
+
+Add support for managing the VDD and VDDA power supplies for the PD692x0
+PSE controller, as well as the VAUX5 and VAUX3P3 power supplies for the
+PD6920x PSE managers.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-10-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 20 ++++++++++++++++++++
+ 1 file changed, 20 insertions(+)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -976,8 +976,10 @@ pd692x0_register_managers_regulator(stru
+       reg_name_len = strlen(dev_name(dev)) + 23;
+       for (i = 0; i < nmanagers; i++) {
++              static const char * const regulators[] = { "vaux5", "vaux3p3" };
+               struct regulator_dev *rdev;
+               char *reg_name;
++              int ret;
+               reg_name = devm_kzalloc(dev, reg_name_len, GFP_KERNEL);
+               if (!reg_name)
+@@ -988,6 +990,17 @@ pd692x0_register_managers_regulator(stru
+               if (IS_ERR(rdev))
+                       return PTR_ERR(rdev);
++              /* VMAIN is described as main supply for the manager.
++               * Add other VAUX power supplies and link them to the
++               * virtual device rdev->dev.
++               */
++              ret = devm_regulator_bulk_get_enable(&rdev->dev,
++                                                   ARRAY_SIZE(regulators),
++                                                   regulators);
++              if (ret)
++                      return dev_err_probe(&rdev->dev, ret,
++                                           "Failed to enable regulators\n");
++
+               priv->manager_reg[i] = rdev;
+       }
+@@ -1640,6 +1653,7 @@ static const struct fw_upload_ops pd692x
+ static int pd692x0_i2c_probe(struct i2c_client *client)
+ {
++      static const char * const regulators[] = { "vdd", "vdda" };
+       struct pd692x0_msg msg, buf = {0}, zero = {0};
+       struct device *dev = &client->dev;
+       struct pd692x0_msg_ver ver;
+@@ -1647,6 +1661,12 @@ static int pd692x0_i2c_probe(struct i2c_
+       struct fw_upload *fwl;
+       int ret;
++      ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulators),
++                                           regulators);
++      if (ret)
++              return dev_err_probe(dev, ret,
++                                   "Failed to enable regulators\n");
++
+       if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+               dev_err(dev, "i2c check functionality failed\n");
+               return -ENXIO;
diff --git a/target/linux/generic/backport-6.12/626-19-v6.17-net-pse-pd-tps23881-Add-static-port-priority-feature.patch b/target/linux/generic/backport-6.12/626-19-v6.17-net-pse-pd-tps23881-Add-static-port-priority-feature.patch
new file mode 100644 (file)
index 0000000..739233a
--- /dev/null
@@ -0,0 +1,392 @@
+From 56cfc97635e9164395c9242f72746454347155ab Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:11 +0200
+Subject: [PATCH] net: pse-pd: tps23881: Add support for static port priority
+ feature
+
+This patch enhances PSE callbacks by introducing support for the static
+port priority feature. It extends interrupt management to handle and report
+detection, classification, and disconnection events. Additionally, it
+introduces the pi_get_pw_req() callback, which provides information about
+the power requested by the Powered Devices.
+
+Interrupt support is essential for the proper functioning of the TPS23881
+controller. Without it, after a power-on (PWON), the controller will
+no longer perform detection and classification. This could lead to
+potential hazards, such as connecting a non-PoE device after a PoE device,
+which might result in magic smoke.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-12-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 244 +++++++++++++++++++++++++++++++---
+ 1 file changed, 228 insertions(+), 16 deletions(-)
+
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -20,20 +20,30 @@
+ #define TPS23881_REG_IT               0x0
+ #define TPS23881_REG_IT_MASK  0x1
++#define TPS23881_REG_IT_DISF  BIT(2)
++#define TPS23881_REG_IT_DETC  BIT(3)
++#define TPS23881_REG_IT_CLASC BIT(4)
+ #define TPS23881_REG_IT_IFAULT        BIT(5)
+ #define TPS23881_REG_IT_SUPF  BIT(7)
++#define TPS23881_REG_DET_EVENT        0x5
+ #define TPS23881_REG_FAULT    0x7
+ #define TPS23881_REG_SUPF_EVENT       0xb
+ #define TPS23881_REG_TSD      BIT(7)
++#define TPS23881_REG_DISC     0xc
+ #define TPS23881_REG_PW_STATUS        0x10
+ #define TPS23881_REG_OP_MODE  0x12
++#define TPS23881_REG_DISC_EN  0x13
+ #define TPS23881_OP_MODE_SEMIAUTO     0xaaaa
+ #define TPS23881_REG_DIS_EN   0x13
+ #define TPS23881_REG_DET_CLA_EN       0x14
+ #define TPS23881_REG_GEN_MASK 0x17
++#define TPS23881_REG_CLCHE    BIT(2)
++#define TPS23881_REG_DECHE    BIT(3)
+ #define TPS23881_REG_NBITACC  BIT(5)
+ #define TPS23881_REG_INTEN    BIT(7)
+ #define TPS23881_REG_PW_EN    0x19
++#define TPS23881_REG_RESET    0x1a
++#define TPS23881_REG_CLRAIN   BIT(7)
+ #define TPS23881_REG_2PAIR_POL1       0x1e
+ #define TPS23881_REG_PORT_MAP 0x26
+ #define TPS23881_REG_PORT_POWER       0x29
+@@ -178,6 +188,7 @@ static int tps23881_pi_enable(struct pse
+       struct i2c_client *client = priv->client;
+       u8 chan;
+       u16 val;
++      int ret;
+       if (id >= TPS23881_MAX_CHANS)
+               return -ERANGE;
+@@ -191,7 +202,22 @@ static int tps23881_pi_enable(struct pse
+                                      BIT(chan % 4));
+       }
+-      return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
++      ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
++      if (ret)
++              return ret;
++
++      /* Enable DC disconnect*/
++      chan = priv->port[id].chan[0];
++      ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN);
++      if (ret < 0)
++              return ret;
++
++      val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4));
++      ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val);
++      if (ret)
++              return ret;
++
++      return 0;
+ }
+ static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id)
+@@ -224,6 +250,17 @@ static int tps23881_pi_disable(struct ps
+        */
+       mdelay(5);
++      /* Disable DC disconnect*/
++      chan = priv->port[id].chan[0];
++      ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN);
++      if (ret < 0)
++              return ret;
++
++      val = tps23881_set_val(ret, chan, 0, 0, BIT(chan % 4));
++      ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val);
++      if (ret)
++              return ret;
++
+       /* Enable detection and classification */
+       ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_CLA_EN);
+       if (ret < 0)
+@@ -919,6 +956,47 @@ static int tps23881_setup_pi_matrix(stru
+       return ret;
+ }
++static int tps23881_power_class_table[] = {
++      -ERANGE,
++      4000,
++      7000,
++      15500,
++      30000,
++      15500,
++      15500,
++      -ERANGE,
++      45000,
++      60000,
++      75000,
++      90000,
++      15500,
++      45000,
++      -ERANGE,
++      -ERANGE,
++};
++
++static int tps23881_pi_get_pw_req(struct pse_controller_dev *pcdev, int id)
++{
++      struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++      struct i2c_client *client = priv->client;
++      u8 reg, chan;
++      int ret;
++      u16 val;
++
++      /* For a 4-pair the classification need 5ms to be completed */
++      if (priv->port[id].is_4p)
++              mdelay(5);
++
++      chan = priv->port[id].chan[0];
++      reg = TPS23881_REG_DISC + (chan % 4);
++      ret = i2c_smbus_read_word_data(client, reg);
++      if (ret < 0)
++              return ret;
++
++      val = tps23881_calc_val(ret, chan, 4, 0xf);
++      return tps23881_power_class_table[val];
++}
++
+ static const struct pse_controller_ops tps23881_ops = {
+       .setup_pi_matrix = tps23881_setup_pi_matrix,
+       .pi_enable = tps23881_pi_enable,
+@@ -931,6 +1009,7 @@ static const struct pse_controller_ops t
+       .pi_get_pw_limit = tps23881_pi_get_pw_limit,
+       .pi_set_pw_limit = tps23881_pi_set_pw_limit,
+       .pi_get_pw_limit_ranges = tps23881_pi_get_pw_limit_ranges,
++      .pi_get_pw_req = tps23881_pi_get_pw_req,
+ };
+ static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin";
+@@ -1088,17 +1167,113 @@ static void tps23881_irq_event_over_temp
+       }
+ }
+-static void tps23881_irq_event_over_current(struct tps23881_priv *priv,
+-                                          u16 reg_val,
+-                                          unsigned long *notifs,
+-                                          unsigned long *notifs_mask)
++static int tps23881_irq_event_over_current(struct tps23881_priv *priv,
++                                         u16 reg_val,
++                                         unsigned long *notifs,
++                                         unsigned long *notifs_mask)
+ {
++      int i, ret;
+       u8 chans;
+       chans = tps23881_irq_export_chans_helper(reg_val, 0);
++      if (!chans)
++              return 0;
++
++      tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask,
++                                 ETHTOOL_PSE_EVENT_OVER_CURRENT |
++                                 ETHTOOL_C33_PSE_EVENT_DISCONNECTION);
++
++      /* Over Current event resets the power limit registers so we need
++       * to configured it again.
++       */
++      for_each_set_bit(i, notifs_mask, priv->pcdev.nr_lines) {
++              if (priv->port[i].pw_pol < 0)
++                      continue;
++
++              ret = tps23881_pi_enable_manual_pol(priv, i);
++              if (ret < 0)
++                      return ret;
++
++              /* Set power policy */
++              ret = tps23881_pi_set_pw_pol_limit(priv, i,
++                                                 priv->port[i].pw_pol,
++                                                 priv->port[i].is_4p);
++              if (ret < 0)
++                      return ret;
++      }
++
++      return 0;
++}
++
++static void tps23881_irq_event_disconnection(struct tps23881_priv *priv,
++                                           u16 reg_val,
++                                           unsigned long *notifs,
++                                           unsigned long *notifs_mask)
++{
++      u8 chans;
++
++      chans = tps23881_irq_export_chans_helper(reg_val, 4);
+       if (chans)
+               tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask,
+-                                         ETHTOOL_PSE_EVENT_OVER_CURRENT);
++                                         ETHTOOL_C33_PSE_EVENT_DISCONNECTION);
++}
++
++static int tps23881_irq_event_detection(struct tps23881_priv *priv,
++                                      u16 reg_val,
++                                      unsigned long *notifs,
++                                      unsigned long *notifs_mask)
++{
++      enum ethtool_pse_event event;
++      int reg, ret, i, val;
++      unsigned long chans;
++
++      chans = tps23881_irq_export_chans_helper(reg_val, 0);
++      for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) {
++              reg = TPS23881_REG_DISC + (i % 4);
++              ret = i2c_smbus_read_word_data(priv->client, reg);
++              if (ret < 0)
++                      return ret;
++
++              val = tps23881_calc_val(ret, i, 0, 0xf);
++              /* If detection valid */
++              if (val == 0x4)
++                      event = ETHTOOL_C33_PSE_EVENT_DETECTION;
++              else
++                      event = ETHTOOL_C33_PSE_EVENT_DISCONNECTION;
++
++              tps23881_set_notifs_helper(priv, BIT(i), notifs,
++                                         notifs_mask, event);
++      }
++
++      return 0;
++}
++
++static int tps23881_irq_event_classification(struct tps23881_priv *priv,
++                                           u16 reg_val,
++                                           unsigned long *notifs,
++                                           unsigned long *notifs_mask)
++{
++      int reg, ret, val, i;
++      unsigned long chans;
++
++      chans = tps23881_irq_export_chans_helper(reg_val, 4);
++      for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) {
++              reg = TPS23881_REG_DISC + (i % 4);
++              ret = i2c_smbus_read_word_data(priv->client, reg);
++              if (ret < 0)
++                      return ret;
++
++              val = tps23881_calc_val(ret, i, 4, 0xf);
++              /* Do not report classification event for unknown class */
++              if (!val || val == 0x8 || val == 0xf)
++                      continue;
++
++              tps23881_set_notifs_helper(priv, BIT(i), notifs,
++                                         notifs_mask,
++                                         ETHTOOL_C33_PSE_EVENT_CLASSIFICATION);
++      }
++
++      return 0;
+ }
+ static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg,
+@@ -1106,7 +1281,7 @@ static int tps23881_irq_event_handler(st
+                                     unsigned long *notifs_mask)
+ {
+       struct i2c_client *client = priv->client;
+-      int ret;
++      int ret, val;
+       /* The Supply event bit is repeated twice so we only need to read
+        * the one from the first byte.
+@@ -1118,13 +1293,36 @@ static int tps23881_irq_event_handler(st
+               tps23881_irq_event_over_temp(priv, ret, notifs, notifs_mask);
+       }
+-      if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8)) {
++      if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8 |
++                 TPS23881_REG_IT_DISF | TPS23881_REG_IT_DISF << 8)) {
+               ret = i2c_smbus_read_word_data(client, TPS23881_REG_FAULT);
+               if (ret < 0)
+                       return ret;
+-              tps23881_irq_event_over_current(priv, ret, notifs, notifs_mask);
++              ret = tps23881_irq_event_over_current(priv, ret, notifs,
++                                                    notifs_mask);
++              if (ret)
++                      return ret;
++
++              tps23881_irq_event_disconnection(priv, ret, notifs, notifs_mask);
+       }
++      if (reg & (TPS23881_REG_IT_DETC | TPS23881_REG_IT_DETC << 8 |
++                 TPS23881_REG_IT_CLASC | TPS23881_REG_IT_CLASC << 8)) {
++              ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_EVENT);
++              if (ret < 0)
++                      return ret;
++
++              val = ret;
++              ret = tps23881_irq_event_detection(priv, val, notifs,
++                                                 notifs_mask);
++              if (ret)
++                      return ret;
++
++              ret = tps23881_irq_event_classification(priv, val, notifs,
++                                                      notifs_mask);
++              if (ret)
++                      return ret;
++      }
+       return 0;
+ }
+@@ -1178,7 +1376,14 @@ static int tps23881_setup_irq(struct tps
+       int ret;
+       u16 val;
+-      val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF;
++      if (!irq) {
++              dev_err(&client->dev, "interrupt is missing");
++              return -EINVAL;
++      }
++
++      val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF |
++            TPS23881_REG_IT_DETC | TPS23881_REG_IT_CLASC |
++            TPS23881_REG_IT_DISF;
+       val |= val << 8;
+       ret = i2c_smbus_write_word_data(client, TPS23881_REG_IT_MASK, val);
+       if (ret)
+@@ -1188,11 +1393,19 @@ static int tps23881_setup_irq(struct tps
+       if (ret < 0)
+               return ret;
+-      val = (u16)(ret | TPS23881_REG_INTEN | TPS23881_REG_INTEN << 8);
++      val = TPS23881_REG_INTEN | TPS23881_REG_CLCHE | TPS23881_REG_DECHE;
++      val |= val << 8;
++      val |= (u16)ret;
+       ret = i2c_smbus_write_word_data(client, TPS23881_REG_GEN_MASK, val);
+       if (ret < 0)
+               return ret;
++      /* Reset interrupts registers */
++      ret = i2c_smbus_write_word_data(client, TPS23881_REG_RESET,
++                                      TPS23881_REG_CLRAIN);
++      if (ret < 0)
++              return ret;
++
+       return devm_pse_irq_helper(&priv->pcdev, irq, 0, &irq_desc);
+ }
+@@ -1270,17 +1483,16 @@ static int tps23881_i2c_probe(struct i2c
+       priv->pcdev.dev = dev;
+       priv->pcdev.types = ETHTOOL_PSE_C33;
+       priv->pcdev.nr_lines = TPS23881_MAX_CHANS;
++      priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_STATIC;
+       ret = devm_pse_controller_register(dev, &priv->pcdev);
+       if (ret) {
+               return dev_err_probe(dev, ret,
+                                    "failed to register PSE controller\n");
+       }
+-      if (client->irq) {
+-              ret = tps23881_setup_irq(priv, client->irq);
+-              if (ret)
+-                      return ret;
+-      }
++      ret = tps23881_setup_irq(priv, client->irq);
++      if (ret)
++              return ret;
+       return ret;
+ }
diff --git a/target/linux/generic/backport-6.12/626-20-v6.17-net-pse-pd-pd692x0-reduce-stack-usage.patch b/target/linux/generic/backport-6.12/626-20-v6.17-net-pse-pd-pd692x0-reduce-stack-usage.patch
new file mode 100644 (file)
index 0000000..d5e1aaf
--- /dev/null
@@ -0,0 +1,55 @@
+From d12b3dc106090b358fb67b7c0c717a0884327ddf Mon Sep 17 00:00:00 2001
+From: Arnd Bergmann <arnd@arndb.de>
+Date: Wed, 9 Jul 2025 17:32:04 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: reduce stack usage in
+ pd692x0_setup_pi_matrix
+
+The pd692x0_manager array in this function is really too big to fit on the
+stack, though this never triggered a warning until a recent patch made
+it slightly bigger:
+
+drivers/net/pse-pd/pd692x0.c: In function 'pd692x0_setup_pi_matrix':
+drivers/net/pse-pd/pd692x0.c:1210:1: error: the frame size of 1584 bytes is larger than 1536 bytes [-Werror=frame-larger-than=]
+
+Change the function to dynamically allocate the array here.
+
+Fixes: 359754013e6a ("net: pse-pd: pd692x0: Add support for PSE PI priority feature")
+Signed-off-by: Arnd Bergmann <arnd@arndb.de>
+Reviewed-by: Kory Maincent <kory.maincent@bootlin.com>
+Link: https://patch.msgid.link/20250709153210.1920125-1-arnd@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 8 ++++++--
+ 1 file changed, 6 insertions(+), 2 deletions(-)
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -860,7 +860,7 @@ out:
+ static int
+ pd692x0_of_get_managers(struct pd692x0_priv *priv,
+-                      struct pd692x0_manager manager[PD692X0_MAX_MANAGERS])
++                      struct pd692x0_manager *manager)
+ {
+       struct device_node *managers_node, *node;
+       int ret, nmanagers, i, j;
+@@ -1164,7 +1164,7 @@ pd692x0_write_ports_matrix(struct pd692x
+ static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
+ {
+-      struct pd692x0_manager manager[PD692X0_MAX_MANAGERS] = {0};
++      struct pd692x0_manager *manager __free(kfree) = NULL;
+       struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+       struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS];
+       int ret, i, j, nmanagers;
+@@ -1174,6 +1174,10 @@ static int pd692x0_setup_pi_matrix(struc
+           priv->fw_state != PD692X0_FW_COMPLETE)
+               return 0;
++      manager = kcalloc(PD692X0_MAX_MANAGERS, sizeof(*manager), GFP_KERNEL);
++      if (!manager)
++              return -ENOMEM;
++
+       ret = pd692x0_of_get_managers(priv, manager);
+       if (ret < 0)
+               return ret;
diff --git a/target/linux/generic/backport-6.12/626-21-v6.18-net-pse-pd-pd692x0-Fix-power-budget-leak.patch b/target/linux/generic/backport-6.12/626-21-v6.18-net-pse-pd-pd692x0-Fix-power-budget-leak.patch
new file mode 100644 (file)
index 0000000..af23745
--- /dev/null
@@ -0,0 +1,121 @@
+From 1c67f9c54cdc70627e3f6472b89cd3d895df974c Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@bootlin.com>
+Date: Wed, 20 Aug 2025 15:27:07 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Fix power budget leak in manager setup
+ error path
+
+Fix a resource leak where manager power budgets were freed on both
+success and error paths during manager setup. Power budgets should
+only be freed on error paths after regulator registration or during
+driver removal.
+
+Refactor cleanup logic by extracting OF node cleanup and power budget
+freeing into separate helper functions for better maintainability.
+
+Fixes: 359754013e6a ("net: pse-pd: pd692x0: Add support for PSE PI priority feature")
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Link: https://patch.msgid.link/20250820132708.837255-1-kory.maincent@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 59 +++++++++++++++++++++++++++---------
+ 1 file changed, 44 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -1162,12 +1162,44 @@ pd692x0_write_ports_matrix(struct pd692x
+       return 0;
+ }
++static void pd692x0_of_put_managers(struct pd692x0_priv *priv,
++                                  struct pd692x0_manager *manager,
++                                  int nmanagers)
++{
++      int i, j;
++
++      for (i = 0; i < nmanagers; i++) {
++              for (j = 0; j < manager[i].nports; j++)
++                      of_node_put(manager[i].port_node[j]);
++              of_node_put(manager[i].node);
++      }
++}
++
++static void pd692x0_managers_free_pw_budget(struct pd692x0_priv *priv)
++{
++      int i;
++
++      for (i = 0; i < PD692X0_MAX_MANAGERS; i++) {
++              struct regulator *supply;
++
++              if (!priv->manager_reg[i] || !priv->manager_pw_budget[i])
++                      continue;
++
++              supply = priv->manager_reg[i]->supply;
++              if (!supply)
++                      continue;
++
++              regulator_free_power_budget(supply,
++                                          priv->manager_pw_budget[i]);
++      }
++}
++
+ static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
+ {
+       struct pd692x0_manager *manager __free(kfree) = NULL;
+       struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+       struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS];
+-      int ret, i, j, nmanagers;
++      int ret, nmanagers;
+       /* Should we flash the port matrix */
+       if (priv->fw_state != PD692X0_FW_OK &&
+@@ -1185,31 +1217,27 @@ static int pd692x0_setup_pi_matrix(struc
+       nmanagers = ret;
+       ret = pd692x0_register_managers_regulator(priv, manager, nmanagers);
+       if (ret)
+-              goto out;
++              goto err_of_managers;
+       ret = pd692x0_configure_managers(priv, nmanagers);
+       if (ret)
+-              goto out;
++              goto err_of_managers;
+       ret = pd692x0_set_ports_matrix(priv, manager, nmanagers, port_matrix);
+       if (ret)
+-              goto out;
++              goto err_managers_req_pw;
+       ret = pd692x0_write_ports_matrix(priv, port_matrix);
+       if (ret)
+-              goto out;
+-
+-out:
+-      for (i = 0; i < nmanagers; i++) {
+-              struct regulator *supply = priv->manager_reg[i]->supply;
++              goto err_managers_req_pw;
+-              regulator_free_power_budget(supply,
+-                                          priv->manager_pw_budget[i]);
++      pd692x0_of_put_managers(priv, manager, nmanagers);
++      return 0;
+-              for (j = 0; j < manager[i].nports; j++)
+-                      of_node_put(manager[i].port_node[j]);
+-              of_node_put(manager[i].node);
+-      }
++err_managers_req_pw:
++      pd692x0_managers_free_pw_budget(priv);
++err_of_managers:
++      pd692x0_of_put_managers(priv, manager, nmanagers);
+       return ret;
+ }
+@@ -1748,6 +1776,7 @@ static void pd692x0_i2c_remove(struct i2
+ {
+       struct pd692x0_priv *priv = i2c_get_clientdata(client);
++      pd692x0_managers_free_pw_budget(priv);
+       firmware_upload_unregister(priv->fwl);
+ }
diff --git a/target/linux/generic/backport-6.12/626-22-v6.18-net-pse-pd-pd692x0-Skip-power-budget-when-undefined.patch b/target/linux/generic/backport-6.12/626-22-v6.18-net-pse-pd-pd692x0-Skip-power-budget-when-undefined.patch
new file mode 100644 (file)
index 0000000..98da7b5
--- /dev/null
@@ -0,0 +1,37 @@
+From 7ef353879f714602b43f98662069f4fb86536761 Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@bootlin.com>
+Date: Wed, 20 Aug 2025 15:33:21 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Skip power budget configuration when
+ undefined
+
+If the power supply's power budget is not defined in the device tree,
+the current code still requests power and configures the PSE manager
+with a 0W power limit, which is undesirable behavior.
+
+Skip power budget configuration entirely when the budget is zero,
+avoiding unnecessary power requests and preventing invalid 0W limits
+from being set on the PSE manager.
+
+Fixes: 359754013e6a ("net: pse-pd: pd692x0: Add support for PSE PI priority feature")
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250820133321.841054-1-kory.maincent@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -1041,6 +1041,10 @@ pd692x0_configure_managers(struct pd692x
+               int pw_budget;
+               pw_budget = regulator_get_unclaimed_power_budget(supply);
++              if (!pw_budget)
++                      /* Do nothing if no power budget */
++                      continue;
++
+               /* Max power budget per manager */
+               if (pw_budget > 6000000)
+                       pw_budget = 6000000;
diff --git a/target/linux/generic/backport-6.12/626-23-v6.18-net-pse-pd-Add-Si3474-PSE-controller-driver.patch b/target/linux/generic/backport-6.12/626-23-v6.18-net-pse-pd-Add-Si3474-PSE-controller-driver.patch
new file mode 100644 (file)
index 0000000..2f4087f
--- /dev/null
@@ -0,0 +1,636 @@
+From a2317231df4b22e6634fe3d8645e7cef848acf49 Mon Sep 17 00:00:00 2001
+From: Piotr Kubik <piotr.kubik@adtran.com>
+Date: Tue, 26 Aug 2025 14:41:58 +0000
+Subject: [PATCH] net: pse-pd: Add Si3474 PSE controller driver
+
+Add a driver for the Skyworks Si3474 I2C Power Sourcing Equipment
+controller.
+
+Driver supports basic features of Si3474 IC:
+- get port status,
+- get port power,
+- get port voltage,
+- enable/disable port power.
+
+Only 4p configurations are supported at this moment.
+
+Signed-off-by: Piotr Kubik <piotr.kubik@adtran.com>
+Reviewed-by: Kory Maincent <kory.maincent@bootlin.com>
+Link: https://patch.msgid.link/9b72c8cd-c8d3-4053-9c80-671b9481d166@adtran.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/Kconfig  |  11 +
+ drivers/net/pse-pd/Makefile |   1 +
+ drivers/net/pse-pd/si3474.c | 578 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 590 insertions(+)
+ create mode 100644 drivers/net/pse-pd/si3474.c
+--- a/drivers/net/pse-pd/Kconfig
++++ b/drivers/net/pse-pd/Kconfig
+@@ -32,6 +32,17 @@ config PSE_PD692X0
+         To compile this driver as a module, choose M here: the
+         module will be called pd692x0.
++config PSE_SI3474
++      tristate "Si3474 PSE controller"
++      depends on I2C
++      help
++        This module provides support for Si3474 regulator based Ethernet
++        Power Sourcing Equipment.
++        Only 4-pair PSE configurations are supported.
++
++        To compile this driver as a module, choose M here: the
++        module will be called si3474.
++
+ config PSE_TPS23881
+       tristate "TPS23881 PSE controller"
+       depends on I2C
+--- a/drivers/net/pse-pd/Makefile
++++ b/drivers/net/pse-pd/Makefile
+@@ -5,4 +5,5 @@ obj-$(CONFIG_PSE_CONTROLLER) += pse_core
+ obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o
+ obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
++obj-$(CONFIG_PSE_SI3474) += si3474.o
+ obj-$(CONFIG_PSE_TPS23881) += tps23881.o
+--- /dev/null
++++ b/drivers/net/pse-pd/si3474.c
+@@ -0,0 +1,578 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Driver for the Skyworks Si3474 PoE PSE Controller
++ *
++ * Chip Architecture & Terminology:
++ *
++ * The Si3474 is a single-chip PoE PSE controller managing 8 physical power
++ * delivery channels. Internally, it's structured into two logical "Quads".
++ *
++ * Quad 0: Manages physical channels ('ports' in datasheet) 0, 1, 2, 3
++ * Quad 1: Manages physical channels ('ports' in datasheet) 4, 5, 6, 7
++ *
++ * Each Quad is accessed via a separate I2C address. The base address range is
++ * set by hardware pins A1-A4, and the specific address selects Quad 0 (usually
++ * the lower/even address) or Quad 1 (usually the higher/odd address).
++ * See datasheet Table 2.2 for the address mapping.
++ *
++ * While the Quads manage channel-specific operations, the Si3474 package has
++ * several resources shared across the entire chip:
++ * - Single RESETb input pin.
++ * - Single INTb output pin (signals interrupts from *either* Quad).
++ * - Single OSS input pin (Emergency Shutdown).
++ * - Global I2C Address (0x7F) used for firmware updates.
++ * - Global status monitoring (Temperature, VDD/VPWR Undervoltage Lockout).
++ *
++ * Driver Architecture:
++ *
++ * To handle the mix of per-Quad access and shared resources correctly, this
++ * driver treats the entire Si3474 package as one logical device. The driver
++ * instance associated with the primary I2C address (Quad 0) takes ownership.
++ * It discovers and manages the I2C client for the secondary address (Quad 1).
++ * This primary instance handles shared resources like IRQ management and
++ * registers a single PSE controller device representing all logical PIs.
++ * Internal functions route I2C commands to the appropriate Quad's i2c_client
++ * based on the target channel or PI.
++ *
++ * Terminology Mapping:
++ *
++ * - "PI" (Power Interface): Refers to the logical PSE port as defined by
++ * IEEE 802.3 (typically corresponds to an RJ45 connector). This is the
++ * `id` (0-7) used in the pse_controller_ops.
++ * - "Channel": Refers to one of the 8 physical power control paths within
++ * the Si3474 chip itself (hardware channels 0-7). This terminology is
++ * used internally within the driver to avoid confusion with 'ports'.
++ * - "Quad": One of the two internal 4-channel management units within the
++ * Si3474, each accessed via its own I2C address.
++ *
++ * Relationship:
++ * - A 2-Pair PoE PI uses 1 Channel.
++ * - A 4-Pair PoE PI uses 2 Channels.
++ *
++ * ASCII Schematic:
++ *
++ * +-----------------------------------------------------+
++ * |                    Si3474 Chip                      |
++ * |                                                     |
++ * | +---------------------+     +---------------------+ |
++ * | |      Quad 0         |     |      Quad 1         | |
++ * | | Channels 0, 1, 2, 3 |     | Channels 4, 5, 6, 7 | |
++ * | +----------^----------+     +-------^-------------+ |
++ * | I2C Addr 0 |                        | I2C Addr 1    |
++ * |            +------------------------+               |
++ * | (Primary Driver Instance) (Managed by Primary)      |
++ * |                                                     |
++ * | Shared Resources (affect whole chip):               |
++ * |  - Single INTb Output -> Handled by Primary         |
++ * |  - Single RESETb Input                              |
++ * |  - Single OSS Input   -> Handled by Primary         |
++ * |  - Global I2C Addr (0x7F) for Firmware Update       |
++ * |  - Global Status (Temp, VDD/VPWR UVLO)              |
++ * +-----------------------------------------------------+
++ *        |   |   |   |        |   |   |   |
++ *        Ch0 Ch1 Ch2 Ch3      Ch4 Ch5 Ch6 Ch7  (Physical Channels)
++ *
++ * Example Mapping (Logical PI to Physical Channel(s)):
++ * * 2-Pair Mode (8 PIs):
++ * PI 0 -> Ch 0
++ * PI 1 -> Ch 1
++ * ...
++ * PI 7 -> Ch 7
++ * * 4-Pair Mode (4 PIs):
++ * PI 0 -> Ch 0 + Ch 1  (Managed via Quad 0 Addr)
++ * PI 1 -> Ch 2 + Ch 3  (Managed via Quad 0 Addr)
++ * PI 2 -> Ch 4 + Ch 5  (Managed via Quad 1 Addr)
++ * PI 3 -> Ch 6 + Ch 7  (Managed via Quad 1 Addr)
++ * (Note: Actual mapping depends on Device Tree and PORT_REMAP config)
++ */
++
++#include <linux/i2c.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/pse-pd/pse.h>
++
++#define SI3474_MAX_CHANS 8
++
++#define MANUFACTURER_ID 0x08
++#define IC_ID 0x05
++#define SI3474_DEVICE_ID (MANUFACTURER_ID << 3 | IC_ID)
++
++/* Misc registers */
++#define VENDOR_IC_ID_REG 0x1B
++#define TEMPERATURE_REG 0x2C
++#define FIRMWARE_REVISION_REG 0x41
++#define CHIP_REVISION_REG 0x43
++
++/* Main status registers */
++#define POWER_STATUS_REG 0x10
++#define PORT_MODE_REG 0x12
++#define DETECT_CLASS_ENABLE_REG 0x14
++
++/* PORTn Current */
++#define PORT1_CURRENT_LSB_REG 0x30
++
++/* PORTn Current [mA], return in [nA] */
++/* 1000 * ((PORTn_CURRENT_MSB << 8) + PORTn_CURRENT_LSB) / 16384 */
++#define SI3474_NA_STEP (1000 * 1000 * 1000 / 16384)
++
++/* VPWR Voltage */
++#define VPWR_LSB_REG 0x2E
++#define VPWR_MSB_REG 0x2F
++
++/* PORTn Voltage */
++#define PORT1_VOLTAGE_LSB_REG 0x32
++
++/* VPWR Voltage [V], return in [uV] */
++/* 60 * (( VPWR_MSB << 8) + VPWR_LSB) / 16384 */
++#define SI3474_UV_STEP (1000 * 1000 * 60 / 16384)
++
++/* Helper macros */
++#define CHAN_IDX(chan) ((chan) % 4)
++#define CHAN_BIT(chan) BIT(CHAN_IDX(chan))
++#define CHAN_UPPER_BIT(chan) BIT(CHAN_IDX(chan) + 4)
++
++#define CHAN_MASK(chan) (0x03U << (2 * CHAN_IDX(chan)))
++#define CHAN_REG(base, chan) ((base) + (CHAN_IDX(chan) * 4))
++
++struct si3474_pi_desc {
++      u8 chan[2];
++      bool is_4p;
++};
++
++struct si3474_priv {
++      struct i2c_client *client[2];
++      struct pse_controller_dev pcdev;
++      struct device_node *np;
++      struct si3474_pi_desc pi[SI3474_MAX_CHANS];
++};
++
++static struct si3474_priv *to_si3474_priv(struct pse_controller_dev *pcdev)
++{
++      return container_of(pcdev, struct si3474_priv, pcdev);
++}
++
++static void si3474_get_channels(struct si3474_priv *priv, int id,
++                              u8 *chan0, u8 *chan1)
++{
++      *chan0 = priv->pi[id].chan[0];
++      *chan1 = priv->pi[id].chan[1];
++}
++
++static struct i2c_client *si3474_get_chan_client(struct si3474_priv *priv,
++                                               u8 chan)
++{
++      return (chan < 4) ? priv->client[0] : priv->client[1];
++}
++
++static int si3474_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
++                                   struct pse_admin_state *admin_state)
++{
++      struct si3474_priv *priv = to_si3474_priv(pcdev);
++      struct i2c_client *client;
++      bool is_enabled;
++      u8 chan0, chan1;
++      s32 ret;
++
++      si3474_get_channels(priv, id, &chan0, &chan1);
++      client = si3474_get_chan_client(priv, chan0);
++
++      ret = i2c_smbus_read_byte_data(client, PORT_MODE_REG);
++      if (ret < 0) {
++              admin_state->c33_admin_state =
++                      ETHTOOL_C33_PSE_ADMIN_STATE_UNKNOWN;
++              return ret;
++      }
++
++      is_enabled = ret & (CHAN_MASK(chan0) | CHAN_MASK(chan1));
++
++      if (is_enabled)
++              admin_state->c33_admin_state =
++                      ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
++      else
++              admin_state->c33_admin_state =
++                      ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
++
++      return 0;
++}
++
++static int si3474_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
++                                 struct pse_pw_status *pw_status)
++{
++      struct si3474_priv *priv = to_si3474_priv(pcdev);
++      struct i2c_client *client;
++      bool delivering;
++      u8 chan0, chan1;
++      s32 ret;
++
++      si3474_get_channels(priv, id, &chan0, &chan1);
++      client = si3474_get_chan_client(priv, chan0);
++
++      ret = i2c_smbus_read_byte_data(client, POWER_STATUS_REG);
++      if (ret < 0) {
++              pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_UNKNOWN;
++              return ret;
++      }
++
++      delivering = ret & (CHAN_UPPER_BIT(chan0) | CHAN_UPPER_BIT(chan1));
++
++      if (delivering)
++              pw_status->c33_pw_status =
++                      ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
++      else
++              pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
++
++      return 0;
++}
++
++static int si3474_get_of_channels(struct si3474_priv *priv)
++{
++      struct pse_pi *pi;
++      u32 chan_id;
++      u8 pi_no;
++      s32 ret;
++
++      for (pi_no = 0; pi_no < SI3474_MAX_CHANS; pi_no++) {
++              pi = &priv->pcdev.pi[pi_no];
++              bool pairset_found = false;
++              u8 pairset_no;
++
++              for (pairset_no = 0; pairset_no < 2; pairset_no++) {
++                      if (!pi->pairset[pairset_no].np)
++                              continue;
++
++                      pairset_found = true;
++
++                      ret = of_property_read_u32(pi->pairset[pairset_no].np,
++                                                 "reg", &chan_id);
++                      if (ret) {
++                              dev_err(&priv->client[0]->dev,
++                                      "Failed to read channel reg property\n");
++                              return ret;
++                      }
++                      if (chan_id > SI3474_MAX_CHANS) {
++                              dev_err(&priv->client[0]->dev,
++                                      "Incorrect channel number: %d\n", chan_id);
++                              return -EINVAL;
++                      }
++
++                      priv->pi[pi_no].chan[pairset_no] = chan_id;
++                      /* Mark as 4-pair if second pairset is present */
++                      priv->pi[pi_no].is_4p = (pairset_no == 1);
++              }
++
++              if (pairset_found && !priv->pi[pi_no].is_4p) {
++                      dev_err(&priv->client[0]->dev,
++                              "Second pairset is missing for PI %pOF, only 4p configs are supported\n",
++                              pi->np);
++                      return -EINVAL;
++              }
++      }
++
++      return 0;
++}
++
++static int si3474_setup_pi_matrix(struct pse_controller_dev *pcdev)
++{
++      struct si3474_priv *priv = to_si3474_priv(pcdev);
++      s32 ret;
++
++      ret = si3474_get_of_channels(priv);
++      if (ret < 0)
++              dev_warn(&priv->client[0]->dev,
++                       "Unable to parse DT PSE power interface matrix\n");
++
++      return ret;
++}
++
++static int si3474_pi_enable(struct pse_controller_dev *pcdev, int id)
++{
++      struct si3474_priv *priv = to_si3474_priv(pcdev);
++      struct i2c_client *client;
++      u8 chan0, chan1;
++      s32 ret;
++      u8 val;
++
++      si3474_get_channels(priv, id, &chan0, &chan1);
++      client = si3474_get_chan_client(priv, chan0);
++
++      /* Release PI from shutdown */
++      ret = i2c_smbus_read_byte_data(client, PORT_MODE_REG);
++      if (ret < 0)
++              return ret;
++
++      val = (u8)ret;
++      val |= CHAN_MASK(chan0);
++      val |= CHAN_MASK(chan1);
++
++      ret = i2c_smbus_write_byte_data(client, PORT_MODE_REG, val);
++      if (ret)
++              return ret;
++
++      /* DETECT_CLASS_ENABLE must be set when using AUTO mode,
++       * otherwise PI does not power up - datasheet section 2.10.2
++       */
++      val = CHAN_BIT(chan0) | CHAN_UPPER_BIT(chan0) |
++            CHAN_BIT(chan1) | CHAN_UPPER_BIT(chan1);
++
++      ret = i2c_smbus_write_byte_data(client, DETECT_CLASS_ENABLE_REG, val);
++      if (ret)
++              return ret;
++
++      return 0;
++}
++
++static int si3474_pi_disable(struct pse_controller_dev *pcdev, int id)
++{
++      struct si3474_priv *priv = to_si3474_priv(pcdev);
++      struct i2c_client *client;
++      u8 chan0, chan1;
++      s32 ret;
++      u8 val;
++
++      si3474_get_channels(priv, id, &chan0, &chan1);
++      client = si3474_get_chan_client(priv, chan0);
++
++      /* Set PI in shutdown mode */
++      ret = i2c_smbus_read_byte_data(client, PORT_MODE_REG);
++      if (ret < 0)
++              return ret;
++
++      val = (u8)ret;
++      val &= ~CHAN_MASK(chan0);
++      val &= ~CHAN_MASK(chan1);
++
++      ret = i2c_smbus_write_byte_data(client, PORT_MODE_REG, val);
++      if (ret)
++              return ret;
++
++      return 0;
++}
++
++static int si3474_pi_get_chan_current(struct si3474_priv *priv, u8 chan)
++{
++      struct i2c_client *client;
++      u64 tmp_64;
++      s32 ret;
++      u8 reg;
++
++      client = si3474_get_chan_client(priv, chan);
++
++      /* Registers 0x30 to 0x3d */
++      reg = CHAN_REG(PORT1_CURRENT_LSB_REG, chan);
++
++      ret = i2c_smbus_read_word_data(client, reg);
++      if (ret < 0)
++              return ret;
++
++      tmp_64 = ret * SI3474_NA_STEP;
++
++      /* uA = nA / 1000 */
++      tmp_64 = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000);
++      return (int)tmp_64;
++}
++
++static int si3474_pi_get_chan_voltage(struct si3474_priv *priv, u8 chan)
++{
++      struct i2c_client *client;
++      s32 ret;
++      u32 val;
++      u8 reg;
++
++      client = si3474_get_chan_client(priv, chan);
++
++      /* Registers 0x32 to 0x3f */
++      reg = CHAN_REG(PORT1_VOLTAGE_LSB_REG, chan);
++
++      ret = i2c_smbus_read_word_data(client, reg);
++      if (ret < 0)
++              return ret;
++
++      val = ret * SI3474_UV_STEP;
++
++      return (int)val;
++}
++
++static int si3474_pi_get_voltage(struct pse_controller_dev *pcdev, int id)
++{
++      struct si3474_priv *priv = to_si3474_priv(pcdev);
++      struct i2c_client *client;
++      u8 chan0, chan1;
++      s32 ret;
++
++      si3474_get_channels(priv, id, &chan0, &chan1);
++      client = si3474_get_chan_client(priv, chan0);
++
++      /* Check which channels are enabled*/
++      ret = i2c_smbus_read_byte_data(client, POWER_STATUS_REG);
++      if (ret < 0)
++              return ret;
++
++      /* Take voltage from the first enabled channel */
++      if (ret & CHAN_BIT(chan0))
++              ret = si3474_pi_get_chan_voltage(priv, chan0);
++      else if (ret & CHAN_BIT(chan1))
++              ret = si3474_pi_get_chan_voltage(priv, chan1);
++      else
++              /* 'should' be no voltage in this case */
++              return 0;
++
++      return ret;
++}
++
++static int si3474_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
++{
++      struct si3474_priv *priv = to_si3474_priv(pcdev);
++      u8 chan0, chan1;
++      u32 uV, uA;
++      u64 tmp_64;
++      s32 ret;
++
++      ret = si3474_pi_get_voltage(&priv->pcdev, id);
++
++      /* Do not read currents if voltage is 0 */
++      if (ret <= 0)
++              return ret;
++      uV = ret;
++
++      si3474_get_channels(priv, id, &chan0, &chan1);
++
++      ret = si3474_pi_get_chan_current(priv, chan0);
++      if (ret < 0)
++              return ret;
++      uA = ret;
++
++      ret = si3474_pi_get_chan_current(priv, chan1);
++      if (ret < 0)
++              return ret;
++      uA += ret;
++
++      tmp_64 = uV;
++      tmp_64 *= uA;
++      /* mW = uV * uA / 1000000000 */
++      return DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000);
++}
++
++static const struct pse_controller_ops si3474_ops = {
++      .setup_pi_matrix = si3474_setup_pi_matrix,
++      .pi_enable = si3474_pi_enable,
++      .pi_disable = si3474_pi_disable,
++      .pi_get_actual_pw = si3474_pi_get_actual_pw,
++      .pi_get_voltage = si3474_pi_get_voltage,
++      .pi_get_admin_state = si3474_pi_get_admin_state,
++      .pi_get_pw_status = si3474_pi_get_pw_status,
++};
++
++static void si3474_ancillary_i2c_remove(void *data)
++{
++      struct i2c_client *client = data;
++
++      i2c_unregister_device(client);
++}
++
++static int si3474_i2c_probe(struct i2c_client *client)
++{
++      struct device *dev = &client->dev;
++      struct si3474_priv *priv;
++      u8 fw_version;
++      s32 ret;
++
++      if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
++              dev_err(dev, "i2c check functionality failed\n");
++              return -ENXIO;
++      }
++
++      priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++      if (!priv)
++              return -ENOMEM;
++
++      ret = i2c_smbus_read_byte_data(client, VENDOR_IC_ID_REG);
++      if (ret < 0)
++              return ret;
++
++      if (ret != SI3474_DEVICE_ID) {
++              dev_err(dev, "Wrong device ID: 0x%x\n", ret);
++              return -ENXIO;
++      }
++
++      ret = i2c_smbus_read_byte_data(client, FIRMWARE_REVISION_REG);
++      if (ret < 0)
++              return ret;
++      fw_version = ret;
++
++      ret = i2c_smbus_read_byte_data(client, CHIP_REVISION_REG);
++      if (ret < 0)
++              return ret;
++
++      dev_dbg(dev, "Chip revision: 0x%x, firmware version: 0x%x\n",
++              ret, fw_version);
++
++      priv->client[0] = client;
++      i2c_set_clientdata(client, priv);
++
++      priv->client[1] = i2c_new_ancillary_device(priv->client[0], "secondary",
++                                                 priv->client[0]->addr + 1);
++      if (IS_ERR(priv->client[1]))
++              return PTR_ERR(priv->client[1]);
++
++      ret = devm_add_action_or_reset(dev, si3474_ancillary_i2c_remove, priv->client[1]);
++      if (ret < 0) {
++              dev_err(&priv->client[1]->dev, "Cannot register remove callback\n");
++              return ret;
++      }
++
++      ret = i2c_smbus_read_byte_data(priv->client[1], VENDOR_IC_ID_REG);
++      if (ret < 0) {
++              dev_err(&priv->client[1]->dev, "Cannot access secondary PSE controller\n");
++              return ret;
++      }
++
++      if (ret != SI3474_DEVICE_ID) {
++              dev_err(&priv->client[1]->dev,
++                      "Wrong device ID for secondary PSE controller: 0x%x\n", ret);
++              return -ENXIO;
++      }
++
++      priv->np = dev->of_node;
++      priv->pcdev.owner = THIS_MODULE;
++      priv->pcdev.ops = &si3474_ops;
++      priv->pcdev.dev = dev;
++      priv->pcdev.types = ETHTOOL_PSE_C33;
++      priv->pcdev.nr_lines = SI3474_MAX_CHANS;
++
++      ret = devm_pse_controller_register(dev, &priv->pcdev);
++      if (ret) {
++              dev_err(dev, "Failed to register PSE controller: 0x%x\n", ret);
++              return ret;
++      }
++
++      return 0;
++}
++
++static const struct i2c_device_id si3474_id[] = {
++      { "si3474" },
++      {}
++};
++MODULE_DEVICE_TABLE(i2c, si3474_id);
++
++static const struct of_device_id si3474_of_match[] = {
++      {
++              .compatible = "skyworks,si3474",
++      },
++      {},
++};
++MODULE_DEVICE_TABLE(of, si3474_of_match);
++
++static struct i2c_driver si3474_driver = {
++      .probe = si3474_i2c_probe,
++      .id_table = si3474_id,
++      .driver = {
++              .name = "si3474",
++              .of_match_table = si3474_of_match,
++      },
++};
++module_i2c_driver(si3474_driver);
++
++MODULE_AUTHOR("Piotr Kubik <piotr.kubik@adtran.com>");
++MODULE_DESCRIPTION("Skyworks Si3474 PoE PSE Controller driver");
++MODULE_LICENSE("GPL");
diff --git a/target/linux/generic/backport-6.12/626-24-v6.19-net-pse-pd-tps23881-Fix-current-measurement-scaling.patch b/target/linux/generic/backport-6.12/626-24-v6.19-net-pse-pd-tps23881-Fix-current-measurement-scaling.patch
new file mode 100644 (file)
index 0000000..5150da3
--- /dev/null
@@ -0,0 +1,31 @@
+From 2c95a756e0cfc19af6d0b32b0c6cf3bada334998 Mon Sep 17 00:00:00 2001
+From: Thomas Wismer <thomas.wismer@scs.ch>
+Date: Mon, 6 Oct 2025 22:40:29 +0200
+Subject: [PATCH] net: pse-pd: tps23881: Fix current measurement scaling
+
+The TPS23881 improves on the TPS23880 with current sense resistors reduced
+from 255 mOhm to 200 mOhm. This has a direct impact on the scaling of the
+current measurement. However, the latest TPS23881 data sheet from May 2023
+still shows the scaling of the TPS23880 model.
+
+Fixes: 7f076ce3f1733 ("net: pse-pd: tps23881: Add support for power limit and measurement features")
+Signed-off-by: Thomas Wismer <thomas.wismer@scs.ch>
+Acked-by: Kory Maincent <kory.maincent@bootlin.com>
+Link: https://patch.msgid.link/20251006204029.7169-2-thomas@wismer.xyz
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -62,7 +62,7 @@
+ #define TPS23881_REG_SRAM_DATA        0x61
+ #define TPS23881_UV_STEP      3662
+-#define TPS23881_NA_STEP      70190
++#define TPS23881_NA_STEP      89500
+ #define TPS23881_MW_STEP      500
+ #define TPS23881_MIN_PI_PW_LIMIT_MW   2000
diff --git a/target/linux/generic/backport-6.12/626-25-v6.19-net-pse-pd-pd692x0-Replace-__free-macro.patch b/target/linux/generic/backport-6.12/626-25-v6.19-net-pse-pd-pd692x0-Replace-__free-macro.patch
new file mode 100644 (file)
index 0000000..fafa5af
--- /dev/null
@@ -0,0 +1,57 @@
+From f197902cd21ae833850679b216bb62c0d056bbb3 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Mon, 13 Oct 2025 16:05:31 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Replace __free macro with explicit
+ kfree calls
+
+Replace __free(kfree) with explicit kfree() calls to follow the net
+subsystem policy of avoiding automatic cleanup macros as described in
+the documentation.
+
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/20251013-feature_pd692x0_reboot_keep_conf-v2-1-68ab082a93dd@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -1200,9 +1200,9 @@ static void pd692x0_managers_free_pw_bud
+ static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
+ {
+-      struct pd692x0_manager *manager __free(kfree) = NULL;
+       struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+       struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS];
++      struct pd692x0_manager *manager;
+       int ret, nmanagers;
+       /* Should we flash the port matrix */
+@@ -1216,7 +1216,7 @@ static int pd692x0_setup_pi_matrix(struc
+       ret = pd692x0_of_get_managers(priv, manager);
+       if (ret < 0)
+-              return ret;
++              goto err_free_manager;
+       nmanagers = ret;
+       ret = pd692x0_register_managers_regulator(priv, manager, nmanagers);
+@@ -1236,12 +1236,15 @@ static int pd692x0_setup_pi_matrix(struc
+               goto err_managers_req_pw;
+       pd692x0_of_put_managers(priv, manager, nmanagers);
++      kfree(manager);
+       return 0;
+ err_managers_req_pw:
+       pd692x0_managers_free_pw_budget(priv);
+ err_of_managers:
+       pd692x0_of_put_managers(priv, manager, nmanagers);
++err_free_manager:
++      kfree(manager);
+       return ret;
+ }
diff --git a/target/linux/generic/backport-6.12/626-26-v6.19-net-pse-pd-pd692x0-Separate-configuration-parsing.patch b/target/linux/generic/backport-6.12/626-26-v6.19-net-pse-pd-pd692x0-Separate-configuration-parsing.patch
new file mode 100644 (file)
index 0000000..e88bbd0
--- /dev/null
@@ -0,0 +1,291 @@
+From 6fa1f8b64a47edd7d8420d8fd1008507aee2853e Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Mon, 13 Oct 2025 16:05:32 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Separate configuration parsing from
+ hardware setup
+
+Cache the port matrix configuration in driver private data to enable
+PSE controller reconfiguration. This refactoring separates device tree
+parsing from hardware configuration application, allowing settings to be
+reapplied without reparsing the device tree.
+
+This refactoring is a prerequisite for preserving PSE configuration
+across reboots to prevent power disruption to connected devices.
+
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/20251013-feature_pd692x0_reboot_keep_conf-v2-2-68ab082a93dd@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 115 +++++++++++++++++++++++------------
+ 1 file changed, 76 insertions(+), 39 deletions(-)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -85,6 +85,11 @@ enum {
+       PD692X0_MSG_CNT
+ };
++struct pd692x0_matrix {
++      u8 hw_port_a;
++      u8 hw_port_b;
++};
++
+ struct pd692x0_priv {
+       struct i2c_client *client;
+       struct pse_controller_dev pcdev;
+@@ -101,6 +106,8 @@ struct pd692x0_priv {
+       enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS];
+       struct regulator_dev *manager_reg[PD692X0_MAX_MANAGERS];
+       int manager_pw_budget[PD692X0_MAX_MANAGERS];
++      int nmanagers;
++      struct pd692x0_matrix *port_matrix;
+ };
+ /* Template list of communication messages. The non-null bytes defined here
+@@ -809,11 +816,6 @@ struct pd692x0_manager {
+       int nports;
+ };
+-struct pd692x0_matrix {
+-      u8 hw_port_a;
+-      u8 hw_port_b;
+-};
+-
+ static int
+ pd692x0_of_get_ports_manager(struct pd692x0_priv *priv,
+                            struct pd692x0_manager *manager,
+@@ -903,7 +905,8 @@ pd692x0_of_get_managers(struct pd692x0_p
+       }
+       of_node_put(managers_node);
+-      return nmanagers;
++      priv->nmanagers = nmanagers;
++      return 0;
+ out:
+       for (i = 0; i < nmanagers; i++) {
+@@ -963,8 +966,7 @@ pd692x0_register_manager_regulator(struc
+ static int
+ pd692x0_register_managers_regulator(struct pd692x0_priv *priv,
+-                                  const struct pd692x0_manager *manager,
+-                                  int nmanagers)
++                                  const struct pd692x0_manager *manager)
+ {
+       struct device *dev = &priv->client->dev;
+       size_t reg_name_len;
+@@ -975,7 +977,7 @@ pd692x0_register_managers_regulator(stru
+        */
+       reg_name_len = strlen(dev_name(dev)) + 23;
+-      for (i = 0; i < nmanagers; i++) {
++      for (i = 0; i < priv->nmanagers; i++) {
+               static const char * const regulators[] = { "vaux5", "vaux3p3" };
+               struct regulator_dev *rdev;
+               char *reg_name;
+@@ -1008,10 +1010,14 @@ pd692x0_register_managers_regulator(stru
+ }
+ static int
+-pd692x0_conf_manager_power_budget(struct pd692x0_priv *priv, int id, int pw)
++pd692x0_conf_manager_power_budget(struct pd692x0_priv *priv, int id)
+ {
+       struct pd692x0_msg msg, buf;
+-      int ret, pw_mW = pw / 1000;
++      int ret, pw_mW;
++
++      pw_mW = priv->manager_pw_budget[id] / 1000;
++      if (!pw_mW)
++              return 0;
+       msg = pd692x0_msg_template_list[PD692X0_MSG_GET_POWER_BANK];
+       msg.data[0] = id;
+@@ -1032,11 +1038,11 @@ pd692x0_conf_manager_power_budget(struct
+ }
+ static int
+-pd692x0_configure_managers(struct pd692x0_priv *priv, int nmanagers)
++pd692x0_req_managers_pw_budget(struct pd692x0_priv *priv)
+ {
+       int i, ret;
+-      for (i = 0; i < nmanagers; i++) {
++      for (i = 0; i < priv->nmanagers; i++) {
+               struct regulator *supply = priv->manager_reg[i]->supply;
+               int pw_budget;
+@@ -1053,7 +1059,18 @@ pd692x0_configure_managers(struct pd692x
+                       return ret;
+               priv->manager_pw_budget[i] = pw_budget;
+-              ret = pd692x0_conf_manager_power_budget(priv, i, pw_budget);
++      }
++
++      return 0;
++}
++
++static int
++pd692x0_configure_managers(struct pd692x0_priv *priv)
++{
++      int i, ret;
++
++      for (i = 0; i < priv->nmanagers; i++) {
++              ret = pd692x0_conf_manager_power_budget(priv, i);
+               if (ret < 0)
+                       return ret;
+       }
+@@ -1101,10 +1118,9 @@ pd692x0_set_port_matrix(const struct pse
+ static int
+ pd692x0_set_ports_matrix(struct pd692x0_priv *priv,
+-                       const struct pd692x0_manager *manager,
+-                       int nmanagers,
+-                       struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS])
++                       const struct pd692x0_manager *manager)
+ {
++      struct pd692x0_matrix *port_matrix = priv->port_matrix;
+       struct pse_controller_dev *pcdev = &priv->pcdev;
+       int i, ret;
+@@ -1117,7 +1133,7 @@ pd692x0_set_ports_matrix(struct pd692x0_
+       /* Update with values for every PSE PIs */
+       for (i = 0; i < pcdev->nr_lines; i++) {
+               ret = pd692x0_set_port_matrix(&pcdev->pi[i].pairset[0],
+-                                            manager, nmanagers,
++                                            manager, priv->nmanagers,
+                                             &port_matrix[i]);
+               if (ret) {
+                       dev_err(&priv->client->dev,
+@@ -1126,7 +1142,7 @@ pd692x0_set_ports_matrix(struct pd692x0_
+               }
+               ret = pd692x0_set_port_matrix(&pcdev->pi[i].pairset[1],
+-                                            manager, nmanagers,
++                                            manager, priv->nmanagers,
+                                             &port_matrix[i]);
+               if (ret) {
+                       dev_err(&priv->client->dev,
+@@ -1139,9 +1155,9 @@ pd692x0_set_ports_matrix(struct pd692x0_
+ }
+ static int
+-pd692x0_write_ports_matrix(struct pd692x0_priv *priv,
+-                         const struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS])
++pd692x0_write_ports_matrix(struct pd692x0_priv *priv)
+ {
++      struct pd692x0_matrix *port_matrix = priv->port_matrix;
+       struct pd692x0_msg msg, buf;
+       int ret, i;
+@@ -1166,13 +1182,32 @@ pd692x0_write_ports_matrix(struct pd692x
+       return 0;
+ }
++static int pd692x0_hw_conf_init(struct pd692x0_priv *priv)
++{
++      int ret;
++
++      /* Is PD692x0 ready to be configured? */
++      if (priv->fw_state != PD692X0_FW_OK &&
++          priv->fw_state != PD692X0_FW_COMPLETE)
++              return 0;
++
++      ret = pd692x0_configure_managers(priv);
++      if (ret)
++              return ret;
++
++      ret = pd692x0_write_ports_matrix(priv);
++      if (ret)
++              return ret;
++
++      return 0;
++}
++
+ static void pd692x0_of_put_managers(struct pd692x0_priv *priv,
+-                                  struct pd692x0_manager *manager,
+-                                  int nmanagers)
++                                  struct pd692x0_manager *manager)
+ {
+       int i, j;
+-      for (i = 0; i < nmanagers; i++) {
++      for (i = 0; i < priv->nmanagers; i++) {
+               for (j = 0; j < manager[i].nports; j++)
+                       of_node_put(manager[i].port_node[j]);
+               of_node_put(manager[i].node);
+@@ -1201,48 +1236,50 @@ static void pd692x0_managers_free_pw_bud
+ static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
+ {
+       struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+-      struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS];
++      struct pd692x0_matrix *port_matrix;
+       struct pd692x0_manager *manager;
+-      int ret, nmanagers;
+-
+-      /* Should we flash the port matrix */
+-      if (priv->fw_state != PD692X0_FW_OK &&
+-          priv->fw_state != PD692X0_FW_COMPLETE)
+-              return 0;
++      int ret;
+       manager = kcalloc(PD692X0_MAX_MANAGERS, sizeof(*manager), GFP_KERNEL);
+       if (!manager)
+               return -ENOMEM;
++      port_matrix = devm_kcalloc(&priv->client->dev, PD692X0_MAX_PIS,
++                                 sizeof(*port_matrix), GFP_KERNEL);
++      if (!port_matrix) {
++              ret = -ENOMEM;
++              goto err_free_manager;
++      }
++      priv->port_matrix = port_matrix;
++
+       ret = pd692x0_of_get_managers(priv, manager);
+       if (ret < 0)
+               goto err_free_manager;
+-      nmanagers = ret;
+-      ret = pd692x0_register_managers_regulator(priv, manager, nmanagers);
++      ret = pd692x0_register_managers_regulator(priv, manager);
+       if (ret)
+               goto err_of_managers;
+-      ret = pd692x0_configure_managers(priv, nmanagers);
++      ret = pd692x0_req_managers_pw_budget(priv);
+       if (ret)
+               goto err_of_managers;
+-      ret = pd692x0_set_ports_matrix(priv, manager, nmanagers, port_matrix);
++      ret = pd692x0_set_ports_matrix(priv, manager);
+       if (ret)
+               goto err_managers_req_pw;
+-      ret = pd692x0_write_ports_matrix(priv, port_matrix);
++      ret = pd692x0_hw_conf_init(priv);
+       if (ret)
+               goto err_managers_req_pw;
+-      pd692x0_of_put_managers(priv, manager, nmanagers);
++      pd692x0_of_put_managers(priv, manager);
+       kfree(manager);
+       return 0;
+ err_managers_req_pw:
+       pd692x0_managers_free_pw_budget(priv);
+ err_of_managers:
+-      pd692x0_of_put_managers(priv, manager, nmanagers);
++      pd692x0_of_put_managers(priv, manager);
+ err_free_manager:
+       kfree(manager);
+       return ret;
+@@ -1647,7 +1684,7 @@ static enum fw_upload_err pd692x0_fw_pol
+               return FW_UPLOAD_ERR_FW_INVALID;
+       }
+-      ret = pd692x0_setup_pi_matrix(&priv->pcdev);
++      ret = pd692x0_hw_conf_init(priv);
+       if (ret < 0) {
+               dev_err(&client->dev, "Error configuring ports matrix (%pe)\n",
+                       ERR_PTR(ret));
diff --git a/target/linux/generic/backport-6.12/626-27-v6.19-net-pse-pd-pd692x0-Preserve-PSE-configuration.patch b/target/linux/generic/backport-6.12/626-27-v6.19-net-pse-pd-pd692x0-Preserve-PSE-configuration.patch
new file mode 100644 (file)
index 0000000..4ae835e
--- /dev/null
@@ -0,0 +1,111 @@
+From 8f3d044b34fe99b894046edb84605456195cabc0 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Mon, 13 Oct 2025 16:05:33 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Preserve PSE configuration across
+ reboots
+
+Detect when PSE hardware is already configured (user byte == 42) and
+skip hardware initialization to prevent power interruption to connected
+devices during system reboots.
+
+Previously, the driver would always reconfigure the PSE hardware on
+probe, causing a port matrix reflash that resulted in temporary power
+loss to all connected devices. This change maintains power continuity
+by preserving existing configuration when the PSE has been previously
+initialized.
+
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/20251013-feature_pd692x0_reboot_keep_conf-v2-3-68ab082a93dd@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 35 ++++++++++++++++++++++++++++++++---
+ 1 file changed, 32 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -30,6 +30,8 @@
+ #define PD692X0_FW_MIN_VER    5
+ #define PD692X0_FW_PATCH_VER  5
++#define PD692X0_USER_BYTE     42
++
+ enum pd692x0_fw_state {
+       PD692X0_FW_UNKNOWN,
+       PD692X0_FW_OK,
+@@ -80,6 +82,7 @@ enum {
+       PD692X0_MSG_GET_PORT_PARAM,
+       PD692X0_MSG_GET_POWER_BANK,
+       PD692X0_MSG_SET_POWER_BANK,
++      PD692X0_MSG_SET_USER_BYTE,
+       /* add new message above here */
+       PD692X0_MSG_CNT
+@@ -103,6 +106,7 @@ struct pd692x0_priv {
+       bool last_cmd_key;
+       unsigned long last_cmd_key_time;
++      bool cfg_saved;
+       enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS];
+       struct regulator_dev *manager_reg[PD692X0_MAX_MANAGERS];
+       int manager_pw_budget[PD692X0_MAX_MANAGERS];
+@@ -193,6 +197,12 @@ static const struct pd692x0_msg pd692x0_
+               .key = PD692X0_KEY_CMD,
+               .sub = {0x07, 0x0b, 0x57},
+       },
++      [PD692X0_MSG_SET_USER_BYTE] = {
++              .key = PD692X0_KEY_PRG,
++              .sub = {0x41, PD692X0_USER_BYTE},
++              .data = {0x4e, 0x4e, 0x4e, 0x4e,
++                       0x4e, 0x4e, 0x4e, 0x4e},
++      },
+ };
+ static u8 pd692x0_build_msg(struct pd692x0_msg *msg, u8 echo)
+@@ -1233,6 +1243,15 @@ static void pd692x0_managers_free_pw_bud
+       }
+ }
++static int
++pd692x0_save_user_byte(struct pd692x0_priv *priv)
++{
++      struct pd692x0_msg msg, buf;
++
++      msg = pd692x0_msg_template_list[PD692X0_MSG_SET_USER_BYTE];
++      return pd692x0_sendrecv_msg(priv, &msg, &buf);
++}
++
+ static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
+ {
+       struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+@@ -1268,9 +1287,16 @@ static int pd692x0_setup_pi_matrix(struc
+       if (ret)
+               goto err_managers_req_pw;
+-      ret = pd692x0_hw_conf_init(priv);
+-      if (ret)
+-              goto err_managers_req_pw;
++      /* Do not init the conf if it is already saved */
++      if (!priv->cfg_saved) {
++              ret = pd692x0_hw_conf_init(priv);
++              if (ret)
++                      goto err_managers_req_pw;
++
++              ret = pd692x0_save_user_byte(priv);
++              if (ret)
++                      goto err_managers_req_pw;
++      }
+       pd692x0_of_put_managers(priv, manager);
+       kfree(manager);
+@@ -1793,6 +1819,9 @@ static int pd692x0_i2c_probe(struct i2c_
+               }
+       }
++      if (buf.data[2] == PD692X0_USER_BYTE)
++              priv->cfg_saved = true;
++
+       priv->np = dev->of_node;
+       priv->pcdev.nr_lines = PD692X0_MAX_PIS;
+       priv->pcdev.owner = THIS_MODULE;
diff --git a/target/linux/generic/backport-6.12/626-28-v6.19-net-pse-pd-tps23881-Add-support-for-TPS23881B.patch b/target/linux/generic/backport-6.12/626-28-v6.19-net-pse-pd-tps23881-Add-support-for-TPS23881B.patch
new file mode 100644 (file)
index 0000000..2c188db
--- /dev/null
@@ -0,0 +1,170 @@
+From 4d07797faaa19aa8e80e10a04ca1a72c643ef5cf Mon Sep 17 00:00:00 2001
+From: Thomas Wismer <thomas.wismer@scs.ch>
+Date: Wed, 29 Oct 2025 22:23:09 +0100
+Subject: [PATCH] net: pse-pd: tps23881: Add support for TPS23881B
+
+The TPS23881B uses different firmware than the TPS23881. Trying to load the
+TPS23881 firmware on a TPS23881B device fails and must be omitted.
+
+The TPS23881B ships with a more recent ROM firmware. Moreover, no updated
+firmware has been released yet and so the firmware loading step must be
+skipped. As of today, the TPS23881B is intended to use its ROM firmware.
+
+Signed-off-by: Thomas Wismer <thomas.wismer@scs.ch>
+Reviewed-by: Kory Maincent <kory.maincent@bootlin.com>
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20251029212312.108749-2-thomas@wismer.xyz
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 69 +++++++++++++++++++++++++++--------
+ 1 file changed, 54 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -55,8 +55,6 @@
+ #define TPS23881_REG_TPON     BIT(0)
+ #define TPS23881_REG_FWREV    0x41
+ #define TPS23881_REG_DEVID    0x43
+-#define TPS23881_REG_DEVID_MASK       0xF0
+-#define TPS23881_DEVICE_ID    0x02
+ #define TPS23881_REG_CHAN1_CLASS      0x4c
+ #define TPS23881_REG_SRAM_CTRL        0x60
+ #define TPS23881_REG_SRAM_DATA        0x61
+@@ -1012,8 +1010,28 @@ static const struct pse_controller_ops t
+       .pi_get_pw_req = tps23881_pi_get_pw_req,
+ };
+-static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin";
+-static const char fw_sram_name[] = "ti/tps23881/tps23881-sram-14.bin";
++struct tps23881_info {
++      u8 dev_id;      /* device ID and silicon revision */
++      const char *fw_parity_name;     /* parity code firmware file name */
++      const char *fw_sram_name;       /* SRAM code firmware file name */
++};
++
++enum tps23881_model {
++      TPS23881,
++      TPS23881B,
++};
++
++static const struct tps23881_info tps23881_info[] = {
++      [TPS23881] = {
++              .dev_id = 0x22,
++              .fw_parity_name = "ti/tps23881/tps23881-parity-14.bin",
++              .fw_sram_name = "ti/tps23881/tps23881-sram-14.bin",
++      },
++      [TPS23881B] = {
++              .dev_id = 0x24,
++              /* skip SRAM load, ROM provides Clause 145 hardware-level support */
++      },
++};
+ struct tps23881_fw_conf {
+       u8 reg;
+@@ -1085,16 +1103,17 @@ out:
+       return ret;
+ }
+-static int tps23881_flash_sram_fw(struct i2c_client *client)
++static int tps23881_flash_sram_fw(struct i2c_client *client,
++                                const struct tps23881_info *info)
+ {
+       int ret;
+-      ret = tps23881_flash_sram_fw_part(client, fw_parity_name,
++      ret = tps23881_flash_sram_fw_part(client, info->fw_parity_name,
+                                         tps23881_fw_parity_conf);
+       if (ret)
+               return ret;
+-      ret = tps23881_flash_sram_fw_part(client, fw_sram_name,
++      ret = tps23881_flash_sram_fw_part(client, info->fw_sram_name,
+                                         tps23881_fw_sram_conf);
+       if (ret)
+               return ret;
+@@ -1412,6 +1431,7 @@ static int tps23881_setup_irq(struct tps
+ static int tps23881_i2c_probe(struct i2c_client *client)
+ {
+       struct device *dev = &client->dev;
++      const struct tps23881_info *info;
+       struct tps23881_priv *priv;
+       struct gpio_desc *reset;
+       int ret;
+@@ -1422,6 +1442,10 @@ static int tps23881_i2c_probe(struct i2c
+               return -ENXIO;
+       }
++      info = i2c_get_match_data(client);
++      if (!info)
++              return -EINVAL;
++
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+@@ -1440,7 +1464,7 @@ static int tps23881_i2c_probe(struct i2c
+                * to Load TPS2388x SRAM and Parity Code over I2C" (Rev E))
+                * indicates we should delay that programming by at least 50ms. So
+                * we'll wait the entire 50ms here to ensure we're safe to go to the
+-               * SRAM loading proceedure.
++               * SRAM loading procedure.
+                */
+               msleep(50);
+       }
+@@ -1449,20 +1473,27 @@ static int tps23881_i2c_probe(struct i2c
+       if (ret < 0)
+               return ret;
+-      if (FIELD_GET(TPS23881_REG_DEVID_MASK, ret) != TPS23881_DEVICE_ID) {
++      if (ret != info->dev_id) {
+               dev_err(dev, "Wrong device ID\n");
+               return -ENXIO;
+       }
+-      ret = tps23881_flash_sram_fw(client);
+-      if (ret < 0)
+-              return ret;
++      if (info->fw_sram_name) {
++              ret = tps23881_flash_sram_fw(client, info);
++              if (ret < 0)
++                      return ret;
++      }
+       ret = i2c_smbus_read_byte_data(client, TPS23881_REG_FWREV);
+       if (ret < 0)
+               return ret;
+-      dev_info(&client->dev, "Firmware revision 0x%x\n", ret);
++      if (ret == 0xFF) {
++              dev_err(&client->dev, "Device entered safe mode\n");
++              return -ENXIO;
++      }
++      dev_info(&client->dev, "Firmware revision 0x%x%s\n", ret,
++               ret == 0x00 ? " (ROM firmware)" : "");
+       /* Set configuration B, 16 bit access on a single device address */
+       ret = i2c_smbus_read_byte_data(client, TPS23881_REG_GEN_MASK);
+@@ -1498,13 +1529,21 @@ static int tps23881_i2c_probe(struct i2c
+ }
+ static const struct i2c_device_id tps23881_id[] = {
+-      { "tps23881" },
++      { "tps23881", .driver_data = (kernel_ulong_t)&tps23881_info[TPS23881] },
++      { "tps23881b", .driver_data = (kernel_ulong_t)&tps23881_info[TPS23881B] },
+       { }
+ };
+ MODULE_DEVICE_TABLE(i2c, tps23881_id);
+ static const struct of_device_id tps23881_of_match[] = {
+-      { .compatible = "ti,tps23881", },
++      {
++              .compatible = "ti,tps23881",
++              .data = &tps23881_info[TPS23881]
++      },
++      {
++              .compatible = "ti,tps23881b",
++              .data = &tps23881_info[TPS23881B]
++      },
+       { },
+ };
+ MODULE_DEVICE_TABLE(of, tps23881_of_match);
diff --git a/target/linux/generic/backport-6.12/626-v6.16-net-bridge-locally-receive-all-multicast-packets-if-.patch b/target/linux/generic/backport-6.12/626-v6.16-net-bridge-locally-receive-all-multicast-packets-if-.patch
new file mode 100644 (file)
index 0000000..d9bd8c7
--- /dev/null
@@ -0,0 +1,41 @@
+From a496d2f0fd612ab9e10700afe00dc9267bad788b Mon Sep 17 00:00:00 2001
+From: Shengyu Qu <wiagn233@outlook.com>
+Date: Mon, 14 Apr 2025 18:56:01 +0800
+Subject: net: bridge: locally receive all multicast packets if IFF_ALLMULTI is
+ set
+
+If multicast snooping is enabled, multicast packets may not always end up
+on the local bridge interface, if the host is not a member of the multicast
+group. Similar to how IFF_PROMISC allows all packets to be received
+locally, let IFF_ALLMULTI allow all multicast packets to be received.
+
+OpenWrt uses a user space daemon for DHCPv6/RA/NDP handling, and in relay
+mode it sets the ALLMULTI flag in order to receive all relevant queries on
+the network.
+
+This works for normal network interfaces and non-snooping bridges, but not
+snooping bridges (unless multicast routing is enabled).
+
+Reported-by: Felix Fietkau <nbd@nbd.name>
+Closes: https://github.com/openwrt/openwrt/issues/15857#issuecomment-2662851243
+Signed-off-by: Shengyu Qu <wiagn233@outlook.com>
+Reviewed-by: Ido Schimmel <idosch@nvidia.com>
+Acked-by: Nikolay Aleksandrov <razor@blackwall.org>
+Link: https://patch.msgid.link/OSZPR01MB8434308370ACAFA90A22980798B32@OSZPR01MB8434.jpnprd01.prod.outlook.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/bridge/br_input.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/net/bridge/br_input.c
++++ b/net/bridge/br_input.c
+@@ -184,7 +184,8 @@ int br_handle_frame_finish(struct net *n
+               if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
+                   br_multicast_querier_exists(brmctx, eth_hdr(skb), mdst)) {
+                       if ((mdst && mdst->host_joined) ||
+-                          br_multicast_is_router(brmctx, skb)) {
++                          br_multicast_is_router(brmctx, skb) ||
++                          br->dev->flags & IFF_ALLMULTI) {
+                               local_rcv = true;
+                               DEV_STATS_INC(br->dev, multicast);
+                       }
diff --git a/target/linux/generic/backport-6.12/627-01-v6.17-net-pse-pd-Add-ethtool_netlink_generated-header.patch b/target/linux/generic/backport-6.12/627-01-v6.17-net-pse-pd-Add-ethtool_netlink_generated-header.patch
new file mode 100644 (file)
index 0000000..f773fb6
--- /dev/null
@@ -0,0 +1,79 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: OpenWrt PSE-PD Backport <openwrt@openwrt.org>
+Date: Fri, 31 Jan 2025 00:00:00 +0000
+Subject: [PATCH] net: pse-pd: Add ethtool netlink definitions for PSE events
+ and priority
+
+GENERATED PATCH - OpenWrt PSE-PD Backport
+
+This patch:
+1. Adds ETHTOOL_A_PSE_PW_D_ID, ETHTOOL_A_PSE_PRIO_MAX, ETHTOOL_A_PSE_PRIO to
+   the ETHTOOL_A_PSE_* enum in ethtool_netlink.h (backported from kernel 6.17+)
+2. Creates include/uapi/linux/ethtool_netlink_generated.h with PSE event
+   definitions (in mainline 6.17+, this file is auto-generated)
+3. Adds ETHTOOL_MSG_PSE_NTF to the kernel message types enum
+
+These definitions are required by PSE event reporting and priority patches.
+
+Signed-off-by: OpenWrt PSE-PD Backport <openwrt@openwrt.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ include/uapi/linux/ethtool_netlink.h           |  4 ++
+ include/uapi/linux/ethtool_netlink_generated.h | 31 ++++++++++++++++++++++++++
+ 2 files changed, 35 insertions(+)
+ create mode 100644 include/uapi/linux/ethtool_netlink_generated.h
+
+--- a/include/uapi/linux/ethtool_netlink.h
++++ b/include/uapi/linux/ethtool_netlink.h
+@@ -115,6 +115,7 @@ enum {
+       ETHTOOL_MSG_PHY_GET_REPLY,
+       ETHTOOL_MSG_PHY_NTF,
++      ETHTOOL_MSG_PSE_NTF,
+       /* add new constants above here */
+       __ETHTOOL_MSG_KERNEL_CNT,
+       ETHTOOL_MSG_KERNEL_MAX = __ETHTOOL_MSG_KERNEL_CNT - 1
+@@ -970,6 +971,9 @@ enum {
+       ETHTOOL_A_C33_PSE_EXT_SUBSTATE,         /* u32 */
+       ETHTOOL_A_C33_PSE_AVAIL_PW_LIMIT,       /* u32 */
+       ETHTOOL_A_C33_PSE_PW_LIMIT_RANGES,      /* nest - _C33_PSE_PW_LIMIT_* */
++      ETHTOOL_A_PSE_PW_D_ID,                  /* u32 */
++      ETHTOOL_A_PSE_PRIO_MAX,                 /* u32 */
++      ETHTOOL_A_PSE_PRIO,                     /* u32 */
+       /* add new constants above here */
+       __ETHTOOL_A_PSE_CNT,
+--- /dev/null
++++ b/include/uapi/linux/ethtool_netlink_generated.h
+@@ -0,0 +1,31 @@
++/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
++/* PSE ethtool netlink definitions - OpenWrt backport for 6.12 */
++
++#ifndef _UAPI_LINUX_ETHTOOL_NETLINK_GENERATED_H
++#define _UAPI_LINUX_ETHTOOL_NETLINK_GENERATED_H
++
++enum ethtool_pse_event {
++      ETHTOOL_PSE_EVENT_OVER_CURRENT = 1,
++      ETHTOOL_PSE_EVENT_OVER_TEMP = 2,
++      ETHTOOL_C33_PSE_EVENT_DETECTION = 4,
++      ETHTOOL_C33_PSE_EVENT_CLASSIFICATION = 8,
++      ETHTOOL_C33_PSE_EVENT_DISCONNECTION = 16,
++      ETHTOOL_PSE_EVENT_OVER_BUDGET = 32,
++      ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR = 64,
++};
++
++enum {
++      ETHTOOL_A_PSE_NTF_HEADER = 1,
++      ETHTOOL_A_PSE_NTF_EVENTS,
++      __ETHTOOL_A_PSE_NTF_CNT,
++      ETHTOOL_A_PSE_NTF_MAX = (__ETHTOOL_A_PSE_NTF_CNT - 1)
++};
++
++enum {
++      ETHTOOL_A_PSE_NTF_EVT_UNSPEC,
++      ETHTOOL_A_PSE_NTF_EVT_INDEX,
++      __ETHTOOL_A_PSE_NTF_EVT_CNT,
++      ETHTOOL_A_PSE_NTF_EVT_MAX = (__ETHTOOL_A_PSE_NTF_EVT_CNT - 1)
++};
++
++#endif /* _UAPI_LINUX_ETHTOOL_NETLINK_GENERATED_H */
diff --git a/target/linux/generic/backport-6.12/627-02-v6.17-net-ethtool-Add-PSE-port-priority-support.patch b/target/linux/generic/backport-6.12/627-02-v6.17-net-ethtool-Add-PSE-port-priority-support.patch
new file mode 100644 (file)
index 0000000..06d9b11
--- /dev/null
@@ -0,0 +1,68 @@
+# ADAPTED FOR OPENWRT 6.12 - Documentation changes removed
+# Original commit: eeb0c8f72f49
+From eeb0c8f72f49a21984981188404cfd3700edbaff Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:07 +0200
+Subject: [PATCH] net: ethtool: Add PSE port priority support feature
+
+This patch expands the status information provided by ethtool for PSE c33
+with current port priority and max port priority. It also adds a call to
+pse_ethtool_set_prio() to configure the PSE port priority.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-8-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
+---
+ net/ethtool/pse-pd.c | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+--- a/net/ethtool/pse-pd.c
++++ b/net/ethtool/pse-pd.c
+@@ -111,6 +111,9 @@ static int pse_reply_size(const struct e
+               len += st->c33_pw_limit_nb_ranges *
+                      (nla_total_size(0) +
+                       nla_total_size(sizeof(u32)) * 2);
++      if (st->prio_max)
++              /* _PSE_PRIO_MAX + _PSE_PRIO */
++              len += nla_total_size(sizeof(u32)) * 2;
+       return len;
+ }
+@@ -205,6 +208,11 @@ static int pse_fill_reply(struct sk_buff
+           pse_put_pw_limit_ranges(skb, st))
+               return -EMSGSIZE;
++      if (st->prio_max &&
++          (nla_put_u32(skb, ETHTOOL_A_PSE_PRIO_MAX, st->prio_max) ||
++           nla_put_u32(skb, ETHTOOL_A_PSE_PRIO, st->prio)))
++              return -EMSGSIZE;
++
+       return 0;
+ }
+@@ -226,6 +234,7 @@ const struct nla_policy ethnl_pse_set_po
+               NLA_POLICY_RANGE(NLA_U32, ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED,
+                                ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED),
+       [ETHTOOL_A_C33_PSE_AVAIL_PW_LIMIT] = { .type = NLA_U32 },
++      [ETHTOOL_A_PSE_PRIO] = { .type = NLA_U32 },
+ };
+ static int
+@@ -283,6 +292,15 @@ ethnl_set_pse(struct ethnl_req_info *req
+               if (ret)
+                       return ret;
+       }
++
++      if (tb[ETHTOOL_A_PSE_PRIO]) {
++              unsigned int prio;
++
++              prio = nla_get_u32(tb[ETHTOOL_A_PSE_PRIO]);
++              ret = pse_ethtool_set_prio(phydev->psec, info->extack, prio);
++              if (ret)
++                      return ret;
++      }
+       /* These values are already validated by the ethnl_pse_set_policy */
+       if (tb[ETHTOOL_A_PODL_PSE_ADMIN_CONTROL] ||
diff --git a/target/linux/generic/backport-6.12/630-v6.13-net-usb-qmi_wwan-add-Quectel-RG255C.patch b/target/linux/generic/backport-6.12/630-v6.13-net-usb-qmi_wwan-add-Quectel-RG255C.patch
new file mode 100644 (file)
index 0000000..a1eb835
--- /dev/null
@@ -0,0 +1,47 @@
+From 5c964c8a97c12145104f5d2782aa1ffccf3a93dd Mon Sep 17 00:00:00 2001
+From: Martin Hou <martin.hou@foxmail.com>
+Date: Mon, 16 Dec 2024 11:06:18 +0800
+Subject: [PATCH] net: usb: qmi_wwan: add Quectel RG255C
+
+Add support for Quectel RG255C which is based on Qualcomm SDX35 chip.
+The composition is DM / NMEA / AT / QMI.
+
+T:  Bus=01 Lev=01 Prnt=01 Port=04 Cnt=01 Dev#=  2 Spd=480  MxCh= 0
+D:  Ver= 2.01 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs=  1
+P:  Vendor=2c7c ProdID=0316 Rev= 5.15
+S:  Manufacturer=Quectel
+S:  Product=RG255C-CN
+S:  SerialNumber=c68192c1
+C:* #Ifs= 4 Cfg#= 1 Atr=a0 MxPwr=500mA
+I:* If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option
+E:  Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
+E:  Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
+I:* If#= 1 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=option
+E:  Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
+E:  Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
+I:* If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option
+E:  Ad=84(I) Atr=03(Int.) MxPS=  10 Ivl=32ms
+E:  Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
+E:  Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
+I:* If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=50 Driver=qmi_wwan
+E:  Ad=86(I) Atr=03(Int.) MxPS=   8 Ivl=32ms
+E:  Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
+E:  Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
+
+Signed-off-by: Martin Hou <martin.hou@foxmail.com>
+Link: https://patch.msgid.link/tencent_17DDD787B48E8A5AB8379ED69E23A0CD9309@qq.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/usb/qmi_wwan.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/usb/qmi_wwan.c
++++ b/drivers/net/usb/qmi_wwan.c
+@@ -1442,6 +1442,7 @@ static const struct usb_device_id produc
+       {QMI_QUIRK_SET_DTR(0x2c7c, 0x0195, 4)}, /* Quectel EG95 */
+       {QMI_FIXED_INTF(0x2c7c, 0x0296, 4)},    /* Quectel BG96 */
+       {QMI_QUIRK_SET_DTR(0x2c7c, 0x030e, 4)}, /* Quectel EM05GV2 */
++      {QMI_QUIRK_SET_DTR(0x2c7c, 0x0316, 3)}, /* Quectel RG255C */
+       {QMI_QUIRK_SET_DTR(0x2cb7, 0x0104, 4)}, /* Fibocom NL678 series */
+       {QMI_QUIRK_SET_DTR(0x2cb7, 0x0112, 0)}, /* Fibocom FG132 */
+       {QMI_FIXED_INTF(0x0489, 0xe0b4, 0)},    /* Foxconn T77W968 LTE */
diff --git a/target/linux/generic/backport-6.12/630-v6.14-PM-sleep-wakeirq-Introduce-device-managed-variant-of.patch b/target/linux/generic/backport-6.12/630-v6.14-PM-sleep-wakeirq-Introduce-device-managed-variant-of.patch
new file mode 100644 (file)
index 0000000..06edffa
--- /dev/null
@@ -0,0 +1,74 @@
+From fd8318a32573d73eb20637a0c80689de0dc98169 Mon Sep 17 00:00:00 2001
+From: Peng Fan <peng.fan@nxp.com>
+Date: Fri, 3 Jan 2025 16:41:13 +0800
+Subject: [PATCH] PM: sleep: wakeirq: Introduce device-managed variant of
+ dev_pm_set_wake_irq()
+
+Add device-managed variant of dev_pm_set_wake_irq which automatically
+clear the wake irq on device destruction to simplify error handling
+and resource management in drivers.
+
+Signed-off-by: Peng Fan <peng.fan@nxp.com>
+Link: https://patch.msgid.link/20250103-wake_irq-v2-1-e3aeff5e9966@nxp.com
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+---
+ drivers/base/power/wakeirq.c | 26 ++++++++++++++++++++++++++
+ include/linux/pm_wakeirq.h   |  6 ++++++
+ 2 files changed, 32 insertions(+)
+
+--- a/drivers/base/power/wakeirq.c
++++ b/drivers/base/power/wakeirq.c
+@@ -106,6 +106,32 @@ void dev_pm_clear_wake_irq(struct device
+ }
+ EXPORT_SYMBOL_GPL(dev_pm_clear_wake_irq);
++static void devm_pm_clear_wake_irq(void *dev)
++{
++      dev_pm_clear_wake_irq(dev);
++}
++
++/**
++ * devm_pm_set_wake_irq - device-managed variant of dev_pm_set_wake_irq
++ * @dev: Device entry
++ * @irq: Device IO interrupt
++ *
++ *
++ * Attach a device IO interrupt as a wake IRQ, same with dev_pm_set_wake_irq,
++ * but the device will be auto clear wake capability on driver detach.
++ */
++int devm_pm_set_wake_irq(struct device *dev, int irq)
++{
++      int ret;
++
++      ret = dev_pm_set_wake_irq(dev, irq);
++      if (ret)
++              return ret;
++
++      return devm_add_action_or_reset(dev, devm_pm_clear_wake_irq, dev);
++}
++EXPORT_SYMBOL_GPL(devm_pm_set_wake_irq);
++
+ /**
+  * handle_threaded_wake_irq - Handler for dedicated wake-up interrupts
+  * @irq: Device specific dedicated wake-up interrupt
+--- a/include/linux/pm_wakeirq.h
++++ b/include/linux/pm_wakeirq.h
+@@ -10,6 +10,7 @@ extern int dev_pm_set_wake_irq(struct de
+ extern int dev_pm_set_dedicated_wake_irq(struct device *dev, int irq);
+ extern int dev_pm_set_dedicated_wake_irq_reverse(struct device *dev, int irq);
+ extern void dev_pm_clear_wake_irq(struct device *dev);
++extern int devm_pm_set_wake_irq(struct device *dev, int irq);
+ #else /* !CONFIG_PM */
+@@ -32,5 +33,10 @@ static inline void dev_pm_clear_wake_irq
+ {
+ }
++static inline int devm_pm_set_wake_irq(struct device *dev, int irq)
++{
++      return 0;
++}
++
+ #endif        /* CONFIG_PM */
+ #endif        /* _LINUX_PM_WAKEIRQ_H */
diff --git a/target/linux/generic/backport-6.12/700-01-v7.0-net-sched-Export-mq-functions-for-reuse.patch b/target/linux/generic/backport-6.12/700-01-v7.0-net-sched-Export-mq-functions-for-reuse.patch
new file mode 100644 (file)
index 0000000..01439cd
--- /dev/null
@@ -0,0 +1,255 @@
+From 8560edcc05114e42d4d580baaf124bce8c291d55 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com>
+Date: Fri, 9 Jan 2026 14:15:30 +0100
+Subject: [PATCH 1/7] net/sched: Export mq functions for reuse
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+To enable the cake_mq qdisc to reuse code from the mq qdisc, export a
+bunch of functions from sch_mq. Split common functionality out from some
+functions so it can be composed with other code, and export other
+functions wholesale. To discourage wanton reuse, put the symbols into a
+new NET_SCHED_INTERNAL namespace, and a sch_priv.h header file.
+
+No functional change intended.
+
+Reviewed-by: Willem de Bruijn <willemb@google.com>
+Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
+Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-1-8d613fece5d8@redhat.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ MAINTAINERS            |  1 +
+ include/net/sch_priv.h | 27 ++++++++++++++++
+ net/sched/sch_mq.c     | 71 +++++++++++++++++++++++++++++-------------
+ 3 files changed, 77 insertions(+), 22 deletions(-)
+ create mode 100644 include/net/sch_priv.h
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -22541,6 +22541,7 @@ L:     netdev@vger.kernel.org
+ S:    Maintained
+ F:    include/net/pkt_cls.h
+ F:    include/net/pkt_sched.h
++F:    include/net/sch_priv.h
+ F:    include/net/tc_act/
+ F:    include/uapi/linux/pkt_cls.h
+ F:    include/uapi/linux/pkt_sched.h
+--- /dev/null
++++ b/include/net/sch_priv.h
+@@ -0,0 +1,27 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++#ifndef __NET_SCHED_PRIV_H
++#define __NET_SCHED_PRIV_H
++
++#include <net/sch_generic.h>
++
++struct mq_sched {
++      struct Qdisc            **qdiscs;
++};
++
++int mq_init_common(struct Qdisc *sch, struct nlattr *opt,
++                 struct netlink_ext_ack *extack,
++                 const struct Qdisc_ops *qdisc_ops);
++void mq_destroy_common(struct Qdisc *sch);
++void mq_attach(struct Qdisc *sch);
++void mq_dump_common(struct Qdisc *sch, struct sk_buff *skb);
++struct netdev_queue *mq_select_queue(struct Qdisc *sch,
++                                   struct tcmsg *tcm);
++struct Qdisc *mq_leaf(struct Qdisc *sch, unsigned long cl);
++unsigned long mq_find(struct Qdisc *sch, u32 classid);
++int mq_dump_class(struct Qdisc *sch, unsigned long cl,
++                struct sk_buff *skb, struct tcmsg *tcm);
++int mq_dump_class_stats(struct Qdisc *sch, unsigned long cl,
++                      struct gnet_dump *d);
++void mq_walk(struct Qdisc *sch, struct qdisc_walker *arg);
++
++#endif
+--- a/net/sched/sch_mq.c
++++ b/net/sched/sch_mq.c
+@@ -15,11 +15,7 @@
+ #include <net/netlink.h>
+ #include <net/pkt_cls.h>
+ #include <net/pkt_sched.h>
+-#include <net/sch_generic.h>
+-
+-struct mq_sched {
+-      struct Qdisc            **qdiscs;
+-};
++#include <net/sch_priv.h>
+ static int mq_offload(struct Qdisc *sch, enum tc_mq_command cmd)
+ {
+@@ -49,23 +45,29 @@ static int mq_offload_stats(struct Qdisc
+       return qdisc_offload_dump_helper(sch, TC_SETUP_QDISC_MQ, &opt);
+ }
+-static void mq_destroy(struct Qdisc *sch)
++void mq_destroy_common(struct Qdisc *sch)
+ {
+       struct net_device *dev = qdisc_dev(sch);
+       struct mq_sched *priv = qdisc_priv(sch);
+       unsigned int ntx;
+-      mq_offload(sch, TC_MQ_DESTROY);
+-
+       if (!priv->qdiscs)
+               return;
+       for (ntx = 0; ntx < dev->num_tx_queues && priv->qdiscs[ntx]; ntx++)
+               qdisc_put(priv->qdiscs[ntx]);
+       kfree(priv->qdiscs);
+ }
++EXPORT_SYMBOL_NS_GPL(mq_destroy_common, NET_SCHED_INTERNAL);
+-static int mq_init(struct Qdisc *sch, struct nlattr *opt,
+-                 struct netlink_ext_ack *extack)
++static void mq_destroy(struct Qdisc *sch)
++{
++      mq_offload(sch, TC_MQ_DESTROY);
++      mq_destroy_common(sch);
++}
++
++int mq_init_common(struct Qdisc *sch, struct nlattr *opt,
++                 struct netlink_ext_ack *extack,
++                 const struct Qdisc_ops *qdisc_ops)
+ {
+       struct net_device *dev = qdisc_dev(sch);
+       struct mq_sched *priv = qdisc_priv(sch);
+@@ -87,7 +89,8 @@ static int mq_init(struct Qdisc *sch, st
+       for (ntx = 0; ntx < dev->num_tx_queues; ntx++) {
+               dev_queue = netdev_get_tx_queue(dev, ntx);
+-              qdisc = qdisc_create_dflt(dev_queue, get_default_qdisc_ops(dev, ntx),
++              qdisc = qdisc_create_dflt(dev_queue,
++                                        qdisc_ops ?: get_default_qdisc_ops(dev, ntx),
+                                         TC_H_MAKE(TC_H_MAJ(sch->handle),
+                                                   TC_H_MIN(ntx + 1)),
+                                         extack);
+@@ -98,12 +101,24 @@ static int mq_init(struct Qdisc *sch, st
+       }
+       sch->flags |= TCQ_F_MQROOT;
++      return 0;
++}
++EXPORT_SYMBOL_NS_GPL(mq_init_common, NET_SCHED_INTERNAL);
++
++static int mq_init(struct Qdisc *sch, struct nlattr *opt,
++                 struct netlink_ext_ack *extack)
++{
++      int ret;
++
++      ret = mq_init_common(sch, opt, extack, NULL);
++      if (ret)
++              return ret;
+       mq_offload(sch, TC_MQ_CREATE);
+       return 0;
+ }
+-static void mq_attach(struct Qdisc *sch)
++void mq_attach(struct Qdisc *sch)
+ {
+       struct net_device *dev = qdisc_dev(sch);
+       struct mq_sched *priv = qdisc_priv(sch);
+@@ -124,8 +139,9 @@ static void mq_attach(struct Qdisc *sch)
+       kfree(priv->qdiscs);
+       priv->qdiscs = NULL;
+ }
++EXPORT_SYMBOL_NS_GPL(mq_attach, NET_SCHED_INTERNAL);
+-static int mq_dump(struct Qdisc *sch, struct sk_buff *skb)
++void mq_dump_common(struct Qdisc *sch, struct sk_buff *skb)
+ {
+       struct net_device *dev = qdisc_dev(sch);
+       struct Qdisc *qdisc;
+@@ -152,7 +168,12 @@ static int mq_dump(struct Qdisc *sch, st
+               spin_unlock_bh(qdisc_lock(qdisc));
+       }
++}
++EXPORT_SYMBOL_NS_GPL(mq_dump_common, NET_SCHED_INTERNAL);
++static int mq_dump(struct Qdisc *sch, struct sk_buff *skb)
++{
++      mq_dump_common(sch, skb);
+       return mq_offload_stats(sch);
+ }
+@@ -166,11 +187,12 @@ static struct netdev_queue *mq_queue_get
+       return netdev_get_tx_queue(dev, ntx);
+ }
+-static struct netdev_queue *mq_select_queue(struct Qdisc *sch,
+-                                          struct tcmsg *tcm)
++struct netdev_queue *mq_select_queue(struct Qdisc *sch,
++                                   struct tcmsg *tcm)
+ {
+       return mq_queue_get(sch, TC_H_MIN(tcm->tcm_parent));
+ }
++EXPORT_SYMBOL_NS_GPL(mq_select_queue, NET_SCHED_INTERNAL);
+ static int mq_graft(struct Qdisc *sch, unsigned long cl, struct Qdisc *new,
+                   struct Qdisc **old, struct netlink_ext_ack *extack)
+@@ -198,14 +220,15 @@ static int mq_graft(struct Qdisc *sch, u
+       return 0;
+ }
+-static struct Qdisc *mq_leaf(struct Qdisc *sch, unsigned long cl)
++struct Qdisc *mq_leaf(struct Qdisc *sch, unsigned long cl)
+ {
+       struct netdev_queue *dev_queue = mq_queue_get(sch, cl);
+       return rtnl_dereference(dev_queue->qdisc_sleeping);
+ }
++EXPORT_SYMBOL_NS_GPL(mq_leaf, NET_SCHED_INTERNAL);
+-static unsigned long mq_find(struct Qdisc *sch, u32 classid)
++unsigned long mq_find(struct Qdisc *sch, u32 classid)
+ {
+       unsigned int ntx = TC_H_MIN(classid);
+@@ -213,9 +236,10 @@ static unsigned long mq_find(struct Qdis
+               return 0;
+       return ntx;
+ }
++EXPORT_SYMBOL_NS_GPL(mq_find, NET_SCHED_INTERNAL);
+-static int mq_dump_class(struct Qdisc *sch, unsigned long cl,
+-                       struct sk_buff *skb, struct tcmsg *tcm)
++int mq_dump_class(struct Qdisc *sch, unsigned long cl,
++                struct sk_buff *skb, struct tcmsg *tcm)
+ {
+       struct netdev_queue *dev_queue = mq_queue_get(sch, cl);
+@@ -224,9 +248,10 @@ static int mq_dump_class(struct Qdisc *s
+       tcm->tcm_info = rtnl_dereference(dev_queue->qdisc_sleeping)->handle;
+       return 0;
+ }
++EXPORT_SYMBOL_NS_GPL(mq_dump_class, NET_SCHED_INTERNAL);
+-static int mq_dump_class_stats(struct Qdisc *sch, unsigned long cl,
+-                             struct gnet_dump *d)
++int mq_dump_class_stats(struct Qdisc *sch, unsigned long cl,
++                      struct gnet_dump *d)
+ {
+       struct netdev_queue *dev_queue = mq_queue_get(sch, cl);
+@@ -236,8 +261,9 @@ static int mq_dump_class_stats(struct Qd
+               return -1;
+       return 0;
+ }
++EXPORT_SYMBOL_NS_GPL(mq_dump_class_stats, NET_SCHED_INTERNAL);
+-static void mq_walk(struct Qdisc *sch, struct qdisc_walker *arg)
++void mq_walk(struct Qdisc *sch, struct qdisc_walker *arg)
+ {
+       struct net_device *dev = qdisc_dev(sch);
+       unsigned int ntx;
+@@ -251,6 +277,7 @@ static void mq_walk(struct Qdisc *sch, s
+                       break;
+       }
+ }
++EXPORT_SYMBOL_NS_GPL(mq_walk, NET_SCHED_INTERNAL);
+ static const struct Qdisc_class_ops mq_class_ops = {
+       .select_queue   = mq_select_queue,
diff --git a/target/linux/generic/backport-6.12/700-02-v7.0-net-sched-sch_cake-Factor-out-config-variables-into-.patch b/target/linux/generic/backport-6.12/700-02-v7.0-net-sched-sch_cake-Factor-out-config-variables-into-.patch
new file mode 100644 (file)
index 0000000..8df110b
--- /dev/null
@@ -0,0 +1,636 @@
+From 23090d3e9db80c2a374df1e636247eff9b6dd972 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com>
+Date: Fri, 9 Jan 2026 14:15:31 +0100
+Subject: [PATCH 2/7] net/sched: sch_cake: Factor out config variables into
+ separate struct
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Factor out all the user-configurable variables into a separate struct
+and embed it into struct cake_sched_data. This is done in preparation
+for sharing the configuration across multiple instances of cake in an mq
+setup.
+
+No functional change is intended with this patch.
+
+Reviewed-by: Jamal Hadi Salim <jhs@mojatatu.com>
+Reviewed-by: Willem de Bruijn <willemb@google.com>
+Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
+Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-2-8d613fece5d8@redhat.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ net/sched/sch_cake.c | 245 +++++++++++++++++++++++--------------------
+ 1 file changed, 133 insertions(+), 112 deletions(-)
+
+--- a/net/sched/sch_cake.c
++++ b/net/sched/sch_cake.c
+@@ -197,40 +197,42 @@ struct cake_tin_data {
+       u32     way_collisions;
+ }; /* number of tins is small, so size of this struct doesn't matter much */
++struct cake_sched_config {
++      u64             rate_bps;
++      u64             interval;
++      u64             target;
++      u32             buffer_config_limit;
++      u32             fwmark_mask;
++      u16             fwmark_shft;
++      s16             rate_overhead;
++      u16             rate_mpu;
++      u16             rate_flags;
++      u8              tin_mode;
++      u8              flow_mode;
++      u8              atm_mode;
++      u8              ack_filter;
++};
++
+ struct cake_sched_data {
+       struct tcf_proto __rcu *filter_list; /* optional external classifier */
+       struct tcf_block *block;
+       struct cake_tin_data *tins;
++      struct cake_sched_config *config;
+       struct cake_heap_entry overflow_heap[CAKE_QUEUES * CAKE_MAX_TINS];
+-      u16             overflow_timeout;
+-
+-      u16             tin_cnt;
+-      u8              tin_mode;
+-      u8              flow_mode;
+-      u8              ack_filter;
+-      u8              atm_mode;
+-
+-      u32             fwmark_mask;
+-      u16             fwmark_shft;
+       /* time_next = time_this + ((len * rate_ns) >> rate_shft) */
+-      u16             rate_shft;
+       ktime_t         time_next_packet;
+       ktime_t         failsafe_next_packet;
+       u64             rate_ns;
+-      u64             rate_bps;
+-      u16             rate_flags;
+-      s16             rate_overhead;
+-      u16             rate_mpu;
+-      u64             interval;
+-      u64             target;
++      u16             rate_shft;
++      u16             overflow_timeout;
++      u16             tin_cnt;
+       /* resource tracking */
+       u32             buffer_used;
+       u32             buffer_max_used;
+       u32             buffer_limit;
+-      u32             buffer_config_limit;
+       /* indices for dequeue */
+       u16             cur_tin;
+@@ -1195,7 +1197,7 @@ static bool cake_tcph_may_drop(const str
+ static struct sk_buff *cake_ack_filter(struct cake_sched_data *q,
+                                      struct cake_flow *flow)
+ {
+-      bool aggressive = q->ack_filter == CAKE_ACK_AGGRESSIVE;
++      bool aggressive = q->config->ack_filter == CAKE_ACK_AGGRESSIVE;
+       struct sk_buff *elig_ack = NULL, *elig_ack_prev = NULL;
+       struct sk_buff *skb_check, *skb_prev = NULL;
+       const struct ipv6hdr *ipv6h, *ipv6h_check;
+@@ -1355,15 +1357,17 @@ static u64 cake_ewma(u64 avg, u64 sample
+       return avg;
+ }
+-static u32 cake_calc_overhead(struct cake_sched_data *q, u32 len, u32 off)
++static u32 cake_calc_overhead(struct cake_sched_data *qd, u32 len, u32 off)
+ {
++      struct cake_sched_config *q = qd->config;
++
+       if (q->rate_flags & CAKE_FLAG_OVERHEAD)
+               len -= off;
+-      if (q->max_netlen < len)
+-              q->max_netlen = len;
+-      if (q->min_netlen > len)
+-              q->min_netlen = len;
++      if (qd->max_netlen < len)
++              qd->max_netlen = len;
++      if (qd->min_netlen > len)
++              qd->min_netlen = len;
+       len += q->rate_overhead;
+@@ -1382,10 +1386,10 @@ static u32 cake_calc_overhead(struct cak
+               len += (len + 63) / 64;
+       }
+-      if (q->max_adjlen < len)
+-              q->max_adjlen = len;
+-      if (q->min_adjlen > len)
+-              q->min_adjlen = len;
++      if (qd->max_adjlen < len)
++              qd->max_adjlen = len;
++      if (qd->min_adjlen > len)
++              qd->min_adjlen = len;
+       return len;
+ }
+@@ -1587,7 +1591,7 @@ static unsigned int cake_drop(struct Qdi
+       b->tin_dropped++;
+       sch->qstats.drops++;
+-      if (q->rate_flags & CAKE_FLAG_INGRESS)
++      if (q->config->rate_flags & CAKE_FLAG_INGRESS)
+               cake_advance_shaper(q, b, skb, now, true);
+       __qdisc_drop(skb, to_free);
+@@ -1657,7 +1661,8 @@ static u8 cake_handle_diffserv(struct sk
+ static struct cake_tin_data *cake_select_tin(struct Qdisc *sch,
+                                            struct sk_buff *skb)
+ {
+-      struct cake_sched_data *q = qdisc_priv(sch);
++      struct cake_sched_data *qd = qdisc_priv(sch);
++      struct cake_sched_config *q = qd->config;
+       u32 tin, mark;
+       bool wash;
+       u8 dscp;
+@@ -1674,24 +1679,24 @@ static struct cake_tin_data *cake_select
+       if (q->tin_mode == CAKE_DIFFSERV_BESTEFFORT)
+               tin = 0;
+-      else if (mark && mark <= q->tin_cnt)
+-              tin = q->tin_order[mark - 1];
++      else if (mark && mark <= qd->tin_cnt)
++              tin = qd->tin_order[mark - 1];
+       else if (TC_H_MAJ(skb->priority) == sch->handle &&
+                TC_H_MIN(skb->priority) > 0 &&
+-               TC_H_MIN(skb->priority) <= q->tin_cnt)
+-              tin = q->tin_order[TC_H_MIN(skb->priority) - 1];
++               TC_H_MIN(skb->priority) <= qd->tin_cnt)
++              tin = qd->tin_order[TC_H_MIN(skb->priority) - 1];
+       else {
+               if (!wash)
+                       dscp = cake_handle_diffserv(skb, wash);
+-              tin = q->tin_index[dscp];
++              tin = qd->tin_index[dscp];
+-              if (unlikely(tin >= q->tin_cnt))
++              if (unlikely(tin >= qd->tin_cnt))
+                       tin = 0;
+       }
+-      return &q->tins[tin];
++      return &qd->tins[tin];
+ }
+ static u32 cake_classify(struct Qdisc *sch, struct cake_tin_data **t,
+@@ -1747,7 +1752,7 @@ static s32 cake_enqueue(struct sk_buff *
+       bool same_flow = false;
+       /* choose flow to insert into */
+-      idx = cake_classify(sch, &b, skb, q->flow_mode, &ret);
++      idx = cake_classify(sch, &b, skb, q->config->flow_mode, &ret);
+       if (idx == 0) {
+               if (ret & __NET_XMIT_BYPASS)
+                       qdisc_qstats_drop(sch);
+@@ -1782,7 +1787,7 @@ static s32 cake_enqueue(struct sk_buff *
+       if (unlikely(len > b->max_skblen))
+               b->max_skblen = len;
+-      if (skb_is_gso(skb) && q->rate_flags & CAKE_FLAG_SPLIT_GSO) {
++      if (skb_is_gso(skb) && q->config->rate_flags & CAKE_FLAG_SPLIT_GSO) {
+               struct sk_buff *segs, *nskb;
+               netdev_features_t features = netif_skb_features(skb);
+               unsigned int slen = 0, numsegs = 0;
+@@ -1823,7 +1828,7 @@ static s32 cake_enqueue(struct sk_buff *
+               get_cobalt_cb(skb)->adjusted_len = cake_overhead(q, skb);
+               flow_queue_add(flow, skb);
+-              if (q->ack_filter)
++              if (q->config->ack_filter)
+                       ack = cake_ack_filter(q, flow);
+               if (ack) {
+@@ -1832,7 +1837,7 @@ static s32 cake_enqueue(struct sk_buff *
+                       ack_pkt_len = qdisc_pkt_len(ack);
+                       b->bytes += ack_pkt_len;
+                       q->buffer_used += skb->truesize - ack->truesize;
+-                      if (q->rate_flags & CAKE_FLAG_INGRESS)
++                      if (q->config->rate_flags & CAKE_FLAG_INGRESS)
+                               cake_advance_shaper(q, b, ack, now, true);
+                       qdisc_tree_reduce_backlog(sch, 1, ack_pkt_len);
+@@ -1855,7 +1860,7 @@ static s32 cake_enqueue(struct sk_buff *
+               cake_heapify_up(q, b->overflow_idx[idx]);
+       /* incoming bandwidth capacity estimate */
+-      if (q->rate_flags & CAKE_FLAG_AUTORATE_INGRESS) {
++      if (q->config->rate_flags & CAKE_FLAG_AUTORATE_INGRESS) {
+               u64 packet_interval = \
+                       ktime_to_ns(ktime_sub(now, q->last_packet_time));
+@@ -1887,7 +1892,7 @@ static s32 cake_enqueue(struct sk_buff *
+                       if (ktime_after(now,
+                                       ktime_add_ms(q->last_reconfig_time,
+                                                    250))) {
+-                              q->rate_bps = (q->avg_peak_bandwidth * 15) >> 4;
++                              q->config->rate_bps = (q->avg_peak_bandwidth * 15) >> 4;
+                               cake_reconfigure(sch);
+                       }
+               }
+@@ -1907,7 +1912,7 @@ static s32 cake_enqueue(struct sk_buff *
+               flow->set = CAKE_SET_SPARSE;
+               b->sparse_flow_count++;
+-              flow->deficit = cake_get_flow_quantum(b, flow, q->flow_mode);
++              flow->deficit = cake_get_flow_quantum(b, flow, q->config->flow_mode);
+       } else if (flow->set == CAKE_SET_SPARSE_WAIT) {
+               /* this flow was empty, accounted as a sparse flow, but actually
+                * in the bulk rotation.
+@@ -1916,8 +1921,8 @@ static s32 cake_enqueue(struct sk_buff *
+               b->sparse_flow_count--;
+               b->bulk_flow_count++;
+-              cake_inc_srchost_bulk_flow_count(b, flow, q->flow_mode);
+-              cake_inc_dsthost_bulk_flow_count(b, flow, q->flow_mode);
++              cake_inc_srchost_bulk_flow_count(b, flow, q->config->flow_mode);
++              cake_inc_dsthost_bulk_flow_count(b, flow, q->config->flow_mode);
+       }
+       if (q->buffer_used > q->buffer_max_used)
+@@ -2103,8 +2108,8 @@ retry:
+                               b->sparse_flow_count--;
+                               b->bulk_flow_count++;
+-                              cake_inc_srchost_bulk_flow_count(b, flow, q->flow_mode);
+-                              cake_inc_dsthost_bulk_flow_count(b, flow, q->flow_mode);
++                              cake_inc_srchost_bulk_flow_count(b, flow, q->config->flow_mode);
++                              cake_inc_dsthost_bulk_flow_count(b, flow, q->config->flow_mode);
+                               flow->set = CAKE_SET_BULK;
+                       } else {
+@@ -2116,7 +2121,7 @@ retry:
+                       }
+               }
+-              flow->deficit += cake_get_flow_quantum(b, flow, q->flow_mode);
++              flow->deficit += cake_get_flow_quantum(b, flow, q->config->flow_mode);
+               list_move_tail(&flow->flowchain, &b->old_flows);
+               goto retry;
+@@ -2140,8 +2145,8 @@ retry:
+                               if (flow->set == CAKE_SET_BULK) {
+                                       b->bulk_flow_count--;
+-                                      cake_dec_srchost_bulk_flow_count(b, flow, q->flow_mode);
+-                                      cake_dec_dsthost_bulk_flow_count(b, flow, q->flow_mode);
++                                      cake_dec_srchost_bulk_flow_count(b, flow, q->config->flow_mode);
++                                      cake_dec_dsthost_bulk_flow_count(b, flow, q->config->flow_mode);
+                                       b->decaying_flow_count++;
+                               } else if (flow->set == CAKE_SET_SPARSE ||
+@@ -2159,8 +2164,8 @@ retry:
+                               else if (flow->set == CAKE_SET_BULK) {
+                                       b->bulk_flow_count--;
+-                                      cake_dec_srchost_bulk_flow_count(b, flow, q->flow_mode);
+-                                      cake_dec_dsthost_bulk_flow_count(b, flow, q->flow_mode);
++                                      cake_dec_srchost_bulk_flow_count(b, flow, q->config->flow_mode);
++                                      cake_dec_dsthost_bulk_flow_count(b, flow, q->config->flow_mode);
+                               } else
+                                       b->decaying_flow_count--;
+@@ -2172,13 +2177,13 @@ retry:
+               /* Last packet in queue may be marked, shouldn't be dropped */
+               if (!cobalt_should_drop(&flow->cvars, &b->cparams, now, skb,
+                                       (b->bulk_flow_count *
+-                                       !!(q->rate_flags &
++                                       !!(q->config->rate_flags &
+                                           CAKE_FLAG_INGRESS))) ||
+                   !flow->head)
+                       break;
+               /* drop this packet, get another one */
+-              if (q->rate_flags & CAKE_FLAG_INGRESS) {
++              if (q->config->rate_flags & CAKE_FLAG_INGRESS) {
+                       len = cake_advance_shaper(q, b, skb,
+                                                 now, true);
+                       flow->deficit -= len;
+@@ -2189,7 +2194,7 @@ retry:
+               qdisc_tree_reduce_backlog(sch, 1, qdisc_pkt_len(skb));
+               qdisc_qstats_drop(sch);
+               kfree_skb(skb);
+-              if (q->rate_flags & CAKE_FLAG_INGRESS)
++              if (q->config->rate_flags & CAKE_FLAG_INGRESS)
+                       goto retry;
+       }
+@@ -2311,7 +2316,7 @@ static int cake_config_besteffort(struct
+       struct cake_sched_data *q = qdisc_priv(sch);
+       struct cake_tin_data *b = &q->tins[0];
+       u32 mtu = psched_mtu(qdisc_dev(sch));
+-      u64 rate = q->rate_bps;
++      u64 rate = q->config->rate_bps;
+       q->tin_cnt = 1;
+@@ -2319,7 +2324,7 @@ static int cake_config_besteffort(struct
+       q->tin_order = normal_order;
+       cake_set_rate(b, rate, mtu,
+-                    us_to_ns(q->target), us_to_ns(q->interval));
++                    us_to_ns(q->config->target), us_to_ns(q->config->interval));
+       b->tin_quantum = 65535;
+       return 0;
+@@ -2330,7 +2335,7 @@ static int cake_config_precedence(struct
+       /* convert high-level (user visible) parameters into internal format */
+       struct cake_sched_data *q = qdisc_priv(sch);
+       u32 mtu = psched_mtu(qdisc_dev(sch));
+-      u64 rate = q->rate_bps;
++      u64 rate = q->config->rate_bps;
+       u32 quantum = 256;
+       u32 i;
+@@ -2341,8 +2346,8 @@ static int cake_config_precedence(struct
+       for (i = 0; i < q->tin_cnt; i++) {
+               struct cake_tin_data *b = &q->tins[i];
+-              cake_set_rate(b, rate, mtu, us_to_ns(q->target),
+-                            us_to_ns(q->interval));
++              cake_set_rate(b, rate, mtu, us_to_ns(q->config->target),
++                            us_to_ns(q->config->interval));
+               b->tin_quantum = max_t(u16, 1U, quantum);
+@@ -2419,7 +2424,7 @@ static int cake_config_diffserv8(struct
+       struct cake_sched_data *q = qdisc_priv(sch);
+       u32 mtu = psched_mtu(qdisc_dev(sch));
+-      u64 rate = q->rate_bps;
++      u64 rate = q->config->rate_bps;
+       u32 quantum = 256;
+       u32 i;
+@@ -2433,8 +2438,8 @@ static int cake_config_diffserv8(struct
+       for (i = 0; i < q->tin_cnt; i++) {
+               struct cake_tin_data *b = &q->tins[i];
+-              cake_set_rate(b, rate, mtu, us_to_ns(q->target),
+-                            us_to_ns(q->interval));
++              cake_set_rate(b, rate, mtu, us_to_ns(q->config->target),
++                            us_to_ns(q->config->interval));
+               b->tin_quantum = max_t(u16, 1U, quantum);
+@@ -2463,7 +2468,7 @@ static int cake_config_diffserv4(struct
+       struct cake_sched_data *q = qdisc_priv(sch);
+       u32 mtu = psched_mtu(qdisc_dev(sch));
+-      u64 rate = q->rate_bps;
++      u64 rate = q->config->rate_bps;
+       u32 quantum = 1024;
+       q->tin_cnt = 4;
+@@ -2474,13 +2479,13 @@ static int cake_config_diffserv4(struct
+       /* class characteristics */
+       cake_set_rate(&q->tins[0], rate, mtu,
+-                    us_to_ns(q->target), us_to_ns(q->interval));
++                    us_to_ns(q->config->target), us_to_ns(q->config->interval));
+       cake_set_rate(&q->tins[1], rate >> 4, mtu,
+-                    us_to_ns(q->target), us_to_ns(q->interval));
++                    us_to_ns(q->config->target), us_to_ns(q->config->interval));
+       cake_set_rate(&q->tins[2], rate >> 1, mtu,
+-                    us_to_ns(q->target), us_to_ns(q->interval));
++                    us_to_ns(q->config->target), us_to_ns(q->config->interval));
+       cake_set_rate(&q->tins[3], rate >> 2, mtu,
+-                    us_to_ns(q->target), us_to_ns(q->interval));
++                    us_to_ns(q->config->target), us_to_ns(q->config->interval));
+       /* bandwidth-sharing weights */
+       q->tins[0].tin_quantum = quantum;
+@@ -2500,7 +2505,7 @@ static int cake_config_diffserv3(struct
+  */
+       struct cake_sched_data *q = qdisc_priv(sch);
+       u32 mtu = psched_mtu(qdisc_dev(sch));
+-      u64 rate = q->rate_bps;
++      u64 rate = q->config->rate_bps;
+       u32 quantum = 1024;
+       q->tin_cnt = 3;
+@@ -2511,11 +2516,11 @@ static int cake_config_diffserv3(struct
+       /* class characteristics */
+       cake_set_rate(&q->tins[0], rate, mtu,
+-                    us_to_ns(q->target), us_to_ns(q->interval));
++                    us_to_ns(q->config->target), us_to_ns(q->config->interval));
+       cake_set_rate(&q->tins[1], rate >> 4, mtu,
+-                    us_to_ns(q->target), us_to_ns(q->interval));
++                    us_to_ns(q->config->target), us_to_ns(q->config->interval));
+       cake_set_rate(&q->tins[2], rate >> 2, mtu,
+-                    us_to_ns(q->target), us_to_ns(q->interval));
++                    us_to_ns(q->config->target), us_to_ns(q->config->interval));
+       /* bandwidth-sharing weights */
+       q->tins[0].tin_quantum = quantum;
+@@ -2527,7 +2532,8 @@ static int cake_config_diffserv3(struct
+ static void cake_reconfigure(struct Qdisc *sch)
+ {
+-      struct cake_sched_data *q = qdisc_priv(sch);
++      struct cake_sched_data *qd = qdisc_priv(sch);
++      struct cake_sched_config *q = qd->config;
+       int c, ft;
+       switch (q->tin_mode) {
+@@ -2553,36 +2559,37 @@ static void cake_reconfigure(struct Qdis
+               break;
+       }
+-      for (c = q->tin_cnt; c < CAKE_MAX_TINS; c++) {
++      for (c = qd->tin_cnt; c < CAKE_MAX_TINS; c++) {
+               cake_clear_tin(sch, c);
+-              q->tins[c].cparams.mtu_time = q->tins[ft].cparams.mtu_time;
++              qd->tins[c].cparams.mtu_time = qd->tins[ft].cparams.mtu_time;
+       }
+-      q->rate_ns   = q->tins[ft].tin_rate_ns;
+-      q->rate_shft = q->tins[ft].tin_rate_shft;
++      qd->rate_ns   = qd->tins[ft].tin_rate_ns;
++      qd->rate_shft = qd->tins[ft].tin_rate_shft;
+       if (q->buffer_config_limit) {
+-              q->buffer_limit = q->buffer_config_limit;
++              qd->buffer_limit = q->buffer_config_limit;
+       } else if (q->rate_bps) {
+               u64 t = q->rate_bps * q->interval;
+               do_div(t, USEC_PER_SEC / 4);
+-              q->buffer_limit = max_t(u32, t, 4U << 20);
++              qd->buffer_limit = max_t(u32, t, 4U << 20);
+       } else {
+-              q->buffer_limit = ~0;
++              qd->buffer_limit = ~0;
+       }
+       sch->flags &= ~TCQ_F_CAN_BYPASS;
+-      q->buffer_limit = min(q->buffer_limit,
+-                            max(sch->limit * psched_mtu(qdisc_dev(sch)),
+-                                q->buffer_config_limit));
++      qd->buffer_limit = min(qd->buffer_limit,
++                             max(sch->limit * psched_mtu(qdisc_dev(sch)),
++                                 q->buffer_config_limit));
+ }
+ static int cake_change(struct Qdisc *sch, struct nlattr *opt,
+                      struct netlink_ext_ack *extack)
+ {
+-      struct cake_sched_data *q = qdisc_priv(sch);
++      struct cake_sched_data *qd = qdisc_priv(sch);
++      struct cake_sched_config *q = qd->config;
+       struct nlattr *tb[TCA_CAKE_MAX + 1];
+       u16 rate_flags;
+       u8 flow_mode;
+@@ -2636,19 +2643,19 @@ static int cake_change(struct Qdisc *sch
+                          nla_get_s32(tb[TCA_CAKE_OVERHEAD]));
+               rate_flags |= CAKE_FLAG_OVERHEAD;
+-              q->max_netlen = 0;
+-              q->max_adjlen = 0;
+-              q->min_netlen = ~0;
+-              q->min_adjlen = ~0;
++              qd->max_netlen = 0;
++              qd->max_adjlen = 0;
++              qd->min_netlen = ~0;
++              qd->min_adjlen = ~0;
+       }
+       if (tb[TCA_CAKE_RAW]) {
+               rate_flags &= ~CAKE_FLAG_OVERHEAD;
+-              q->max_netlen = 0;
+-              q->max_adjlen = 0;
+-              q->min_netlen = ~0;
+-              q->min_adjlen = ~0;
++              qd->max_netlen = 0;
++              qd->max_adjlen = 0;
++              qd->min_netlen = ~0;
++              qd->min_adjlen = ~0;
+       }
+       if (tb[TCA_CAKE_MPU])
+@@ -2704,7 +2711,7 @@ static int cake_change(struct Qdisc *sch
+       WRITE_ONCE(q->rate_flags, rate_flags);
+       WRITE_ONCE(q->flow_mode, flow_mode);
+-      if (q->tins) {
++      if (qd->tins) {
+               sch_tree_lock(sch);
+               cake_reconfigure(sch);
+               sch_tree_unlock(sch);
+@@ -2720,14 +2727,20 @@ static void cake_destroy(struct Qdisc *s
+       qdisc_watchdog_cancel(&q->watchdog);
+       tcf_block_put(q->block);
+       kvfree(q->tins);
++      kvfree(q->config);
+ }
+ static int cake_init(struct Qdisc *sch, struct nlattr *opt,
+                    struct netlink_ext_ack *extack)
+ {
+-      struct cake_sched_data *q = qdisc_priv(sch);
++      struct cake_sched_data *qd = qdisc_priv(sch);
++      struct cake_sched_config *q;
+       int i, j, err;
++      q = kzalloc(sizeof(*q), GFP_KERNEL);
++      if (!q)
++              return -ENOMEM;
++
+       sch->limit = 10240;
+       q->tin_mode = CAKE_DIFFSERV_DIFFSERV3;
+       q->flow_mode  = CAKE_FLOW_TRIPLE;
+@@ -2739,33 +2752,36 @@ static int cake_init(struct Qdisc *sch,
+                              * for 5 to 10% of interval
+                              */
+       q->rate_flags |= CAKE_FLAG_SPLIT_GSO;
+-      q->cur_tin = 0;
+-      q->cur_flow  = 0;
++      qd->cur_tin = 0;
++      qd->cur_flow  = 0;
++      qd->config = q;
+-      qdisc_watchdog_init(&q->watchdog, sch);
++      qdisc_watchdog_init(&qd->watchdog, sch);
+       if (opt) {
+               err = cake_change(sch, opt, extack);
+               if (err)
+-                      return err;
++                      goto err;
+       }
+-      err = tcf_block_get(&q->block, &q->filter_list, sch, extack);
++      err = tcf_block_get(&qd->block, &qd->filter_list, sch, extack);
+       if (err)
+-              return err;
++              goto err;
+       quantum_div[0] = ~0;
+       for (i = 1; i <= CAKE_QUEUES; i++)
+               quantum_div[i] = 65535 / i;
+-      q->tins = kvcalloc(CAKE_MAX_TINS, sizeof(struct cake_tin_data),
+-                         GFP_KERNEL);
+-      if (!q->tins)
+-              return -ENOMEM;
++      qd->tins = kvcalloc(CAKE_MAX_TINS, sizeof(struct cake_tin_data),
++                          GFP_KERNEL);
++      if (!qd->tins) {
++              err = -ENOMEM;
++              goto err;
++      }
+       for (i = 0; i < CAKE_MAX_TINS; i++) {
+-              struct cake_tin_data *b = q->tins + i;
++              struct cake_tin_data *b = qd->tins + i;
+               INIT_LIST_HEAD(&b->new_flows);
+               INIT_LIST_HEAD(&b->old_flows);
+@@ -2781,22 +2797,27 @@ static int cake_init(struct Qdisc *sch,
+                       INIT_LIST_HEAD(&flow->flowchain);
+                       cobalt_vars_init(&flow->cvars);
+-                      q->overflow_heap[k].t = i;
+-                      q->overflow_heap[k].b = j;
++                      qd->overflow_heap[k].t = i;
++                      qd->overflow_heap[k].b = j;
+                       b->overflow_idx[j] = k;
+               }
+       }
+       cake_reconfigure(sch);
+-      q->avg_peak_bandwidth = q->rate_bps;
+-      q->min_netlen = ~0;
+-      q->min_adjlen = ~0;
++      qd->avg_peak_bandwidth = q->rate_bps;
++      qd->min_netlen = ~0;
++      qd->min_adjlen = ~0;
+       return 0;
++err:
++      kvfree(qd->config);
++      qd->config = NULL;
++      return err;
+ }
+ static int cake_dump(struct Qdisc *sch, struct sk_buff *skb)
+ {
+-      struct cake_sched_data *q = qdisc_priv(sch);
++      struct cake_sched_data *qd = qdisc_priv(sch);
++      struct cake_sched_config *q = qd->config;
+       struct nlattr *opts;
+       u16 rate_flags;
+       u8 flow_mode;
diff --git a/target/linux/generic/backport-6.12/700-03-v7.0-net-sched-sch_cake-Add-cake_mq-qdisc-for-using-cake-.patch b/target/linux/generic/backport-6.12/700-03-v7.0-net-sched-sch_cake-Add-cake_mq-qdisc-for-using-cake-.patch
new file mode 100644 (file)
index 0000000..645b27f
--- /dev/null
@@ -0,0 +1,130 @@
+From c076e98c66afe06e41e118eb96d6b15019b822f2 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com>
+Date: Fri, 9 Jan 2026 14:15:32 +0100
+Subject: [PATCH 3/7] net/sched: sch_cake: Add cake_mq qdisc for using cake on
+ mq devices
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add a cake_mq qdisc which installs cake instances on each hardware
+queue on a multi-queue device.
+
+This is just a copy of sch_mq that installs cake instead of the default
+qdisc on each queue. Subsequent commits will add sharing of the config
+between cake instances, as well as a multi-queue aware shaper algorithm.
+
+Reviewed-by: Willem de Bruijn <willemb@google.com>
+Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
+Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-3-8d613fece5d8@redhat.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ net/sched/sch_cake.c | 79 +++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 78 insertions(+), 1 deletion(-)
+
+--- a/net/sched/sch_cake.c
++++ b/net/sched/sch_cake.c
+@@ -67,6 +67,7 @@
+ #include <linux/if_vlan.h>
+ #include <net/gso.h>
+ #include <net/pkt_sched.h>
++#include <net/sch_priv.h>
+ #include <net/pkt_cls.h>
+ #include <net/tcp.h>
+ #include <net/flow_dissector.h>
+@@ -3154,14 +3155,89 @@ static struct Qdisc_ops cake_qdisc_ops _
+ };
+ MODULE_ALIAS_NET_SCH("cake");
++struct cake_mq_sched {
++      struct mq_sched mq_priv; /* must be first */
++};
++
++static void cake_mq_destroy(struct Qdisc *sch)
++{
++      mq_destroy_common(sch);
++}
++
++static int cake_mq_init(struct Qdisc *sch, struct nlattr *opt,
++                      struct netlink_ext_ack *extack)
++{
++      int ret;
++
++      ret = mq_init_common(sch, opt, extack, &cake_qdisc_ops);
++      if (ret)
++              return ret;
++
++      return 0;
++}
++
++static int cake_mq_dump(struct Qdisc *sch, struct sk_buff *skb)
++{
++      mq_dump_common(sch, skb);
++      return 0;
++}
++
++static int cake_mq_change(struct Qdisc *sch, struct nlattr *opt,
++                        struct netlink_ext_ack *extack)
++{
++      return -EOPNOTSUPP;
++}
++
++static int cake_mq_graft(struct Qdisc *sch, unsigned long cl, struct Qdisc *new,
++                       struct Qdisc **old, struct netlink_ext_ack *extack)
++{
++      NL_SET_ERR_MSG(extack, "can't replace cake_mq sub-qdiscs");
++      return -EOPNOTSUPP;
++}
++
++static const struct Qdisc_class_ops cake_mq_class_ops = {
++      .select_queue   = mq_select_queue,
++      .graft          = cake_mq_graft,
++      .leaf           = mq_leaf,
++      .find           = mq_find,
++      .walk           = mq_walk,
++      .dump           = mq_dump_class,
++      .dump_stats     = mq_dump_class_stats,
++};
++
++static struct Qdisc_ops cake_mq_qdisc_ops __read_mostly = {
++      .cl_ops         =       &cake_mq_class_ops,
++      .id             =       "cake_mq",
++      .priv_size      =       sizeof(struct cake_mq_sched),
++      .init           =       cake_mq_init,
++      .destroy        =       cake_mq_destroy,
++      .attach         =       mq_attach,
++      .change         =       cake_mq_change,
++      .change_real_num_tx = mq_change_real_num_tx,
++      .dump           =       cake_mq_dump,
++      .owner          =       THIS_MODULE,
++};
++MODULE_ALIAS_NET_SCH("cake_mq");
++
+ static int __init cake_module_init(void)
+ {
+-      return register_qdisc(&cake_qdisc_ops);
++      int ret;
++
++      ret = register_qdisc(&cake_qdisc_ops);
++      if (ret)
++              return ret;
++
++      ret = register_qdisc(&cake_mq_qdisc_ops);
++      if (ret)
++              unregister_qdisc(&cake_qdisc_ops);
++
++      return ret;
+ }
+ static void __exit cake_module_exit(void)
+ {
+       unregister_qdisc(&cake_qdisc_ops);
++      unregister_qdisc(&cake_mq_qdisc_ops);
+ }
+ module_init(cake_module_init)
+@@ -3169,3 +3245,4 @@ module_exit(cake_module_exit)
+ MODULE_AUTHOR("Jonathan Morton");
+ MODULE_LICENSE("Dual BSD/GPL");
+ MODULE_DESCRIPTION("The CAKE shaper.");
++MODULE_IMPORT_NS(NET_SCHED_INTERNAL);
diff --git a/target/linux/generic/backport-6.12/700-04-v7.0-net-sched-sch_cake-Share-config-across-cake_mq-sub-q.patch b/target/linux/generic/backport-6.12/700-04-v7.0-net-sched-sch_cake-Share-config-across-cake_mq-sub-q.patch
new file mode 100644 (file)
index 0000000..e481ef9
--- /dev/null
@@ -0,0 +1,318 @@
+From 68e71714d1e02ca388ea99defbe76061b7dd09a3 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com>
+Date: Fri, 9 Jan 2026 14:15:33 +0100
+Subject: [PATCH 4/7] net/sched: sch_cake: Share config across cake_mq
+ sub-qdiscs
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This adds support for configuring the cake_mq instance directly, sharing
+the config across the cake sub-qdiscs.
+
+Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
+Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-4-8d613fece5d8@redhat.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ net/sched/sch_cake.c | 173 +++++++++++++++++++++++++++++++++----------
+ 1 file changed, 133 insertions(+), 40 deletions(-)
+
+--- a/net/sched/sch_cake.c
++++ b/net/sched/sch_cake.c
+@@ -212,6 +212,7 @@ struct cake_sched_config {
+       u8              flow_mode;
+       u8              atm_mode;
+       u8              ack_filter;
++      u8              is_shared;
+ };
+ struct cake_sched_data {
+@@ -2586,14 +2587,12 @@ static void cake_reconfigure(struct Qdis
+                                  q->buffer_config_limit));
+ }
+-static int cake_change(struct Qdisc *sch, struct nlattr *opt,
+-                     struct netlink_ext_ack *extack)
++static int cake_config_change(struct cake_sched_config *q, struct nlattr *opt,
++                            struct netlink_ext_ack *extack, bool *overhead_changed)
+ {
+-      struct cake_sched_data *qd = qdisc_priv(sch);
+-      struct cake_sched_config *q = qd->config;
+       struct nlattr *tb[TCA_CAKE_MAX + 1];
+-      u16 rate_flags;
+-      u8 flow_mode;
++      u16 rate_flags = q->rate_flags;
++      u8 flow_mode = q->flow_mode;
+       int err;
+       err = nla_parse_nested_deprecated(tb, TCA_CAKE_MAX, opt, cake_policy,
+@@ -2601,7 +2600,6 @@ static int cake_change(struct Qdisc *sch
+       if (err < 0)
+               return err;
+-      flow_mode = q->flow_mode;
+       if (tb[TCA_CAKE_NAT]) {
+ #if IS_ENABLED(CONFIG_NF_CONNTRACK)
+               flow_mode &= ~CAKE_FLOW_NAT_FLAG;
+@@ -2614,6 +2612,19 @@ static int cake_change(struct Qdisc *sch
+ #endif
+       }
++      if (tb[TCA_CAKE_AUTORATE]) {
++              if (!!nla_get_u32(tb[TCA_CAKE_AUTORATE])) {
++                      if (q->is_shared) {
++                              NL_SET_ERR_MSG_ATTR(extack, tb[TCA_CAKE_AUTORATE],
++                                                  "Can't use autorate-ingress with cake_mq");
++                              return -EOPNOTSUPP;
++                      }
++                      rate_flags |= CAKE_FLAG_AUTORATE_INGRESS;
++              } else {
++                      rate_flags &= ~CAKE_FLAG_AUTORATE_INGRESS;
++              }
++      }
++
+       if (tb[TCA_CAKE_BASE_RATE64])
+               WRITE_ONCE(q->rate_bps,
+                          nla_get_u64(tb[TCA_CAKE_BASE_RATE64]));
+@@ -2622,7 +2633,6 @@ static int cake_change(struct Qdisc *sch
+               WRITE_ONCE(q->tin_mode,
+                          nla_get_u32(tb[TCA_CAKE_DIFFSERV_MODE]));
+-      rate_flags = q->rate_flags;
+       if (tb[TCA_CAKE_WASH]) {
+               if (!!nla_get_u32(tb[TCA_CAKE_WASH]))
+                       rate_flags |= CAKE_FLAG_WASH;
+@@ -2643,20 +2653,12 @@ static int cake_change(struct Qdisc *sch
+               WRITE_ONCE(q->rate_overhead,
+                          nla_get_s32(tb[TCA_CAKE_OVERHEAD]));
+               rate_flags |= CAKE_FLAG_OVERHEAD;
+-
+-              qd->max_netlen = 0;
+-              qd->max_adjlen = 0;
+-              qd->min_netlen = ~0;
+-              qd->min_adjlen = ~0;
++              *overhead_changed = true;
+       }
+       if (tb[TCA_CAKE_RAW]) {
+               rate_flags &= ~CAKE_FLAG_OVERHEAD;
+-
+-              qd->max_netlen = 0;
+-              qd->max_adjlen = 0;
+-              qd->min_netlen = ~0;
+-              qd->min_adjlen = ~0;
++              *overhead_changed = true;
+       }
+       if (tb[TCA_CAKE_MPU])
+@@ -2675,13 +2677,6 @@ static int cake_change(struct Qdisc *sch
+               WRITE_ONCE(q->target, max(target, 1U));
+       }
+-      if (tb[TCA_CAKE_AUTORATE]) {
+-              if (!!nla_get_u32(tb[TCA_CAKE_AUTORATE]))
+-                      rate_flags |= CAKE_FLAG_AUTORATE_INGRESS;
+-              else
+-                      rate_flags &= ~CAKE_FLAG_AUTORATE_INGRESS;
+-      }
+-
+       if (tb[TCA_CAKE_INGRESS]) {
+               if (!!nla_get_u32(tb[TCA_CAKE_INGRESS]))
+                       rate_flags |= CAKE_FLAG_INGRESS;
+@@ -2712,6 +2707,34 @@ static int cake_change(struct Qdisc *sch
+       WRITE_ONCE(q->rate_flags, rate_flags);
+       WRITE_ONCE(q->flow_mode, flow_mode);
++
++      return 0;
++}
++
++static int cake_change(struct Qdisc *sch, struct nlattr *opt,
++                     struct netlink_ext_ack *extack)
++{
++      struct cake_sched_data *qd = qdisc_priv(sch);
++      struct cake_sched_config *q = qd->config;
++      bool overhead_changed = false;
++      int ret;
++
++      if (q->is_shared) {
++              NL_SET_ERR_MSG(extack, "can't reconfigure cake_mq sub-qdiscs");
++              return -EOPNOTSUPP;
++      }
++
++      ret = cake_config_change(q, opt, extack, &overhead_changed);
++      if (ret)
++              return ret;
++
++      if (overhead_changed) {
++              qd->max_netlen = 0;
++              qd->max_adjlen = 0;
++              qd->min_netlen = ~0;
++              qd->min_adjlen = ~0;
++      }
++
+       if (qd->tins) {
+               sch_tree_lock(sch);
+               cake_reconfigure(sch);
+@@ -2728,7 +2751,23 @@ static void cake_destroy(struct Qdisc *s
+       qdisc_watchdog_cancel(&q->watchdog);
+       tcf_block_put(q->block);
+       kvfree(q->tins);
+-      kvfree(q->config);
++      if (q->config && !q->config->is_shared)
++              kvfree(q->config);
++}
++
++static void cake_config_init(struct cake_sched_config *q, bool is_shared)
++{
++      q->tin_mode = CAKE_DIFFSERV_DIFFSERV3;
++      q->flow_mode  = CAKE_FLOW_TRIPLE;
++
++      q->rate_bps = 0; /* unlimited by default */
++
++      q->interval = 100000; /* 100ms default */
++      q->target   =   5000; /* 5ms: codel RFC argues
++                             * for 5 to 10% of interval
++                             */
++      q->rate_flags |= CAKE_FLAG_SPLIT_GSO;
++      q->is_shared = is_shared;
+ }
+ static int cake_init(struct Qdisc *sch, struct nlattr *opt,
+@@ -2742,17 +2781,9 @@ static int cake_init(struct Qdisc *sch,
+       if (!q)
+               return -ENOMEM;
+-      sch->limit = 10240;
+-      q->tin_mode = CAKE_DIFFSERV_DIFFSERV3;
+-      q->flow_mode  = CAKE_FLOW_TRIPLE;
++      cake_config_init(q, false);
+-      q->rate_bps = 0; /* unlimited by default */
+-
+-      q->interval = 100000; /* 100ms default */
+-      q->target   =   5000; /* 5ms: codel RFC argues
+-                             * for 5 to 10% of interval
+-                             */
+-      q->rate_flags |= CAKE_FLAG_SPLIT_GSO;
++      sch->limit = 10240;
+       qd->cur_tin = 0;
+       qd->cur_flow  = 0;
+       qd->config = q;
+@@ -2815,10 +2846,21 @@ err:
+       return err;
+ }
+-static int cake_dump(struct Qdisc *sch, struct sk_buff *skb)
++static void cake_config_replace(struct Qdisc *sch, struct cake_sched_config *cfg)
+ {
+       struct cake_sched_data *qd = qdisc_priv(sch);
+       struct cake_sched_config *q = qd->config;
++
++      qd->config = cfg;
++
++      if (!q->is_shared)
++              kvfree(q);
++
++      cake_reconfigure(sch);
++}
++
++static int cake_config_dump(struct cake_sched_config *q, struct sk_buff *skb)
++{
+       struct nlattr *opts;
+       u16 rate_flags;
+       u8 flow_mode;
+@@ -2894,6 +2936,13 @@ nla_put_failure:
+       return -1;
+ }
++static int cake_dump(struct Qdisc *sch, struct sk_buff *skb)
++{
++      struct cake_sched_data *qd = qdisc_priv(sch);
++
++      return cake_config_dump(qd->config, skb);
++}
++
+ static int cake_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
+ {
+       struct nlattr *stats = nla_nest_start_noflag(d->skb, TCA_STATS_APP);
+@@ -3157,6 +3206,7 @@ MODULE_ALIAS_NET_SCH("cake");
+ struct cake_mq_sched {
+       struct mq_sched mq_priv; /* must be first */
++      struct cake_sched_config cake_config;
+ };
+ static void cake_mq_destroy(struct Qdisc *sch)
+@@ -3167,25 +3217,68 @@ static void cake_mq_destroy(struct Qdisc
+ static int cake_mq_init(struct Qdisc *sch, struct nlattr *opt,
+                       struct netlink_ext_ack *extack)
+ {
+-      int ret;
++      struct cake_mq_sched *priv = qdisc_priv(sch);
++      struct net_device *dev = qdisc_dev(sch);
++      int ret, ntx;
++      bool _unused;
++
++      cake_config_init(&priv->cake_config, true);
++      if (opt) {
++              ret = cake_config_change(&priv->cake_config, opt, extack, &_unused);
++              if (ret)
++                      return ret;
++      }
+       ret = mq_init_common(sch, opt, extack, &cake_qdisc_ops);
+       if (ret)
+               return ret;
++      for (ntx = 0; ntx < dev->num_tx_queues; ntx++)
++              cake_config_replace(priv->mq_priv.qdiscs[ntx], &priv->cake_config);
++
+       return 0;
+ }
+ static int cake_mq_dump(struct Qdisc *sch, struct sk_buff *skb)
+ {
++      struct cake_mq_sched *priv = qdisc_priv(sch);
++
+       mq_dump_common(sch, skb);
+-      return 0;
++      return cake_config_dump(&priv->cake_config, skb);
+ }
+ static int cake_mq_change(struct Qdisc *sch, struct nlattr *opt,
+                         struct netlink_ext_ack *extack)
+ {
+-      return -EOPNOTSUPP;
++      struct cake_mq_sched *priv = qdisc_priv(sch);
++      struct net_device *dev = qdisc_dev(sch);
++      bool overhead_changed = false;
++      unsigned int ntx;
++      int ret;
++
++      ret = cake_config_change(&priv->cake_config, opt, extack, &overhead_changed);
++      if (ret)
++              return ret;
++
++      for (ntx = 0; ntx < dev->num_tx_queues; ntx++) {
++              struct Qdisc *chld = rtnl_dereference(netdev_get_tx_queue(dev, ntx)->qdisc_sleeping);
++              struct cake_sched_data *qd = qdisc_priv(chld);
++
++              if (overhead_changed) {
++                      qd->max_netlen = 0;
++                      qd->max_adjlen = 0;
++                      qd->min_netlen = ~0;
++                      qd->min_adjlen = ~0;
++              }
++
++              if (qd->tins) {
++                      sch_tree_lock(chld);
++                      cake_reconfigure(chld);
++                      sch_tree_unlock(chld);
++              }
++      }
++
++      return 0;
+ }
+ static int cake_mq_graft(struct Qdisc *sch, unsigned long cl, struct Qdisc *new,
diff --git a/target/linux/generic/backport-6.12/700-05-v7.0-net-sched-sch_cake-share-shaper-state-across-sub-ins.patch b/target/linux/generic/backport-6.12/700-05-v7.0-net-sched-sch_cake-share-shaper-state-across-sub-ins.patch
new file mode 100644 (file)
index 0000000..62bf38d
--- /dev/null
@@ -0,0 +1,175 @@
+From 090dde0780266eb80126fa970870bafdb4e937c3 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20K=C3=B6ppeler?= <j.koeppeler@tu-berlin.de>
+Date: Fri, 9 Jan 2026 14:15:34 +0100
+Subject: [PATCH 5/7] net/sched: sch_cake: share shaper state across
+ sub-instances of cake_mq
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This commit adds shared shaper state across the cake instances beneath a
+cake_mq qdisc. It works by periodically tracking the number of active
+instances, and scaling the configured rate by the number of active
+queues.
+
+The scan is lockless and simply reads the qlen and the last_active state
+variable of each of the instances configured beneath the parent cake_mq
+instance. Locking is not required since the values are only updated by
+the owning instance, and eventual consistency is sufficient for the
+purpose of estimating the number of active queues.
+
+The interval for scanning the number of active queues is set to 200 us.
+We found this to be a good tradeoff between overhead and response time.
+For a detailed analysis of this aspect see the Netdevconf talk:
+
+https://netdevconf.info/0x19/docs/netdev-0x19-paper16-talk-paper.pdf
+
+Reviewed-by: Jamal Hadi Salim <jhs@mojatatu.com>
+Signed-off-by: Jonas Köppeler <j.koeppeler@tu-berlin.de>
+Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
+Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-5-8d613fece5d8@redhat.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ Documentation/netlink/specs/tc.yaml |  3 ++
+ include/uapi/linux/pkt_sched.h      |  1 +
+ net/sched/sch_cake.c                | 51 +++++++++++++++++++++++++++++
+ 3 files changed, 55 insertions(+)
+
+--- a/Documentation/netlink/specs/tc.yaml
++++ b/Documentation/netlink/specs/tc.yaml
+@@ -2161,6 +2161,9 @@ attribute-sets:
+       -
+         name: blue-timer-us
+         type: s32
++      -
++        name: active-queues
++        type: u32
+   -
+     name: tc-cake-tin-stats-attrs
+     attributes:
+--- a/include/uapi/linux/pkt_sched.h
++++ b/include/uapi/linux/pkt_sched.h
+@@ -1034,6 +1034,7 @@ enum {
+       TCA_CAKE_STATS_DROP_NEXT_US,
+       TCA_CAKE_STATS_P_DROP,
+       TCA_CAKE_STATS_BLUE_TIMER_US,
++      TCA_CAKE_STATS_ACTIVE_QUEUES,
+       __TCA_CAKE_STATS_MAX
+ };
+ #define TCA_CAKE_STATS_MAX (__TCA_CAKE_STATS_MAX - 1)
+--- a/net/sched/sch_cake.c
++++ b/net/sched/sch_cake.c
+@@ -202,6 +202,7 @@ struct cake_sched_config {
+       u64             rate_bps;
+       u64             interval;
+       u64             target;
++      u64             sync_time;
+       u32             buffer_config_limit;
+       u32             fwmark_mask;
+       u16             fwmark_shft;
+@@ -258,6 +259,11 @@ struct cake_sched_data {
+       u16             max_adjlen;
+       u16             min_netlen;
+       u16             min_adjlen;
++
++      /* mq sync state */
++      u64             last_checked_active;
++      u64             last_active;
++      u32             active_queues;
+ };
+ enum {
+@@ -384,6 +390,8 @@ static const u32 inv_sqrt_cache[REC_INV_
+       1239850263, 1191209601, 1147878294, 1108955788
+ };
++static void cake_set_rate(struct cake_tin_data *b, u64 rate, u32 mtu,
++                        u64 target_ns, u64 rtt_est_ns);
+ /* http://en.wikipedia.org/wiki/Methods_of_computing_square_roots
+  * new_invsqrt = (invsqrt / 2) * (3 - count * invsqrt^2)
+  *
+@@ -2003,6 +2011,40 @@ static struct sk_buff *cake_dequeue(stru
+       u64 delay;
+       u32 len;
++      if (q->config->is_shared && now - q->last_checked_active >= q->config->sync_time) {
++              struct net_device *dev = qdisc_dev(sch);
++              struct cake_sched_data *other_priv;
++              u64 new_rate = q->config->rate_bps;
++              u64 other_qlen, other_last_active;
++              struct Qdisc *other_sch;
++              u32 num_active_qs = 1;
++              unsigned int ntx;
++
++              for (ntx = 0; ntx < dev->num_tx_queues; ntx++) {
++                      other_sch = rcu_dereference(netdev_get_tx_queue(dev, ntx)->qdisc_sleeping);
++                      other_priv = qdisc_priv(other_sch);
++
++                      if (other_priv == q)
++                              continue;
++
++                      other_qlen = READ_ONCE(other_sch->q.qlen);
++                      other_last_active = READ_ONCE(other_priv->last_active);
++
++                      if (other_qlen || other_last_active > q->last_checked_active)
++                              num_active_qs++;
++              }
++
++              if (num_active_qs > 1)
++                      new_rate = div64_u64(q->config->rate_bps, num_active_qs);
++
++              /* mtu = 0 is used to only update the rate and not mess with cobalt params */
++              cake_set_rate(b, new_rate, 0, 0, 0);
++              q->last_checked_active = now;
++              q->active_queues = num_active_qs;
++              q->rate_ns = b->tin_rate_ns;
++              q->rate_shft = b->tin_rate_shft;
++      }
++
+ begin:
+       if (!sch->q.qlen)
+               return NULL;
+@@ -2202,6 +2244,7 @@ retry:
+       b->tin_ecn_mark += !!flow->cvars.ecn_marked;
+       qdisc_bstats_update(sch, skb);
++      WRITE_ONCE(q->last_active, now);
+       /* collect delay stats */
+       delay = ktime_to_ns(ktime_sub(now, cobalt_get_enqueue_time(skb)));
+@@ -2302,6 +2345,9 @@ static void cake_set_rate(struct cake_ti
+       b->tin_rate_ns   = rate_ns;
+       b->tin_rate_shft = rate_shft;
++      if (mtu == 0)
++              return;
++
+       byte_target_ns = (byte_target * rate_ns) >> rate_shft;
+       b->cparams.target = max((byte_target_ns * 3) / 2, target_ns);
+@@ -2768,6 +2814,7 @@ static void cake_config_init(struct cake
+                              */
+       q->rate_flags |= CAKE_FLAG_SPLIT_GSO;
+       q->is_shared = is_shared;
++      q->sync_time = 200 * NSEC_PER_USEC;
+ }
+ static int cake_init(struct Qdisc *sch, struct nlattr *opt,
+@@ -2839,6 +2886,9 @@ static int cake_init(struct Qdisc *sch,
+       qd->avg_peak_bandwidth = q->rate_bps;
+       qd->min_netlen = ~0;
+       qd->min_adjlen = ~0;
++      qd->active_queues = 0;
++      qd->last_checked_active = 0;
++
+       return 0;
+ err:
+       kvfree(qd->config);
+@@ -2971,6 +3021,7 @@ static int cake_dump_stats(struct Qdisc
+       PUT_STAT_U32(MAX_ADJLEN, q->max_adjlen);
+       PUT_STAT_U32(MIN_NETLEN, q->min_netlen);
+       PUT_STAT_U32(MIN_ADJLEN, q->min_adjlen);
++      PUT_STAT_U32(ACTIVE_QUEUES, q->active_queues);
+ #undef PUT_STAT_U32
+ #undef PUT_STAT_U64
diff --git a/target/linux/generic/backport-6.12/700-06-v7.0-selftests-tc-testing-add-selftests-for-cake_mq-qdisc.patch b/target/linux/generic/backport-6.12/700-06-v7.0-selftests-tc-testing-add-selftests-for-cake_mq-qdisc.patch
new file mode 100644 (file)
index 0000000..c012a15
--- /dev/null
@@ -0,0 +1,606 @@
+From e5e7c7c6a027ce2f001fc60dc388a560c01427fd Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20K=C3=B6ppeler?= <j.koeppeler@tu-berlin.de>
+Date: Fri, 9 Jan 2026 14:15:35 +0100
+Subject: [PATCH 6/7] selftests/tc-testing: add selftests for cake_mq qdisc
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Test 684b: Create CAKE_MQ with default setting (4 queues)
+Test 7ee8: Create CAKE_MQ with bandwidth limit (4 queues)
+Test 1f87: Create CAKE_MQ with rtt time (4 queues)
+Test e9cf: Create CAKE_MQ with besteffort flag (4 queues)
+Test 7c05: Create CAKE_MQ with diffserv8 flag (4 queues)
+Test 5a77: Create CAKE_MQ with diffserv4 flag (4 queues)
+Test 8f7a: Create CAKE_MQ with flowblind flag (4 queues)
+Test 7ef7: Create CAKE_MQ with dsthost and nat flag (4 queues)
+Test 2e4d: Create CAKE_MQ with wash flag (4 queues)
+Test b3e6: Create CAKE_MQ with flowblind and no-split-gso flag (4 queues)
+Test 62cd: Create CAKE_MQ with dual-srchost and ack-filter flag (4 queues)
+Test 0df3: Create CAKE_MQ with dual-dsthost and ack-filter-aggressive flag (4 queues)
+Test 9a75: Create CAKE_MQ with memlimit and ptm flag (4 queues)
+Test cdef: Create CAKE_MQ with fwmark and atm flag (4 queues)
+Test 93dd: Create CAKE_MQ with overhead 0 and mpu (4 queues)
+Test 1475: Create CAKE_MQ with conservative and ingress flag (4 queues)
+Test 7bf1: Delete CAKE_MQ with conservative and ingress flag (4 queues)
+Test ee55: Replace CAKE_MQ with mpu (4 queues)
+Test 6df9: Change CAKE_MQ with mpu (4 queues)
+Test 67e2: Show CAKE_MQ class (4 queues)
+Test 2de4: Change bandwidth of CAKE_MQ (4 queues)
+Test 5f62: Fail to create CAKE_MQ with autorate-ingress flag (4 queues)
+Test 038e: Fail to change setting of sub-qdisc under CAKE_MQ
+Test 7bdc: Fail to replace sub-qdisc under CAKE_MQ
+Test 18e0: Fail to install CAKE_MQ on single queue device
+
+Reviewed-by: Victor Nogueira <victor@mojatatu.com>
+Signed-off-by: Jonas Köppeler <j.koeppeler@tu-berlin.de>
+Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
+Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-6-8d613fece5d8@redhat.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ .../tc-testing/tc-tests/qdiscs/cake_mq.json   | 559 ++++++++++++++++++
+ 1 file changed, 559 insertions(+)
+ create mode 100644 tools/testing/selftests/tc-testing/tc-tests/qdiscs/cake_mq.json
+
+--- /dev/null
++++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/cake_mq.json
+@@ -0,0 +1,559 @@
++[
++    {
++        "id": "684b",
++        "name": "Create CAKE_MQ with default setting (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device || true",
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "7ee8",
++        "name": "Create CAKE_MQ with bandwidth limit (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq bandwidth 1000",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth 1Kbit diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "1f87",
++        "name": "Create CAKE_MQ with rtt time (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq rtt 200",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 200us raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "e9cf",
++        "name": "Create CAKE_MQ with besteffort flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq besteffort",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited besteffort triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "7c05",
++        "name": "Create CAKE_MQ with diffserv8 flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq diffserv8",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv8 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "5a77",
++        "name": "Create CAKE_MQ with diffserv4 flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq diffserv4",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv4 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "8f7a",
++        "name": "Create CAKE_MQ with flowblind flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq flowblind",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 flowblind nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "7ef7",
++        "name": "Create CAKE_MQ with dsthost and nat flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq dsthost nat",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 dsthost nat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "2e4d",
++        "name": "Create CAKE_MQ with wash flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq hosts wash",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 hosts nonat wash no-ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "b3e6",
++        "name": "Create CAKE_MQ with flowblind and no-split-gso flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq flowblind no-split-gso",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 flowblind nonat nowash no-ack-filter no-split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "62cd",
++        "name": "Create CAKE_MQ with dual-srchost and ack-filter flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq dual-srchost ack-filter",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 dual-srchost nonat nowash ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "0df3",
++        "name": "Create CAKE_MQ with dual-dsthost and ack-filter-aggressive flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq dual-dsthost ack-filter-aggressive",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 dual-dsthost nonat nowash ack-filter-aggressive split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "9a75",
++        "name": "Create CAKE_MQ with memlimit and ptm flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq memlimit 10000 ptm",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw ptm overhead 0 memlimit 10000b ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "cdef",
++        "name": "Create CAKE_MQ with fwmark and atm flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq fwmark 8 atm",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw atm overhead 0 fwmark 0x8 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "93dd",
++        "name": "Create CAKE_MQ with overhead 0 and mpu (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq overhead 128 mpu 256",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms noatm overhead 128 mpu 256 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "1475",
++        "name": "Create CAKE_MQ with conservative and ingress flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq conservative ingress",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash ingress no-ack-filter split-gso rtt 100ms atm overhead 48 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "7bf1",
++        "name": "Delete CAKE_MQ with conservative and ingress flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device",
++            "$TC qdisc add dev $ETH handle 1: root cake_mq conservative ingress"
++        ],
++        "cmdUnderTest": "$TC qdisc del dev $ETH handle 1: root",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash ingress no-ack-filter split-gso rtt 100ms atm overhead 48 ",
++        "matchCount": "0",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "ee55",
++        "name": "Replace CAKE_MQ with mpu (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device",
++            "$TC qdisc add dev $ETH handle 1: root cake_mq overhead 128 mpu 256"
++        ],
++        "cmdUnderTest": "$TC qdisc replace dev $ETH handle 1: root cake_mq mpu 128",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms noatm overhead 128 mpu 128 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "6df9",
++        "name": "Change CAKE_MQ with mpu (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device",
++            "$TC qdisc add dev $ETH handle 1: root cake_mq overhead 128 mpu 256"
++        ],
++        "cmdUnderTest": "$TC qdisc change dev $ETH handle 1: root cake_mq mpu 128",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms noatm overhead 128 mpu 128 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "67e2",
++        "name": "Show CAKE_MQ class (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq",
++        "expExitCode": "0",
++        "verifyCmd": "$TC class show dev $ETH",
++        "matchPattern": "class cake_mq",
++        "matchCount": "4",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "2de4",
++        "name": "Change bandwidth of CAKE_MQ (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device",
++            "$TC qdisc add dev $ETH handle 1: root cake_mq"
++        ],
++        "cmdUnderTest": "$TC qdisc replace dev $ETH handle 1: root cake_mq bandwidth 1000",
++        "expExitCode": "0",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth 1Kbit diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "5f62",
++        "name": "Fail to create CAKE_MQ with autorate-ingress flag (4 queues)",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq autorate-ingress",
++        "expExitCode": "2",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited autorate-ingress diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "0",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "038e",
++        "name": "Fail to change setting of sub-qdisc under CAKE_MQ",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device",
++            "$TC qdisc add dev $ETH handle 1: root cake_mq"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH parent 1:1 cake besteffort flows",
++        "expExitCode": "2",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "7bdc",
++        "name": "Fail to replace sub-qdisc under CAKE_MQ",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 4\" > /sys/bus/netdevsim/new_device",
++            "$TC qdisc add dev $ETH handle 1: root cake_mq"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH parent 1:1 fq",
++        "expExitCode": "2",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "5",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    },
++    {
++        "id": "18e0",
++        "name": "Fail to install CAKE_MQ on single queue device",
++        "category": [
++            "qdisc",
++            "cake_mq"
++        ],
++        "plugins": {
++            "requires": "nsPlugin"
++        },
++        "setup": [
++            "echo \"1 1 1\" > /sys/bus/netdevsim/new_device"
++        ],
++        "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq",
++        "expExitCode": "2",
++        "verifyCmd": "$TC qdisc show dev $ETH",
++        "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ",
++        "matchCount": "0",
++        "teardown": [
++            "echo \"1\" > /sys/bus/netdevsim/del_device"
++        ]
++    }
++]
diff --git a/target/linux/generic/backport-6.12/700-07-v7.0-net-sched-cake-avoid-separate-allocation-of-struct-c.patch b/target/linux/generic/backport-6.12/700-07-v7.0-net-sched-cake-avoid-separate-allocation-of-struct-c.patch
new file mode 100644 (file)
index 0000000..1ba4cd3
--- /dev/null
@@ -0,0 +1,112 @@
+From 060969bc048d2f85149b7f1a8bbb56e6b1434cae Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com>
+Date: Tue, 13 Jan 2026 15:31:56 +0100
+Subject: [PATCH 7/7] net/sched: cake: avoid separate allocation of struct
+ cake_sched_config
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Paolo pointed out that we can avoid separately allocating struct
+cake_sched_config even in the non-mq case, by embedding it into struct
+cake_sched_data. This reduces the complexity of the logic that swaps the
+pointers and frees the old value, at the cost of adding 56 bytes to the
+latter. Since cake_sched_data is already almost 17k bytes, this seems
+like a reasonable tradeoff.
+
+Suggested-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
+Fixes: bc0ce2bad36c ("net/sched: sch_cake: Factor out config variables into separate struct")
+Link: https://patch.msgid.link/20260113143157.2581680-1-toke@redhat.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/sched/sch_cake.c | 29 ++++++-----------------------
+ 1 file changed, 6 insertions(+), 23 deletions(-)
+
+--- a/net/sched/sch_cake.c
++++ b/net/sched/sch_cake.c
+@@ -221,6 +221,7 @@ struct cake_sched_data {
+       struct tcf_block *block;
+       struct cake_tin_data *tins;
+       struct cake_sched_config *config;
++      struct cake_sched_config initial_config;
+       struct cake_heap_entry overflow_heap[CAKE_QUEUES * CAKE_MAX_TINS];
+@@ -2797,8 +2798,6 @@ static void cake_destroy(struct Qdisc *s
+       qdisc_watchdog_cancel(&q->watchdog);
+       tcf_block_put(q->block);
+       kvfree(q->tins);
+-      if (q->config && !q->config->is_shared)
+-              kvfree(q->config);
+ }
+ static void cake_config_init(struct cake_sched_config *q, bool is_shared)
+@@ -2821,13 +2820,9 @@ static int cake_init(struct Qdisc *sch,
+                    struct netlink_ext_ack *extack)
+ {
+       struct cake_sched_data *qd = qdisc_priv(sch);
+-      struct cake_sched_config *q;
++      struct cake_sched_config *q = &qd->initial_config;
+       int i, j, err;
+-      q = kzalloc(sizeof(*q), GFP_KERNEL);
+-      if (!q)
+-              return -ENOMEM;
+-
+       cake_config_init(q, false);
+       sch->limit = 10240;
+@@ -2839,14 +2834,13 @@ static int cake_init(struct Qdisc *sch,
+       if (opt) {
+               err = cake_change(sch, opt, extack);
+-
+               if (err)
+-                      goto err;
++                      return err;
+       }
+       err = tcf_block_get(&qd->block, &qd->filter_list, sch, extack);
+       if (err)
+-              goto err;
++              return err;
+       quantum_div[0] = ~0;
+       for (i = 1; i <= CAKE_QUEUES; i++)
+@@ -2854,10 +2848,8 @@ static int cake_init(struct Qdisc *sch,
+       qd->tins = kvcalloc(CAKE_MAX_TINS, sizeof(struct cake_tin_data),
+                           GFP_KERNEL);
+-      if (!qd->tins) {
+-              err = -ENOMEM;
+-              goto err;
+-      }
++      if (!qd->tins)
++              return -ENOMEM;
+       for (i = 0; i < CAKE_MAX_TINS; i++) {
+               struct cake_tin_data *b = qd->tins + i;
+@@ -2890,22 +2882,13 @@ static int cake_init(struct Qdisc *sch,
+       qd->last_checked_active = 0;
+       return 0;
+-err:
+-      kvfree(qd->config);
+-      qd->config = NULL;
+-      return err;
+ }
+ static void cake_config_replace(struct Qdisc *sch, struct cake_sched_config *cfg)
+ {
+       struct cake_sched_data *qd = qdisc_priv(sch);
+-      struct cake_sched_config *q = qd->config;
+       qd->config = cfg;
+-
+-      if (!q->is_shared)
+-              kvfree(q);
+-
+       cake_reconfigure(sch);
+ }
diff --git a/target/linux/generic/backport-6.12/701-01-v7.0-net-sched-sch_cake-avoid-sync-overhead-when-unlimite.patch b/target/linux/generic/backport-6.12/701-01-v7.0-net-sched-sch_cake-avoid-sync-overhead-when-unlimite.patch
new file mode 100644 (file)
index 0000000..60a003c
--- /dev/null
@@ -0,0 +1,32 @@
+From 0b3cd139be565b85f4a3579e376152b9def6256a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20K=C3=B6ppeler?= <j.koeppeler@tu-berlin.de>
+Date: Thu, 26 Feb 2026 12:40:15 +0100
+Subject: [PATCH] net/sched: sch_cake: avoid sync overhead when unlimited
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Skip inter-instance sync when no rate limit is configured, as it serves
+no purpose and only adds overhead.
+
+Fixes: 1bddd758bac2 ("net/sched: sch_cake: share shaper state across sub-instances of cake_mq")
+Signed-off-by: Jonas Köppeler <j.koeppeler@tu-berlin.de>
+Acked-by: Toke Høiland-Jørgensen <toke@toke.dk>
+Link: https://patch.msgid.link/20260226-cake-mq-skip-sync-bandwidth-unlimited-v1-1-01830bb4db87@tu-berlin.de
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/sched/sch_cake.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/net/sched/sch_cake.c
++++ b/net/sched/sch_cake.c
+@@ -2012,7 +2012,8 @@ static struct sk_buff *cake_dequeue(stru
+       u64 delay;
+       u32 len;
+-      if (q->config->is_shared && now - q->last_checked_active >= q->config->sync_time) {
++      if (q->config->is_shared && q->rate_ns &&
++          now - q->last_checked_active >= q->config->sync_time) {
+               struct net_device *dev = qdisc_dev(sch);
+               struct cake_sched_data *other_priv;
+               u64 new_rate = q->config->rate_bps;
diff --git a/target/linux/generic/backport-6.12/701-02-v7.0-net-sched-sch_cake-fixup-cake_mq-rate-adjustment-for.patch b/target/linux/generic/backport-6.12/701-02-v7.0-net-sched-sch_cake-fixup-cake_mq-rate-adjustment-for.patch
new file mode 100644 (file)
index 0000000..357f6b6
--- /dev/null
@@ -0,0 +1,187 @@
+From 15c2715a52645fd8e6e18b7abc3449292d118c7c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20K=C3=B6ppeler?= <j.koeppeler@tu-berlin.de>
+Date: Thu, 26 Feb 2026 12:40:16 +0100
+Subject: [PATCH] net/sched: sch_cake: fixup cake_mq rate adjustment for
+ diffserv config
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+cake_mq's rate adjustment during the sync periods did not adjust the
+rates for every tin in a diffserv config. This lead to inconsistencies
+of rates between the tins. Fix this by setting the rates for all tins
+during synchronization.
+
+Fixes: 1bddd758bac2 ("net/sched: sch_cake: share shaper state across sub-instances of cake_mq")
+Signed-off-by: Jonas Köppeler <j.koeppeler@tu-berlin.de>
+Acked-by: Toke Høiland-Jørgensen <toke@toke.dk>
+Link: https://patch.msgid.link/20260226-cake-mq-skip-sync-bandwidth-unlimited-v1-2-01830bb4db87@tu-berlin.de
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/sched/sch_cake.c | 50 ++++++++++++++++++++------------------------
+ 1 file changed, 23 insertions(+), 27 deletions(-)
+
+--- a/net/sched/sch_cake.c
++++ b/net/sched/sch_cake.c
+@@ -391,8 +391,8 @@ static const u32 inv_sqrt_cache[REC_INV_
+       1239850263, 1191209601, 1147878294, 1108955788
+ };
+-static void cake_set_rate(struct cake_tin_data *b, u64 rate, u32 mtu,
+-                        u64 target_ns, u64 rtt_est_ns);
++static void cake_configure_rates(struct Qdisc *sch, u64 rate, bool rate_adjust);
++
+ /* http://en.wikipedia.org/wiki/Methods_of_computing_square_roots
+  * new_invsqrt = (invsqrt / 2) * (3 - count * invsqrt^2)
+  *
+@@ -2039,12 +2039,9 @@ static struct sk_buff *cake_dequeue(stru
+               if (num_active_qs > 1)
+                       new_rate = div64_u64(q->config->rate_bps, num_active_qs);
+-              /* mtu = 0 is used to only update the rate and not mess with cobalt params */
+-              cake_set_rate(b, new_rate, 0, 0, 0);
++              cake_configure_rates(sch, new_rate, true);
+               q->last_checked_active = now;
+               q->active_queues = num_active_qs;
+-              q->rate_ns = b->tin_rate_ns;
+-              q->rate_shft = b->tin_rate_shft;
+       }
+ begin:
+@@ -2361,12 +2358,10 @@ static void cake_set_rate(struct cake_ti
+       b->cparams.p_dec = 1 << 20; /* 1/4096 */
+ }
+-static int cake_config_besteffort(struct Qdisc *sch)
++static int cake_config_besteffort(struct Qdisc *sch, u64 rate, u32 mtu)
+ {
+       struct cake_sched_data *q = qdisc_priv(sch);
+       struct cake_tin_data *b = &q->tins[0];
+-      u32 mtu = psched_mtu(qdisc_dev(sch));
+-      u64 rate = q->config->rate_bps;
+       q->tin_cnt = 1;
+@@ -2380,12 +2375,10 @@ static int cake_config_besteffort(struct
+       return 0;
+ }
+-static int cake_config_precedence(struct Qdisc *sch)
++static int cake_config_precedence(struct Qdisc *sch, u64 rate, u32 mtu)
+ {
+       /* convert high-level (user visible) parameters into internal format */
+       struct cake_sched_data *q = qdisc_priv(sch);
+-      u32 mtu = psched_mtu(qdisc_dev(sch));
+-      u64 rate = q->config->rate_bps;
+       u32 quantum = 256;
+       u32 i;
+@@ -2456,7 +2449,7 @@ static int cake_config_precedence(struct
+  *    Total 12 traffic classes.
+  */
+-static int cake_config_diffserv8(struct Qdisc *sch)
++static int cake_config_diffserv8(struct Qdisc *sch, u64 rate, u32 mtu)
+ {
+ /*    Pruned list of traffic classes for typical applications:
+  *
+@@ -2473,8 +2466,6 @@ static int cake_config_diffserv8(struct
+  */
+       struct cake_sched_data *q = qdisc_priv(sch);
+-      u32 mtu = psched_mtu(qdisc_dev(sch));
+-      u64 rate = q->config->rate_bps;
+       u32 quantum = 256;
+       u32 i;
+@@ -2504,7 +2495,7 @@ static int cake_config_diffserv8(struct
+       return 0;
+ }
+-static int cake_config_diffserv4(struct Qdisc *sch)
++static int cake_config_diffserv4(struct Qdisc *sch, u64 rate, u32 mtu)
+ {
+ /*  Further pruned list of traffic classes for four-class system:
+  *
+@@ -2517,8 +2508,6 @@ static int cake_config_diffserv4(struct
+  */
+       struct cake_sched_data *q = qdisc_priv(sch);
+-      u32 mtu = psched_mtu(qdisc_dev(sch));
+-      u64 rate = q->config->rate_bps;
+       u32 quantum = 1024;
+       q->tin_cnt = 4;
+@@ -2546,7 +2535,7 @@ static int cake_config_diffserv4(struct
+       return 0;
+ }
+-static int cake_config_diffserv3(struct Qdisc *sch)
++static int cake_config_diffserv3(struct Qdisc *sch, u64 rate, u32 mtu)
+ {
+ /*  Simplified Diffserv structure with 3 tins.
+  *            Latency Sensitive       (CS7, CS6, EF, VA, TOS4)
+@@ -2554,8 +2543,6 @@ static int cake_config_diffserv3(struct
+  *            Low Priority            (LE, CS1)
+  */
+       struct cake_sched_data *q = qdisc_priv(sch);
+-      u32 mtu = psched_mtu(qdisc_dev(sch));
+-      u64 rate = q->config->rate_bps;
+       u32 quantum = 1024;
+       q->tin_cnt = 3;
+@@ -2580,32 +2567,33 @@ static int cake_config_diffserv3(struct
+       return 0;
+ }
+-static void cake_reconfigure(struct Qdisc *sch)
++static void cake_configure_rates(struct Qdisc *sch, u64 rate, bool rate_adjust)
+ {
++      u32 mtu = likely(rate_adjust) ? 0 : psched_mtu(qdisc_dev(sch));
+       struct cake_sched_data *qd = qdisc_priv(sch);
+       struct cake_sched_config *q = qd->config;
+       int c, ft;
+       switch (q->tin_mode) {
+       case CAKE_DIFFSERV_BESTEFFORT:
+-              ft = cake_config_besteffort(sch);
++              ft = cake_config_besteffort(sch, rate, mtu);
+               break;
+       case CAKE_DIFFSERV_PRECEDENCE:
+-              ft = cake_config_precedence(sch);
++              ft = cake_config_precedence(sch, rate, mtu);
+               break;
+       case CAKE_DIFFSERV_DIFFSERV8:
+-              ft = cake_config_diffserv8(sch);
++              ft = cake_config_diffserv8(sch, rate, mtu);
+               break;
+       case CAKE_DIFFSERV_DIFFSERV4:
+-              ft = cake_config_diffserv4(sch);
++              ft = cake_config_diffserv4(sch, rate, mtu);
+               break;
+       case CAKE_DIFFSERV_DIFFSERV3:
+       default:
+-              ft = cake_config_diffserv3(sch);
++              ft = cake_config_diffserv3(sch, rate, mtu);
+               break;
+       }
+@@ -2616,6 +2604,14 @@ static void cake_reconfigure(struct Qdis
+       qd->rate_ns   = qd->tins[ft].tin_rate_ns;
+       qd->rate_shft = qd->tins[ft].tin_rate_shft;
++}
++
++static void cake_reconfigure(struct Qdisc *sch)
++{
++      struct cake_sched_data *qd = qdisc_priv(sch);
++      struct cake_sched_config *q = qd->config;
++
++      cake_configure_rates(sch, qd->config->rate_bps, false);
+       if (q->buffer_config_limit) {
+               qd->buffer_limit = q->buffer_config_limit;
diff --git a/target/linux/generic/backport-6.12/710-v6.16-igc-enable-HW-vlan-tag-insertion-stripping-by-defaul.patch b/target/linux/generic/backport-6.12/710-v6.16-igc-enable-HW-vlan-tag-insertion-stripping-by-defaul.patch
new file mode 100644 (file)
index 0000000..48d03f1
--- /dev/null
@@ -0,0 +1,32 @@
+From 8cae5a0d91fea01d90ce7c1827e26934a22ca2fa Mon Sep 17 00:00:00 2001
+From: Rui Salvaterra <rsalvaterra@gmail.com>
+Date: Wed, 5 Mar 2025 11:53:56 +0000
+Subject: [PATCH] igc: enable HW vlan tag insertion/stripping by default
+
+This is enabled by default in other Intel drivers I've checked (e1000, e1000e,
+iavf, igb and ice). Fixes an out-of-the-box performance issue when running
+OpenWrt on typical mini-PCs with igc-supported Ethernet controllers and 802.1Q
+VLAN configurations, as ethtool isn't part of the default packages and sane
+defaults are expected.
+
+In my specific case, with an Intel N100-based machine with four I226-V Ethernet
+controllers, my upload performance increased from under 30 Mb/s to the expected
+~1 Gb/s.
+
+Signed-off-by: Rui Salvaterra <rsalvaterra@gmail.com>
+---
+ drivers/net/ethernet/intel/igc/igc_main.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/net/ethernet/intel/igc/igc_main.c
++++ b/drivers/net/ethernet/intel/igc/igc_main.c
+@@ -7070,6 +7070,9 @@ static int igc_probe(struct pci_dev *pde
+       netdev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT |
+                              NETDEV_XDP_ACT_XSK_ZEROCOPY;
++      /* enable HW vlan tag insertion/stripping by default */
++      netdev->features |= NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX;
++
+       /* MTU range: 68 - 9216 */
+       netdev->min_mtu = ETH_MIN_MTU;
+       netdev->max_mtu = MAX_STD_JUMBO_FRAME_SIZE;
diff --git a/target/linux/generic/backport-6.12/720-01-v6.13-net-phy-mediatek-ge-soc-Fix-coding-style.patch b/target/linux/generic/backport-6.12/720-01-v6.13-net-phy-mediatek-ge-soc-Fix-coding-style.patch
new file mode 100644 (file)
index 0000000..25bd96f
--- /dev/null
@@ -0,0 +1,88 @@
+From 277f96c1f3f71d6e1d3bcf650d7cd84c1442210f Mon Sep 17 00:00:00 2001
+From: "SkyLake.Huang" <skylake.huang@mediatek.com>
+Date: Thu, 17 Oct 2024 11:22:11 +0800
+Subject: [PATCH 01/20] net: phy: mediatek-ge-soc: Fix coding style
+
+This patch fixes spelling errors, re-arrange vars with
+reverse Xmas tree and remove unnecessary parens in
+mediatek-ge-soc.c.
+
+Signed-off-by: SkyLake.Huang <skylake.huang@mediatek.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ drivers/net/phy/mediatek-ge-soc.c | 36 ++++++++++++++++---------------
+ 1 file changed, 19 insertions(+), 17 deletions(-)
+
+--- a/drivers/net/phy/mediatek-ge-soc.c
++++ b/drivers/net/phy/mediatek-ge-soc.c
+@@ -408,16 +408,17 @@ static int tx_offset_cal_efuse(struct ph
+ static int tx_amp_fill_result(struct phy_device *phydev, u16 *buf)
+ {
+-      int i;
+-      int bias[16] = {};
+-      const int vals_9461[16] = { 7, 1, 4, 7,
+-                                  7, 1, 4, 7,
+-                                  7, 1, 4, 7,
+-                                  7, 1, 4, 7 };
+       const int vals_9481[16] = { 10, 6, 6, 10,
+                                   10, 6, 6, 10,
+                                   10, 6, 6, 10,
+                                   10, 6, 6, 10 };
++      const int vals_9461[16] = { 7, 1, 4, 7,
++                                  7, 1, 4, 7,
++                                  7, 1, 4, 7,
++                                  7, 1, 4, 7 };
++      int bias[16] = {};
++      int i;
++
+       switch (phydev->drv->phy_id) {
+       case MTK_GPHY_ID_MT7981:
+               /* We add some calibration to efuse values
+@@ -1069,10 +1070,10 @@ static int start_cal(struct phy_device *
+ static int mt798x_phy_calibration(struct phy_device *phydev)
+ {
++      struct nvmem_cell *cell;
+       int ret = 0;
+-      u32 *buf;
+       size_t len;
+-      struct nvmem_cell *cell;
++      u32 *buf;
+       cell = nvmem_cell_get(&phydev->mdio.dev, "phy-cal-data");
+       if (IS_ERR(cell)) {
+@@ -1210,14 +1211,15 @@ static int mt798x_phy_led_brightness_set
+       return mt798x_phy_hw_led_on_set(phydev, index, (value != LED_OFF));
+ }
+-static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_FULL_DUPLEX) |
+-                                               BIT(TRIGGER_NETDEV_HALF_DUPLEX) |
+-                                               BIT(TRIGGER_NETDEV_LINK)        |
+-                                               BIT(TRIGGER_NETDEV_LINK_10)     |
+-                                               BIT(TRIGGER_NETDEV_LINK_100)    |
+-                                               BIT(TRIGGER_NETDEV_LINK_1000)   |
+-                                               BIT(TRIGGER_NETDEV_RX)          |
+-                                               BIT(TRIGGER_NETDEV_TX));
++static const unsigned long supported_triggers =
++      BIT(TRIGGER_NETDEV_FULL_DUPLEX) |
++      BIT(TRIGGER_NETDEV_HALF_DUPLEX) |
++      BIT(TRIGGER_NETDEV_LINK)        |
++      BIT(TRIGGER_NETDEV_LINK_10)     |
++      BIT(TRIGGER_NETDEV_LINK_100)    |
++      BIT(TRIGGER_NETDEV_LINK_1000)   |
++      BIT(TRIGGER_NETDEV_RX)          |
++      BIT(TRIGGER_NETDEV_TX);
+ static int mt798x_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
+                                         unsigned long rules)
+@@ -1415,7 +1417,7 @@ static int mt7988_phy_probe_shared(struc
+        * LED_C and LED_D respectively. At the same time those pins are used to
+        * bootstrap configuration of the reference clock source (LED_A),
+        * DRAM DDRx16b x2/x1 (LED_B) and boot device (LED_C, LED_D).
+-       * In practise this is done using a LED and a resistor pulling the pin
++       * In practice this is done using a LED and a resistor pulling the pin
+        * either to GND or to VIO.
+        * The detected value at boot time is accessible at run-time using the
+        * TPBANK0 register located in the gpio base of the pinctrl, in order
diff --git a/target/linux/generic/backport-6.12/720-02-v6.13-net-phy-mediatek-ge-soc-Shrink-line-wrapping-to-80-c.patch b/target/linux/generic/backport-6.12/720-02-v6.13-net-phy-mediatek-ge-soc-Shrink-line-wrapping-to-80-c.patch
new file mode 100644 (file)
index 0000000..fbf3a99
--- /dev/null
@@ -0,0 +1,271 @@
+From c0dc1b412f9d840c51c5ee8927bf066e15a59550 Mon Sep 17 00:00:00 2001
+From: "SkyLake.Huang" <skylake.huang@mediatek.com>
+Date: Thu, 17 Oct 2024 11:22:12 +0800
+Subject: [PATCH 02/20] net: phy: mediatek-ge-soc: Shrink line wrapping to 80
+ characters
+
+This patch shrinks line wrapping to 80 chars. Also, in
+tx_amp_fill_result(), use FIELD_PREP() to prettify code.
+
+Signed-off-by: SkyLake.Huang <skylake.huang@mediatek.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ drivers/net/phy/mediatek-ge-soc.c | 125 +++++++++++++++++++++---------
+ 1 file changed, 88 insertions(+), 37 deletions(-)
+
+--- a/drivers/net/phy/mediatek-ge-soc.c
++++ b/drivers/net/phy/mediatek-ge-soc.c
+@@ -342,7 +342,8 @@ static int cal_cycle(struct phy_device *
+       ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+                                       MTK_PHY_RG_AD_CAL_CLK, reg_val,
+                                       reg_val & MTK_PHY_DA_CAL_CLK, 500,
+-                                      ANALOG_INTERNAL_OPERATION_MAX_US, false);
++                                      ANALOG_INTERNAL_OPERATION_MAX_US,
++                                      false);
+       if (ret) {
+               phydev_err(phydev, "Calibration cycle timeout\n");
+               return ret;
+@@ -441,40 +442,72 @@ static int tx_amp_fill_result(struct phy
+       }
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TXVLD_DA_RG,
+-                     MTK_PHY_DA_TX_I2MPB_A_GBE_MASK, (buf[0] + bias[0]) << 10);
++                     MTK_PHY_DA_TX_I2MPB_A_GBE_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_GBE_MASK,
++                                buf[0] + bias[0]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TXVLD_DA_RG,
+-                     MTK_PHY_DA_TX_I2MPB_A_TBT_MASK, buf[0] + bias[1]);
++                     MTK_PHY_DA_TX_I2MPB_A_TBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_TBT_MASK,
++                                buf[0] + bias[1]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_A2,
+-                     MTK_PHY_DA_TX_I2MPB_A_HBT_MASK, (buf[0] + bias[2]) << 10);
++                     MTK_PHY_DA_TX_I2MPB_A_HBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_HBT_MASK,
++                                buf[0] + bias[2]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_A2,
+-                     MTK_PHY_DA_TX_I2MPB_A_TST_MASK, buf[0] + bias[3]);
++                     MTK_PHY_DA_TX_I2MPB_A_TST_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_TST_MASK,
++                                buf[0] + bias[3]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B1,
+-                     MTK_PHY_DA_TX_I2MPB_B_GBE_MASK, (buf[1] + bias[4]) << 8);
++                     MTK_PHY_DA_TX_I2MPB_B_GBE_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_GBE_MASK,
++                                buf[1] + bias[4]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B1,
+-                     MTK_PHY_DA_TX_I2MPB_B_TBT_MASK, buf[1] + bias[5]);
++                     MTK_PHY_DA_TX_I2MPB_B_TBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_TBT_MASK,
++                                buf[1] + bias[5]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B2,
+-                     MTK_PHY_DA_TX_I2MPB_B_HBT_MASK, (buf[1] + bias[6]) << 8);
++                     MTK_PHY_DA_TX_I2MPB_B_HBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_HBT_MASK,
++                                buf[1] + bias[6]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B2,
+-                     MTK_PHY_DA_TX_I2MPB_B_TST_MASK, buf[1] + bias[7]);
++                     MTK_PHY_DA_TX_I2MPB_B_TST_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_TST_MASK,
++                                buf[1] + bias[7]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C1,
+-                     MTK_PHY_DA_TX_I2MPB_C_GBE_MASK, (buf[2] + bias[8]) << 8);
++                     MTK_PHY_DA_TX_I2MPB_C_GBE_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_GBE_MASK,
++                                buf[2] + bias[8]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C1,
+-                     MTK_PHY_DA_TX_I2MPB_C_TBT_MASK, buf[2] + bias[9]);
++                     MTK_PHY_DA_TX_I2MPB_C_TBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_TBT_MASK,
++                                buf[2] + bias[9]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C2,
+-                     MTK_PHY_DA_TX_I2MPB_C_HBT_MASK, (buf[2] + bias[10]) << 8);
++                     MTK_PHY_DA_TX_I2MPB_C_HBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_HBT_MASK,
++                                buf[2] + bias[10]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C2,
+-                     MTK_PHY_DA_TX_I2MPB_C_TST_MASK, buf[2] + bias[11]);
++                     MTK_PHY_DA_TX_I2MPB_C_TST_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_TST_MASK,
++                                buf[2] + bias[11]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D1,
+-                     MTK_PHY_DA_TX_I2MPB_D_GBE_MASK, (buf[3] + bias[12]) << 8);
++                     MTK_PHY_DA_TX_I2MPB_D_GBE_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_GBE_MASK,
++                                buf[3] + bias[12]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D1,
+-                     MTK_PHY_DA_TX_I2MPB_D_TBT_MASK, buf[3] + bias[13]);
++                     MTK_PHY_DA_TX_I2MPB_D_TBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_TBT_MASK,
++                                buf[3] + bias[13]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D2,
+-                     MTK_PHY_DA_TX_I2MPB_D_HBT_MASK, (buf[3] + bias[14]) << 8);
++                     MTK_PHY_DA_TX_I2MPB_D_HBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_HBT_MASK,
++                                buf[3] + bias[14]));
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D2,
+-                     MTK_PHY_DA_TX_I2MPB_D_TST_MASK, buf[3] + bias[15]);
++                     MTK_PHY_DA_TX_I2MPB_D_TST_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_TST_MASK,
++                                buf[3] + bias[15]));
+       return 0;
+ }
+@@ -663,7 +696,8 @@ static int tx_vcm_cal_sw(struct phy_devi
+               goto restore;
+       /* We calibrate TX-VCM in different logic. Check upper index and then
+-       * lower index. If this calibration is valid, apply lower index's result.
++       * lower index. If this calibration is valid, apply lower index's
++       * result.
+        */
+       ret = upper_ret - lower_ret;
+       if (ret == 1) {
+@@ -692,7 +726,8 @@ static int tx_vcm_cal_sw(struct phy_devi
+       } else if (upper_idx == TXRESERVE_MAX && upper_ret == 0 &&
+                  lower_ret == 0) {
+               ret = 0;
+-              phydev_warn(phydev, "TX-VCM SW cal result at high margin 0x%x\n",
++              phydev_warn(phydev,
++                          "TX-VCM SW cal result at high margin 0x%x\n",
+                           upper_idx);
+       } else {
+               ret = -EINVAL;
+@@ -796,7 +831,8 @@ static void mt7981_phy_finetune(struct p
+       /* TR_OPEN_LOOP_EN = 1, lpf_x_average = 9 */
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG234,
+-                     MTK_PHY_TR_OPEN_LOOP_EN_MASK | MTK_PHY_LPF_X_AVERAGE_MASK,
++                     MTK_PHY_TR_OPEN_LOOP_EN_MASK |
++                     MTK_PHY_LPF_X_AVERAGE_MASK,
+                      BIT(0) | FIELD_PREP(MTK_PHY_LPF_X_AVERAGE_MASK, 0x9));
+       /* rg_tr_lpf_cnt_val = 512 */
+@@ -865,7 +901,8 @@ static void mt7988_phy_finetune(struct p
+       /* TR_OPEN_LOOP_EN = 1, lpf_x_average = 10 */
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG234,
+-                     MTK_PHY_TR_OPEN_LOOP_EN_MASK | MTK_PHY_LPF_X_AVERAGE_MASK,
++                     MTK_PHY_TR_OPEN_LOOP_EN_MASK |
++                     MTK_PHY_LPF_X_AVERAGE_MASK,
+                      BIT(0) | FIELD_PREP(MTK_PHY_LPF_X_AVERAGE_MASK, 0xa));
+       /* rg_tr_lpf_cnt_val = 1023 */
+@@ -977,7 +1014,8 @@ static void mt798x_phy_eee(struct phy_de
+       phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+       phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_3);
+-      __phy_modify(phydev, MTK_PHY_LPI_REG_14, MTK_PHY_LPI_WAKE_TIMER_1000_MASK,
++      __phy_modify(phydev, MTK_PHY_LPI_REG_14,
++                   MTK_PHY_LPI_WAKE_TIMER_1000_MASK,
+                    FIELD_PREP(MTK_PHY_LPI_WAKE_TIMER_1000_MASK, 0x19c));
+       __phy_modify(phydev, MTK_PHY_LPI_REG_1c, MTK_PHY_SMI_DET_ON_THRESH_MASK,
+@@ -987,7 +1025,8 @@ static void mt798x_phy_eee(struct phy_de
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+                      MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122,
+                      MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK,
+-                     FIELD_PREP(MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK, 0xff));
++                     FIELD_PREP(MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK,
++                                0xff));
+ }
+ static int cal_sw(struct phy_device *phydev, enum CAL_ITEM cal_item,
+@@ -1147,7 +1186,8 @@ static int mt798x_phy_hw_led_on_set(stru
+                                       (index ? 16 : 0), &priv->led_state);
+       if (changed)
+               return phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
+-                                    MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL,
++                                    MTK_PHY_LED1_ON_CTRL :
++                                    MTK_PHY_LED0_ON_CTRL,
+                                     MTK_PHY_LED_ON_MASK,
+                                     on ? MTK_PHY_LED_ON_FORCE_ON : 0);
+       else
+@@ -1157,7 +1197,8 @@ static int mt798x_phy_hw_led_on_set(stru
+ static int mt798x_phy_hw_led_blink_set(struct phy_device *phydev, u8 index,
+                                      bool blinking)
+ {
+-      unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK + (index ? 16 : 0);
++      unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK +
++                               (index ? 16 : 0);
+       struct mtk_socphy_priv *priv = phydev->priv;
+       bool changed;
+@@ -1170,8 +1211,10 @@ static int mt798x_phy_hw_led_blink_set(s
+                             (index ? 16 : 0), &priv->led_state);
+       if (changed)
+               return phy_write_mmd(phydev, MDIO_MMD_VEND2, index ?
+-                                   MTK_PHY_LED1_BLINK_CTRL : MTK_PHY_LED0_BLINK_CTRL,
+-                                   blinking ? MTK_PHY_LED_BLINK_FORCE_BLINK : 0);
++                                   MTK_PHY_LED1_BLINK_CTRL :
++                                   MTK_PHY_LED0_BLINK_CTRL,
++                                   blinking ?
++                                   MTK_PHY_LED_BLINK_FORCE_BLINK : 0);
+       else
+               return 0;
+ }
+@@ -1237,7 +1280,8 @@ static int mt798x_phy_led_hw_is_supporte
+ static int mt798x_phy_led_hw_control_get(struct phy_device *phydev, u8 index,
+                                        unsigned long *rules)
+ {
+-      unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK + (index ? 16 : 0);
++      unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK +
++                               (index ? 16 : 0);
+       unsigned int bit_netdev = MTK_PHY_LED_STATE_NETDEV + (index ? 16 : 0);
+       unsigned int bit_on = MTK_PHY_LED_STATE_FORCE_ON + (index ? 16 : 0);
+       struct mtk_socphy_priv *priv = phydev->priv;
+@@ -1258,8 +1302,8 @@ static int mt798x_phy_led_hw_control_get
+       if (blink < 0)
+               return -EIO;
+-      if ((on & (MTK_PHY_LED_ON_LINK | MTK_PHY_LED_ON_FDX | MTK_PHY_LED_ON_HDX |
+-                 MTK_PHY_LED_ON_LINKDOWN)) ||
++      if ((on & (MTK_PHY_LED_ON_LINK | MTK_PHY_LED_ON_FDX |
++                 MTK_PHY_LED_ON_HDX | MTK_PHY_LED_ON_LINKDOWN)) ||
+           (blink & (MTK_PHY_LED_BLINK_RX | MTK_PHY_LED_BLINK_TX)))
+               set_bit(bit_netdev, &priv->led_state);
+       else
+@@ -1333,17 +1377,23 @@ static int mt798x_phy_led_hw_control_set
+       if (rules & BIT(TRIGGER_NETDEV_RX)) {
+               blink |= (on & MTK_PHY_LED_ON_LINK) ?
+-                        (((on & MTK_PHY_LED_ON_LINK10) ? MTK_PHY_LED_BLINK_10RX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK100) ? MTK_PHY_LED_BLINK_100RX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK1000) ? MTK_PHY_LED_BLINK_1000RX : 0)) :
++                        (((on & MTK_PHY_LED_ON_LINK10) ?
++                          MTK_PHY_LED_BLINK_10RX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK100) ?
++                          MTK_PHY_LED_BLINK_100RX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK1000) ?
++                          MTK_PHY_LED_BLINK_1000RX : 0)) :
+                         MTK_PHY_LED_BLINK_RX;
+       }
+       if (rules & BIT(TRIGGER_NETDEV_TX)) {
+               blink |= (on & MTK_PHY_LED_ON_LINK) ?
+-                        (((on & MTK_PHY_LED_ON_LINK10) ? MTK_PHY_LED_BLINK_10TX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK100) ? MTK_PHY_LED_BLINK_100TX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK1000) ? MTK_PHY_LED_BLINK_1000TX : 0)) :
++                        (((on & MTK_PHY_LED_ON_LINK10) ?
++                          MTK_PHY_LED_BLINK_10TX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK100) ?
++                          MTK_PHY_LED_BLINK_100TX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK1000) ?
++                          MTK_PHY_LED_BLINK_1000TX : 0)) :
+                         MTK_PHY_LED_BLINK_TX;
+       }
+@@ -1400,7 +1450,8 @@ static int mt7988_phy_fix_leds_polaritie
+       /* Only now setup pinctrl to avoid bogus blinking */
+       pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "gbe-led");
+       if (IS_ERR(pinctrl))
+-              dev_err(&phydev->mdio.bus->dev, "Failed to setup PHY LED pinctrl\n");
++              dev_err(&phydev->mdio.bus->dev,
++                      "Failed to setup PHY LED pinctrl\n");
+       return 0;
+ }
diff --git a/target/linux/generic/backport-6.12/720-03-v6.13-net-phy-mediatek-ge-soc-Propagate-error-code-correct.patch b/target/linux/generic/backport-6.12/720-03-v6.13-net-phy-mediatek-ge-soc-Propagate-error-code-correct.patch
new file mode 100644 (file)
index 0000000..1f6f5f5
--- /dev/null
@@ -0,0 +1,40 @@
+From bcbbfb4f62c4ba35783cc617997a2e92d91e3940 Mon Sep 17 00:00:00 2001
+From: "SkyLake.Huang" <skylake.huang@mediatek.com>
+Date: Thu, 17 Oct 2024 11:22:13 +0800
+Subject: [PATCH 03/20] net: phy: mediatek-ge-soc: Propagate error code
+ correctly in cal_cycle()
+
+This patch propagates error code correctly in cal_cycle()
+and improve with FIELD_GET().
+
+Signed-off-by: SkyLake.Huang <skylake.huang@mediatek.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ drivers/net/phy/mediatek-ge-soc.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/mediatek-ge-soc.c
++++ b/drivers/net/phy/mediatek-ge-soc.c
+@@ -110,7 +110,7 @@
+ #define   MTK_PHY_CR_TX_AMP_OFFSET_D_MASK     GENMASK(6, 0)
+ #define MTK_PHY_RG_AD_CAL_COMP                        0x17a
+-#define   MTK_PHY_AD_CAL_COMP_OUT_SHIFT               (8)
++#define   MTK_PHY_AD_CAL_COMP_OUT_MASK                GENMASK(8, 8)
+ #define MTK_PHY_RG_AD_CAL_CLK                 0x17b
+ #define   MTK_PHY_DA_CAL_CLK                  BIT(0)
+@@ -351,8 +351,10 @@ static int cal_cycle(struct phy_device *
+       phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CALIN,
+                          MTK_PHY_DA_CALIN_FLAG);
+-      ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CAL_COMP) >>
+-                         MTK_PHY_AD_CAL_COMP_OUT_SHIFT;
++      ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CAL_COMP);
++      if (ret < 0)
++              return ret;
++      ret = FIELD_GET(MTK_PHY_AD_CAL_COMP_OUT_MASK, ret);
+       phydev_dbg(phydev, "cal_val: 0x%x, ret: %d\n", cal_val, ret);
+       return ret;
diff --git a/target/linux/generic/backport-6.12/720-04-v6.13-net-phy-mediatek-Re-organize-MediaTek-ethernet-phy-d.patch b/target/linux/generic/backport-6.12/720-04-v6.13-net-phy-mediatek-Re-organize-MediaTek-ethernet-phy-d.patch
new file mode 100644 (file)
index 0000000..81e2510
--- /dev/null
@@ -0,0 +1,3565 @@
+From e062f073dc0df4fcd338043cb0b69b6bcd31e4af Mon Sep 17 00:00:00 2001
+From: "SkyLake.Huang" <skylake.huang@mediatek.com>
+Date: Sat, 9 Nov 2024 00:34:51 +0800
+Subject: [PATCH 04/20] net: phy: mediatek: Re-organize MediaTek ethernet phy
+ drivers
+
+Re-organize MediaTek ethernet phy driver files and get ready to integrate
+some common functions and add new 2.5G phy driver.
+mtk-ge.c: MT7530 Gphy on MT7621 & MT7531 Gphy
+mtk-ge-soc.c: Built-in Gphy on MT7981 & Built-in switch Gphy on MT7988
+mtk-2p5ge.c: Planned for built-in 2.5G phy on MT7988
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: SkyLake.Huang <skylake.huang@mediatek.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ MAINTAINERS                                   |  4 ++--
+ drivers/net/phy/Kconfig                       | 17 +-------------
+ drivers/net/phy/Makefile                      |  3 +--
+ drivers/net/phy/mediatek/Kconfig              | 22 +++++++++++++++++++
+ drivers/net/phy/mediatek/Makefile             |  3 +++
+ .../mtk-ge-soc.c}                             |  0
+ .../phy/{mediatek-ge.c => mediatek/mtk-ge.c}  |  0
+ 7 files changed, 29 insertions(+), 20 deletions(-)
+ create mode 100644 drivers/net/phy/mediatek/Kconfig
+ create mode 100644 drivers/net/phy/mediatek/Makefile
+ rename drivers/net/phy/{mediatek-ge-soc.c => mediatek/mtk-ge-soc.c} (100%)
+ rename drivers/net/phy/{mediatek-ge.c => mediatek/mtk-ge.c} (100%)
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -14427,8 +14427,8 @@ M:     Qingfang Deng <dqfext@gmail.com>
+ M:    SkyLake Huang <SkyLake.Huang@mediatek.com>
+ L:    netdev@vger.kernel.org
+ S:    Maintained
+-F:    drivers/net/phy/mediatek-ge-soc.c
+-F:    drivers/net/phy/mediatek-ge.c
++F:    drivers/net/phy/mediatek/mtk-ge-soc.c
++F:    drivers/net/phy/mediatek/mtk-ge.c
+ F:    drivers/phy/mediatek/phy-mtk-xfi-tphy.c
+ MEDIATEK I2C CONTROLLER DRIVER
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -266,22 +266,7 @@ config MAXLINEAR_GPHY
+         Support for the Maxlinear GPY115, GPY211, GPY212, GPY215,
+         GPY241, GPY245 PHYs.
+-config MEDIATEK_GE_PHY
+-      tristate "MediaTek Gigabit Ethernet PHYs"
+-      help
+-        Supports the MediaTek Gigabit Ethernet PHYs.
+-
+-config MEDIATEK_GE_SOC_PHY
+-      tristate "MediaTek SoC Ethernet PHYs"
+-      depends on (ARM64 && ARCH_MEDIATEK) || COMPILE_TEST
+-      depends on NVMEM_MTK_EFUSE
+-      help
+-        Supports MediaTek SoC built-in Gigabit Ethernet PHYs.
+-
+-        Include support for built-in Ethernet PHYs which are present in
+-        the MT7981 and MT7988 SoCs. These PHYs need calibration data
+-        present in the SoCs efuse and will dynamically calibrate VCM
+-        (common-mode voltage) during startup.
++source "drivers/net/phy/mediatek/Kconfig"
+ config MICREL_PHY
+       tristate "Micrel PHYs"
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -74,8 +74,7 @@ obj-$(CONFIG_MARVELL_PHY)    += marvell.o
+ obj-$(CONFIG_MARVELL_88Q2XXX_PHY)     += marvell-88q2xxx.o
+ obj-$(CONFIG_MARVELL_88X2222_PHY)     += marvell-88x2222.o
+ obj-$(CONFIG_MAXLINEAR_GPHY)  += mxl-gpy.o
+-obj-$(CONFIG_MEDIATEK_GE_PHY) += mediatek-ge.o
+-obj-$(CONFIG_MEDIATEK_GE_SOC_PHY)     += mediatek-ge-soc.o
++obj-y                         += mediatek/
+ obj-$(CONFIG_MESON_GXL_PHY)   += meson-gxl.o
+ obj-$(CONFIG_MICREL_KS8995MA) += spi_ks8995.o
+ obj-$(CONFIG_MICREL_PHY)      += micrel.o
+--- /dev/null
++++ b/drivers/net/phy/mediatek/Kconfig
+@@ -0,0 +1,22 @@
++# SPDX-License-Identifier: GPL-2.0-only
++config MEDIATEK_GE_PHY
++      tristate "MediaTek Gigabit Ethernet PHYs"
++      help
++        Supports the MediaTek non-built-in Gigabit Ethernet PHYs.
++
++        Non-built-in Gigabit Ethernet PHYs include mt7530/mt7531.
++        You may find mt7530 inside mt7621. This driver shares some
++        common operations with MediaTek SoC built-in Gigabit
++        Ethernet PHYs.
++
++config MEDIATEK_GE_SOC_PHY
++      tristate "MediaTek SoC Ethernet PHYs"
++      depends on (ARM64 && ARCH_MEDIATEK) || COMPILE_TEST
++      depends on NVMEM_MTK_EFUSE
++      help
++        Supports MediaTek SoC built-in Gigabit Ethernet PHYs.
++
++        Include support for built-in Ethernet PHYs which are present in
++        the MT7981 and MT7988 SoCs. These PHYs need calibration data
++        present in the SoCs efuse and will dynamically calibrate VCM
++        (common-mode voltage) during startup.
+--- /dev/null
++++ b/drivers/net/phy/mediatek/Makefile
+@@ -0,0 +1,3 @@
++# SPDX-License-Identifier: GPL-2.0
++obj-$(CONFIG_MEDIATEK_GE_PHY)         += mtk-ge.o
++obj-$(CONFIG_MEDIATEK_GE_SOC_PHY)     += mtk-ge-soc.o
+--- a/drivers/net/phy/mediatek-ge-soc.c
++++ /dev/null
+@@ -1,1610 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0+
+-#include <linux/bitfield.h>
+-#include <linux/bitmap.h>
+-#include <linux/mfd/syscon.h>
+-#include <linux/module.h>
+-#include <linux/nvmem-consumer.h>
+-#include <linux/pinctrl/consumer.h>
+-#include <linux/phy.h>
+-#include <linux/regmap.h>
+-
+-#define MTK_GPHY_ID_MT7981                    0x03a29461
+-#define MTK_GPHY_ID_MT7988                    0x03a29481
+-
+-#define MTK_EXT_PAGE_ACCESS                   0x1f
+-#define MTK_PHY_PAGE_STANDARD                 0x0000
+-#define MTK_PHY_PAGE_EXTENDED_3                       0x0003
+-
+-#define MTK_PHY_LPI_REG_14                    0x14
+-#define MTK_PHY_LPI_WAKE_TIMER_1000_MASK      GENMASK(8, 0)
+-
+-#define MTK_PHY_LPI_REG_1c                    0x1c
+-#define MTK_PHY_SMI_DET_ON_THRESH_MASK                GENMASK(13, 8)
+-
+-#define MTK_PHY_PAGE_EXTENDED_2A30            0x2a30
+-#define MTK_PHY_PAGE_EXTENDED_52B5            0x52b5
+-
+-#define ANALOG_INTERNAL_OPERATION_MAX_US      20
+-#define TXRESERVE_MIN                         0
+-#define TXRESERVE_MAX                         7
+-
+-#define MTK_PHY_ANARG_RG                      0x10
+-#define   MTK_PHY_TCLKOFFSET_MASK             GENMASK(12, 8)
+-
+-/* Registers on MDIO_MMD_VEND1 */
+-#define MTK_PHY_TXVLD_DA_RG                   0x12
+-#define   MTK_PHY_DA_TX_I2MPB_A_GBE_MASK      GENMASK(15, 10)
+-#define   MTK_PHY_DA_TX_I2MPB_A_TBT_MASK      GENMASK(5, 0)
+-
+-#define MTK_PHY_TX_I2MPB_TEST_MODE_A2         0x16
+-#define   MTK_PHY_DA_TX_I2MPB_A_HBT_MASK      GENMASK(15, 10)
+-#define   MTK_PHY_DA_TX_I2MPB_A_TST_MASK      GENMASK(5, 0)
+-
+-#define MTK_PHY_TX_I2MPB_TEST_MODE_B1         0x17
+-#define   MTK_PHY_DA_TX_I2MPB_B_GBE_MASK      GENMASK(13, 8)
+-#define   MTK_PHY_DA_TX_I2MPB_B_TBT_MASK      GENMASK(5, 0)
+-
+-#define MTK_PHY_TX_I2MPB_TEST_MODE_B2         0x18
+-#define   MTK_PHY_DA_TX_I2MPB_B_HBT_MASK      GENMASK(13, 8)
+-#define   MTK_PHY_DA_TX_I2MPB_B_TST_MASK      GENMASK(5, 0)
+-
+-#define MTK_PHY_TX_I2MPB_TEST_MODE_C1         0x19
+-#define   MTK_PHY_DA_TX_I2MPB_C_GBE_MASK      GENMASK(13, 8)
+-#define   MTK_PHY_DA_TX_I2MPB_C_TBT_MASK      GENMASK(5, 0)
+-
+-#define MTK_PHY_TX_I2MPB_TEST_MODE_C2         0x20
+-#define   MTK_PHY_DA_TX_I2MPB_C_HBT_MASK      GENMASK(13, 8)
+-#define   MTK_PHY_DA_TX_I2MPB_C_TST_MASK      GENMASK(5, 0)
+-
+-#define MTK_PHY_TX_I2MPB_TEST_MODE_D1         0x21
+-#define   MTK_PHY_DA_TX_I2MPB_D_GBE_MASK      GENMASK(13, 8)
+-#define   MTK_PHY_DA_TX_I2MPB_D_TBT_MASK      GENMASK(5, 0)
+-
+-#define MTK_PHY_TX_I2MPB_TEST_MODE_D2         0x22
+-#define   MTK_PHY_DA_TX_I2MPB_D_HBT_MASK      GENMASK(13, 8)
+-#define   MTK_PHY_DA_TX_I2MPB_D_TST_MASK      GENMASK(5, 0)
+-
+-#define MTK_PHY_RXADC_CTRL_RG7                        0xc6
+-#define   MTK_PHY_DA_AD_BUF_BIAS_LP_MASK      GENMASK(9, 8)
+-
+-#define MTK_PHY_RXADC_CTRL_RG9                        0xc8
+-#define   MTK_PHY_DA_RX_PSBN_TBT_MASK         GENMASK(14, 12)
+-#define   MTK_PHY_DA_RX_PSBN_HBT_MASK         GENMASK(10, 8)
+-#define   MTK_PHY_DA_RX_PSBN_GBE_MASK         GENMASK(6, 4)
+-#define   MTK_PHY_DA_RX_PSBN_LP_MASK          GENMASK(2, 0)
+-
+-#define MTK_PHY_LDO_OUTPUT_V                  0xd7
+-
+-#define MTK_PHY_RG_ANA_CAL_RG0                        0xdb
+-#define   MTK_PHY_RG_CAL_CKINV                        BIT(12)
+-#define   MTK_PHY_RG_ANA_CALEN                        BIT(8)
+-#define   MTK_PHY_RG_ZCALEN_A                 BIT(0)
+-
+-#define MTK_PHY_RG_ANA_CAL_RG1                        0xdc
+-#define   MTK_PHY_RG_ZCALEN_B                 BIT(12)
+-#define   MTK_PHY_RG_ZCALEN_C                 BIT(8)
+-#define   MTK_PHY_RG_ZCALEN_D                 BIT(4)
+-#define   MTK_PHY_RG_TXVOS_CALEN              BIT(0)
+-
+-#define MTK_PHY_RG_ANA_CAL_RG5                        0xe0
+-#define   MTK_PHY_RG_REXT_TRIM_MASK           GENMASK(13, 8)
+-
+-#define MTK_PHY_RG_TX_FILTER                  0xfe
+-
+-#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG120    0x120
+-#define   MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK       GENMASK(12, 8)
+-#define   MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK       GENMASK(4, 0)
+-
+-#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122    0x122
+-#define   MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK     GENMASK(7, 0)
+-
+-#define MTK_PHY_RG_TESTMUX_ADC_CTRL           0x144
+-#define   MTK_PHY_RG_TXEN_DIG_MASK            GENMASK(5, 5)
+-
+-#define MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B               0x172
+-#define   MTK_PHY_CR_TX_AMP_OFFSET_A_MASK     GENMASK(13, 8)
+-#define   MTK_PHY_CR_TX_AMP_OFFSET_B_MASK     GENMASK(6, 0)
+-
+-#define MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D               0x173
+-#define   MTK_PHY_CR_TX_AMP_OFFSET_C_MASK     GENMASK(13, 8)
+-#define   MTK_PHY_CR_TX_AMP_OFFSET_D_MASK     GENMASK(6, 0)
+-
+-#define MTK_PHY_RG_AD_CAL_COMP                        0x17a
+-#define   MTK_PHY_AD_CAL_COMP_OUT_MASK                GENMASK(8, 8)
+-
+-#define MTK_PHY_RG_AD_CAL_CLK                 0x17b
+-#define   MTK_PHY_DA_CAL_CLK                  BIT(0)
+-
+-#define MTK_PHY_RG_AD_CALIN                   0x17c
+-#define   MTK_PHY_DA_CALIN_FLAG                       BIT(0)
+-
+-#define MTK_PHY_RG_DASN_DAC_IN0_A             0x17d
+-#define   MTK_PHY_DASN_DAC_IN0_A_MASK         GENMASK(9, 0)
+-
+-#define MTK_PHY_RG_DASN_DAC_IN0_B             0x17e
+-#define   MTK_PHY_DASN_DAC_IN0_B_MASK         GENMASK(9, 0)
+-
+-#define MTK_PHY_RG_DASN_DAC_IN0_C             0x17f
+-#define   MTK_PHY_DASN_DAC_IN0_C_MASK         GENMASK(9, 0)
+-
+-#define MTK_PHY_RG_DASN_DAC_IN0_D             0x180
+-#define   MTK_PHY_DASN_DAC_IN0_D_MASK         GENMASK(9, 0)
+-
+-#define MTK_PHY_RG_DASN_DAC_IN1_A             0x181
+-#define   MTK_PHY_DASN_DAC_IN1_A_MASK         GENMASK(9, 0)
+-
+-#define MTK_PHY_RG_DASN_DAC_IN1_B             0x182
+-#define   MTK_PHY_DASN_DAC_IN1_B_MASK         GENMASK(9, 0)
+-
+-#define MTK_PHY_RG_DASN_DAC_IN1_C             0x183
+-#define   MTK_PHY_DASN_DAC_IN1_C_MASK         GENMASK(9, 0)
+-
+-#define MTK_PHY_RG_DASN_DAC_IN1_D             0x184
+-#define   MTK_PHY_DASN_DAC_IN1_D_MASK         GENMASK(9, 0)
+-
+-#define MTK_PHY_RG_DEV1E_REG19b                       0x19b
+-#define   MTK_PHY_BYPASS_DSP_LPI_READY                BIT(8)
+-
+-#define MTK_PHY_RG_LP_IIR2_K1_L                       0x22a
+-#define MTK_PHY_RG_LP_IIR2_K1_U                       0x22b
+-#define MTK_PHY_RG_LP_IIR2_K2_L                       0x22c
+-#define MTK_PHY_RG_LP_IIR2_K2_U                       0x22d
+-#define MTK_PHY_RG_LP_IIR2_K3_L                       0x22e
+-#define MTK_PHY_RG_LP_IIR2_K3_U                       0x22f
+-#define MTK_PHY_RG_LP_IIR2_K4_L                       0x230
+-#define MTK_PHY_RG_LP_IIR2_K4_U                       0x231
+-#define MTK_PHY_RG_LP_IIR2_K5_L                       0x232
+-#define MTK_PHY_RG_LP_IIR2_K5_U                       0x233
+-
+-#define MTK_PHY_RG_DEV1E_REG234                       0x234
+-#define   MTK_PHY_TR_OPEN_LOOP_EN_MASK                GENMASK(0, 0)
+-#define   MTK_PHY_LPF_X_AVERAGE_MASK          GENMASK(7, 4)
+-#define   MTK_PHY_TR_LP_IIR_EEE_EN            BIT(12)
+-
+-#define MTK_PHY_RG_LPF_CNT_VAL                        0x235
+-
+-#define MTK_PHY_RG_DEV1E_REG238                       0x238
+-#define   MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK  GENMASK(8, 0)
+-#define   MTK_PHY_LPI_SLV_SEND_TX_EN          BIT(12)
+-
+-#define MTK_PHY_RG_DEV1E_REG239                       0x239
+-#define   MTK_PHY_LPI_SEND_LOC_TIMER_MASK     GENMASK(8, 0)
+-#define   MTK_PHY_LPI_TXPCS_LOC_RCV           BIT(12)
+-
+-#define MTK_PHY_RG_DEV1E_REG27C                       0x27c
+-#define   MTK_PHY_VGASTATE_FFE_THR_ST1_MASK   GENMASK(12, 8)
+-#define MTK_PHY_RG_DEV1E_REG27D                       0x27d
+-#define   MTK_PHY_VGASTATE_FFE_THR_ST2_MASK   GENMASK(4, 0)
+-
+-#define MTK_PHY_RG_DEV1E_REG2C7                       0x2c7
+-#define   MTK_PHY_MAX_GAIN_MASK                       GENMASK(4, 0)
+-#define   MTK_PHY_MIN_GAIN_MASK                       GENMASK(12, 8)
+-
+-#define MTK_PHY_RG_DEV1E_REG2D1                       0x2d1
+-#define   MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK        GENMASK(7, 0)
+-#define   MTK_PHY_LPI_SKIP_SD_SLV_TR          BIT(8)
+-#define   MTK_PHY_LPI_TR_READY                        BIT(9)
+-#define   MTK_PHY_LPI_VCO_EEE_STG0_EN         BIT(10)
+-
+-#define MTK_PHY_RG_DEV1E_REG323                       0x323
+-#define   MTK_PHY_EEE_WAKE_MAS_INT_DC         BIT(0)
+-#define   MTK_PHY_EEE_WAKE_SLV_INT_DC         BIT(4)
+-
+-#define MTK_PHY_RG_DEV1E_REG324                       0x324
+-#define   MTK_PHY_SMI_DETCNT_MAX_MASK         GENMASK(5, 0)
+-#define   MTK_PHY_SMI_DET_MAX_EN              BIT(8)
+-
+-#define MTK_PHY_RG_DEV1E_REG326                       0x326
+-#define   MTK_PHY_LPI_MODE_SD_ON              BIT(0)
+-#define   MTK_PHY_RESET_RANDUPD_CNT           BIT(1)
+-#define   MTK_PHY_TREC_UPDATE_ENAB_CLR                BIT(2)
+-#define   MTK_PHY_LPI_QUIT_WAIT_DFE_SIG_DET_OFF       BIT(4)
+-#define   MTK_PHY_TR_READY_SKIP_AFE_WAKEUP    BIT(5)
+-
+-#define MTK_PHY_LDO_PUMP_EN_PAIRAB            0x502
+-#define MTK_PHY_LDO_PUMP_EN_PAIRCD            0x503
+-
+-#define MTK_PHY_DA_TX_R50_PAIR_A              0x53d
+-#define MTK_PHY_DA_TX_R50_PAIR_B              0x53e
+-#define MTK_PHY_DA_TX_R50_PAIR_C              0x53f
+-#define MTK_PHY_DA_TX_R50_PAIR_D              0x540
+-
+-/* Registers on MDIO_MMD_VEND2 */
+-#define MTK_PHY_LED0_ON_CTRL                  0x24
+-#define MTK_PHY_LED1_ON_CTRL                  0x26
+-#define   MTK_PHY_LED_ON_MASK                 GENMASK(6, 0)
+-#define   MTK_PHY_LED_ON_LINK1000             BIT(0)
+-#define   MTK_PHY_LED_ON_LINK100              BIT(1)
+-#define   MTK_PHY_LED_ON_LINK10                       BIT(2)
+-#define   MTK_PHY_LED_ON_LINK                 (MTK_PHY_LED_ON_LINK10 |\
+-                                               MTK_PHY_LED_ON_LINK100 |\
+-                                               MTK_PHY_LED_ON_LINK1000)
+-#define   MTK_PHY_LED_ON_LINKDOWN             BIT(3)
+-#define   MTK_PHY_LED_ON_FDX                  BIT(4) /* Full duplex */
+-#define   MTK_PHY_LED_ON_HDX                  BIT(5) /* Half duplex */
+-#define   MTK_PHY_LED_ON_FORCE_ON             BIT(6)
+-#define   MTK_PHY_LED_ON_POLARITY             BIT(14)
+-#define   MTK_PHY_LED_ON_ENABLE                       BIT(15)
+-
+-#define MTK_PHY_LED0_BLINK_CTRL                       0x25
+-#define MTK_PHY_LED1_BLINK_CTRL                       0x27
+-#define   MTK_PHY_LED_BLINK_1000TX            BIT(0)
+-#define   MTK_PHY_LED_BLINK_1000RX            BIT(1)
+-#define   MTK_PHY_LED_BLINK_100TX             BIT(2)
+-#define   MTK_PHY_LED_BLINK_100RX             BIT(3)
+-#define   MTK_PHY_LED_BLINK_10TX              BIT(4)
+-#define   MTK_PHY_LED_BLINK_10RX              BIT(5)
+-#define   MTK_PHY_LED_BLINK_RX                        (MTK_PHY_LED_BLINK_10RX |\
+-                                               MTK_PHY_LED_BLINK_100RX |\
+-                                               MTK_PHY_LED_BLINK_1000RX)
+-#define   MTK_PHY_LED_BLINK_TX                        (MTK_PHY_LED_BLINK_10TX |\
+-                                               MTK_PHY_LED_BLINK_100TX |\
+-                                               MTK_PHY_LED_BLINK_1000TX)
+-#define   MTK_PHY_LED_BLINK_COLLISION         BIT(6)
+-#define   MTK_PHY_LED_BLINK_RX_CRC_ERR                BIT(7)
+-#define   MTK_PHY_LED_BLINK_RX_IDLE_ERR               BIT(8)
+-#define   MTK_PHY_LED_BLINK_FORCE_BLINK               BIT(9)
+-
+-#define MTK_PHY_LED1_DEFAULT_POLARITIES               BIT(1)
+-
+-#define MTK_PHY_RG_BG_RASEL                   0x115
+-#define   MTK_PHY_RG_BG_RASEL_MASK            GENMASK(2, 0)
+-
+-/* 'boottrap' register reflecting the configuration of the 4 PHY LEDs */
+-#define RG_GPIO_MISC_TPBANK0                  0x6f0
+-#define   RG_GPIO_MISC_TPBANK0_BOOTMODE               GENMASK(11, 8)
+-
+-/* These macro privides efuse parsing for internal phy. */
+-#define EFS_DA_TX_I2MPB_A(x)                  (((x) >> 0) & GENMASK(5, 0))
+-#define EFS_DA_TX_I2MPB_B(x)                  (((x) >> 6) & GENMASK(5, 0))
+-#define EFS_DA_TX_I2MPB_C(x)                  (((x) >> 12) & GENMASK(5, 0))
+-#define EFS_DA_TX_I2MPB_D(x)                  (((x) >> 18) & GENMASK(5, 0))
+-#define EFS_DA_TX_AMP_OFFSET_A(x)             (((x) >> 24) & GENMASK(5, 0))
+-
+-#define EFS_DA_TX_AMP_OFFSET_B(x)             (((x) >> 0) & GENMASK(5, 0))
+-#define EFS_DA_TX_AMP_OFFSET_C(x)             (((x) >> 6) & GENMASK(5, 0))
+-#define EFS_DA_TX_AMP_OFFSET_D(x)             (((x) >> 12) & GENMASK(5, 0))
+-#define EFS_DA_TX_R50_A(x)                    (((x) >> 18) & GENMASK(5, 0))
+-#define EFS_DA_TX_R50_B(x)                    (((x) >> 24) & GENMASK(5, 0))
+-
+-#define EFS_DA_TX_R50_C(x)                    (((x) >> 0) & GENMASK(5, 0))
+-#define EFS_DA_TX_R50_D(x)                    (((x) >> 6) & GENMASK(5, 0))
+-
+-#define EFS_RG_BG_RASEL(x)                    (((x) >> 4) & GENMASK(2, 0))
+-#define EFS_RG_REXT_TRIM(x)                   (((x) >> 7) & GENMASK(5, 0))
+-
+-enum {
+-      NO_PAIR,
+-      PAIR_A,
+-      PAIR_B,
+-      PAIR_C,
+-      PAIR_D,
+-};
+-
+-enum calibration_mode {
+-      EFUSE_K,
+-      SW_K
+-};
+-
+-enum CAL_ITEM {
+-      REXT,
+-      TX_OFFSET,
+-      TX_AMP,
+-      TX_R50,
+-      TX_VCM
+-};
+-
+-enum CAL_MODE {
+-      EFUSE_M,
+-      SW_M
+-};
+-
+-#define MTK_PHY_LED_STATE_FORCE_ON    0
+-#define MTK_PHY_LED_STATE_FORCE_BLINK 1
+-#define MTK_PHY_LED_STATE_NETDEV      2
+-
+-struct mtk_socphy_priv {
+-      unsigned long           led_state;
+-};
+-
+-struct mtk_socphy_shared {
+-      u32                     boottrap;
+-      struct mtk_socphy_priv  priv[4];
+-};
+-
+-static int mtk_socphy_read_page(struct phy_device *phydev)
+-{
+-      return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
+-}
+-
+-static int mtk_socphy_write_page(struct phy_device *phydev, int page)
+-{
+-      return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page);
+-}
+-
+-/* One calibration cycle consists of:
+- * 1.Set DA_CALIN_FLAG high to start calibration. Keep it high
+- *   until AD_CAL_COMP is ready to output calibration result.
+- * 2.Wait until DA_CAL_CLK is available.
+- * 3.Fetch AD_CAL_COMP_OUT.
+- */
+-static int cal_cycle(struct phy_device *phydev, int devad,
+-                   u32 regnum, u16 mask, u16 cal_val)
+-{
+-      int reg_val;
+-      int ret;
+-
+-      phy_modify_mmd(phydev, devad, regnum,
+-                     mask, cal_val);
+-      phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CALIN,
+-                       MTK_PHY_DA_CALIN_FLAG);
+-
+-      ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+-                                      MTK_PHY_RG_AD_CAL_CLK, reg_val,
+-                                      reg_val & MTK_PHY_DA_CAL_CLK, 500,
+-                                      ANALOG_INTERNAL_OPERATION_MAX_US,
+-                                      false);
+-      if (ret) {
+-              phydev_err(phydev, "Calibration cycle timeout\n");
+-              return ret;
+-      }
+-
+-      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CALIN,
+-                         MTK_PHY_DA_CALIN_FLAG);
+-      ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CAL_COMP);
+-      if (ret < 0)
+-              return ret;
+-      ret = FIELD_GET(MTK_PHY_AD_CAL_COMP_OUT_MASK, ret);
+-      phydev_dbg(phydev, "cal_val: 0x%x, ret: %d\n", cal_val, ret);
+-
+-      return ret;
+-}
+-
+-static int rext_fill_result(struct phy_device *phydev, u16 *buf)
+-{
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5,
+-                     MTK_PHY_RG_REXT_TRIM_MASK, buf[0] << 8);
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_RG_BG_RASEL,
+-                     MTK_PHY_RG_BG_RASEL_MASK, buf[1]);
+-
+-      return 0;
+-}
+-
+-static int rext_cal_efuse(struct phy_device *phydev, u32 *buf)
+-{
+-      u16 rext_cal_val[2];
+-
+-      rext_cal_val[0] = EFS_RG_REXT_TRIM(buf[3]);
+-      rext_cal_val[1] = EFS_RG_BG_RASEL(buf[3]);
+-      rext_fill_result(phydev, rext_cal_val);
+-
+-      return 0;
+-}
+-
+-static int tx_offset_fill_result(struct phy_device *phydev, u16 *buf)
+-{
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B,
+-                     MTK_PHY_CR_TX_AMP_OFFSET_A_MASK, buf[0] << 8);
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B,
+-                     MTK_PHY_CR_TX_AMP_OFFSET_B_MASK, buf[1]);
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D,
+-                     MTK_PHY_CR_TX_AMP_OFFSET_C_MASK, buf[2] << 8);
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D,
+-                     MTK_PHY_CR_TX_AMP_OFFSET_D_MASK, buf[3]);
+-
+-      return 0;
+-}
+-
+-static int tx_offset_cal_efuse(struct phy_device *phydev, u32 *buf)
+-{
+-      u16 tx_offset_cal_val[4];
+-
+-      tx_offset_cal_val[0] = EFS_DA_TX_AMP_OFFSET_A(buf[0]);
+-      tx_offset_cal_val[1] = EFS_DA_TX_AMP_OFFSET_B(buf[1]);
+-      tx_offset_cal_val[2] = EFS_DA_TX_AMP_OFFSET_C(buf[1]);
+-      tx_offset_cal_val[3] = EFS_DA_TX_AMP_OFFSET_D(buf[1]);
+-
+-      tx_offset_fill_result(phydev, tx_offset_cal_val);
+-
+-      return 0;
+-}
+-
+-static int tx_amp_fill_result(struct phy_device *phydev, u16 *buf)
+-{
+-      const int vals_9481[16] = { 10, 6, 6, 10,
+-                                  10, 6, 6, 10,
+-                                  10, 6, 6, 10,
+-                                  10, 6, 6, 10 };
+-      const int vals_9461[16] = { 7, 1, 4, 7,
+-                                  7, 1, 4, 7,
+-                                  7, 1, 4, 7,
+-                                  7, 1, 4, 7 };
+-      int bias[16] = {};
+-      int i;
+-
+-      switch (phydev->drv->phy_id) {
+-      case MTK_GPHY_ID_MT7981:
+-              /* We add some calibration to efuse values
+-               * due to board level influence.
+-               * GBE: +7, TBT: +1, HBT: +4, TST: +7
+-               */
+-              memcpy(bias, (const void *)vals_9461, sizeof(bias));
+-              break;
+-      case MTK_GPHY_ID_MT7988:
+-              memcpy(bias, (const void *)vals_9481, sizeof(bias));
+-              break;
+-      }
+-
+-      /* Prevent overflow */
+-      for (i = 0; i < 12; i++) {
+-              if (buf[i >> 2] + bias[i] > 63) {
+-                      buf[i >> 2] = 63;
+-                      bias[i] = 0;
+-              }
+-      }
+-
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TXVLD_DA_RG,
+-                     MTK_PHY_DA_TX_I2MPB_A_GBE_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_GBE_MASK,
+-                                buf[0] + bias[0]));
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TXVLD_DA_RG,
+-                     MTK_PHY_DA_TX_I2MPB_A_TBT_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_TBT_MASK,
+-                                buf[0] + bias[1]));
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_A2,
+-                     MTK_PHY_DA_TX_I2MPB_A_HBT_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_HBT_MASK,
+-                                buf[0] + bias[2]));
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_A2,
+-                     MTK_PHY_DA_TX_I2MPB_A_TST_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_TST_MASK,
+-                                buf[0] + bias[3]));
+-
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B1,
+-                     MTK_PHY_DA_TX_I2MPB_B_GBE_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_GBE_MASK,
+-                                buf[1] + bias[4]));
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B1,
+-                     MTK_PHY_DA_TX_I2MPB_B_TBT_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_TBT_MASK,
+-                                buf[1] + bias[5]));
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B2,
+-                     MTK_PHY_DA_TX_I2MPB_B_HBT_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_HBT_MASK,
+-                                buf[1] + bias[6]));
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B2,
+-                     MTK_PHY_DA_TX_I2MPB_B_TST_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_TST_MASK,
+-                                buf[1] + bias[7]));
+-
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C1,
+-                     MTK_PHY_DA_TX_I2MPB_C_GBE_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_GBE_MASK,
+-                                buf[2] + bias[8]));
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C1,
+-                     MTK_PHY_DA_TX_I2MPB_C_TBT_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_TBT_MASK,
+-                                buf[2] + bias[9]));
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C2,
+-                     MTK_PHY_DA_TX_I2MPB_C_HBT_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_HBT_MASK,
+-                                buf[2] + bias[10]));
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C2,
+-                     MTK_PHY_DA_TX_I2MPB_C_TST_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_TST_MASK,
+-                                buf[2] + bias[11]));
+-
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D1,
+-                     MTK_PHY_DA_TX_I2MPB_D_GBE_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_GBE_MASK,
+-                                buf[3] + bias[12]));
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D1,
+-                     MTK_PHY_DA_TX_I2MPB_D_TBT_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_TBT_MASK,
+-                                buf[3] + bias[13]));
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D2,
+-                     MTK_PHY_DA_TX_I2MPB_D_HBT_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_HBT_MASK,
+-                                buf[3] + bias[14]));
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D2,
+-                     MTK_PHY_DA_TX_I2MPB_D_TST_MASK,
+-                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_TST_MASK,
+-                                buf[3] + bias[15]));
+-
+-      return 0;
+-}
+-
+-static int tx_amp_cal_efuse(struct phy_device *phydev, u32 *buf)
+-{
+-      u16 tx_amp_cal_val[4];
+-
+-      tx_amp_cal_val[0] = EFS_DA_TX_I2MPB_A(buf[0]);
+-      tx_amp_cal_val[1] = EFS_DA_TX_I2MPB_B(buf[0]);
+-      tx_amp_cal_val[2] = EFS_DA_TX_I2MPB_C(buf[0]);
+-      tx_amp_cal_val[3] = EFS_DA_TX_I2MPB_D(buf[0]);
+-      tx_amp_fill_result(phydev, tx_amp_cal_val);
+-
+-      return 0;
+-}
+-
+-static int tx_r50_fill_result(struct phy_device *phydev, u16 tx_r50_cal_val,
+-                            u8 txg_calen_x)
+-{
+-      int bias = 0;
+-      u16 reg, val;
+-
+-      if (phydev->drv->phy_id == MTK_GPHY_ID_MT7988)
+-              bias = -1;
+-
+-      val = clamp_val(bias + tx_r50_cal_val, 0, 63);
+-
+-      switch (txg_calen_x) {
+-      case PAIR_A:
+-              reg = MTK_PHY_DA_TX_R50_PAIR_A;
+-              break;
+-      case PAIR_B:
+-              reg = MTK_PHY_DA_TX_R50_PAIR_B;
+-              break;
+-      case PAIR_C:
+-              reg = MTK_PHY_DA_TX_R50_PAIR_C;
+-              break;
+-      case PAIR_D:
+-              reg = MTK_PHY_DA_TX_R50_PAIR_D;
+-              break;
+-      default:
+-              return -EINVAL;
+-      }
+-
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, reg, val | val << 8);
+-
+-      return 0;
+-}
+-
+-static int tx_r50_cal_efuse(struct phy_device *phydev, u32 *buf,
+-                          u8 txg_calen_x)
+-{
+-      u16 tx_r50_cal_val;
+-
+-      switch (txg_calen_x) {
+-      case PAIR_A:
+-              tx_r50_cal_val = EFS_DA_TX_R50_A(buf[1]);
+-              break;
+-      case PAIR_B:
+-              tx_r50_cal_val = EFS_DA_TX_R50_B(buf[1]);
+-              break;
+-      case PAIR_C:
+-              tx_r50_cal_val = EFS_DA_TX_R50_C(buf[2]);
+-              break;
+-      case PAIR_D:
+-              tx_r50_cal_val = EFS_DA_TX_R50_D(buf[2]);
+-              break;
+-      default:
+-              return -EINVAL;
+-      }
+-      tx_r50_fill_result(phydev, tx_r50_cal_val, txg_calen_x);
+-
+-      return 0;
+-}
+-
+-static int tx_vcm_cal_sw(struct phy_device *phydev, u8 rg_txreserve_x)
+-{
+-      u8 lower_idx, upper_idx, txreserve_val;
+-      u8 lower_ret, upper_ret;
+-      int ret;
+-
+-      phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+-                       MTK_PHY_RG_ANA_CALEN);
+-      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+-                         MTK_PHY_RG_CAL_CKINV);
+-      phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
+-                       MTK_PHY_RG_TXVOS_CALEN);
+-
+-      switch (rg_txreserve_x) {
+-      case PAIR_A:
+-              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                                 MTK_PHY_RG_DASN_DAC_IN0_A,
+-                                 MTK_PHY_DASN_DAC_IN0_A_MASK);
+-              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                                 MTK_PHY_RG_DASN_DAC_IN1_A,
+-                                 MTK_PHY_DASN_DAC_IN1_A_MASK);
+-              phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                               MTK_PHY_RG_ANA_CAL_RG0,
+-                               MTK_PHY_RG_ZCALEN_A);
+-              break;
+-      case PAIR_B:
+-              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                                 MTK_PHY_RG_DASN_DAC_IN0_B,
+-                                 MTK_PHY_DASN_DAC_IN0_B_MASK);
+-              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                                 MTK_PHY_RG_DASN_DAC_IN1_B,
+-                                 MTK_PHY_DASN_DAC_IN1_B_MASK);
+-              phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                               MTK_PHY_RG_ANA_CAL_RG1,
+-                               MTK_PHY_RG_ZCALEN_B);
+-              break;
+-      case PAIR_C:
+-              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                                 MTK_PHY_RG_DASN_DAC_IN0_C,
+-                                 MTK_PHY_DASN_DAC_IN0_C_MASK);
+-              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                                 MTK_PHY_RG_DASN_DAC_IN1_C,
+-                                 MTK_PHY_DASN_DAC_IN1_C_MASK);
+-              phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                               MTK_PHY_RG_ANA_CAL_RG1,
+-                               MTK_PHY_RG_ZCALEN_C);
+-              break;
+-      case PAIR_D:
+-              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                                 MTK_PHY_RG_DASN_DAC_IN0_D,
+-                                 MTK_PHY_DASN_DAC_IN0_D_MASK);
+-              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                                 MTK_PHY_RG_DASN_DAC_IN1_D,
+-                                 MTK_PHY_DASN_DAC_IN1_D_MASK);
+-              phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                               MTK_PHY_RG_ANA_CAL_RG1,
+-                               MTK_PHY_RG_ZCALEN_D);
+-              break;
+-      default:
+-              ret = -EINVAL;
+-              goto restore;
+-      }
+-
+-      lower_idx = TXRESERVE_MIN;
+-      upper_idx = TXRESERVE_MAX;
+-
+-      phydev_dbg(phydev, "Start TX-VCM SW cal.\n");
+-      while ((upper_idx - lower_idx) > 1) {
+-              txreserve_val = DIV_ROUND_CLOSEST(lower_idx + upper_idx, 2);
+-              ret = cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9,
+-                              MTK_PHY_DA_RX_PSBN_TBT_MASK |
+-                              MTK_PHY_DA_RX_PSBN_HBT_MASK |
+-                              MTK_PHY_DA_RX_PSBN_GBE_MASK |
+-                              MTK_PHY_DA_RX_PSBN_LP_MASK,
+-                              txreserve_val << 12 | txreserve_val << 8 |
+-                              txreserve_val << 4 | txreserve_val);
+-              if (ret == 1) {
+-                      upper_idx = txreserve_val;
+-                      upper_ret = ret;
+-              } else if (ret == 0) {
+-                      lower_idx = txreserve_val;
+-                      lower_ret = ret;
+-              } else {
+-                      goto restore;
+-              }
+-      }
+-
+-      if (lower_idx == TXRESERVE_MIN) {
+-              lower_ret = cal_cycle(phydev, MDIO_MMD_VEND1,
+-                                    MTK_PHY_RXADC_CTRL_RG9,
+-                                    MTK_PHY_DA_RX_PSBN_TBT_MASK |
+-                                    MTK_PHY_DA_RX_PSBN_HBT_MASK |
+-                                    MTK_PHY_DA_RX_PSBN_GBE_MASK |
+-                                    MTK_PHY_DA_RX_PSBN_LP_MASK,
+-                                    lower_idx << 12 | lower_idx << 8 |
+-                                    lower_idx << 4 | lower_idx);
+-              ret = lower_ret;
+-      } else if (upper_idx == TXRESERVE_MAX) {
+-              upper_ret = cal_cycle(phydev, MDIO_MMD_VEND1,
+-                                    MTK_PHY_RXADC_CTRL_RG9,
+-                                    MTK_PHY_DA_RX_PSBN_TBT_MASK |
+-                                    MTK_PHY_DA_RX_PSBN_HBT_MASK |
+-                                    MTK_PHY_DA_RX_PSBN_GBE_MASK |
+-                                    MTK_PHY_DA_RX_PSBN_LP_MASK,
+-                                    upper_idx << 12 | upper_idx << 8 |
+-                                    upper_idx << 4 | upper_idx);
+-              ret = upper_ret;
+-      }
+-      if (ret < 0)
+-              goto restore;
+-
+-      /* We calibrate TX-VCM in different logic. Check upper index and then
+-       * lower index. If this calibration is valid, apply lower index's
+-       * result.
+-       */
+-      ret = upper_ret - lower_ret;
+-      if (ret == 1) {
+-              ret = 0;
+-              /* Make sure we use upper_idx in our calibration system */
+-              cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9,
+-                        MTK_PHY_DA_RX_PSBN_TBT_MASK |
+-                        MTK_PHY_DA_RX_PSBN_HBT_MASK |
+-                        MTK_PHY_DA_RX_PSBN_GBE_MASK |
+-                        MTK_PHY_DA_RX_PSBN_LP_MASK,
+-                        upper_idx << 12 | upper_idx << 8 |
+-                        upper_idx << 4 | upper_idx);
+-              phydev_dbg(phydev, "TX-VCM SW cal result: 0x%x\n", upper_idx);
+-      } else if (lower_idx == TXRESERVE_MIN && upper_ret == 1 &&
+-                 lower_ret == 1) {
+-              ret = 0;
+-              cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9,
+-                        MTK_PHY_DA_RX_PSBN_TBT_MASK |
+-                        MTK_PHY_DA_RX_PSBN_HBT_MASK |
+-                        MTK_PHY_DA_RX_PSBN_GBE_MASK |
+-                        MTK_PHY_DA_RX_PSBN_LP_MASK,
+-                        lower_idx << 12 | lower_idx << 8 |
+-                        lower_idx << 4 | lower_idx);
+-              phydev_warn(phydev, "TX-VCM SW cal result at low margin 0x%x\n",
+-                          lower_idx);
+-      } else if (upper_idx == TXRESERVE_MAX && upper_ret == 0 &&
+-                 lower_ret == 0) {
+-              ret = 0;
+-              phydev_warn(phydev,
+-                          "TX-VCM SW cal result at high margin 0x%x\n",
+-                          upper_idx);
+-      } else {
+-              ret = -EINVAL;
+-      }
+-
+-restore:
+-      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+-                         MTK_PHY_RG_ANA_CALEN);
+-      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
+-                         MTK_PHY_RG_TXVOS_CALEN);
+-      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+-                         MTK_PHY_RG_ZCALEN_A);
+-      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
+-                         MTK_PHY_RG_ZCALEN_B | MTK_PHY_RG_ZCALEN_C |
+-                         MTK_PHY_RG_ZCALEN_D);
+-
+-      return ret;
+-}
+-
+-static void mt798x_phy_common_finetune(struct phy_device *phydev)
+-{
+-      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+-      /* SlvDSPreadyTime = 24, MasDSPreadyTime = 24 */
+-      __phy_write(phydev, 0x11, 0xc71);
+-      __phy_write(phydev, 0x12, 0xc);
+-      __phy_write(phydev, 0x10, 0x8fae);
+-
+-      /* EnabRandUpdTrig = 1 */
+-      __phy_write(phydev, 0x11, 0x2f00);
+-      __phy_write(phydev, 0x12, 0xe);
+-      __phy_write(phydev, 0x10, 0x8fb0);
+-
+-      /* NormMseLoThresh = 85 */
+-      __phy_write(phydev, 0x11, 0x55a0);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x83aa);
+-
+-      /* FfeUpdGainForce = 1(Enable), FfeUpdGainForceVal = 4 */
+-      __phy_write(phydev, 0x11, 0x240);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x9680);
+-
+-      /* TrFreeze = 0 (mt7988 default) */
+-      __phy_write(phydev, 0x11, 0x0);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x9686);
+-
+-      /* SSTrKp100 = 5 */
+-      /* SSTrKf100 = 6 */
+-      /* SSTrKp1000Mas = 5 */
+-      /* SSTrKf1000Mas = 6 */
+-      /* SSTrKp1000Slv = 5 */
+-      /* SSTrKf1000Slv = 6 */
+-      __phy_write(phydev, 0x11, 0xbaef);
+-      __phy_write(phydev, 0x12, 0x2e);
+-      __phy_write(phydev, 0x10, 0x968c);
+-      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+-}
+-
+-static void mt7981_phy_finetune(struct phy_device *phydev)
+-{
+-      u16 val[8] = { 0x01ce, 0x01c1,
+-                     0x020f, 0x0202,
+-                     0x03d0, 0x03c0,
+-                     0x0013, 0x0005 };
+-      int i, k;
+-
+-      /* 100M eye finetune:
+-       * Keep middle level of TX MLT3 shapper as default.
+-       * Only change TX MLT3 overshoot level here.
+-       */
+-      for (k = 0, i = 1; i < 12; i++) {
+-              if (i % 3 == 0)
+-                      continue;
+-              phy_write_mmd(phydev, MDIO_MMD_VEND1, i, val[k++]);
+-      }
+-
+-      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+-      /* ResetSyncOffset = 6 */
+-      __phy_write(phydev, 0x11, 0x600);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x8fc0);
+-
+-      /* VgaDecRate = 1 */
+-      __phy_write(phydev, 0x11, 0x4c2a);
+-      __phy_write(phydev, 0x12, 0x3e);
+-      __phy_write(phydev, 0x10, 0x8fa4);
+-
+-      /* MrvlTrFix100Kp = 3, MrvlTrFix100Kf = 2,
+-       * MrvlTrFix1000Kp = 3, MrvlTrFix1000Kf = 2
+-       */
+-      __phy_write(phydev, 0x11, 0xd10a);
+-      __phy_write(phydev, 0x12, 0x34);
+-      __phy_write(phydev, 0x10, 0x8f82);
+-
+-      /* VcoSlicerThreshBitsHigh */
+-      __phy_write(phydev, 0x11, 0x5555);
+-      __phy_write(phydev, 0x12, 0x55);
+-      __phy_write(phydev, 0x10, 0x8ec0);
+-      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+-
+-      /* TR_OPEN_LOOP_EN = 1, lpf_x_average = 9 */
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG234,
+-                     MTK_PHY_TR_OPEN_LOOP_EN_MASK |
+-                     MTK_PHY_LPF_X_AVERAGE_MASK,
+-                     BIT(0) | FIELD_PREP(MTK_PHY_LPF_X_AVERAGE_MASK, 0x9));
+-
+-      /* rg_tr_lpf_cnt_val = 512 */
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LPF_CNT_VAL, 0x200);
+-
+-      /* IIR2 related */
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K1_L, 0x82);
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K1_U, 0x0);
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K2_L, 0x103);
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K2_U, 0x0);
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K3_L, 0x82);
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K3_U, 0x0);
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K4_L, 0xd177);
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K4_U, 0x3);
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K5_L, 0x2c82);
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K5_U, 0xe);
+-
+-      /* FFE peaking */
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG27C,
+-                     MTK_PHY_VGASTATE_FFE_THR_ST1_MASK, 0x1b << 8);
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG27D,
+-                     MTK_PHY_VGASTATE_FFE_THR_ST2_MASK, 0x1e);
+-
+-      /* Disable LDO pump */
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LDO_PUMP_EN_PAIRAB, 0x0);
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LDO_PUMP_EN_PAIRCD, 0x0);
+-      /* Adjust LDO output voltage */
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LDO_OUTPUT_V, 0x2222);
+-}
+-
+-static void mt7988_phy_finetune(struct phy_device *phydev)
+-{
+-      u16 val[12] = { 0x0187, 0x01cd, 0x01c8, 0x0182,
+-                      0x020d, 0x0206, 0x0384, 0x03d0,
+-                      0x03c6, 0x030a, 0x0011, 0x0005 };
+-      int i;
+-
+-      /* Set default MLT3 shaper first */
+-      for (i = 0; i < 12; i++)
+-              phy_write_mmd(phydev, MDIO_MMD_VEND1, i, val[i]);
+-
+-      /* TCT finetune */
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_TX_FILTER, 0x5);
+-
+-      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+-      /* ResetSyncOffset = 5 */
+-      __phy_write(phydev, 0x11, 0x500);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x8fc0);
+-
+-      /* VgaDecRate is 1 at default on mt7988 */
+-
+-      /* MrvlTrFix100Kp = 6, MrvlTrFix100Kf = 7,
+-       * MrvlTrFix1000Kp = 6, MrvlTrFix1000Kf = 7
+-       */
+-      __phy_write(phydev, 0x11, 0xb90a);
+-      __phy_write(phydev, 0x12, 0x6f);
+-      __phy_write(phydev, 0x10, 0x8f82);
+-
+-      /* RemAckCntLimitCtrl = 1 */
+-      __phy_write(phydev, 0x11, 0xfbba);
+-      __phy_write(phydev, 0x12, 0xc3);
+-      __phy_write(phydev, 0x10, 0x87f8);
+-
+-      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+-
+-      /* TR_OPEN_LOOP_EN = 1, lpf_x_average = 10 */
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG234,
+-                     MTK_PHY_TR_OPEN_LOOP_EN_MASK |
+-                     MTK_PHY_LPF_X_AVERAGE_MASK,
+-                     BIT(0) | FIELD_PREP(MTK_PHY_LPF_X_AVERAGE_MASK, 0xa));
+-
+-      /* rg_tr_lpf_cnt_val = 1023 */
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LPF_CNT_VAL, 0x3ff);
+-}
+-
+-static void mt798x_phy_eee(struct phy_device *phydev)
+-{
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+-                     MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG120,
+-                     MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK |
+-                     MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK,
+-                     FIELD_PREP(MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK, 0x0) |
+-                     FIELD_PREP(MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK, 0x14));
+-
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+-                     MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122,
+-                     MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK,
+-                     FIELD_PREP(MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK,
+-                                0xff));
+-
+-      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                         MTK_PHY_RG_TESTMUX_ADC_CTRL,
+-                         MTK_PHY_RG_TXEN_DIG_MASK);
+-
+-      phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                       MTK_PHY_RG_DEV1E_REG19b, MTK_PHY_BYPASS_DSP_LPI_READY);
+-
+-      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+-                         MTK_PHY_RG_DEV1E_REG234, MTK_PHY_TR_LP_IIR_EEE_EN);
+-
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG238,
+-                     MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK |
+-                     MTK_PHY_LPI_SLV_SEND_TX_EN,
+-                     FIELD_PREP(MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK, 0x120));
+-
+-      /* Keep MTK_PHY_LPI_SEND_LOC_TIMER as 375 */
+-      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG239,
+-                         MTK_PHY_LPI_TXPCS_LOC_RCV);
+-
+-      /* This also fixes some IoT issues, such as CH340 */
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG2C7,
+-                     MTK_PHY_MAX_GAIN_MASK | MTK_PHY_MIN_GAIN_MASK,
+-                     FIELD_PREP(MTK_PHY_MAX_GAIN_MASK, 0x8) |
+-                     FIELD_PREP(MTK_PHY_MIN_GAIN_MASK, 0x13));
+-
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG2D1,
+-                     MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK,
+-                     FIELD_PREP(MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK,
+-                                0x33) |
+-                     MTK_PHY_LPI_SKIP_SD_SLV_TR | MTK_PHY_LPI_TR_READY |
+-                     MTK_PHY_LPI_VCO_EEE_STG0_EN);
+-
+-      phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG323,
+-                       MTK_PHY_EEE_WAKE_MAS_INT_DC |
+-                       MTK_PHY_EEE_WAKE_SLV_INT_DC);
+-
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG324,
+-                     MTK_PHY_SMI_DETCNT_MAX_MASK,
+-                     FIELD_PREP(MTK_PHY_SMI_DETCNT_MAX_MASK, 0x3f) |
+-                     MTK_PHY_SMI_DET_MAX_EN);
+-
+-      phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG326,
+-                       MTK_PHY_LPI_MODE_SD_ON | MTK_PHY_RESET_RANDUPD_CNT |
+-                       MTK_PHY_TREC_UPDATE_ENAB_CLR |
+-                       MTK_PHY_LPI_QUIT_WAIT_DFE_SIG_DET_OFF |
+-                       MTK_PHY_TR_READY_SKIP_AFE_WAKEUP);
+-
+-      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+-      /* Regsigdet_sel_1000 = 0 */
+-      __phy_write(phydev, 0x11, 0xb);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x9690);
+-
+-      /* REG_EEE_st2TrKf1000 = 2 */
+-      __phy_write(phydev, 0x11, 0x114f);
+-      __phy_write(phydev, 0x12, 0x2);
+-      __phy_write(phydev, 0x10, 0x969a);
+-
+-      /* RegEEE_slv_wake_tr_timer_tar = 6, RegEEE_slv_remtx_timer_tar = 20 */
+-      __phy_write(phydev, 0x11, 0x3028);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x969e);
+-
+-      /* RegEEE_slv_wake_int_timer_tar = 8 */
+-      __phy_write(phydev, 0x11, 0x5010);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x96a0);
+-
+-      /* RegEEE_trfreeze_timer2 = 586 */
+-      __phy_write(phydev, 0x11, 0x24a);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x96a8);
+-
+-      /* RegEEE100Stg1_tar = 16 */
+-      __phy_write(phydev, 0x11, 0x3210);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x96b8);
+-
+-      /* REGEEE_wake_slv_tr_wait_dfesigdet_en = 0 */
+-      __phy_write(phydev, 0x11, 0x1463);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x96ca);
+-
+-      /* DfeTailEnableVgaThresh1000 = 27 */
+-      __phy_write(phydev, 0x11, 0x36);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x8f80);
+-      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+-
+-      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_3);
+-      __phy_modify(phydev, MTK_PHY_LPI_REG_14,
+-                   MTK_PHY_LPI_WAKE_TIMER_1000_MASK,
+-                   FIELD_PREP(MTK_PHY_LPI_WAKE_TIMER_1000_MASK, 0x19c));
+-
+-      __phy_modify(phydev, MTK_PHY_LPI_REG_1c, MTK_PHY_SMI_DET_ON_THRESH_MASK,
+-                   FIELD_PREP(MTK_PHY_SMI_DET_ON_THRESH_MASK, 0xc));
+-      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+-
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+-                     MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122,
+-                     MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK,
+-                     FIELD_PREP(MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK,
+-                                0xff));
+-}
+-
+-static int cal_sw(struct phy_device *phydev, enum CAL_ITEM cal_item,
+-                u8 start_pair, u8 end_pair)
+-{
+-      u8 pair_n;
+-      int ret;
+-
+-      for (pair_n = start_pair; pair_n <= end_pair; pair_n++) {
+-              /* TX_OFFSET & TX_AMP have no SW calibration. */
+-              switch (cal_item) {
+-              case TX_VCM:
+-                      ret = tx_vcm_cal_sw(phydev, pair_n);
+-                      break;
+-              default:
+-                      return -EINVAL;
+-              }
+-              if (ret)
+-                      return ret;
+-      }
+-      return 0;
+-}
+-
+-static int cal_efuse(struct phy_device *phydev, enum CAL_ITEM cal_item,
+-                   u8 start_pair, u8 end_pair, u32 *buf)
+-{
+-      u8 pair_n;
+-      int ret;
+-
+-      for (pair_n = start_pair; pair_n <= end_pair; pair_n++) {
+-              /* TX_VCM has no efuse calibration. */
+-              switch (cal_item) {
+-              case REXT:
+-                      ret = rext_cal_efuse(phydev, buf);
+-                      break;
+-              case TX_OFFSET:
+-                      ret = tx_offset_cal_efuse(phydev, buf);
+-                      break;
+-              case TX_AMP:
+-                      ret = tx_amp_cal_efuse(phydev, buf);
+-                      break;
+-              case TX_R50:
+-                      ret = tx_r50_cal_efuse(phydev, buf, pair_n);
+-                      break;
+-              default:
+-                      return -EINVAL;
+-              }
+-              if (ret)
+-                      return ret;
+-      }
+-
+-      return 0;
+-}
+-
+-static int start_cal(struct phy_device *phydev, enum CAL_ITEM cal_item,
+-                   enum CAL_MODE cal_mode, u8 start_pair,
+-                   u8 end_pair, u32 *buf)
+-{
+-      int ret;
+-
+-      switch (cal_mode) {
+-      case EFUSE_M:
+-              ret = cal_efuse(phydev, cal_item, start_pair,
+-                              end_pair, buf);
+-              break;
+-      case SW_M:
+-              ret = cal_sw(phydev, cal_item, start_pair, end_pair);
+-              break;
+-      default:
+-              return -EINVAL;
+-      }
+-
+-      if (ret) {
+-              phydev_err(phydev, "cal %d failed\n", cal_item);
+-              return -EIO;
+-      }
+-
+-      return 0;
+-}
+-
+-static int mt798x_phy_calibration(struct phy_device *phydev)
+-{
+-      struct nvmem_cell *cell;
+-      int ret = 0;
+-      size_t len;
+-      u32 *buf;
+-
+-      cell = nvmem_cell_get(&phydev->mdio.dev, "phy-cal-data");
+-      if (IS_ERR(cell)) {
+-              if (PTR_ERR(cell) == -EPROBE_DEFER)
+-                      return PTR_ERR(cell);
+-              return 0;
+-      }
+-
+-      buf = (u32 *)nvmem_cell_read(cell, &len);
+-      nvmem_cell_put(cell);
+-      if (IS_ERR(buf))
+-              return PTR_ERR(buf);
+-
+-      if (!buf[0] || !buf[1] || !buf[2] || !buf[3] || len < 4 * sizeof(u32)) {
+-              phydev_err(phydev, "invalid efuse data\n");
+-              ret = -EINVAL;
+-              goto out;
+-      }
+-
+-      ret = start_cal(phydev, REXT, EFUSE_M, NO_PAIR, NO_PAIR, buf);
+-      if (ret)
+-              goto out;
+-      ret = start_cal(phydev, TX_OFFSET, EFUSE_M, NO_PAIR, NO_PAIR, buf);
+-      if (ret)
+-              goto out;
+-      ret = start_cal(phydev, TX_AMP, EFUSE_M, NO_PAIR, NO_PAIR, buf);
+-      if (ret)
+-              goto out;
+-      ret = start_cal(phydev, TX_R50, EFUSE_M, PAIR_A, PAIR_D, buf);
+-      if (ret)
+-              goto out;
+-      ret = start_cal(phydev, TX_VCM, SW_M, PAIR_A, PAIR_A, buf);
+-      if (ret)
+-              goto out;
+-
+-out:
+-      kfree(buf);
+-      return ret;
+-}
+-
+-static int mt798x_phy_config_init(struct phy_device *phydev)
+-{
+-      switch (phydev->drv->phy_id) {
+-      case MTK_GPHY_ID_MT7981:
+-              mt7981_phy_finetune(phydev);
+-              break;
+-      case MTK_GPHY_ID_MT7988:
+-              mt7988_phy_finetune(phydev);
+-              break;
+-      }
+-
+-      mt798x_phy_common_finetune(phydev);
+-      mt798x_phy_eee(phydev);
+-
+-      return mt798x_phy_calibration(phydev);
+-}
+-
+-static int mt798x_phy_hw_led_on_set(struct phy_device *phydev, u8 index,
+-                                  bool on)
+-{
+-      unsigned int bit_on = MTK_PHY_LED_STATE_FORCE_ON + (index ? 16 : 0);
+-      struct mtk_socphy_priv *priv = phydev->priv;
+-      bool changed;
+-
+-      if (on)
+-              changed = !test_and_set_bit(bit_on, &priv->led_state);
+-      else
+-              changed = !!test_and_clear_bit(bit_on, &priv->led_state);
+-
+-      changed |= !!test_and_clear_bit(MTK_PHY_LED_STATE_NETDEV +
+-                                      (index ? 16 : 0), &priv->led_state);
+-      if (changed)
+-              return phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
+-                                    MTK_PHY_LED1_ON_CTRL :
+-                                    MTK_PHY_LED0_ON_CTRL,
+-                                    MTK_PHY_LED_ON_MASK,
+-                                    on ? MTK_PHY_LED_ON_FORCE_ON : 0);
+-      else
+-              return 0;
+-}
+-
+-static int mt798x_phy_hw_led_blink_set(struct phy_device *phydev, u8 index,
+-                                     bool blinking)
+-{
+-      unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK +
+-                               (index ? 16 : 0);
+-      struct mtk_socphy_priv *priv = phydev->priv;
+-      bool changed;
+-
+-      if (blinking)
+-              changed = !test_and_set_bit(bit_blink, &priv->led_state);
+-      else
+-              changed = !!test_and_clear_bit(bit_blink, &priv->led_state);
+-
+-      changed |= !!test_bit(MTK_PHY_LED_STATE_NETDEV +
+-                            (index ? 16 : 0), &priv->led_state);
+-      if (changed)
+-              return phy_write_mmd(phydev, MDIO_MMD_VEND2, index ?
+-                                   MTK_PHY_LED1_BLINK_CTRL :
+-                                   MTK_PHY_LED0_BLINK_CTRL,
+-                                   blinking ?
+-                                   MTK_PHY_LED_BLINK_FORCE_BLINK : 0);
+-      else
+-              return 0;
+-}
+-
+-static int mt798x_phy_led_blink_set(struct phy_device *phydev, u8 index,
+-                                  unsigned long *delay_on,
+-                                  unsigned long *delay_off)
+-{
+-      bool blinking = false;
+-      int err = 0;
+-
+-      if (index > 1)
+-              return -EINVAL;
+-
+-      if (delay_on && delay_off && (*delay_on > 0) && (*delay_off > 0)) {
+-              blinking = true;
+-              *delay_on = 50;
+-              *delay_off = 50;
+-      }
+-
+-      err = mt798x_phy_hw_led_blink_set(phydev, index, blinking);
+-      if (err)
+-              return err;
+-
+-      return mt798x_phy_hw_led_on_set(phydev, index, false);
+-}
+-
+-static int mt798x_phy_led_brightness_set(struct phy_device *phydev,
+-                                       u8 index, enum led_brightness value)
+-{
+-      int err;
+-
+-      err = mt798x_phy_hw_led_blink_set(phydev, index, false);
+-      if (err)
+-              return err;
+-
+-      return mt798x_phy_hw_led_on_set(phydev, index, (value != LED_OFF));
+-}
+-
+-static const unsigned long supported_triggers =
+-      BIT(TRIGGER_NETDEV_FULL_DUPLEX) |
+-      BIT(TRIGGER_NETDEV_HALF_DUPLEX) |
+-      BIT(TRIGGER_NETDEV_LINK)        |
+-      BIT(TRIGGER_NETDEV_LINK_10)     |
+-      BIT(TRIGGER_NETDEV_LINK_100)    |
+-      BIT(TRIGGER_NETDEV_LINK_1000)   |
+-      BIT(TRIGGER_NETDEV_RX)          |
+-      BIT(TRIGGER_NETDEV_TX);
+-
+-static int mt798x_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
+-                                        unsigned long rules)
+-{
+-      if (index > 1)
+-              return -EINVAL;
+-
+-      /* All combinations of the supported triggers are allowed */
+-      if (rules & ~supported_triggers)
+-              return -EOPNOTSUPP;
+-
+-      return 0;
+-};
+-
+-static int mt798x_phy_led_hw_control_get(struct phy_device *phydev, u8 index,
+-                                       unsigned long *rules)
+-{
+-      unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK +
+-                               (index ? 16 : 0);
+-      unsigned int bit_netdev = MTK_PHY_LED_STATE_NETDEV + (index ? 16 : 0);
+-      unsigned int bit_on = MTK_PHY_LED_STATE_FORCE_ON + (index ? 16 : 0);
+-      struct mtk_socphy_priv *priv = phydev->priv;
+-      int on, blink;
+-
+-      if (index > 1)
+-              return -EINVAL;
+-
+-      on = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+-                        index ? MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL);
+-
+-      if (on < 0)
+-              return -EIO;
+-
+-      blink = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+-                           index ? MTK_PHY_LED1_BLINK_CTRL :
+-                                   MTK_PHY_LED0_BLINK_CTRL);
+-      if (blink < 0)
+-              return -EIO;
+-
+-      if ((on & (MTK_PHY_LED_ON_LINK | MTK_PHY_LED_ON_FDX |
+-                 MTK_PHY_LED_ON_HDX | MTK_PHY_LED_ON_LINKDOWN)) ||
+-          (blink & (MTK_PHY_LED_BLINK_RX | MTK_PHY_LED_BLINK_TX)))
+-              set_bit(bit_netdev, &priv->led_state);
+-      else
+-              clear_bit(bit_netdev, &priv->led_state);
+-
+-      if (on & MTK_PHY_LED_ON_FORCE_ON)
+-              set_bit(bit_on, &priv->led_state);
+-      else
+-              clear_bit(bit_on, &priv->led_state);
+-
+-      if (blink & MTK_PHY_LED_BLINK_FORCE_BLINK)
+-              set_bit(bit_blink, &priv->led_state);
+-      else
+-              clear_bit(bit_blink, &priv->led_state);
+-
+-      if (!rules)
+-              return 0;
+-
+-      if (on & MTK_PHY_LED_ON_LINK)
+-              *rules |= BIT(TRIGGER_NETDEV_LINK);
+-
+-      if (on & MTK_PHY_LED_ON_LINK10)
+-              *rules |= BIT(TRIGGER_NETDEV_LINK_10);
+-
+-      if (on & MTK_PHY_LED_ON_LINK100)
+-              *rules |= BIT(TRIGGER_NETDEV_LINK_100);
+-
+-      if (on & MTK_PHY_LED_ON_LINK1000)
+-              *rules |= BIT(TRIGGER_NETDEV_LINK_1000);
+-
+-      if (on & MTK_PHY_LED_ON_FDX)
+-              *rules |= BIT(TRIGGER_NETDEV_FULL_DUPLEX);
+-
+-      if (on & MTK_PHY_LED_ON_HDX)
+-              *rules |= BIT(TRIGGER_NETDEV_HALF_DUPLEX);
+-
+-      if (blink & MTK_PHY_LED_BLINK_RX)
+-              *rules |= BIT(TRIGGER_NETDEV_RX);
+-
+-      if (blink & MTK_PHY_LED_BLINK_TX)
+-              *rules |= BIT(TRIGGER_NETDEV_TX);
+-
+-      return 0;
+-};
+-
+-static int mt798x_phy_led_hw_control_set(struct phy_device *phydev, u8 index,
+-                                       unsigned long rules)
+-{
+-      unsigned int bit_netdev = MTK_PHY_LED_STATE_NETDEV + (index ? 16 : 0);
+-      struct mtk_socphy_priv *priv = phydev->priv;
+-      u16 on = 0, blink = 0;
+-      int ret;
+-
+-      if (index > 1)
+-              return -EINVAL;
+-
+-      if (rules & BIT(TRIGGER_NETDEV_FULL_DUPLEX))
+-              on |= MTK_PHY_LED_ON_FDX;
+-
+-      if (rules & BIT(TRIGGER_NETDEV_HALF_DUPLEX))
+-              on |= MTK_PHY_LED_ON_HDX;
+-
+-      if (rules & (BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK)))
+-              on |= MTK_PHY_LED_ON_LINK10;
+-
+-      if (rules & (BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK)))
+-              on |= MTK_PHY_LED_ON_LINK100;
+-
+-      if (rules & (BIT(TRIGGER_NETDEV_LINK_1000) | BIT(TRIGGER_NETDEV_LINK)))
+-              on |= MTK_PHY_LED_ON_LINK1000;
+-
+-      if (rules & BIT(TRIGGER_NETDEV_RX)) {
+-              blink |= (on & MTK_PHY_LED_ON_LINK) ?
+-                        (((on & MTK_PHY_LED_ON_LINK10) ?
+-                          MTK_PHY_LED_BLINK_10RX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK100) ?
+-                          MTK_PHY_LED_BLINK_100RX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK1000) ?
+-                          MTK_PHY_LED_BLINK_1000RX : 0)) :
+-                        MTK_PHY_LED_BLINK_RX;
+-      }
+-
+-      if (rules & BIT(TRIGGER_NETDEV_TX)) {
+-              blink |= (on & MTK_PHY_LED_ON_LINK) ?
+-                        (((on & MTK_PHY_LED_ON_LINK10) ?
+-                          MTK_PHY_LED_BLINK_10TX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK100) ?
+-                          MTK_PHY_LED_BLINK_100TX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK1000) ?
+-                          MTK_PHY_LED_BLINK_1000TX : 0)) :
+-                        MTK_PHY_LED_BLINK_TX;
+-      }
+-
+-      if (blink || on)
+-              set_bit(bit_netdev, &priv->led_state);
+-      else
+-              clear_bit(bit_netdev, &priv->led_state);
+-
+-      ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
+-                              MTK_PHY_LED1_ON_CTRL :
+-                              MTK_PHY_LED0_ON_CTRL,
+-                           MTK_PHY_LED_ON_FDX     |
+-                           MTK_PHY_LED_ON_HDX     |
+-                           MTK_PHY_LED_ON_LINK,
+-                           on);
+-
+-      if (ret)
+-              return ret;
+-
+-      return phy_write_mmd(phydev, MDIO_MMD_VEND2, index ?
+-                              MTK_PHY_LED1_BLINK_CTRL :
+-                              MTK_PHY_LED0_BLINK_CTRL, blink);
+-};
+-
+-static bool mt7988_phy_led_get_polarity(struct phy_device *phydev, int led_num)
+-{
+-      struct mtk_socphy_shared *priv = phydev->shared->priv;
+-      u32 polarities;
+-
+-      if (led_num == 0)
+-              polarities = ~(priv->boottrap);
+-      else
+-              polarities = MTK_PHY_LED1_DEFAULT_POLARITIES;
+-
+-      if (polarities & BIT(phydev->mdio.addr))
+-              return true;
+-
+-      return false;
+-}
+-
+-static int mt7988_phy_fix_leds_polarities(struct phy_device *phydev)
+-{
+-      struct pinctrl *pinctrl;
+-      int index;
+-
+-      /* Setup LED polarity according to bootstrap use of LED pins */
+-      for (index = 0; index < 2; ++index)
+-              phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
+-                              MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL,
+-                             MTK_PHY_LED_ON_POLARITY,
+-                             mt7988_phy_led_get_polarity(phydev, index) ?
+-                              MTK_PHY_LED_ON_POLARITY : 0);
+-
+-      /* Only now setup pinctrl to avoid bogus blinking */
+-      pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "gbe-led");
+-      if (IS_ERR(pinctrl))
+-              dev_err(&phydev->mdio.bus->dev,
+-                      "Failed to setup PHY LED pinctrl\n");
+-
+-      return 0;
+-}
+-
+-static int mt7988_phy_probe_shared(struct phy_device *phydev)
+-{
+-      struct device_node *np = dev_of_node(&phydev->mdio.bus->dev);
+-      struct mtk_socphy_shared *shared = phydev->shared->priv;
+-      struct regmap *regmap;
+-      u32 reg;
+-      int ret;
+-
+-      /* The LED0 of the 4 PHYs in MT7988 are wired to SoC pins LED_A, LED_B,
+-       * LED_C and LED_D respectively. At the same time those pins are used to
+-       * bootstrap configuration of the reference clock source (LED_A),
+-       * DRAM DDRx16b x2/x1 (LED_B) and boot device (LED_C, LED_D).
+-       * In practice this is done using a LED and a resistor pulling the pin
+-       * either to GND or to VIO.
+-       * The detected value at boot time is accessible at run-time using the
+-       * TPBANK0 register located in the gpio base of the pinctrl, in order
+-       * to read it here it needs to be referenced by a phandle called
+-       * 'mediatek,pio' in the MDIO bus hosting the PHY.
+-       * The 4 bits in TPBANK0 are kept as package shared data and are used to
+-       * set LED polarity for each of the LED0.
+-       */
+-      regmap = syscon_regmap_lookup_by_phandle(np, "mediatek,pio");
+-      if (IS_ERR(regmap))
+-              return PTR_ERR(regmap);
+-
+-      ret = regmap_read(regmap, RG_GPIO_MISC_TPBANK0, &reg);
+-      if (ret)
+-              return ret;
+-
+-      shared->boottrap = FIELD_GET(RG_GPIO_MISC_TPBANK0_BOOTMODE, reg);
+-
+-      return 0;
+-}
+-
+-static void mt798x_phy_leds_state_init(struct phy_device *phydev)
+-{
+-      int i;
+-
+-      for (i = 0; i < 2; ++i)
+-              mt798x_phy_led_hw_control_get(phydev, i, NULL);
+-}
+-
+-static int mt7988_phy_probe(struct phy_device *phydev)
+-{
+-      struct mtk_socphy_shared *shared;
+-      struct mtk_socphy_priv *priv;
+-      int err;
+-
+-      if (phydev->mdio.addr > 3)
+-              return -EINVAL;
+-
+-      err = devm_phy_package_join(&phydev->mdio.dev, phydev, 0,
+-                                  sizeof(struct mtk_socphy_shared));
+-      if (err)
+-              return err;
+-
+-      if (phy_package_probe_once(phydev)) {
+-              err = mt7988_phy_probe_shared(phydev);
+-              if (err)
+-                      return err;
+-      }
+-
+-      shared = phydev->shared->priv;
+-      priv = &shared->priv[phydev->mdio.addr];
+-
+-      phydev->priv = priv;
+-
+-      mt798x_phy_leds_state_init(phydev);
+-
+-      err = mt7988_phy_fix_leds_polarities(phydev);
+-      if (err)
+-              return err;
+-
+-      /* Disable TX power saving at probing to:
+-       * 1. Meet common mode compliance test criteria
+-       * 2. Make sure that TX-VCM calibration works fine
+-       */
+-      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG7,
+-                     MTK_PHY_DA_AD_BUF_BIAS_LP_MASK, 0x3 << 8);
+-
+-      return mt798x_phy_calibration(phydev);
+-}
+-
+-static int mt7981_phy_probe(struct phy_device *phydev)
+-{
+-      struct mtk_socphy_priv *priv;
+-
+-      priv = devm_kzalloc(&phydev->mdio.dev, sizeof(struct mtk_socphy_priv),
+-                          GFP_KERNEL);
+-      if (!priv)
+-              return -ENOMEM;
+-
+-      phydev->priv = priv;
+-
+-      mt798x_phy_leds_state_init(phydev);
+-
+-      return mt798x_phy_calibration(phydev);
+-}
+-
+-static struct phy_driver mtk_socphy_driver[] = {
+-      {
+-              PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981),
+-              .name           = "MediaTek MT7981 PHY",
+-              .config_init    = mt798x_phy_config_init,
+-              .config_intr    = genphy_no_config_intr,
+-              .handle_interrupt = genphy_handle_interrupt_no_ack,
+-              .probe          = mt7981_phy_probe,
+-              .suspend        = genphy_suspend,
+-              .resume         = genphy_resume,
+-              .read_page      = mtk_socphy_read_page,
+-              .write_page     = mtk_socphy_write_page,
+-              .led_blink_set  = mt798x_phy_led_blink_set,
+-              .led_brightness_set = mt798x_phy_led_brightness_set,
+-              .led_hw_is_supported = mt798x_phy_led_hw_is_supported,
+-              .led_hw_control_set = mt798x_phy_led_hw_control_set,
+-              .led_hw_control_get = mt798x_phy_led_hw_control_get,
+-      },
+-      {
+-              PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988),
+-              .name           = "MediaTek MT7988 PHY",
+-              .config_init    = mt798x_phy_config_init,
+-              .config_intr    = genphy_no_config_intr,
+-              .handle_interrupt = genphy_handle_interrupt_no_ack,
+-              .probe          = mt7988_phy_probe,
+-              .suspend        = genphy_suspend,
+-              .resume         = genphy_resume,
+-              .read_page      = mtk_socphy_read_page,
+-              .write_page     = mtk_socphy_write_page,
+-              .led_blink_set  = mt798x_phy_led_blink_set,
+-              .led_brightness_set = mt798x_phy_led_brightness_set,
+-              .led_hw_is_supported = mt798x_phy_led_hw_is_supported,
+-              .led_hw_control_set = mt798x_phy_led_hw_control_set,
+-              .led_hw_control_get = mt798x_phy_led_hw_control_get,
+-      },
+-};
+-
+-module_phy_driver(mtk_socphy_driver);
+-
+-static struct mdio_device_id __maybe_unused mtk_socphy_tbl[] = {
+-      { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981) },
+-      { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988) },
+-      { }
+-};
+-
+-MODULE_DESCRIPTION("MediaTek SoC Gigabit Ethernet PHY driver");
+-MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
+-MODULE_AUTHOR("SkyLake Huang <SkyLake.Huang@mediatek.com>");
+-MODULE_LICENSE("GPL");
+-
+-MODULE_DEVICE_TABLE(mdio, mtk_socphy_tbl);
+--- a/drivers/net/phy/mediatek-ge.c
++++ /dev/null
+@@ -1,111 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0+
+-#include <linux/bitfield.h>
+-#include <linux/module.h>
+-#include <linux/phy.h>
+-
+-#define MTK_EXT_PAGE_ACCESS           0x1f
+-#define MTK_PHY_PAGE_STANDARD         0x0000
+-#define MTK_PHY_PAGE_EXTENDED         0x0001
+-#define MTK_PHY_PAGE_EXTENDED_2               0x0002
+-#define MTK_PHY_PAGE_EXTENDED_3               0x0003
+-#define MTK_PHY_PAGE_EXTENDED_2A30    0x2a30
+-#define MTK_PHY_PAGE_EXTENDED_52B5    0x52b5
+-
+-static int mtk_gephy_read_page(struct phy_device *phydev)
+-{
+-      return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
+-}
+-
+-static int mtk_gephy_write_page(struct phy_device *phydev, int page)
+-{
+-      return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page);
+-}
+-
+-static void mtk_gephy_config_init(struct phy_device *phydev)
+-{
+-      /* Enable HW auto downshift */
+-      phy_modify_paged(phydev, MTK_PHY_PAGE_EXTENDED, 0x14, 0, BIT(4));
+-
+-      /* Increase SlvDPSready time */
+-      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+-      __phy_write(phydev, 0x10, 0xafae);
+-      __phy_write(phydev, 0x12, 0x2f);
+-      __phy_write(phydev, 0x10, 0x8fae);
+-      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+-
+-      /* Adjust 100_mse_threshold */
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x123, 0xffff);
+-
+-      /* Disable mcc */
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xa6, 0x300);
+-}
+-
+-static int mt7530_phy_config_init(struct phy_device *phydev)
+-{
+-      mtk_gephy_config_init(phydev);
+-
+-      /* Increase post_update_timer */
+-      phy_write_paged(phydev, MTK_PHY_PAGE_EXTENDED_3, 0x11, 0x4b);
+-
+-      return 0;
+-}
+-
+-static int mt7531_phy_config_init(struct phy_device *phydev)
+-{
+-      mtk_gephy_config_init(phydev);
+-
+-      /* PHY link down power saving enable */
+-      phy_set_bits(phydev, 0x17, BIT(4));
+-      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0xc6, 0x300);
+-
+-      /* Set TX Pair delay selection */
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x13, 0x404);
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x14, 0x404);
+-
+-      return 0;
+-}
+-
+-static struct phy_driver mtk_gephy_driver[] = {
+-      {
+-              PHY_ID_MATCH_EXACT(0x03a29412),
+-              .name           = "MediaTek MT7530 PHY",
+-              .config_init    = mt7530_phy_config_init,
+-              /* Interrupts are handled by the switch, not the PHY
+-               * itself.
+-               */
+-              .config_intr    = genphy_no_config_intr,
+-              .handle_interrupt = genphy_handle_interrupt_no_ack,
+-              .suspend        = genphy_suspend,
+-              .resume         = genphy_resume,
+-              .read_page      = mtk_gephy_read_page,
+-              .write_page     = mtk_gephy_write_page,
+-      },
+-      {
+-              PHY_ID_MATCH_EXACT(0x03a29441),
+-              .name           = "MediaTek MT7531 PHY",
+-              .config_init    = mt7531_phy_config_init,
+-              /* Interrupts are handled by the switch, not the PHY
+-               * itself.
+-               */
+-              .config_intr    = genphy_no_config_intr,
+-              .handle_interrupt = genphy_handle_interrupt_no_ack,
+-              .suspend        = genphy_suspend,
+-              .resume         = genphy_resume,
+-              .read_page      = mtk_gephy_read_page,
+-              .write_page     = mtk_gephy_write_page,
+-      },
+-};
+-
+-module_phy_driver(mtk_gephy_driver);
+-
+-static struct mdio_device_id __maybe_unused mtk_gephy_tbl[] = {
+-      { PHY_ID_MATCH_EXACT(0x03a29441) },
+-      { PHY_ID_MATCH_EXACT(0x03a29412) },
+-      { }
+-};
+-
+-MODULE_DESCRIPTION("MediaTek Gigabit Ethernet PHY driver");
+-MODULE_AUTHOR("DENG, Qingfang <dqfext@gmail.com>");
+-MODULE_LICENSE("GPL");
+-
+-MODULE_DEVICE_TABLE(mdio, mtk_gephy_tbl);
+--- /dev/null
++++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
+@@ -0,0 +1,1610 @@
++// SPDX-License-Identifier: GPL-2.0+
++#include <linux/bitfield.h>
++#include <linux/bitmap.h>
++#include <linux/mfd/syscon.h>
++#include <linux/module.h>
++#include <linux/nvmem-consumer.h>
++#include <linux/pinctrl/consumer.h>
++#include <linux/phy.h>
++#include <linux/regmap.h>
++
++#define MTK_GPHY_ID_MT7981                    0x03a29461
++#define MTK_GPHY_ID_MT7988                    0x03a29481
++
++#define MTK_EXT_PAGE_ACCESS                   0x1f
++#define MTK_PHY_PAGE_STANDARD                 0x0000
++#define MTK_PHY_PAGE_EXTENDED_3                       0x0003
++
++#define MTK_PHY_LPI_REG_14                    0x14
++#define MTK_PHY_LPI_WAKE_TIMER_1000_MASK      GENMASK(8, 0)
++
++#define MTK_PHY_LPI_REG_1c                    0x1c
++#define MTK_PHY_SMI_DET_ON_THRESH_MASK                GENMASK(13, 8)
++
++#define MTK_PHY_PAGE_EXTENDED_2A30            0x2a30
++#define MTK_PHY_PAGE_EXTENDED_52B5            0x52b5
++
++#define ANALOG_INTERNAL_OPERATION_MAX_US      20
++#define TXRESERVE_MIN                         0
++#define TXRESERVE_MAX                         7
++
++#define MTK_PHY_ANARG_RG                      0x10
++#define   MTK_PHY_TCLKOFFSET_MASK             GENMASK(12, 8)
++
++/* Registers on MDIO_MMD_VEND1 */
++#define MTK_PHY_TXVLD_DA_RG                   0x12
++#define   MTK_PHY_DA_TX_I2MPB_A_GBE_MASK      GENMASK(15, 10)
++#define   MTK_PHY_DA_TX_I2MPB_A_TBT_MASK      GENMASK(5, 0)
++
++#define MTK_PHY_TX_I2MPB_TEST_MODE_A2         0x16
++#define   MTK_PHY_DA_TX_I2MPB_A_HBT_MASK      GENMASK(15, 10)
++#define   MTK_PHY_DA_TX_I2MPB_A_TST_MASK      GENMASK(5, 0)
++
++#define MTK_PHY_TX_I2MPB_TEST_MODE_B1         0x17
++#define   MTK_PHY_DA_TX_I2MPB_B_GBE_MASK      GENMASK(13, 8)
++#define   MTK_PHY_DA_TX_I2MPB_B_TBT_MASK      GENMASK(5, 0)
++
++#define MTK_PHY_TX_I2MPB_TEST_MODE_B2         0x18
++#define   MTK_PHY_DA_TX_I2MPB_B_HBT_MASK      GENMASK(13, 8)
++#define   MTK_PHY_DA_TX_I2MPB_B_TST_MASK      GENMASK(5, 0)
++
++#define MTK_PHY_TX_I2MPB_TEST_MODE_C1         0x19
++#define   MTK_PHY_DA_TX_I2MPB_C_GBE_MASK      GENMASK(13, 8)
++#define   MTK_PHY_DA_TX_I2MPB_C_TBT_MASK      GENMASK(5, 0)
++
++#define MTK_PHY_TX_I2MPB_TEST_MODE_C2         0x20
++#define   MTK_PHY_DA_TX_I2MPB_C_HBT_MASK      GENMASK(13, 8)
++#define   MTK_PHY_DA_TX_I2MPB_C_TST_MASK      GENMASK(5, 0)
++
++#define MTK_PHY_TX_I2MPB_TEST_MODE_D1         0x21
++#define   MTK_PHY_DA_TX_I2MPB_D_GBE_MASK      GENMASK(13, 8)
++#define   MTK_PHY_DA_TX_I2MPB_D_TBT_MASK      GENMASK(5, 0)
++
++#define MTK_PHY_TX_I2MPB_TEST_MODE_D2         0x22
++#define   MTK_PHY_DA_TX_I2MPB_D_HBT_MASK      GENMASK(13, 8)
++#define   MTK_PHY_DA_TX_I2MPB_D_TST_MASK      GENMASK(5, 0)
++
++#define MTK_PHY_RXADC_CTRL_RG7                        0xc6
++#define   MTK_PHY_DA_AD_BUF_BIAS_LP_MASK      GENMASK(9, 8)
++
++#define MTK_PHY_RXADC_CTRL_RG9                        0xc8
++#define   MTK_PHY_DA_RX_PSBN_TBT_MASK         GENMASK(14, 12)
++#define   MTK_PHY_DA_RX_PSBN_HBT_MASK         GENMASK(10, 8)
++#define   MTK_PHY_DA_RX_PSBN_GBE_MASK         GENMASK(6, 4)
++#define   MTK_PHY_DA_RX_PSBN_LP_MASK          GENMASK(2, 0)
++
++#define MTK_PHY_LDO_OUTPUT_V                  0xd7
++
++#define MTK_PHY_RG_ANA_CAL_RG0                        0xdb
++#define   MTK_PHY_RG_CAL_CKINV                        BIT(12)
++#define   MTK_PHY_RG_ANA_CALEN                        BIT(8)
++#define   MTK_PHY_RG_ZCALEN_A                 BIT(0)
++
++#define MTK_PHY_RG_ANA_CAL_RG1                        0xdc
++#define   MTK_PHY_RG_ZCALEN_B                 BIT(12)
++#define   MTK_PHY_RG_ZCALEN_C                 BIT(8)
++#define   MTK_PHY_RG_ZCALEN_D                 BIT(4)
++#define   MTK_PHY_RG_TXVOS_CALEN              BIT(0)
++
++#define MTK_PHY_RG_ANA_CAL_RG5                        0xe0
++#define   MTK_PHY_RG_REXT_TRIM_MASK           GENMASK(13, 8)
++
++#define MTK_PHY_RG_TX_FILTER                  0xfe
++
++#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG120    0x120
++#define   MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK       GENMASK(12, 8)
++#define   MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK       GENMASK(4, 0)
++
++#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122    0x122
++#define   MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK     GENMASK(7, 0)
++
++#define MTK_PHY_RG_TESTMUX_ADC_CTRL           0x144
++#define   MTK_PHY_RG_TXEN_DIG_MASK            GENMASK(5, 5)
++
++#define MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B               0x172
++#define   MTK_PHY_CR_TX_AMP_OFFSET_A_MASK     GENMASK(13, 8)
++#define   MTK_PHY_CR_TX_AMP_OFFSET_B_MASK     GENMASK(6, 0)
++
++#define MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D               0x173
++#define   MTK_PHY_CR_TX_AMP_OFFSET_C_MASK     GENMASK(13, 8)
++#define   MTK_PHY_CR_TX_AMP_OFFSET_D_MASK     GENMASK(6, 0)
++
++#define MTK_PHY_RG_AD_CAL_COMP                        0x17a
++#define   MTK_PHY_AD_CAL_COMP_OUT_MASK                GENMASK(8, 8)
++
++#define MTK_PHY_RG_AD_CAL_CLK                 0x17b
++#define   MTK_PHY_DA_CAL_CLK                  BIT(0)
++
++#define MTK_PHY_RG_AD_CALIN                   0x17c
++#define   MTK_PHY_DA_CALIN_FLAG                       BIT(0)
++
++#define MTK_PHY_RG_DASN_DAC_IN0_A             0x17d
++#define   MTK_PHY_DASN_DAC_IN0_A_MASK         GENMASK(9, 0)
++
++#define MTK_PHY_RG_DASN_DAC_IN0_B             0x17e
++#define   MTK_PHY_DASN_DAC_IN0_B_MASK         GENMASK(9, 0)
++
++#define MTK_PHY_RG_DASN_DAC_IN0_C             0x17f
++#define   MTK_PHY_DASN_DAC_IN0_C_MASK         GENMASK(9, 0)
++
++#define MTK_PHY_RG_DASN_DAC_IN0_D             0x180
++#define   MTK_PHY_DASN_DAC_IN0_D_MASK         GENMASK(9, 0)
++
++#define MTK_PHY_RG_DASN_DAC_IN1_A             0x181
++#define   MTK_PHY_DASN_DAC_IN1_A_MASK         GENMASK(9, 0)
++
++#define MTK_PHY_RG_DASN_DAC_IN1_B             0x182
++#define   MTK_PHY_DASN_DAC_IN1_B_MASK         GENMASK(9, 0)
++
++#define MTK_PHY_RG_DASN_DAC_IN1_C             0x183
++#define   MTK_PHY_DASN_DAC_IN1_C_MASK         GENMASK(9, 0)
++
++#define MTK_PHY_RG_DASN_DAC_IN1_D             0x184
++#define   MTK_PHY_DASN_DAC_IN1_D_MASK         GENMASK(9, 0)
++
++#define MTK_PHY_RG_DEV1E_REG19b                       0x19b
++#define   MTK_PHY_BYPASS_DSP_LPI_READY                BIT(8)
++
++#define MTK_PHY_RG_LP_IIR2_K1_L                       0x22a
++#define MTK_PHY_RG_LP_IIR2_K1_U                       0x22b
++#define MTK_PHY_RG_LP_IIR2_K2_L                       0x22c
++#define MTK_PHY_RG_LP_IIR2_K2_U                       0x22d
++#define MTK_PHY_RG_LP_IIR2_K3_L                       0x22e
++#define MTK_PHY_RG_LP_IIR2_K3_U                       0x22f
++#define MTK_PHY_RG_LP_IIR2_K4_L                       0x230
++#define MTK_PHY_RG_LP_IIR2_K4_U                       0x231
++#define MTK_PHY_RG_LP_IIR2_K5_L                       0x232
++#define MTK_PHY_RG_LP_IIR2_K5_U                       0x233
++
++#define MTK_PHY_RG_DEV1E_REG234                       0x234
++#define   MTK_PHY_TR_OPEN_LOOP_EN_MASK                GENMASK(0, 0)
++#define   MTK_PHY_LPF_X_AVERAGE_MASK          GENMASK(7, 4)
++#define   MTK_PHY_TR_LP_IIR_EEE_EN            BIT(12)
++
++#define MTK_PHY_RG_LPF_CNT_VAL                        0x235
++
++#define MTK_PHY_RG_DEV1E_REG238                       0x238
++#define   MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK  GENMASK(8, 0)
++#define   MTK_PHY_LPI_SLV_SEND_TX_EN          BIT(12)
++
++#define MTK_PHY_RG_DEV1E_REG239                       0x239
++#define   MTK_PHY_LPI_SEND_LOC_TIMER_MASK     GENMASK(8, 0)
++#define   MTK_PHY_LPI_TXPCS_LOC_RCV           BIT(12)
++
++#define MTK_PHY_RG_DEV1E_REG27C                       0x27c
++#define   MTK_PHY_VGASTATE_FFE_THR_ST1_MASK   GENMASK(12, 8)
++#define MTK_PHY_RG_DEV1E_REG27D                       0x27d
++#define   MTK_PHY_VGASTATE_FFE_THR_ST2_MASK   GENMASK(4, 0)
++
++#define MTK_PHY_RG_DEV1E_REG2C7                       0x2c7
++#define   MTK_PHY_MAX_GAIN_MASK                       GENMASK(4, 0)
++#define   MTK_PHY_MIN_GAIN_MASK                       GENMASK(12, 8)
++
++#define MTK_PHY_RG_DEV1E_REG2D1                       0x2d1
++#define   MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK        GENMASK(7, 0)
++#define   MTK_PHY_LPI_SKIP_SD_SLV_TR          BIT(8)
++#define   MTK_PHY_LPI_TR_READY                        BIT(9)
++#define   MTK_PHY_LPI_VCO_EEE_STG0_EN         BIT(10)
++
++#define MTK_PHY_RG_DEV1E_REG323                       0x323
++#define   MTK_PHY_EEE_WAKE_MAS_INT_DC         BIT(0)
++#define   MTK_PHY_EEE_WAKE_SLV_INT_DC         BIT(4)
++
++#define MTK_PHY_RG_DEV1E_REG324                       0x324
++#define   MTK_PHY_SMI_DETCNT_MAX_MASK         GENMASK(5, 0)
++#define   MTK_PHY_SMI_DET_MAX_EN              BIT(8)
++
++#define MTK_PHY_RG_DEV1E_REG326                       0x326
++#define   MTK_PHY_LPI_MODE_SD_ON              BIT(0)
++#define   MTK_PHY_RESET_RANDUPD_CNT           BIT(1)
++#define   MTK_PHY_TREC_UPDATE_ENAB_CLR                BIT(2)
++#define   MTK_PHY_LPI_QUIT_WAIT_DFE_SIG_DET_OFF       BIT(4)
++#define   MTK_PHY_TR_READY_SKIP_AFE_WAKEUP    BIT(5)
++
++#define MTK_PHY_LDO_PUMP_EN_PAIRAB            0x502
++#define MTK_PHY_LDO_PUMP_EN_PAIRCD            0x503
++
++#define MTK_PHY_DA_TX_R50_PAIR_A              0x53d
++#define MTK_PHY_DA_TX_R50_PAIR_B              0x53e
++#define MTK_PHY_DA_TX_R50_PAIR_C              0x53f
++#define MTK_PHY_DA_TX_R50_PAIR_D              0x540
++
++/* Registers on MDIO_MMD_VEND2 */
++#define MTK_PHY_LED0_ON_CTRL                  0x24
++#define MTK_PHY_LED1_ON_CTRL                  0x26
++#define   MTK_PHY_LED_ON_MASK                 GENMASK(6, 0)
++#define   MTK_PHY_LED_ON_LINK1000             BIT(0)
++#define   MTK_PHY_LED_ON_LINK100              BIT(1)
++#define   MTK_PHY_LED_ON_LINK10                       BIT(2)
++#define   MTK_PHY_LED_ON_LINK                 (MTK_PHY_LED_ON_LINK10 |\
++                                               MTK_PHY_LED_ON_LINK100 |\
++                                               MTK_PHY_LED_ON_LINK1000)
++#define   MTK_PHY_LED_ON_LINKDOWN             BIT(3)
++#define   MTK_PHY_LED_ON_FDX                  BIT(4) /* Full duplex */
++#define   MTK_PHY_LED_ON_HDX                  BIT(5) /* Half duplex */
++#define   MTK_PHY_LED_ON_FORCE_ON             BIT(6)
++#define   MTK_PHY_LED_ON_POLARITY             BIT(14)
++#define   MTK_PHY_LED_ON_ENABLE                       BIT(15)
++
++#define MTK_PHY_LED0_BLINK_CTRL                       0x25
++#define MTK_PHY_LED1_BLINK_CTRL                       0x27
++#define   MTK_PHY_LED_BLINK_1000TX            BIT(0)
++#define   MTK_PHY_LED_BLINK_1000RX            BIT(1)
++#define   MTK_PHY_LED_BLINK_100TX             BIT(2)
++#define   MTK_PHY_LED_BLINK_100RX             BIT(3)
++#define   MTK_PHY_LED_BLINK_10TX              BIT(4)
++#define   MTK_PHY_LED_BLINK_10RX              BIT(5)
++#define   MTK_PHY_LED_BLINK_RX                        (MTK_PHY_LED_BLINK_10RX |\
++                                               MTK_PHY_LED_BLINK_100RX |\
++                                               MTK_PHY_LED_BLINK_1000RX)
++#define   MTK_PHY_LED_BLINK_TX                        (MTK_PHY_LED_BLINK_10TX |\
++                                               MTK_PHY_LED_BLINK_100TX |\
++                                               MTK_PHY_LED_BLINK_1000TX)
++#define   MTK_PHY_LED_BLINK_COLLISION         BIT(6)
++#define   MTK_PHY_LED_BLINK_RX_CRC_ERR                BIT(7)
++#define   MTK_PHY_LED_BLINK_RX_IDLE_ERR               BIT(8)
++#define   MTK_PHY_LED_BLINK_FORCE_BLINK               BIT(9)
++
++#define MTK_PHY_LED1_DEFAULT_POLARITIES               BIT(1)
++
++#define MTK_PHY_RG_BG_RASEL                   0x115
++#define   MTK_PHY_RG_BG_RASEL_MASK            GENMASK(2, 0)
++
++/* 'boottrap' register reflecting the configuration of the 4 PHY LEDs */
++#define RG_GPIO_MISC_TPBANK0                  0x6f0
++#define   RG_GPIO_MISC_TPBANK0_BOOTMODE               GENMASK(11, 8)
++
++/* These macro privides efuse parsing for internal phy. */
++#define EFS_DA_TX_I2MPB_A(x)                  (((x) >> 0) & GENMASK(5, 0))
++#define EFS_DA_TX_I2MPB_B(x)                  (((x) >> 6) & GENMASK(5, 0))
++#define EFS_DA_TX_I2MPB_C(x)                  (((x) >> 12) & GENMASK(5, 0))
++#define EFS_DA_TX_I2MPB_D(x)                  (((x) >> 18) & GENMASK(5, 0))
++#define EFS_DA_TX_AMP_OFFSET_A(x)             (((x) >> 24) & GENMASK(5, 0))
++
++#define EFS_DA_TX_AMP_OFFSET_B(x)             (((x) >> 0) & GENMASK(5, 0))
++#define EFS_DA_TX_AMP_OFFSET_C(x)             (((x) >> 6) & GENMASK(5, 0))
++#define EFS_DA_TX_AMP_OFFSET_D(x)             (((x) >> 12) & GENMASK(5, 0))
++#define EFS_DA_TX_R50_A(x)                    (((x) >> 18) & GENMASK(5, 0))
++#define EFS_DA_TX_R50_B(x)                    (((x) >> 24) & GENMASK(5, 0))
++
++#define EFS_DA_TX_R50_C(x)                    (((x) >> 0) & GENMASK(5, 0))
++#define EFS_DA_TX_R50_D(x)                    (((x) >> 6) & GENMASK(5, 0))
++
++#define EFS_RG_BG_RASEL(x)                    (((x) >> 4) & GENMASK(2, 0))
++#define EFS_RG_REXT_TRIM(x)                   (((x) >> 7) & GENMASK(5, 0))
++
++enum {
++      NO_PAIR,
++      PAIR_A,
++      PAIR_B,
++      PAIR_C,
++      PAIR_D,
++};
++
++enum calibration_mode {
++      EFUSE_K,
++      SW_K
++};
++
++enum CAL_ITEM {
++      REXT,
++      TX_OFFSET,
++      TX_AMP,
++      TX_R50,
++      TX_VCM
++};
++
++enum CAL_MODE {
++      EFUSE_M,
++      SW_M
++};
++
++#define MTK_PHY_LED_STATE_FORCE_ON    0
++#define MTK_PHY_LED_STATE_FORCE_BLINK 1
++#define MTK_PHY_LED_STATE_NETDEV      2
++
++struct mtk_socphy_priv {
++      unsigned long           led_state;
++};
++
++struct mtk_socphy_shared {
++      u32                     boottrap;
++      struct mtk_socphy_priv  priv[4];
++};
++
++static int mtk_socphy_read_page(struct phy_device *phydev)
++{
++      return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
++}
++
++static int mtk_socphy_write_page(struct phy_device *phydev, int page)
++{
++      return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page);
++}
++
++/* One calibration cycle consists of:
++ * 1.Set DA_CALIN_FLAG high to start calibration. Keep it high
++ *   until AD_CAL_COMP is ready to output calibration result.
++ * 2.Wait until DA_CAL_CLK is available.
++ * 3.Fetch AD_CAL_COMP_OUT.
++ */
++static int cal_cycle(struct phy_device *phydev, int devad,
++                   u32 regnum, u16 mask, u16 cal_val)
++{
++      int reg_val;
++      int ret;
++
++      phy_modify_mmd(phydev, devad, regnum,
++                     mask, cal_val);
++      phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CALIN,
++                       MTK_PHY_DA_CALIN_FLAG);
++
++      ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
++                                      MTK_PHY_RG_AD_CAL_CLK, reg_val,
++                                      reg_val & MTK_PHY_DA_CAL_CLK, 500,
++                                      ANALOG_INTERNAL_OPERATION_MAX_US,
++                                      false);
++      if (ret) {
++              phydev_err(phydev, "Calibration cycle timeout\n");
++              return ret;
++      }
++
++      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CALIN,
++                         MTK_PHY_DA_CALIN_FLAG);
++      ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CAL_COMP);
++      if (ret < 0)
++              return ret;
++      ret = FIELD_GET(MTK_PHY_AD_CAL_COMP_OUT_MASK, ret);
++      phydev_dbg(phydev, "cal_val: 0x%x, ret: %d\n", cal_val, ret);
++
++      return ret;
++}
++
++static int rext_fill_result(struct phy_device *phydev, u16 *buf)
++{
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5,
++                     MTK_PHY_RG_REXT_TRIM_MASK, buf[0] << 8);
++      phy_modify_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_RG_BG_RASEL,
++                     MTK_PHY_RG_BG_RASEL_MASK, buf[1]);
++
++      return 0;
++}
++
++static int rext_cal_efuse(struct phy_device *phydev, u32 *buf)
++{
++      u16 rext_cal_val[2];
++
++      rext_cal_val[0] = EFS_RG_REXT_TRIM(buf[3]);
++      rext_cal_val[1] = EFS_RG_BG_RASEL(buf[3]);
++      rext_fill_result(phydev, rext_cal_val);
++
++      return 0;
++}
++
++static int tx_offset_fill_result(struct phy_device *phydev, u16 *buf)
++{
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B,
++                     MTK_PHY_CR_TX_AMP_OFFSET_A_MASK, buf[0] << 8);
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B,
++                     MTK_PHY_CR_TX_AMP_OFFSET_B_MASK, buf[1]);
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D,
++                     MTK_PHY_CR_TX_AMP_OFFSET_C_MASK, buf[2] << 8);
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D,
++                     MTK_PHY_CR_TX_AMP_OFFSET_D_MASK, buf[3]);
++
++      return 0;
++}
++
++static int tx_offset_cal_efuse(struct phy_device *phydev, u32 *buf)
++{
++      u16 tx_offset_cal_val[4];
++
++      tx_offset_cal_val[0] = EFS_DA_TX_AMP_OFFSET_A(buf[0]);
++      tx_offset_cal_val[1] = EFS_DA_TX_AMP_OFFSET_B(buf[1]);
++      tx_offset_cal_val[2] = EFS_DA_TX_AMP_OFFSET_C(buf[1]);
++      tx_offset_cal_val[3] = EFS_DA_TX_AMP_OFFSET_D(buf[1]);
++
++      tx_offset_fill_result(phydev, tx_offset_cal_val);
++
++      return 0;
++}
++
++static int tx_amp_fill_result(struct phy_device *phydev, u16 *buf)
++{
++      const int vals_9481[16] = { 10, 6, 6, 10,
++                                  10, 6, 6, 10,
++                                  10, 6, 6, 10,
++                                  10, 6, 6, 10 };
++      const int vals_9461[16] = { 7, 1, 4, 7,
++                                  7, 1, 4, 7,
++                                  7, 1, 4, 7,
++                                  7, 1, 4, 7 };
++      int bias[16] = {};
++      int i;
++
++      switch (phydev->drv->phy_id) {
++      case MTK_GPHY_ID_MT7981:
++              /* We add some calibration to efuse values
++               * due to board level influence.
++               * GBE: +7, TBT: +1, HBT: +4, TST: +7
++               */
++              memcpy(bias, (const void *)vals_9461, sizeof(bias));
++              break;
++      case MTK_GPHY_ID_MT7988:
++              memcpy(bias, (const void *)vals_9481, sizeof(bias));
++              break;
++      }
++
++      /* Prevent overflow */
++      for (i = 0; i < 12; i++) {
++              if (buf[i >> 2] + bias[i] > 63) {
++                      buf[i >> 2] = 63;
++                      bias[i] = 0;
++              }
++      }
++
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TXVLD_DA_RG,
++                     MTK_PHY_DA_TX_I2MPB_A_GBE_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_GBE_MASK,
++                                buf[0] + bias[0]));
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TXVLD_DA_RG,
++                     MTK_PHY_DA_TX_I2MPB_A_TBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_TBT_MASK,
++                                buf[0] + bias[1]));
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_A2,
++                     MTK_PHY_DA_TX_I2MPB_A_HBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_HBT_MASK,
++                                buf[0] + bias[2]));
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_A2,
++                     MTK_PHY_DA_TX_I2MPB_A_TST_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_TST_MASK,
++                                buf[0] + bias[3]));
++
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B1,
++                     MTK_PHY_DA_TX_I2MPB_B_GBE_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_GBE_MASK,
++                                buf[1] + bias[4]));
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B1,
++                     MTK_PHY_DA_TX_I2MPB_B_TBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_TBT_MASK,
++                                buf[1] + bias[5]));
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B2,
++                     MTK_PHY_DA_TX_I2MPB_B_HBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_HBT_MASK,
++                                buf[1] + bias[6]));
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B2,
++                     MTK_PHY_DA_TX_I2MPB_B_TST_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_TST_MASK,
++                                buf[1] + bias[7]));
++
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C1,
++                     MTK_PHY_DA_TX_I2MPB_C_GBE_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_GBE_MASK,
++                                buf[2] + bias[8]));
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C1,
++                     MTK_PHY_DA_TX_I2MPB_C_TBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_TBT_MASK,
++                                buf[2] + bias[9]));
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C2,
++                     MTK_PHY_DA_TX_I2MPB_C_HBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_HBT_MASK,
++                                buf[2] + bias[10]));
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C2,
++                     MTK_PHY_DA_TX_I2MPB_C_TST_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_TST_MASK,
++                                buf[2] + bias[11]));
++
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D1,
++                     MTK_PHY_DA_TX_I2MPB_D_GBE_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_GBE_MASK,
++                                buf[3] + bias[12]));
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D1,
++                     MTK_PHY_DA_TX_I2MPB_D_TBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_TBT_MASK,
++                                buf[3] + bias[13]));
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D2,
++                     MTK_PHY_DA_TX_I2MPB_D_HBT_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_HBT_MASK,
++                                buf[3] + bias[14]));
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D2,
++                     MTK_PHY_DA_TX_I2MPB_D_TST_MASK,
++                     FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_TST_MASK,
++                                buf[3] + bias[15]));
++
++      return 0;
++}
++
++static int tx_amp_cal_efuse(struct phy_device *phydev, u32 *buf)
++{
++      u16 tx_amp_cal_val[4];
++
++      tx_amp_cal_val[0] = EFS_DA_TX_I2MPB_A(buf[0]);
++      tx_amp_cal_val[1] = EFS_DA_TX_I2MPB_B(buf[0]);
++      tx_amp_cal_val[2] = EFS_DA_TX_I2MPB_C(buf[0]);
++      tx_amp_cal_val[3] = EFS_DA_TX_I2MPB_D(buf[0]);
++      tx_amp_fill_result(phydev, tx_amp_cal_val);
++
++      return 0;
++}
++
++static int tx_r50_fill_result(struct phy_device *phydev, u16 tx_r50_cal_val,
++                            u8 txg_calen_x)
++{
++      int bias = 0;
++      u16 reg, val;
++
++      if (phydev->drv->phy_id == MTK_GPHY_ID_MT7988)
++              bias = -1;
++
++      val = clamp_val(bias + tx_r50_cal_val, 0, 63);
++
++      switch (txg_calen_x) {
++      case PAIR_A:
++              reg = MTK_PHY_DA_TX_R50_PAIR_A;
++              break;
++      case PAIR_B:
++              reg = MTK_PHY_DA_TX_R50_PAIR_B;
++              break;
++      case PAIR_C:
++              reg = MTK_PHY_DA_TX_R50_PAIR_C;
++              break;
++      case PAIR_D:
++              reg = MTK_PHY_DA_TX_R50_PAIR_D;
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, reg, val | val << 8);
++
++      return 0;
++}
++
++static int tx_r50_cal_efuse(struct phy_device *phydev, u32 *buf,
++                          u8 txg_calen_x)
++{
++      u16 tx_r50_cal_val;
++
++      switch (txg_calen_x) {
++      case PAIR_A:
++              tx_r50_cal_val = EFS_DA_TX_R50_A(buf[1]);
++              break;
++      case PAIR_B:
++              tx_r50_cal_val = EFS_DA_TX_R50_B(buf[1]);
++              break;
++      case PAIR_C:
++              tx_r50_cal_val = EFS_DA_TX_R50_C(buf[2]);
++              break;
++      case PAIR_D:
++              tx_r50_cal_val = EFS_DA_TX_R50_D(buf[2]);
++              break;
++      default:
++              return -EINVAL;
++      }
++      tx_r50_fill_result(phydev, tx_r50_cal_val, txg_calen_x);
++
++      return 0;
++}
++
++static int tx_vcm_cal_sw(struct phy_device *phydev, u8 rg_txreserve_x)
++{
++      u8 lower_idx, upper_idx, txreserve_val;
++      u8 lower_ret, upper_ret;
++      int ret;
++
++      phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
++                       MTK_PHY_RG_ANA_CALEN);
++      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
++                         MTK_PHY_RG_CAL_CKINV);
++      phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
++                       MTK_PHY_RG_TXVOS_CALEN);
++
++      switch (rg_txreserve_x) {
++      case PAIR_A:
++              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
++                                 MTK_PHY_RG_DASN_DAC_IN0_A,
++                                 MTK_PHY_DASN_DAC_IN0_A_MASK);
++              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
++                                 MTK_PHY_RG_DASN_DAC_IN1_A,
++                                 MTK_PHY_DASN_DAC_IN1_A_MASK);
++              phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
++                               MTK_PHY_RG_ANA_CAL_RG0,
++                               MTK_PHY_RG_ZCALEN_A);
++              break;
++      case PAIR_B:
++              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
++                                 MTK_PHY_RG_DASN_DAC_IN0_B,
++                                 MTK_PHY_DASN_DAC_IN0_B_MASK);
++              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
++                                 MTK_PHY_RG_DASN_DAC_IN1_B,
++                                 MTK_PHY_DASN_DAC_IN1_B_MASK);
++              phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
++                               MTK_PHY_RG_ANA_CAL_RG1,
++                               MTK_PHY_RG_ZCALEN_B);
++              break;
++      case PAIR_C:
++              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
++                                 MTK_PHY_RG_DASN_DAC_IN0_C,
++                                 MTK_PHY_DASN_DAC_IN0_C_MASK);
++              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
++                                 MTK_PHY_RG_DASN_DAC_IN1_C,
++                                 MTK_PHY_DASN_DAC_IN1_C_MASK);
++              phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
++                               MTK_PHY_RG_ANA_CAL_RG1,
++                               MTK_PHY_RG_ZCALEN_C);
++              break;
++      case PAIR_D:
++              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
++                                 MTK_PHY_RG_DASN_DAC_IN0_D,
++                                 MTK_PHY_DASN_DAC_IN0_D_MASK);
++              phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
++                                 MTK_PHY_RG_DASN_DAC_IN1_D,
++                                 MTK_PHY_DASN_DAC_IN1_D_MASK);
++              phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
++                               MTK_PHY_RG_ANA_CAL_RG1,
++                               MTK_PHY_RG_ZCALEN_D);
++              break;
++      default:
++              ret = -EINVAL;
++              goto restore;
++      }
++
++      lower_idx = TXRESERVE_MIN;
++      upper_idx = TXRESERVE_MAX;
++
++      phydev_dbg(phydev, "Start TX-VCM SW cal.\n");
++      while ((upper_idx - lower_idx) > 1) {
++              txreserve_val = DIV_ROUND_CLOSEST(lower_idx + upper_idx, 2);
++              ret = cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9,
++                              MTK_PHY_DA_RX_PSBN_TBT_MASK |
++                              MTK_PHY_DA_RX_PSBN_HBT_MASK |
++                              MTK_PHY_DA_RX_PSBN_GBE_MASK |
++                              MTK_PHY_DA_RX_PSBN_LP_MASK,
++                              txreserve_val << 12 | txreserve_val << 8 |
++                              txreserve_val << 4 | txreserve_val);
++              if (ret == 1) {
++                      upper_idx = txreserve_val;
++                      upper_ret = ret;
++              } else if (ret == 0) {
++                      lower_idx = txreserve_val;
++                      lower_ret = ret;
++              } else {
++                      goto restore;
++              }
++      }
++
++      if (lower_idx == TXRESERVE_MIN) {
++              lower_ret = cal_cycle(phydev, MDIO_MMD_VEND1,
++                                    MTK_PHY_RXADC_CTRL_RG9,
++                                    MTK_PHY_DA_RX_PSBN_TBT_MASK |
++                                    MTK_PHY_DA_RX_PSBN_HBT_MASK |
++                                    MTK_PHY_DA_RX_PSBN_GBE_MASK |
++                                    MTK_PHY_DA_RX_PSBN_LP_MASK,
++                                    lower_idx << 12 | lower_idx << 8 |
++                                    lower_idx << 4 | lower_idx);
++              ret = lower_ret;
++      } else if (upper_idx == TXRESERVE_MAX) {
++              upper_ret = cal_cycle(phydev, MDIO_MMD_VEND1,
++                                    MTK_PHY_RXADC_CTRL_RG9,
++                                    MTK_PHY_DA_RX_PSBN_TBT_MASK |
++                                    MTK_PHY_DA_RX_PSBN_HBT_MASK |
++                                    MTK_PHY_DA_RX_PSBN_GBE_MASK |
++                                    MTK_PHY_DA_RX_PSBN_LP_MASK,
++                                    upper_idx << 12 | upper_idx << 8 |
++                                    upper_idx << 4 | upper_idx);
++              ret = upper_ret;
++      }
++      if (ret < 0)
++              goto restore;
++
++      /* We calibrate TX-VCM in different logic. Check upper index and then
++       * lower index. If this calibration is valid, apply lower index's
++       * result.
++       */
++      ret = upper_ret - lower_ret;
++      if (ret == 1) {
++              ret = 0;
++              /* Make sure we use upper_idx in our calibration system */
++              cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9,
++                        MTK_PHY_DA_RX_PSBN_TBT_MASK |
++                        MTK_PHY_DA_RX_PSBN_HBT_MASK |
++                        MTK_PHY_DA_RX_PSBN_GBE_MASK |
++                        MTK_PHY_DA_RX_PSBN_LP_MASK,
++                        upper_idx << 12 | upper_idx << 8 |
++                        upper_idx << 4 | upper_idx);
++              phydev_dbg(phydev, "TX-VCM SW cal result: 0x%x\n", upper_idx);
++      } else if (lower_idx == TXRESERVE_MIN && upper_ret == 1 &&
++                 lower_ret == 1) {
++              ret = 0;
++              cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9,
++                        MTK_PHY_DA_RX_PSBN_TBT_MASK |
++                        MTK_PHY_DA_RX_PSBN_HBT_MASK |
++                        MTK_PHY_DA_RX_PSBN_GBE_MASK |
++                        MTK_PHY_DA_RX_PSBN_LP_MASK,
++                        lower_idx << 12 | lower_idx << 8 |
++                        lower_idx << 4 | lower_idx);
++              phydev_warn(phydev, "TX-VCM SW cal result at low margin 0x%x\n",
++                          lower_idx);
++      } else if (upper_idx == TXRESERVE_MAX && upper_ret == 0 &&
++                 lower_ret == 0) {
++              ret = 0;
++              phydev_warn(phydev,
++                          "TX-VCM SW cal result at high margin 0x%x\n",
++                          upper_idx);
++      } else {
++              ret = -EINVAL;
++      }
++
++restore:
++      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
++                         MTK_PHY_RG_ANA_CALEN);
++      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
++                         MTK_PHY_RG_TXVOS_CALEN);
++      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
++                         MTK_PHY_RG_ZCALEN_A);
++      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
++                         MTK_PHY_RG_ZCALEN_B | MTK_PHY_RG_ZCALEN_C |
++                         MTK_PHY_RG_ZCALEN_D);
++
++      return ret;
++}
++
++static void mt798x_phy_common_finetune(struct phy_device *phydev)
++{
++      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
++      /* SlvDSPreadyTime = 24, MasDSPreadyTime = 24 */
++      __phy_write(phydev, 0x11, 0xc71);
++      __phy_write(phydev, 0x12, 0xc);
++      __phy_write(phydev, 0x10, 0x8fae);
++
++      /* EnabRandUpdTrig = 1 */
++      __phy_write(phydev, 0x11, 0x2f00);
++      __phy_write(phydev, 0x12, 0xe);
++      __phy_write(phydev, 0x10, 0x8fb0);
++
++      /* NormMseLoThresh = 85 */
++      __phy_write(phydev, 0x11, 0x55a0);
++      __phy_write(phydev, 0x12, 0x0);
++      __phy_write(phydev, 0x10, 0x83aa);
++
++      /* FfeUpdGainForce = 1(Enable), FfeUpdGainForceVal = 4 */
++      __phy_write(phydev, 0x11, 0x240);
++      __phy_write(phydev, 0x12, 0x0);
++      __phy_write(phydev, 0x10, 0x9680);
++
++      /* TrFreeze = 0 (mt7988 default) */
++      __phy_write(phydev, 0x11, 0x0);
++      __phy_write(phydev, 0x12, 0x0);
++      __phy_write(phydev, 0x10, 0x9686);
++
++      /* SSTrKp100 = 5 */
++      /* SSTrKf100 = 6 */
++      /* SSTrKp1000Mas = 5 */
++      /* SSTrKf1000Mas = 6 */
++      /* SSTrKp1000Slv = 5 */
++      /* SSTrKf1000Slv = 6 */
++      __phy_write(phydev, 0x11, 0xbaef);
++      __phy_write(phydev, 0x12, 0x2e);
++      __phy_write(phydev, 0x10, 0x968c);
++      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
++}
++
++static void mt7981_phy_finetune(struct phy_device *phydev)
++{
++      u16 val[8] = { 0x01ce, 0x01c1,
++                     0x020f, 0x0202,
++                     0x03d0, 0x03c0,
++                     0x0013, 0x0005 };
++      int i, k;
++
++      /* 100M eye finetune:
++       * Keep middle level of TX MLT3 shapper as default.
++       * Only change TX MLT3 overshoot level here.
++       */
++      for (k = 0, i = 1; i < 12; i++) {
++              if (i % 3 == 0)
++                      continue;
++              phy_write_mmd(phydev, MDIO_MMD_VEND1, i, val[k++]);
++      }
++
++      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
++      /* ResetSyncOffset = 6 */
++      __phy_write(phydev, 0x11, 0x600);
++      __phy_write(phydev, 0x12, 0x0);
++      __phy_write(phydev, 0x10, 0x8fc0);
++
++      /* VgaDecRate = 1 */
++      __phy_write(phydev, 0x11, 0x4c2a);
++      __phy_write(phydev, 0x12, 0x3e);
++      __phy_write(phydev, 0x10, 0x8fa4);
++
++      /* MrvlTrFix100Kp = 3, MrvlTrFix100Kf = 2,
++       * MrvlTrFix1000Kp = 3, MrvlTrFix1000Kf = 2
++       */
++      __phy_write(phydev, 0x11, 0xd10a);
++      __phy_write(phydev, 0x12, 0x34);
++      __phy_write(phydev, 0x10, 0x8f82);
++
++      /* VcoSlicerThreshBitsHigh */
++      __phy_write(phydev, 0x11, 0x5555);
++      __phy_write(phydev, 0x12, 0x55);
++      __phy_write(phydev, 0x10, 0x8ec0);
++      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
++
++      /* TR_OPEN_LOOP_EN = 1, lpf_x_average = 9 */
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG234,
++                     MTK_PHY_TR_OPEN_LOOP_EN_MASK |
++                     MTK_PHY_LPF_X_AVERAGE_MASK,
++                     BIT(0) | FIELD_PREP(MTK_PHY_LPF_X_AVERAGE_MASK, 0x9));
++
++      /* rg_tr_lpf_cnt_val = 512 */
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LPF_CNT_VAL, 0x200);
++
++      /* IIR2 related */
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K1_L, 0x82);
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K1_U, 0x0);
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K2_L, 0x103);
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K2_U, 0x0);
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K3_L, 0x82);
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K3_U, 0x0);
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K4_L, 0xd177);
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K4_U, 0x3);
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K5_L, 0x2c82);
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K5_U, 0xe);
++
++      /* FFE peaking */
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG27C,
++                     MTK_PHY_VGASTATE_FFE_THR_ST1_MASK, 0x1b << 8);
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG27D,
++                     MTK_PHY_VGASTATE_FFE_THR_ST2_MASK, 0x1e);
++
++      /* Disable LDO pump */
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LDO_PUMP_EN_PAIRAB, 0x0);
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LDO_PUMP_EN_PAIRCD, 0x0);
++      /* Adjust LDO output voltage */
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LDO_OUTPUT_V, 0x2222);
++}
++
++static void mt7988_phy_finetune(struct phy_device *phydev)
++{
++      u16 val[12] = { 0x0187, 0x01cd, 0x01c8, 0x0182,
++                      0x020d, 0x0206, 0x0384, 0x03d0,
++                      0x03c6, 0x030a, 0x0011, 0x0005 };
++      int i;
++
++      /* Set default MLT3 shaper first */
++      for (i = 0; i < 12; i++)
++              phy_write_mmd(phydev, MDIO_MMD_VEND1, i, val[i]);
++
++      /* TCT finetune */
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_TX_FILTER, 0x5);
++
++      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
++      /* ResetSyncOffset = 5 */
++      __phy_write(phydev, 0x11, 0x500);
++      __phy_write(phydev, 0x12, 0x0);
++      __phy_write(phydev, 0x10, 0x8fc0);
++
++      /* VgaDecRate is 1 at default on mt7988 */
++
++      /* MrvlTrFix100Kp = 6, MrvlTrFix100Kf = 7,
++       * MrvlTrFix1000Kp = 6, MrvlTrFix1000Kf = 7
++       */
++      __phy_write(phydev, 0x11, 0xb90a);
++      __phy_write(phydev, 0x12, 0x6f);
++      __phy_write(phydev, 0x10, 0x8f82);
++
++      /* RemAckCntLimitCtrl = 1 */
++      __phy_write(phydev, 0x11, 0xfbba);
++      __phy_write(phydev, 0x12, 0xc3);
++      __phy_write(phydev, 0x10, 0x87f8);
++
++      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
++
++      /* TR_OPEN_LOOP_EN = 1, lpf_x_average = 10 */
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG234,
++                     MTK_PHY_TR_OPEN_LOOP_EN_MASK |
++                     MTK_PHY_LPF_X_AVERAGE_MASK,
++                     BIT(0) | FIELD_PREP(MTK_PHY_LPF_X_AVERAGE_MASK, 0xa));
++
++      /* rg_tr_lpf_cnt_val = 1023 */
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LPF_CNT_VAL, 0x3ff);
++}
++
++static void mt798x_phy_eee(struct phy_device *phydev)
++{
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1,
++                     MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG120,
++                     MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK |
++                     MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK,
++                     FIELD_PREP(MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK, 0x0) |
++                     FIELD_PREP(MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK, 0x14));
++
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1,
++                     MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122,
++                     MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK,
++                     FIELD_PREP(MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK,
++                                0xff));
++
++      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
++                         MTK_PHY_RG_TESTMUX_ADC_CTRL,
++                         MTK_PHY_RG_TXEN_DIG_MASK);
++
++      phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
++                       MTK_PHY_RG_DEV1E_REG19b, MTK_PHY_BYPASS_DSP_LPI_READY);
++
++      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
++                         MTK_PHY_RG_DEV1E_REG234, MTK_PHY_TR_LP_IIR_EEE_EN);
++
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG238,
++                     MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK |
++                     MTK_PHY_LPI_SLV_SEND_TX_EN,
++                     FIELD_PREP(MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK, 0x120));
++
++      /* Keep MTK_PHY_LPI_SEND_LOC_TIMER as 375 */
++      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG239,
++                         MTK_PHY_LPI_TXPCS_LOC_RCV);
++
++      /* This also fixes some IoT issues, such as CH340 */
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG2C7,
++                     MTK_PHY_MAX_GAIN_MASK | MTK_PHY_MIN_GAIN_MASK,
++                     FIELD_PREP(MTK_PHY_MAX_GAIN_MASK, 0x8) |
++                     FIELD_PREP(MTK_PHY_MIN_GAIN_MASK, 0x13));
++
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG2D1,
++                     MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK,
++                     FIELD_PREP(MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK,
++                                0x33) |
++                     MTK_PHY_LPI_SKIP_SD_SLV_TR | MTK_PHY_LPI_TR_READY |
++                     MTK_PHY_LPI_VCO_EEE_STG0_EN);
++
++      phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG323,
++                       MTK_PHY_EEE_WAKE_MAS_INT_DC |
++                       MTK_PHY_EEE_WAKE_SLV_INT_DC);
++
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG324,
++                     MTK_PHY_SMI_DETCNT_MAX_MASK,
++                     FIELD_PREP(MTK_PHY_SMI_DETCNT_MAX_MASK, 0x3f) |
++                     MTK_PHY_SMI_DET_MAX_EN);
++
++      phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG326,
++                       MTK_PHY_LPI_MODE_SD_ON | MTK_PHY_RESET_RANDUPD_CNT |
++                       MTK_PHY_TREC_UPDATE_ENAB_CLR |
++                       MTK_PHY_LPI_QUIT_WAIT_DFE_SIG_DET_OFF |
++                       MTK_PHY_TR_READY_SKIP_AFE_WAKEUP);
++
++      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
++      /* Regsigdet_sel_1000 = 0 */
++      __phy_write(phydev, 0x11, 0xb);
++      __phy_write(phydev, 0x12, 0x0);
++      __phy_write(phydev, 0x10, 0x9690);
++
++      /* REG_EEE_st2TrKf1000 = 2 */
++      __phy_write(phydev, 0x11, 0x114f);
++      __phy_write(phydev, 0x12, 0x2);
++      __phy_write(phydev, 0x10, 0x969a);
++
++      /* RegEEE_slv_wake_tr_timer_tar = 6, RegEEE_slv_remtx_timer_tar = 20 */
++      __phy_write(phydev, 0x11, 0x3028);
++      __phy_write(phydev, 0x12, 0x0);
++      __phy_write(phydev, 0x10, 0x969e);
++
++      /* RegEEE_slv_wake_int_timer_tar = 8 */
++      __phy_write(phydev, 0x11, 0x5010);
++      __phy_write(phydev, 0x12, 0x0);
++      __phy_write(phydev, 0x10, 0x96a0);
++
++      /* RegEEE_trfreeze_timer2 = 586 */
++      __phy_write(phydev, 0x11, 0x24a);
++      __phy_write(phydev, 0x12, 0x0);
++      __phy_write(phydev, 0x10, 0x96a8);
++
++      /* RegEEE100Stg1_tar = 16 */
++      __phy_write(phydev, 0x11, 0x3210);
++      __phy_write(phydev, 0x12, 0x0);
++      __phy_write(phydev, 0x10, 0x96b8);
++
++      /* REGEEE_wake_slv_tr_wait_dfesigdet_en = 0 */
++      __phy_write(phydev, 0x11, 0x1463);
++      __phy_write(phydev, 0x12, 0x0);
++      __phy_write(phydev, 0x10, 0x96ca);
++
++      /* DfeTailEnableVgaThresh1000 = 27 */
++      __phy_write(phydev, 0x11, 0x36);
++      __phy_write(phydev, 0x12, 0x0);
++      __phy_write(phydev, 0x10, 0x8f80);
++      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
++
++      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_3);
++      __phy_modify(phydev, MTK_PHY_LPI_REG_14,
++                   MTK_PHY_LPI_WAKE_TIMER_1000_MASK,
++                   FIELD_PREP(MTK_PHY_LPI_WAKE_TIMER_1000_MASK, 0x19c));
++
++      __phy_modify(phydev, MTK_PHY_LPI_REG_1c, MTK_PHY_SMI_DET_ON_THRESH_MASK,
++                   FIELD_PREP(MTK_PHY_SMI_DET_ON_THRESH_MASK, 0xc));
++      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
++
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1,
++                     MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122,
++                     MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK,
++                     FIELD_PREP(MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK,
++                                0xff));
++}
++
++static int cal_sw(struct phy_device *phydev, enum CAL_ITEM cal_item,
++                u8 start_pair, u8 end_pair)
++{
++      u8 pair_n;
++      int ret;
++
++      for (pair_n = start_pair; pair_n <= end_pair; pair_n++) {
++              /* TX_OFFSET & TX_AMP have no SW calibration. */
++              switch (cal_item) {
++              case TX_VCM:
++                      ret = tx_vcm_cal_sw(phydev, pair_n);
++                      break;
++              default:
++                      return -EINVAL;
++              }
++              if (ret)
++                      return ret;
++      }
++      return 0;
++}
++
++static int cal_efuse(struct phy_device *phydev, enum CAL_ITEM cal_item,
++                   u8 start_pair, u8 end_pair, u32 *buf)
++{
++      u8 pair_n;
++      int ret;
++
++      for (pair_n = start_pair; pair_n <= end_pair; pair_n++) {
++              /* TX_VCM has no efuse calibration. */
++              switch (cal_item) {
++              case REXT:
++                      ret = rext_cal_efuse(phydev, buf);
++                      break;
++              case TX_OFFSET:
++                      ret = tx_offset_cal_efuse(phydev, buf);
++                      break;
++              case TX_AMP:
++                      ret = tx_amp_cal_efuse(phydev, buf);
++                      break;
++              case TX_R50:
++                      ret = tx_r50_cal_efuse(phydev, buf, pair_n);
++                      break;
++              default:
++                      return -EINVAL;
++              }
++              if (ret)
++                      return ret;
++      }
++
++      return 0;
++}
++
++static int start_cal(struct phy_device *phydev, enum CAL_ITEM cal_item,
++                   enum CAL_MODE cal_mode, u8 start_pair,
++                   u8 end_pair, u32 *buf)
++{
++      int ret;
++
++      switch (cal_mode) {
++      case EFUSE_M:
++              ret = cal_efuse(phydev, cal_item, start_pair,
++                              end_pair, buf);
++              break;
++      case SW_M:
++              ret = cal_sw(phydev, cal_item, start_pair, end_pair);
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      if (ret) {
++              phydev_err(phydev, "cal %d failed\n", cal_item);
++              return -EIO;
++      }
++
++      return 0;
++}
++
++static int mt798x_phy_calibration(struct phy_device *phydev)
++{
++      struct nvmem_cell *cell;
++      int ret = 0;
++      size_t len;
++      u32 *buf;
++
++      cell = nvmem_cell_get(&phydev->mdio.dev, "phy-cal-data");
++      if (IS_ERR(cell)) {
++              if (PTR_ERR(cell) == -EPROBE_DEFER)
++                      return PTR_ERR(cell);
++              return 0;
++      }
++
++      buf = (u32 *)nvmem_cell_read(cell, &len);
++      if (IS_ERR(buf))
++              return PTR_ERR(buf);
++      nvmem_cell_put(cell);
++
++      if (!buf[0] || !buf[1] || !buf[2] || !buf[3] || len < 4 * sizeof(u32)) {
++              phydev_err(phydev, "invalid efuse data\n");
++              ret = -EINVAL;
++              goto out;
++      }
++
++      ret = start_cal(phydev, REXT, EFUSE_M, NO_PAIR, NO_PAIR, buf);
++      if (ret)
++              goto out;
++      ret = start_cal(phydev, TX_OFFSET, EFUSE_M, NO_PAIR, NO_PAIR, buf);
++      if (ret)
++              goto out;
++      ret = start_cal(phydev, TX_AMP, EFUSE_M, NO_PAIR, NO_PAIR, buf);
++      if (ret)
++              goto out;
++      ret = start_cal(phydev, TX_R50, EFUSE_M, PAIR_A, PAIR_D, buf);
++      if (ret)
++              goto out;
++      ret = start_cal(phydev, TX_VCM, SW_M, PAIR_A, PAIR_A, buf);
++      if (ret)
++              goto out;
++
++out:
++      kfree(buf);
++      return ret;
++}
++
++static int mt798x_phy_config_init(struct phy_device *phydev)
++{
++      switch (phydev->drv->phy_id) {
++      case MTK_GPHY_ID_MT7981:
++              mt7981_phy_finetune(phydev);
++              break;
++      case MTK_GPHY_ID_MT7988:
++              mt7988_phy_finetune(phydev);
++              break;
++      }
++
++      mt798x_phy_common_finetune(phydev);
++      mt798x_phy_eee(phydev);
++
++      return mt798x_phy_calibration(phydev);
++}
++
++static int mt798x_phy_hw_led_on_set(struct phy_device *phydev, u8 index,
++                                  bool on)
++{
++      unsigned int bit_on = MTK_PHY_LED_STATE_FORCE_ON + (index ? 16 : 0);
++      struct mtk_socphy_priv *priv = phydev->priv;
++      bool changed;
++
++      if (on)
++              changed = !test_and_set_bit(bit_on, &priv->led_state);
++      else
++              changed = !!test_and_clear_bit(bit_on, &priv->led_state);
++
++      changed |= !!test_and_clear_bit(MTK_PHY_LED_STATE_NETDEV +
++                                      (index ? 16 : 0), &priv->led_state);
++      if (changed)
++              return phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
++                                    MTK_PHY_LED1_ON_CTRL :
++                                    MTK_PHY_LED0_ON_CTRL,
++                                    MTK_PHY_LED_ON_MASK,
++                                    on ? MTK_PHY_LED_ON_FORCE_ON : 0);
++      else
++              return 0;
++}
++
++static int mt798x_phy_hw_led_blink_set(struct phy_device *phydev, u8 index,
++                                     bool blinking)
++{
++      unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK +
++                               (index ? 16 : 0);
++      struct mtk_socphy_priv *priv = phydev->priv;
++      bool changed;
++
++      if (blinking)
++              changed = !test_and_set_bit(bit_blink, &priv->led_state);
++      else
++              changed = !!test_and_clear_bit(bit_blink, &priv->led_state);
++
++      changed |= !!test_bit(MTK_PHY_LED_STATE_NETDEV +
++                            (index ? 16 : 0), &priv->led_state);
++      if (changed)
++              return phy_write_mmd(phydev, MDIO_MMD_VEND2, index ?
++                                   MTK_PHY_LED1_BLINK_CTRL :
++                                   MTK_PHY_LED0_BLINK_CTRL,
++                                   blinking ?
++                                   MTK_PHY_LED_BLINK_FORCE_BLINK : 0);
++      else
++              return 0;
++}
++
++static int mt798x_phy_led_blink_set(struct phy_device *phydev, u8 index,
++                                  unsigned long *delay_on,
++                                  unsigned long *delay_off)
++{
++      bool blinking = false;
++      int err = 0;
++
++      if (index > 1)
++              return -EINVAL;
++
++      if (delay_on && delay_off && (*delay_on > 0) && (*delay_off > 0)) {
++              blinking = true;
++              *delay_on = 50;
++              *delay_off = 50;
++      }
++
++      err = mt798x_phy_hw_led_blink_set(phydev, index, blinking);
++      if (err)
++              return err;
++
++      return mt798x_phy_hw_led_on_set(phydev, index, false);
++}
++
++static int mt798x_phy_led_brightness_set(struct phy_device *phydev,
++                                       u8 index, enum led_brightness value)
++{
++      int err;
++
++      err = mt798x_phy_hw_led_blink_set(phydev, index, false);
++      if (err)
++              return err;
++
++      return mt798x_phy_hw_led_on_set(phydev, index, (value != LED_OFF));
++}
++
++static const unsigned long supported_triggers =
++      BIT(TRIGGER_NETDEV_FULL_DUPLEX) |
++      BIT(TRIGGER_NETDEV_HALF_DUPLEX) |
++      BIT(TRIGGER_NETDEV_LINK)        |
++      BIT(TRIGGER_NETDEV_LINK_10)     |
++      BIT(TRIGGER_NETDEV_LINK_100)    |
++      BIT(TRIGGER_NETDEV_LINK_1000)   |
++      BIT(TRIGGER_NETDEV_RX)          |
++      BIT(TRIGGER_NETDEV_TX);
++
++static int mt798x_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
++                                        unsigned long rules)
++{
++      if (index > 1)
++              return -EINVAL;
++
++      /* All combinations of the supported triggers are allowed */
++      if (rules & ~supported_triggers)
++              return -EOPNOTSUPP;
++
++      return 0;
++};
++
++static int mt798x_phy_led_hw_control_get(struct phy_device *phydev, u8 index,
++                                       unsigned long *rules)
++{
++      unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK +
++                               (index ? 16 : 0);
++      unsigned int bit_netdev = MTK_PHY_LED_STATE_NETDEV + (index ? 16 : 0);
++      unsigned int bit_on = MTK_PHY_LED_STATE_FORCE_ON + (index ? 16 : 0);
++      struct mtk_socphy_priv *priv = phydev->priv;
++      int on, blink;
++
++      if (index > 1)
++              return -EINVAL;
++
++      on = phy_read_mmd(phydev, MDIO_MMD_VEND2,
++                        index ? MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL);
++
++      if (on < 0)
++              return -EIO;
++
++      blink = phy_read_mmd(phydev, MDIO_MMD_VEND2,
++                           index ? MTK_PHY_LED1_BLINK_CTRL :
++                                   MTK_PHY_LED0_BLINK_CTRL);
++      if (blink < 0)
++              return -EIO;
++
++      if ((on & (MTK_PHY_LED_ON_LINK | MTK_PHY_LED_ON_FDX |
++                 MTK_PHY_LED_ON_HDX | MTK_PHY_LED_ON_LINKDOWN)) ||
++          (blink & (MTK_PHY_LED_BLINK_RX | MTK_PHY_LED_BLINK_TX)))
++              set_bit(bit_netdev, &priv->led_state);
++      else
++              clear_bit(bit_netdev, &priv->led_state);
++
++      if (on & MTK_PHY_LED_ON_FORCE_ON)
++              set_bit(bit_on, &priv->led_state);
++      else
++              clear_bit(bit_on, &priv->led_state);
++
++      if (blink & MTK_PHY_LED_BLINK_FORCE_BLINK)
++              set_bit(bit_blink, &priv->led_state);
++      else
++              clear_bit(bit_blink, &priv->led_state);
++
++      if (!rules)
++              return 0;
++
++      if (on & MTK_PHY_LED_ON_LINK)
++              *rules |= BIT(TRIGGER_NETDEV_LINK);
++
++      if (on & MTK_PHY_LED_ON_LINK10)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_10);
++
++      if (on & MTK_PHY_LED_ON_LINK100)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_100);
++
++      if (on & MTK_PHY_LED_ON_LINK1000)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_1000);
++
++      if (on & MTK_PHY_LED_ON_FDX)
++              *rules |= BIT(TRIGGER_NETDEV_FULL_DUPLEX);
++
++      if (on & MTK_PHY_LED_ON_HDX)
++              *rules |= BIT(TRIGGER_NETDEV_HALF_DUPLEX);
++
++      if (blink & MTK_PHY_LED_BLINK_RX)
++              *rules |= BIT(TRIGGER_NETDEV_RX);
++
++      if (blink & MTK_PHY_LED_BLINK_TX)
++              *rules |= BIT(TRIGGER_NETDEV_TX);
++
++      return 0;
++};
++
++static int mt798x_phy_led_hw_control_set(struct phy_device *phydev, u8 index,
++                                       unsigned long rules)
++{
++      unsigned int bit_netdev = MTK_PHY_LED_STATE_NETDEV + (index ? 16 : 0);
++      struct mtk_socphy_priv *priv = phydev->priv;
++      u16 on = 0, blink = 0;
++      int ret;
++
++      if (index > 1)
++              return -EINVAL;
++
++      if (rules & BIT(TRIGGER_NETDEV_FULL_DUPLEX))
++              on |= MTK_PHY_LED_ON_FDX;
++
++      if (rules & BIT(TRIGGER_NETDEV_HALF_DUPLEX))
++              on |= MTK_PHY_LED_ON_HDX;
++
++      if (rules & (BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK)))
++              on |= MTK_PHY_LED_ON_LINK10;
++
++      if (rules & (BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK)))
++              on |= MTK_PHY_LED_ON_LINK100;
++
++      if (rules & (BIT(TRIGGER_NETDEV_LINK_1000) | BIT(TRIGGER_NETDEV_LINK)))
++              on |= MTK_PHY_LED_ON_LINK1000;
++
++      if (rules & BIT(TRIGGER_NETDEV_RX)) {
++              blink |= (on & MTK_PHY_LED_ON_LINK) ?
++                        (((on & MTK_PHY_LED_ON_LINK10) ?
++                          MTK_PHY_LED_BLINK_10RX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK100) ?
++                          MTK_PHY_LED_BLINK_100RX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK1000) ?
++                          MTK_PHY_LED_BLINK_1000RX : 0)) :
++                        MTK_PHY_LED_BLINK_RX;
++      }
++
++      if (rules & BIT(TRIGGER_NETDEV_TX)) {
++              blink |= (on & MTK_PHY_LED_ON_LINK) ?
++                        (((on & MTK_PHY_LED_ON_LINK10) ?
++                          MTK_PHY_LED_BLINK_10TX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK100) ?
++                          MTK_PHY_LED_BLINK_100TX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK1000) ?
++                          MTK_PHY_LED_BLINK_1000TX : 0)) :
++                        MTK_PHY_LED_BLINK_TX;
++      }
++
++      if (blink || on)
++              set_bit(bit_netdev, &priv->led_state);
++      else
++              clear_bit(bit_netdev, &priv->led_state);
++
++      ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
++                              MTK_PHY_LED1_ON_CTRL :
++                              MTK_PHY_LED0_ON_CTRL,
++                           MTK_PHY_LED_ON_FDX     |
++                           MTK_PHY_LED_ON_HDX     |
++                           MTK_PHY_LED_ON_LINK,
++                           on);
++
++      if (ret)
++              return ret;
++
++      return phy_write_mmd(phydev, MDIO_MMD_VEND2, index ?
++                              MTK_PHY_LED1_BLINK_CTRL :
++                              MTK_PHY_LED0_BLINK_CTRL, blink);
++};
++
++static bool mt7988_phy_led_get_polarity(struct phy_device *phydev, int led_num)
++{
++      struct mtk_socphy_shared *priv = phydev->shared->priv;
++      u32 polarities;
++
++      if (led_num == 0)
++              polarities = ~(priv->boottrap);
++      else
++              polarities = MTK_PHY_LED1_DEFAULT_POLARITIES;
++
++      if (polarities & BIT(phydev->mdio.addr))
++              return true;
++
++      return false;
++}
++
++static int mt7988_phy_fix_leds_polarities(struct phy_device *phydev)
++{
++      struct pinctrl *pinctrl;
++      int index;
++
++      /* Setup LED polarity according to bootstrap use of LED pins */
++      for (index = 0; index < 2; ++index)
++              phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
++                              MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL,
++                             MTK_PHY_LED_ON_POLARITY,
++                             mt7988_phy_led_get_polarity(phydev, index) ?
++                              MTK_PHY_LED_ON_POLARITY : 0);
++
++      /* Only now setup pinctrl to avoid bogus blinking */
++      pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "gbe-led");
++      if (IS_ERR(pinctrl))
++              dev_err(&phydev->mdio.bus->dev,
++                      "Failed to setup PHY LED pinctrl\n");
++
++      return 0;
++}
++
++static int mt7988_phy_probe_shared(struct phy_device *phydev)
++{
++      struct device_node *np = dev_of_node(&phydev->mdio.bus->dev);
++      struct mtk_socphy_shared *shared = phydev->shared->priv;
++      struct regmap *regmap;
++      u32 reg;
++      int ret;
++
++      /* The LED0 of the 4 PHYs in MT7988 are wired to SoC pins LED_A, LED_B,
++       * LED_C and LED_D respectively. At the same time those pins are used to
++       * bootstrap configuration of the reference clock source (LED_A),
++       * DRAM DDRx16b x2/x1 (LED_B) and boot device (LED_C, LED_D).
++       * In practice this is done using a LED and a resistor pulling the pin
++       * either to GND or to VIO.
++       * The detected value at boot time is accessible at run-time using the
++       * TPBANK0 register located in the gpio base of the pinctrl, in order
++       * to read it here it needs to be referenced by a phandle called
++       * 'mediatek,pio' in the MDIO bus hosting the PHY.
++       * The 4 bits in TPBANK0 are kept as package shared data and are used to
++       * set LED polarity for each of the LED0.
++       */
++      regmap = syscon_regmap_lookup_by_phandle(np, "mediatek,pio");
++      if (IS_ERR(regmap))
++              return PTR_ERR(regmap);
++
++      ret = regmap_read(regmap, RG_GPIO_MISC_TPBANK0, &reg);
++      if (ret)
++              return ret;
++
++      shared->boottrap = FIELD_GET(RG_GPIO_MISC_TPBANK0_BOOTMODE, reg);
++
++      return 0;
++}
++
++static void mt798x_phy_leds_state_init(struct phy_device *phydev)
++{
++      int i;
++
++      for (i = 0; i < 2; ++i)
++              mt798x_phy_led_hw_control_get(phydev, i, NULL);
++}
++
++static int mt7988_phy_probe(struct phy_device *phydev)
++{
++      struct mtk_socphy_shared *shared;
++      struct mtk_socphy_priv *priv;
++      int err;
++
++      if (phydev->mdio.addr > 3)
++              return -EINVAL;
++
++      err = devm_phy_package_join(&phydev->mdio.dev, phydev, 0,
++                                  sizeof(struct mtk_socphy_shared));
++      if (err)
++              return err;
++
++      if (phy_package_probe_once(phydev)) {
++              err = mt7988_phy_probe_shared(phydev);
++              if (err)
++                      return err;
++      }
++
++      shared = phydev->shared->priv;
++      priv = &shared->priv[phydev->mdio.addr];
++
++      phydev->priv = priv;
++
++      mt798x_phy_leds_state_init(phydev);
++
++      err = mt7988_phy_fix_leds_polarities(phydev);
++      if (err)
++              return err;
++
++      /* Disable TX power saving at probing to:
++       * 1. Meet common mode compliance test criteria
++       * 2. Make sure that TX-VCM calibration works fine
++       */
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG7,
++                     MTK_PHY_DA_AD_BUF_BIAS_LP_MASK, 0x3 << 8);
++
++      return mt798x_phy_calibration(phydev);
++}
++
++static int mt7981_phy_probe(struct phy_device *phydev)
++{
++      struct mtk_socphy_priv *priv;
++
++      priv = devm_kzalloc(&phydev->mdio.dev, sizeof(struct mtk_socphy_priv),
++                          GFP_KERNEL);
++      if (!priv)
++              return -ENOMEM;
++
++      phydev->priv = priv;
++
++      mt798x_phy_leds_state_init(phydev);
++
++      return mt798x_phy_calibration(phydev);
++}
++
++static struct phy_driver mtk_socphy_driver[] = {
++      {
++              PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981),
++              .name           = "MediaTek MT7981 PHY",
++              .config_init    = mt798x_phy_config_init,
++              .config_intr    = genphy_no_config_intr,
++              .handle_interrupt = genphy_handle_interrupt_no_ack,
++              .probe          = mt7981_phy_probe,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++              .read_page      = mtk_socphy_read_page,
++              .write_page     = mtk_socphy_write_page,
++              .led_blink_set  = mt798x_phy_led_blink_set,
++              .led_brightness_set = mt798x_phy_led_brightness_set,
++              .led_hw_is_supported = mt798x_phy_led_hw_is_supported,
++              .led_hw_control_set = mt798x_phy_led_hw_control_set,
++              .led_hw_control_get = mt798x_phy_led_hw_control_get,
++      },
++      {
++              PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988),
++              .name           = "MediaTek MT7988 PHY",
++              .config_init    = mt798x_phy_config_init,
++              .config_intr    = genphy_no_config_intr,
++              .handle_interrupt = genphy_handle_interrupt_no_ack,
++              .probe          = mt7988_phy_probe,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++              .read_page      = mtk_socphy_read_page,
++              .write_page     = mtk_socphy_write_page,
++              .led_blink_set  = mt798x_phy_led_blink_set,
++              .led_brightness_set = mt798x_phy_led_brightness_set,
++              .led_hw_is_supported = mt798x_phy_led_hw_is_supported,
++              .led_hw_control_set = mt798x_phy_led_hw_control_set,
++              .led_hw_control_get = mt798x_phy_led_hw_control_get,
++      },
++};
++
++module_phy_driver(mtk_socphy_driver);
++
++static struct mdio_device_id __maybe_unused mtk_socphy_tbl[] = {
++      { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981) },
++      { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988) },
++      { }
++};
++
++MODULE_DESCRIPTION("MediaTek SoC Gigabit Ethernet PHY driver");
++MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
++MODULE_AUTHOR("SkyLake Huang <SkyLake.Huang@mediatek.com>");
++MODULE_LICENSE("GPL");
++
++MODULE_DEVICE_TABLE(mdio, mtk_socphy_tbl);
+--- /dev/null
++++ b/drivers/net/phy/mediatek/mtk-ge.c
+@@ -0,0 +1,111 @@
++// SPDX-License-Identifier: GPL-2.0+
++#include <linux/bitfield.h>
++#include <linux/module.h>
++#include <linux/phy.h>
++
++#define MTK_EXT_PAGE_ACCESS           0x1f
++#define MTK_PHY_PAGE_STANDARD         0x0000
++#define MTK_PHY_PAGE_EXTENDED         0x0001
++#define MTK_PHY_PAGE_EXTENDED_2               0x0002
++#define MTK_PHY_PAGE_EXTENDED_3               0x0003
++#define MTK_PHY_PAGE_EXTENDED_2A30    0x2a30
++#define MTK_PHY_PAGE_EXTENDED_52B5    0x52b5
++
++static int mtk_gephy_read_page(struct phy_device *phydev)
++{
++      return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
++}
++
++static int mtk_gephy_write_page(struct phy_device *phydev, int page)
++{
++      return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page);
++}
++
++static void mtk_gephy_config_init(struct phy_device *phydev)
++{
++      /* Enable HW auto downshift */
++      phy_modify_paged(phydev, MTK_PHY_PAGE_EXTENDED, 0x14, 0, BIT(4));
++
++      /* Increase SlvDPSready time */
++      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
++      __phy_write(phydev, 0x10, 0xafae);
++      __phy_write(phydev, 0x12, 0x2f);
++      __phy_write(phydev, 0x10, 0x8fae);
++      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
++
++      /* Adjust 100_mse_threshold */
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x123, 0xffff);
++
++      /* Disable mcc */
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xa6, 0x300);
++}
++
++static int mt7530_phy_config_init(struct phy_device *phydev)
++{
++      mtk_gephy_config_init(phydev);
++
++      /* Increase post_update_timer */
++      phy_write_paged(phydev, MTK_PHY_PAGE_EXTENDED_3, 0x11, 0x4b);
++
++      return 0;
++}
++
++static int mt7531_phy_config_init(struct phy_device *phydev)
++{
++      mtk_gephy_config_init(phydev);
++
++      /* PHY link down power saving enable */
++      phy_set_bits(phydev, 0x17, BIT(4));
++      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0xc6, 0x300);
++
++      /* Set TX Pair delay selection */
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x13, 0x404);
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x14, 0x404);
++
++      return 0;
++}
++
++static struct phy_driver mtk_gephy_driver[] = {
++      {
++              PHY_ID_MATCH_EXACT(0x03a29412),
++              .name           = "MediaTek MT7530 PHY",
++              .config_init    = mt7530_phy_config_init,
++              /* Interrupts are handled by the switch, not the PHY
++               * itself.
++               */
++              .config_intr    = genphy_no_config_intr,
++              .handle_interrupt = genphy_handle_interrupt_no_ack,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++              .read_page      = mtk_gephy_read_page,
++              .write_page     = mtk_gephy_write_page,
++      },
++      {
++              PHY_ID_MATCH_EXACT(0x03a29441),
++              .name           = "MediaTek MT7531 PHY",
++              .config_init    = mt7531_phy_config_init,
++              /* Interrupts are handled by the switch, not the PHY
++               * itself.
++               */
++              .config_intr    = genphy_no_config_intr,
++              .handle_interrupt = genphy_handle_interrupt_no_ack,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++              .read_page      = mtk_gephy_read_page,
++              .write_page     = mtk_gephy_write_page,
++      },
++};
++
++module_phy_driver(mtk_gephy_driver);
++
++static struct mdio_device_id __maybe_unused mtk_gephy_tbl[] = {
++      { PHY_ID_MATCH_EXACT(0x03a29441) },
++      { PHY_ID_MATCH_EXACT(0x03a29412) },
++      { }
++};
++
++MODULE_DESCRIPTION("MediaTek Gigabit Ethernet PHY driver");
++MODULE_AUTHOR("DENG, Qingfang <dqfext@gmail.com>");
++MODULE_LICENSE("GPL");
++
++MODULE_DEVICE_TABLE(mdio, mtk_gephy_tbl);
diff --git a/target/linux/generic/backport-6.12/720-05-v6.13-net-phy-mediatek-Move-LED-helper-functions-into-mtk-.patch b/target/linux/generic/backport-6.12/720-05-v6.13-net-phy-mediatek-Move-LED-helper-functions-into-mtk-.patch
new file mode 100644 (file)
index 0000000..63407ac
--- /dev/null
@@ -0,0 +1,774 @@
+From 71d88c7409b91c853d7f9c933f5e27933d656e5e Mon Sep 17 00:00:00 2001
+From: "SkyLake.Huang" <skylake.huang@mediatek.com>
+Date: Sat, 9 Nov 2024 00:34:52 +0800
+Subject: [PATCH 05/20] net: phy: mediatek: Move LED helper functions into mtk
+ phy lib
+
+This patch creates mtk-phy-lib.c & mtk-phy.h and integrates mtk-ge-soc.c's
+LED helper functions so that we can use those helper functions in other
+MTK's ethernet phy driver.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: SkyLake.Huang <skylake.huang@mediatek.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ MAINTAINERS                            |   2 +
+ drivers/net/phy/mediatek/Kconfig       |   4 +
+ drivers/net/phy/mediatek/Makefile      |   1 +
+ drivers/net/phy/mediatek/mtk-ge-soc.c  | 280 +++----------------------
+ drivers/net/phy/mediatek/mtk-phy-lib.c | 254 ++++++++++++++++++++++
+ drivers/net/phy/mediatek/mtk.h         |  86 ++++++++
+ 6 files changed, 372 insertions(+), 255 deletions(-)
+ create mode 100644 drivers/net/phy/mediatek/mtk-phy-lib.c
+ create mode 100644 drivers/net/phy/mediatek/mtk.h
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -14428,7 +14428,9 @@ M:     SkyLake Huang <SkyLake.Huang@mediatek
+ L:    netdev@vger.kernel.org
+ S:    Maintained
+ F:    drivers/net/phy/mediatek/mtk-ge-soc.c
++F:    drivers/net/phy/mediatek/mtk-phy-lib.c
+ F:    drivers/net/phy/mediatek/mtk-ge.c
++F:    drivers/net/phy/mediatek/mtk.h
+ F:    drivers/phy/mediatek/phy-mtk-xfi-tphy.c
+ MEDIATEK I2C CONTROLLER DRIVER
+--- a/drivers/net/phy/mediatek/Kconfig
++++ b/drivers/net/phy/mediatek/Kconfig
+@@ -1,4 +1,7 @@
+ # SPDX-License-Identifier: GPL-2.0-only
++config MTK_NET_PHYLIB
++      tristate
++
+ config MEDIATEK_GE_PHY
+       tristate "MediaTek Gigabit Ethernet PHYs"
+       help
+@@ -13,6 +16,7 @@ config MEDIATEK_GE_SOC_PHY
+       tristate "MediaTek SoC Ethernet PHYs"
+       depends on (ARM64 && ARCH_MEDIATEK) || COMPILE_TEST
+       depends on NVMEM_MTK_EFUSE
++      select MTK_NET_PHYLIB
+       help
+         Supports MediaTek SoC built-in Gigabit Ethernet PHYs.
+--- a/drivers/net/phy/mediatek/Makefile
++++ b/drivers/net/phy/mediatek/Makefile
+@@ -1,3 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0
++obj-$(CONFIG_MTK_NET_PHYLIB)          += mtk-phy-lib.o
+ obj-$(CONFIG_MEDIATEK_GE_PHY)         += mtk-ge.o
+ obj-$(CONFIG_MEDIATEK_GE_SOC_PHY)     += mtk-ge-soc.o
+--- a/drivers/net/phy/mediatek/mtk-ge-soc.c
++++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
+@@ -8,6 +8,8 @@
+ #include <linux/phy.h>
+ #include <linux/regmap.h>
++#include "mtk.h"
++
+ #define MTK_GPHY_ID_MT7981                    0x03a29461
+ #define MTK_GPHY_ID_MT7988                    0x03a29481
+@@ -210,41 +212,6 @@
+ #define MTK_PHY_DA_TX_R50_PAIR_D              0x540
+ /* Registers on MDIO_MMD_VEND2 */
+-#define MTK_PHY_LED0_ON_CTRL                  0x24
+-#define MTK_PHY_LED1_ON_CTRL                  0x26
+-#define   MTK_PHY_LED_ON_MASK                 GENMASK(6, 0)
+-#define   MTK_PHY_LED_ON_LINK1000             BIT(0)
+-#define   MTK_PHY_LED_ON_LINK100              BIT(1)
+-#define   MTK_PHY_LED_ON_LINK10                       BIT(2)
+-#define   MTK_PHY_LED_ON_LINK                 (MTK_PHY_LED_ON_LINK10 |\
+-                                               MTK_PHY_LED_ON_LINK100 |\
+-                                               MTK_PHY_LED_ON_LINK1000)
+-#define   MTK_PHY_LED_ON_LINKDOWN             BIT(3)
+-#define   MTK_PHY_LED_ON_FDX                  BIT(4) /* Full duplex */
+-#define   MTK_PHY_LED_ON_HDX                  BIT(5) /* Half duplex */
+-#define   MTK_PHY_LED_ON_FORCE_ON             BIT(6)
+-#define   MTK_PHY_LED_ON_POLARITY             BIT(14)
+-#define   MTK_PHY_LED_ON_ENABLE                       BIT(15)
+-
+-#define MTK_PHY_LED0_BLINK_CTRL                       0x25
+-#define MTK_PHY_LED1_BLINK_CTRL                       0x27
+-#define   MTK_PHY_LED_BLINK_1000TX            BIT(0)
+-#define   MTK_PHY_LED_BLINK_1000RX            BIT(1)
+-#define   MTK_PHY_LED_BLINK_100TX             BIT(2)
+-#define   MTK_PHY_LED_BLINK_100RX             BIT(3)
+-#define   MTK_PHY_LED_BLINK_10TX              BIT(4)
+-#define   MTK_PHY_LED_BLINK_10RX              BIT(5)
+-#define   MTK_PHY_LED_BLINK_RX                        (MTK_PHY_LED_BLINK_10RX |\
+-                                               MTK_PHY_LED_BLINK_100RX |\
+-                                               MTK_PHY_LED_BLINK_1000RX)
+-#define   MTK_PHY_LED_BLINK_TX                        (MTK_PHY_LED_BLINK_10TX |\
+-                                               MTK_PHY_LED_BLINK_100TX |\
+-                                               MTK_PHY_LED_BLINK_1000TX)
+-#define   MTK_PHY_LED_BLINK_COLLISION         BIT(6)
+-#define   MTK_PHY_LED_BLINK_RX_CRC_ERR                BIT(7)
+-#define   MTK_PHY_LED_BLINK_RX_IDLE_ERR               BIT(8)
+-#define   MTK_PHY_LED_BLINK_FORCE_BLINK               BIT(9)
+-
+ #define MTK_PHY_LED1_DEFAULT_POLARITIES               BIT(1)
+ #define MTK_PHY_RG_BG_RASEL                   0x115
+@@ -299,14 +266,6 @@ enum CAL_MODE {
+       SW_M
+ };
+-#define MTK_PHY_LED_STATE_FORCE_ON    0
+-#define MTK_PHY_LED_STATE_FORCE_BLINK 1
+-#define MTK_PHY_LED_STATE_NETDEV      2
+-
+-struct mtk_socphy_priv {
+-      unsigned long           led_state;
+-};
+-
+ struct mtk_socphy_shared {
+       u32                     boottrap;
+       struct mtk_socphy_priv  priv[4];
+@@ -1172,76 +1131,23 @@ static int mt798x_phy_config_init(struct
+       return mt798x_phy_calibration(phydev);
+ }
+-static int mt798x_phy_hw_led_on_set(struct phy_device *phydev, u8 index,
+-                                  bool on)
+-{
+-      unsigned int bit_on = MTK_PHY_LED_STATE_FORCE_ON + (index ? 16 : 0);
+-      struct mtk_socphy_priv *priv = phydev->priv;
+-      bool changed;
+-
+-      if (on)
+-              changed = !test_and_set_bit(bit_on, &priv->led_state);
+-      else
+-              changed = !!test_and_clear_bit(bit_on, &priv->led_state);
+-
+-      changed |= !!test_and_clear_bit(MTK_PHY_LED_STATE_NETDEV +
+-                                      (index ? 16 : 0), &priv->led_state);
+-      if (changed)
+-              return phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
+-                                    MTK_PHY_LED1_ON_CTRL :
+-                                    MTK_PHY_LED0_ON_CTRL,
+-                                    MTK_PHY_LED_ON_MASK,
+-                                    on ? MTK_PHY_LED_ON_FORCE_ON : 0);
+-      else
+-              return 0;
+-}
+-
+-static int mt798x_phy_hw_led_blink_set(struct phy_device *phydev, u8 index,
+-                                     bool blinking)
+-{
+-      unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK +
+-                               (index ? 16 : 0);
+-      struct mtk_socphy_priv *priv = phydev->priv;
+-      bool changed;
+-
+-      if (blinking)
+-              changed = !test_and_set_bit(bit_blink, &priv->led_state);
+-      else
+-              changed = !!test_and_clear_bit(bit_blink, &priv->led_state);
+-
+-      changed |= !!test_bit(MTK_PHY_LED_STATE_NETDEV +
+-                            (index ? 16 : 0), &priv->led_state);
+-      if (changed)
+-              return phy_write_mmd(phydev, MDIO_MMD_VEND2, index ?
+-                                   MTK_PHY_LED1_BLINK_CTRL :
+-                                   MTK_PHY_LED0_BLINK_CTRL,
+-                                   blinking ?
+-                                   MTK_PHY_LED_BLINK_FORCE_BLINK : 0);
+-      else
+-              return 0;
+-}
+-
+ static int mt798x_phy_led_blink_set(struct phy_device *phydev, u8 index,
+                                   unsigned long *delay_on,
+                                   unsigned long *delay_off)
+ {
+       bool blinking = false;
+-      int err = 0;
+-
+-      if (index > 1)
+-              return -EINVAL;
++      int err;
+-      if (delay_on && delay_off && (*delay_on > 0) && (*delay_off > 0)) {
+-              blinking = true;
+-              *delay_on = 50;
+-              *delay_off = 50;
+-      }
++      err = mtk_phy_led_num_dly_cfg(index, delay_on, delay_off, &blinking);
++      if (err < 0)
++              return err;
+-      err = mt798x_phy_hw_led_blink_set(phydev, index, blinking);
++      err = mtk_phy_hw_led_blink_set(phydev, index, blinking);
+       if (err)
+               return err;
+-      return mt798x_phy_hw_led_on_set(phydev, index, false);
++      return mtk_phy_hw_led_on_set(phydev, index, MTK_GPHY_LED_ON_MASK,
++                                   false);
+ }
+ static int mt798x_phy_led_brightness_set(struct phy_device *phydev,
+@@ -1249,11 +1155,12 @@ static int mt798x_phy_led_brightness_set
+ {
+       int err;
+-      err = mt798x_phy_hw_led_blink_set(phydev, index, false);
++      err = mtk_phy_hw_led_blink_set(phydev, index, false);
+       if (err)
+               return err;
+-      return mt798x_phy_hw_led_on_set(phydev, index, (value != LED_OFF));
++      return mtk_phy_hw_led_on_set(phydev, index, MTK_GPHY_LED_ON_MASK,
++                                   (value != LED_OFF));
+ }
+ static const unsigned long supported_triggers =
+@@ -1269,155 +1176,26 @@ static const unsigned long supported_tri
+ static int mt798x_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
+                                         unsigned long rules)
+ {
+-      if (index > 1)
+-              return -EINVAL;
+-
+-      /* All combinations of the supported triggers are allowed */
+-      if (rules & ~supported_triggers)
+-              return -EOPNOTSUPP;
+-
+-      return 0;
+-};
++      return mtk_phy_led_hw_is_supported(phydev, index, rules,
++                                         supported_triggers);
++}
+ static int mt798x_phy_led_hw_control_get(struct phy_device *phydev, u8 index,
+                                        unsigned long *rules)
+ {
+-      unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK +
+-                               (index ? 16 : 0);
+-      unsigned int bit_netdev = MTK_PHY_LED_STATE_NETDEV + (index ? 16 : 0);
+-      unsigned int bit_on = MTK_PHY_LED_STATE_FORCE_ON + (index ? 16 : 0);
+-      struct mtk_socphy_priv *priv = phydev->priv;
+-      int on, blink;
+-
+-      if (index > 1)
+-              return -EINVAL;
+-
+-      on = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+-                        index ? MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL);
+-
+-      if (on < 0)
+-              return -EIO;
+-
+-      blink = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+-                           index ? MTK_PHY_LED1_BLINK_CTRL :
+-                                   MTK_PHY_LED0_BLINK_CTRL);
+-      if (blink < 0)
+-              return -EIO;
+-
+-      if ((on & (MTK_PHY_LED_ON_LINK | MTK_PHY_LED_ON_FDX |
+-                 MTK_PHY_LED_ON_HDX | MTK_PHY_LED_ON_LINKDOWN)) ||
+-          (blink & (MTK_PHY_LED_BLINK_RX | MTK_PHY_LED_BLINK_TX)))
+-              set_bit(bit_netdev, &priv->led_state);
+-      else
+-              clear_bit(bit_netdev, &priv->led_state);
+-
+-      if (on & MTK_PHY_LED_ON_FORCE_ON)
+-              set_bit(bit_on, &priv->led_state);
+-      else
+-              clear_bit(bit_on, &priv->led_state);
+-
+-      if (blink & MTK_PHY_LED_BLINK_FORCE_BLINK)
+-              set_bit(bit_blink, &priv->led_state);
+-      else
+-              clear_bit(bit_blink, &priv->led_state);
+-
+-      if (!rules)
+-              return 0;
+-
+-      if (on & MTK_PHY_LED_ON_LINK)
+-              *rules |= BIT(TRIGGER_NETDEV_LINK);
+-
+-      if (on & MTK_PHY_LED_ON_LINK10)
+-              *rules |= BIT(TRIGGER_NETDEV_LINK_10);
+-
+-      if (on & MTK_PHY_LED_ON_LINK100)
+-              *rules |= BIT(TRIGGER_NETDEV_LINK_100);
+-
+-      if (on & MTK_PHY_LED_ON_LINK1000)
+-              *rules |= BIT(TRIGGER_NETDEV_LINK_1000);
+-
+-      if (on & MTK_PHY_LED_ON_FDX)
+-              *rules |= BIT(TRIGGER_NETDEV_FULL_DUPLEX);
+-
+-      if (on & MTK_PHY_LED_ON_HDX)
+-              *rules |= BIT(TRIGGER_NETDEV_HALF_DUPLEX);
+-
+-      if (blink & MTK_PHY_LED_BLINK_RX)
+-              *rules |= BIT(TRIGGER_NETDEV_RX);
+-
+-      if (blink & MTK_PHY_LED_BLINK_TX)
+-              *rules |= BIT(TRIGGER_NETDEV_TX);
+-
+-      return 0;
++      return mtk_phy_led_hw_ctrl_get(phydev, index, rules,
++                                     MTK_GPHY_LED_ON_SET,
++                                     MTK_GPHY_LED_RX_BLINK_SET,
++                                     MTK_GPHY_LED_TX_BLINK_SET);
+ };
+ static int mt798x_phy_led_hw_control_set(struct phy_device *phydev, u8 index,
+                                        unsigned long rules)
+ {
+-      unsigned int bit_netdev = MTK_PHY_LED_STATE_NETDEV + (index ? 16 : 0);
+-      struct mtk_socphy_priv *priv = phydev->priv;
+-      u16 on = 0, blink = 0;
+-      int ret;
+-
+-      if (index > 1)
+-              return -EINVAL;
+-
+-      if (rules & BIT(TRIGGER_NETDEV_FULL_DUPLEX))
+-              on |= MTK_PHY_LED_ON_FDX;
+-
+-      if (rules & BIT(TRIGGER_NETDEV_HALF_DUPLEX))
+-              on |= MTK_PHY_LED_ON_HDX;
+-
+-      if (rules & (BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK)))
+-              on |= MTK_PHY_LED_ON_LINK10;
+-
+-      if (rules & (BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK)))
+-              on |= MTK_PHY_LED_ON_LINK100;
+-
+-      if (rules & (BIT(TRIGGER_NETDEV_LINK_1000) | BIT(TRIGGER_NETDEV_LINK)))
+-              on |= MTK_PHY_LED_ON_LINK1000;
+-
+-      if (rules & BIT(TRIGGER_NETDEV_RX)) {
+-              blink |= (on & MTK_PHY_LED_ON_LINK) ?
+-                        (((on & MTK_PHY_LED_ON_LINK10) ?
+-                          MTK_PHY_LED_BLINK_10RX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK100) ?
+-                          MTK_PHY_LED_BLINK_100RX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK1000) ?
+-                          MTK_PHY_LED_BLINK_1000RX : 0)) :
+-                        MTK_PHY_LED_BLINK_RX;
+-      }
+-
+-      if (rules & BIT(TRIGGER_NETDEV_TX)) {
+-              blink |= (on & MTK_PHY_LED_ON_LINK) ?
+-                        (((on & MTK_PHY_LED_ON_LINK10) ?
+-                          MTK_PHY_LED_BLINK_10TX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK100) ?
+-                          MTK_PHY_LED_BLINK_100TX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK1000) ?
+-                          MTK_PHY_LED_BLINK_1000TX : 0)) :
+-                        MTK_PHY_LED_BLINK_TX;
+-      }
+-
+-      if (blink || on)
+-              set_bit(bit_netdev, &priv->led_state);
+-      else
+-              clear_bit(bit_netdev, &priv->led_state);
+-
+-      ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
+-                              MTK_PHY_LED1_ON_CTRL :
+-                              MTK_PHY_LED0_ON_CTRL,
+-                           MTK_PHY_LED_ON_FDX     |
+-                           MTK_PHY_LED_ON_HDX     |
+-                           MTK_PHY_LED_ON_LINK,
+-                           on);
+-
+-      if (ret)
+-              return ret;
+-
+-      return phy_write_mmd(phydev, MDIO_MMD_VEND2, index ?
+-                              MTK_PHY_LED1_BLINK_CTRL :
+-                              MTK_PHY_LED0_BLINK_CTRL, blink);
++      return mtk_phy_led_hw_ctrl_set(phydev, index, rules,
++                                     MTK_GPHY_LED_ON_SET,
++                                     MTK_GPHY_LED_RX_BLINK_SET,
++                                     MTK_GPHY_LED_TX_BLINK_SET);
+ };
+ static bool mt7988_phy_led_get_polarity(struct phy_device *phydev, int led_num)
+@@ -1492,14 +1270,6 @@ static int mt7988_phy_probe_shared(struc
+       return 0;
+ }
+-static void mt798x_phy_leds_state_init(struct phy_device *phydev)
+-{
+-      int i;
+-
+-      for (i = 0; i < 2; ++i)
+-              mt798x_phy_led_hw_control_get(phydev, i, NULL);
+-}
+-
+ static int mt7988_phy_probe(struct phy_device *phydev)
+ {
+       struct mtk_socphy_shared *shared;
+@@ -1525,7 +1295,7 @@ static int mt7988_phy_probe(struct phy_d
+       phydev->priv = priv;
+-      mt798x_phy_leds_state_init(phydev);
++      mtk_phy_leds_state_init(phydev);
+       err = mt7988_phy_fix_leds_polarities(phydev);
+       if (err)
+@@ -1552,7 +1322,7 @@ static int mt7981_phy_probe(struct phy_d
+       phydev->priv = priv;
+-      mt798x_phy_leds_state_init(phydev);
++      mtk_phy_leds_state_init(phydev);
+       return mt798x_phy_calibration(phydev);
+ }
+--- /dev/null
++++ b/drivers/net/phy/mediatek/mtk-phy-lib.c
+@@ -0,0 +1,254 @@
++// SPDX-License-Identifier: GPL-2.0
++#include <linux/phy.h>
++#include <linux/module.h>
++
++#include <linux/netdevice.h>
++
++#include "mtk.h"
++
++int mtk_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
++                              unsigned long rules,
++                              unsigned long supported_triggers)
++{
++      if (index > 1)
++              return -EINVAL;
++
++      /* All combinations of the supported triggers are allowed */
++      if (rules & ~supported_triggers)
++              return -EOPNOTSUPP;
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(mtk_phy_led_hw_is_supported);
++
++int mtk_phy_led_hw_ctrl_get(struct phy_device *phydev, u8 index,
++                          unsigned long *rules, u16 on_set,
++                          u16 rx_blink_set, u16 tx_blink_set)
++{
++      unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK +
++                               (index ? 16 : 0);
++      unsigned int bit_netdev = MTK_PHY_LED_STATE_NETDEV + (index ? 16 : 0);
++      unsigned int bit_on = MTK_PHY_LED_STATE_FORCE_ON + (index ? 16 : 0);
++      struct mtk_socphy_priv *priv = phydev->priv;
++      int on, blink;
++
++      if (index > 1)
++              return -EINVAL;
++
++      on = phy_read_mmd(phydev, MDIO_MMD_VEND2,
++                        index ? MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL);
++
++      if (on < 0)
++              return -EIO;
++
++      blink = phy_read_mmd(phydev, MDIO_MMD_VEND2,
++                           index ? MTK_PHY_LED1_BLINK_CTRL :
++                                   MTK_PHY_LED0_BLINK_CTRL);
++      if (blink < 0)
++              return -EIO;
++
++      if ((on & (on_set | MTK_PHY_LED_ON_FDX |
++                 MTK_PHY_LED_ON_HDX | MTK_PHY_LED_ON_LINKDOWN)) ||
++          (blink & (rx_blink_set | tx_blink_set)))
++              set_bit(bit_netdev, &priv->led_state);
++      else
++              clear_bit(bit_netdev, &priv->led_state);
++
++      if (on & MTK_PHY_LED_ON_FORCE_ON)
++              set_bit(bit_on, &priv->led_state);
++      else
++              clear_bit(bit_on, &priv->led_state);
++
++      if (blink & MTK_PHY_LED_BLINK_FORCE_BLINK)
++              set_bit(bit_blink, &priv->led_state);
++      else
++              clear_bit(bit_blink, &priv->led_state);
++
++      if (!rules)
++              return 0;
++
++      if (on & on_set)
++              *rules |= BIT(TRIGGER_NETDEV_LINK);
++
++      if (on & MTK_PHY_LED_ON_LINK10)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_10);
++
++      if (on & MTK_PHY_LED_ON_LINK100)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_100);
++
++      if (on & MTK_PHY_LED_ON_LINK1000)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_1000);
++
++      if (on & MTK_PHY_LED_ON_LINK2500)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_2500);
++
++      if (on & MTK_PHY_LED_ON_FDX)
++              *rules |= BIT(TRIGGER_NETDEV_FULL_DUPLEX);
++
++      if (on & MTK_PHY_LED_ON_HDX)
++              *rules |= BIT(TRIGGER_NETDEV_HALF_DUPLEX);
++
++      if (blink & rx_blink_set)
++              *rules |= BIT(TRIGGER_NETDEV_RX);
++
++      if (blink & tx_blink_set)
++              *rules |= BIT(TRIGGER_NETDEV_TX);
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(mtk_phy_led_hw_ctrl_get);
++
++int mtk_phy_led_hw_ctrl_set(struct phy_device *phydev, u8 index,
++                          unsigned long rules, u16 on_set,
++                          u16 rx_blink_set, u16 tx_blink_set)
++{
++      unsigned int bit_netdev = MTK_PHY_LED_STATE_NETDEV + (index ? 16 : 0);
++      struct mtk_socphy_priv *priv = phydev->priv;
++      u16 on = 0, blink = 0;
++      int ret;
++
++      if (index > 1)
++              return -EINVAL;
++
++      if (rules & BIT(TRIGGER_NETDEV_FULL_DUPLEX))
++              on |= MTK_PHY_LED_ON_FDX;
++
++      if (rules & BIT(TRIGGER_NETDEV_HALF_DUPLEX))
++              on |= MTK_PHY_LED_ON_HDX;
++
++      if (rules & (BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK)))
++              on |= MTK_PHY_LED_ON_LINK10;
++
++      if (rules & (BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK)))
++              on |= MTK_PHY_LED_ON_LINK100;
++
++      if (rules & (BIT(TRIGGER_NETDEV_LINK_1000) | BIT(TRIGGER_NETDEV_LINK)))
++              on |= MTK_PHY_LED_ON_LINK1000;
++
++      if (rules & (BIT(TRIGGER_NETDEV_LINK_2500) | BIT(TRIGGER_NETDEV_LINK)))
++              on |= MTK_PHY_LED_ON_LINK2500;
++
++      if (rules & BIT(TRIGGER_NETDEV_RX)) {
++              blink |= (on & on_set) ?
++                        (((on & MTK_PHY_LED_ON_LINK10) ?
++                          MTK_PHY_LED_BLINK_10RX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK100) ?
++                          MTK_PHY_LED_BLINK_100RX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK1000) ?
++                          MTK_PHY_LED_BLINK_1000RX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK2500) ?
++                          MTK_PHY_LED_BLINK_2500RX : 0)) :
++                        rx_blink_set;
++      }
++
++      if (rules & BIT(TRIGGER_NETDEV_TX)) {
++              blink |= (on & on_set) ?
++                        (((on & MTK_PHY_LED_ON_LINK10) ?
++                          MTK_PHY_LED_BLINK_10TX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK100) ?
++                          MTK_PHY_LED_BLINK_100TX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK1000) ?
++                          MTK_PHY_LED_BLINK_1000TX : 0) |
++                         ((on & MTK_PHY_LED_ON_LINK2500) ?
++                          MTK_PHY_LED_BLINK_2500TX : 0)) :
++                        tx_blink_set;
++      }
++
++      if (blink || on)
++              set_bit(bit_netdev, &priv->led_state);
++      else
++              clear_bit(bit_netdev, &priv->led_state);
++
++      ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
++                           MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL,
++                           MTK_PHY_LED_ON_FDX | MTK_PHY_LED_ON_HDX | on_set,
++                           on);
++
++      if (ret)
++              return ret;
++
++      return phy_write_mmd(phydev, MDIO_MMD_VEND2, index ?
++                           MTK_PHY_LED1_BLINK_CTRL :
++                           MTK_PHY_LED0_BLINK_CTRL, blink);
++}
++EXPORT_SYMBOL_GPL(mtk_phy_led_hw_ctrl_set);
++
++int mtk_phy_led_num_dly_cfg(u8 index, unsigned long *delay_on,
++                          unsigned long *delay_off, bool *blinking)
++{
++      if (index > 1)
++              return -EINVAL;
++
++      if (delay_on && delay_off && (*delay_on > 0) && (*delay_off > 0)) {
++              *blinking = true;
++              *delay_on = 50;
++              *delay_off = 50;
++      }
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(mtk_phy_led_num_dly_cfg);
++
++int mtk_phy_hw_led_on_set(struct phy_device *phydev, u8 index,
++                        u16 led_on_mask, bool on)
++{
++      unsigned int bit_on = MTK_PHY_LED_STATE_FORCE_ON + (index ? 16 : 0);
++      struct mtk_socphy_priv *priv = phydev->priv;
++      bool changed;
++
++      if (on)
++              changed = !test_and_set_bit(bit_on, &priv->led_state);
++      else
++              changed = !!test_and_clear_bit(bit_on, &priv->led_state);
++
++      changed |= !!test_and_clear_bit(MTK_PHY_LED_STATE_NETDEV +
++                                      (index ? 16 : 0), &priv->led_state);
++      if (changed)
++              return phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
++                                    MTK_PHY_LED1_ON_CTRL :
++                                    MTK_PHY_LED0_ON_CTRL,
++                                    led_on_mask,
++                                    on ? MTK_PHY_LED_ON_FORCE_ON : 0);
++      else
++              return 0;
++}
++EXPORT_SYMBOL_GPL(mtk_phy_hw_led_on_set);
++
++int mtk_phy_hw_led_blink_set(struct phy_device *phydev, u8 index, bool blinking)
++{
++      unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK +
++                               (index ? 16 : 0);
++      struct mtk_socphy_priv *priv = phydev->priv;
++      bool changed;
++
++      if (blinking)
++              changed = !test_and_set_bit(bit_blink, &priv->led_state);
++      else
++              changed = !!test_and_clear_bit(bit_blink, &priv->led_state);
++
++      changed |= !!test_bit(MTK_PHY_LED_STATE_NETDEV +
++                            (index ? 16 : 0), &priv->led_state);
++      if (changed)
++              return phy_write_mmd(phydev, MDIO_MMD_VEND2, index ?
++                                   MTK_PHY_LED1_BLINK_CTRL :
++                                   MTK_PHY_LED0_BLINK_CTRL,
++                                   blinking ?
++                                   MTK_PHY_LED_BLINK_FORCE_BLINK : 0);
++      else
++              return 0;
++}
++EXPORT_SYMBOL_GPL(mtk_phy_hw_led_blink_set);
++
++void mtk_phy_leds_state_init(struct phy_device *phydev)
++{
++      int i;
++
++      for (i = 0; i < 2; ++i)
++              phydev->drv->led_hw_control_get(phydev, i, NULL);
++}
++EXPORT_SYMBOL_GPL(mtk_phy_leds_state_init);
++
++MODULE_DESCRIPTION("MediaTek Ethernet PHY driver common");
++MODULE_AUTHOR("Sky Huang <SkyLake.Huang@mediatek.com>");
++MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
++MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/drivers/net/phy/mediatek/mtk.h
+@@ -0,0 +1,86 @@
++/* SPDX-License-Identifier: GPL-2.0
++ *
++ * Common definition for Mediatek Ethernet PHYs
++ * Author: SkyLake Huang <SkyLake.Huang@mediatek.com>
++ * Copyright (c) 2024 MediaTek Inc.
++ */
++
++#ifndef _MTK_EPHY_H_
++#define _MTK_EPHY_H_
++
++#define MTK_EXT_PAGE_ACCESS                   0x1f
++
++/* Registers on MDIO_MMD_VEND2 */
++#define MTK_PHY_LED0_ON_CTRL                  0x24
++#define MTK_PHY_LED1_ON_CTRL                  0x26
++#define   MTK_GPHY_LED_ON_MASK                        GENMASK(6, 0)
++#define   MTK_2P5GPHY_LED_ON_MASK             GENMASK(7, 0)
++#define   MTK_PHY_LED_ON_LINK1000             BIT(0)
++#define   MTK_PHY_LED_ON_LINK100              BIT(1)
++#define   MTK_PHY_LED_ON_LINK10                       BIT(2)
++#define   MTK_PHY_LED_ON_LINKDOWN             BIT(3)
++#define   MTK_PHY_LED_ON_FDX                  BIT(4) /* Full duplex */
++#define   MTK_PHY_LED_ON_HDX                  BIT(5) /* Half duplex */
++#define   MTK_PHY_LED_ON_FORCE_ON             BIT(6)
++#define   MTK_PHY_LED_ON_LINK2500             BIT(7)
++#define   MTK_PHY_LED_ON_POLARITY             BIT(14)
++#define   MTK_PHY_LED_ON_ENABLE                       BIT(15)
++
++#define MTK_PHY_LED0_BLINK_CTRL                       0x25
++#define MTK_PHY_LED1_BLINK_CTRL                       0x27
++#define   MTK_PHY_LED_BLINK_1000TX            BIT(0)
++#define   MTK_PHY_LED_BLINK_1000RX            BIT(1)
++#define   MTK_PHY_LED_BLINK_100TX             BIT(2)
++#define   MTK_PHY_LED_BLINK_100RX             BIT(3)
++#define   MTK_PHY_LED_BLINK_10TX              BIT(4)
++#define   MTK_PHY_LED_BLINK_10RX              BIT(5)
++#define   MTK_PHY_LED_BLINK_COLLISION         BIT(6)
++#define   MTK_PHY_LED_BLINK_RX_CRC_ERR                BIT(7)
++#define   MTK_PHY_LED_BLINK_RX_IDLE_ERR               BIT(8)
++#define   MTK_PHY_LED_BLINK_FORCE_BLINK               BIT(9)
++#define   MTK_PHY_LED_BLINK_2500TX            BIT(10)
++#define   MTK_PHY_LED_BLINK_2500RX            BIT(11)
++
++#define MTK_GPHY_LED_ON_SET                   (MTK_PHY_LED_ON_LINK1000 | \
++                                               MTK_PHY_LED_ON_LINK100 | \
++                                               MTK_PHY_LED_ON_LINK10)
++#define MTK_GPHY_LED_RX_BLINK_SET             (MTK_PHY_LED_BLINK_1000RX | \
++                                               MTK_PHY_LED_BLINK_100RX | \
++                                               MTK_PHY_LED_BLINK_10RX)
++#define MTK_GPHY_LED_TX_BLINK_SET             (MTK_PHY_LED_BLINK_1000RX | \
++                                               MTK_PHY_LED_BLINK_100RX | \
++                                               MTK_PHY_LED_BLINK_10RX)
++
++#define MTK_2P5GPHY_LED_ON_SET                        (MTK_PHY_LED_ON_LINK2500 | \
++                                               MTK_GPHY_LED_ON_SET)
++#define MTK_2P5GPHY_LED_RX_BLINK_SET          (MTK_PHY_LED_BLINK_2500RX | \
++                                               MTK_GPHY_LED_RX_BLINK_SET)
++#define MTK_2P5GPHY_LED_TX_BLINK_SET          (MTK_PHY_LED_BLINK_2500RX | \
++                                               MTK_GPHY_LED_TX_BLINK_SET)
++
++#define MTK_PHY_LED_STATE_FORCE_ON    0
++#define MTK_PHY_LED_STATE_FORCE_BLINK 1
++#define MTK_PHY_LED_STATE_NETDEV      2
++
++struct mtk_socphy_priv {
++      unsigned long           led_state;
++};
++
++int mtk_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
++                              unsigned long rules,
++                              unsigned long supported_triggers);
++int mtk_phy_led_hw_ctrl_set(struct phy_device *phydev, u8 index,
++                          unsigned long rules, u16 on_set,
++                          u16 rx_blink_set, u16 tx_blink_set);
++int mtk_phy_led_hw_ctrl_get(struct phy_device *phydev, u8 index,
++                          unsigned long *rules, u16 on_set,
++                          u16 rx_blink_set, u16 tx_blink_set);
++int mtk_phy_led_num_dly_cfg(u8 index, unsigned long *delay_on,
++                          unsigned long *delay_off, bool *blinking);
++int mtk_phy_hw_led_on_set(struct phy_device *phydev, u8 index,
++                        u16 led_on_mask, bool on);
++int mtk_phy_hw_led_blink_set(struct phy_device *phydev, u8 index,
++                           bool blinking);
++void mtk_phy_leds_state_init(struct phy_device *phydev);
++
++#endif /* _MTK_EPHY_H_ */
diff --git a/target/linux/generic/backport-6.12/720-06-v6.13-net-phy-mediatek-Improve-readability-of-mtk-phy-lib..patch b/target/linux/generic/backport-6.12/720-06-v6.13-net-phy-mediatek-Improve-readability-of-mtk-phy-lib..patch
new file mode 100644 (file)
index 0000000..19f3a84
--- /dev/null
@@ -0,0 +1,72 @@
+From 3efd0595fc7aaae300f5d9f4f0ae86f432c8d2c7 Mon Sep 17 00:00:00 2001
+From: "SkyLake.Huang" <skylake.huang@mediatek.com>
+Date: Sat, 9 Nov 2024 00:34:53 +0800
+Subject: [PATCH 06/20] net: phy: mediatek: Improve readability of
+ mtk-phy-lib.c's mtk_phy_led_hw_ctrl_set()
+
+This patch removes parens around TRIGGER_NETDEV_RX/TRIGGER_NETDEV_TX in
+mtk_phy_led_hw_ctrl_set(), which improves readability.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: SkyLake.Huang <skylake.huang@mediatek.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/mediatek/mtk-phy-lib.c | 44 ++++++++++++++------------
+ 1 file changed, 24 insertions(+), 20 deletions(-)
+
+--- a/drivers/net/phy/mediatek/mtk-phy-lib.c
++++ b/drivers/net/phy/mediatek/mtk-phy-lib.c
+@@ -129,29 +129,33 @@ int mtk_phy_led_hw_ctrl_set(struct phy_d
+               on |= MTK_PHY_LED_ON_LINK2500;
+       if (rules & BIT(TRIGGER_NETDEV_RX)) {
+-              blink |= (on & on_set) ?
+-                        (((on & MTK_PHY_LED_ON_LINK10) ?
+-                          MTK_PHY_LED_BLINK_10RX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK100) ?
+-                          MTK_PHY_LED_BLINK_100RX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK1000) ?
+-                          MTK_PHY_LED_BLINK_1000RX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK2500) ?
+-                          MTK_PHY_LED_BLINK_2500RX : 0)) :
+-                        rx_blink_set;
++              if (on & on_set) {
++                      if (on & MTK_PHY_LED_ON_LINK10)
++                              blink |= MTK_PHY_LED_BLINK_10RX;
++                      if (on & MTK_PHY_LED_ON_LINK100)
++                              blink |= MTK_PHY_LED_BLINK_100RX;
++                      if (on & MTK_PHY_LED_ON_LINK1000)
++                              blink |= MTK_PHY_LED_BLINK_1000RX;
++                      if (on & MTK_PHY_LED_ON_LINK2500)
++                              blink |= MTK_PHY_LED_BLINK_2500RX;
++              } else {
++                      blink |= rx_blink_set;
++              }
+       }
+       if (rules & BIT(TRIGGER_NETDEV_TX)) {
+-              blink |= (on & on_set) ?
+-                        (((on & MTK_PHY_LED_ON_LINK10) ?
+-                          MTK_PHY_LED_BLINK_10TX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK100) ?
+-                          MTK_PHY_LED_BLINK_100TX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK1000) ?
+-                          MTK_PHY_LED_BLINK_1000TX : 0) |
+-                         ((on & MTK_PHY_LED_ON_LINK2500) ?
+-                          MTK_PHY_LED_BLINK_2500TX : 0)) :
+-                        tx_blink_set;
++              if (on & on_set) {
++                      if (on & MTK_PHY_LED_ON_LINK10)
++                              blink |= MTK_PHY_LED_BLINK_10TX;
++                      if (on & MTK_PHY_LED_ON_LINK100)
++                              blink |= MTK_PHY_LED_BLINK_100TX;
++                      if (on & MTK_PHY_LED_ON_LINK1000)
++                              blink |= MTK_PHY_LED_BLINK_1000TX;
++                      if (on & MTK_PHY_LED_ON_LINK2500)
++                              blink |= MTK_PHY_LED_BLINK_2500TX;
++              } else {
++                      blink |= tx_blink_set;
++              }
+       }
+       if (blink || on)
diff --git a/target/linux/generic/backport-6.12/720-07-v6.13-net-phy-mediatek-Integrate-read-write-page-helper-fu.patch b/target/linux/generic/backport-6.12/720-07-v6.13-net-phy-mediatek-Integrate-read-write-page-helper-fu.patch
new file mode 100644 (file)
index 0000000..a5828bc
--- /dev/null
@@ -0,0 +1,153 @@
+From 50a97d716105a5f35aaecca0bdfe8e23cba0e87f Mon Sep 17 00:00:00 2001
+From: "SkyLake.Huang" <skylake.huang@mediatek.com>
+Date: Sat, 9 Nov 2024 00:34:54 +0800
+Subject: [PATCH 07/20] net: phy: mediatek: Integrate read/write page helper
+ functions
+
+This patch integrates read/write page helper functions as MTK phy lib.
+They are basically the same in mtk-ge.c & mtk-ge-soc.c.
+
+Signed-off-by: SkyLake.Huang <skylake.huang@mediatek.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/mediatek/Kconfig       |  1 +
+ drivers/net/phy/mediatek/mtk-ge-soc.c  | 18 ++++--------------
+ drivers/net/phy/mediatek/mtk-ge.c      | 20 ++++++--------------
+ drivers/net/phy/mediatek/mtk-phy-lib.c | 12 ++++++++++++
+ drivers/net/phy/mediatek/mtk.h         |  3 +++
+ 5 files changed, 26 insertions(+), 28 deletions(-)
+
+--- a/drivers/net/phy/mediatek/Kconfig
++++ b/drivers/net/phy/mediatek/Kconfig
+@@ -4,6 +4,7 @@ config MTK_NET_PHYLIB
+ config MEDIATEK_GE_PHY
+       tristate "MediaTek Gigabit Ethernet PHYs"
++      select MTK_NET_PHYLIB
+       help
+         Supports the MediaTek non-built-in Gigabit Ethernet PHYs.
+--- a/drivers/net/phy/mediatek/mtk-ge-soc.c
++++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
+@@ -271,16 +271,6 @@ struct mtk_socphy_shared {
+       struct mtk_socphy_priv  priv[4];
+ };
+-static int mtk_socphy_read_page(struct phy_device *phydev)
+-{
+-      return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
+-}
+-
+-static int mtk_socphy_write_page(struct phy_device *phydev, int page)
+-{
+-      return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page);
+-}
+-
+ /* One calibration cycle consists of:
+  * 1.Set DA_CALIN_FLAG high to start calibration. Keep it high
+  *   until AD_CAL_COMP is ready to output calibration result.
+@@ -1337,8 +1327,8 @@ static struct phy_driver mtk_socphy_driv
+               .probe          = mt7981_phy_probe,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+-              .read_page      = mtk_socphy_read_page,
+-              .write_page     = mtk_socphy_write_page,
++              .read_page      = mtk_phy_read_page,
++              .write_page     = mtk_phy_write_page,
+               .led_blink_set  = mt798x_phy_led_blink_set,
+               .led_brightness_set = mt798x_phy_led_brightness_set,
+               .led_hw_is_supported = mt798x_phy_led_hw_is_supported,
+@@ -1354,8 +1344,8 @@ static struct phy_driver mtk_socphy_driv
+               .probe          = mt7988_phy_probe,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+-              .read_page      = mtk_socphy_read_page,
+-              .write_page     = mtk_socphy_write_page,
++              .read_page      = mtk_phy_read_page,
++              .write_page     = mtk_phy_write_page,
+               .led_blink_set  = mt798x_phy_led_blink_set,
+               .led_brightness_set = mt798x_phy_led_brightness_set,
+               .led_hw_is_supported = mt798x_phy_led_hw_is_supported,
+--- a/drivers/net/phy/mediatek/mtk-ge.c
++++ b/drivers/net/phy/mediatek/mtk-ge.c
+@@ -3,6 +3,8 @@
+ #include <linux/module.h>
+ #include <linux/phy.h>
++#include "mtk.h"
++
+ #define MTK_EXT_PAGE_ACCESS           0x1f
+ #define MTK_PHY_PAGE_STANDARD         0x0000
+ #define MTK_PHY_PAGE_EXTENDED         0x0001
+@@ -11,16 +13,6 @@
+ #define MTK_PHY_PAGE_EXTENDED_2A30    0x2a30
+ #define MTK_PHY_PAGE_EXTENDED_52B5    0x52b5
+-static int mtk_gephy_read_page(struct phy_device *phydev)
+-{
+-      return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
+-}
+-
+-static int mtk_gephy_write_page(struct phy_device *phydev, int page)
+-{
+-      return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page);
+-}
+-
+ static void mtk_gephy_config_init(struct phy_device *phydev)
+ {
+       /* Enable HW auto downshift */
+@@ -77,8 +69,8 @@ static struct phy_driver mtk_gephy_drive
+               .handle_interrupt = genphy_handle_interrupt_no_ack,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+-              .read_page      = mtk_gephy_read_page,
+-              .write_page     = mtk_gephy_write_page,
++              .read_page      = mtk_phy_read_page,
++              .write_page     = mtk_phy_write_page,
+       },
+       {
+               PHY_ID_MATCH_EXACT(0x03a29441),
+@@ -91,8 +83,8 @@ static struct phy_driver mtk_gephy_drive
+               .handle_interrupt = genphy_handle_interrupt_no_ack,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+-              .read_page      = mtk_gephy_read_page,
+-              .write_page     = mtk_gephy_write_page,
++              .read_page      = mtk_phy_read_page,
++              .write_page     = mtk_phy_write_page,
+       },
+ };
+--- a/drivers/net/phy/mediatek/mtk-phy-lib.c
++++ b/drivers/net/phy/mediatek/mtk-phy-lib.c
+@@ -6,6 +6,18 @@
+ #include "mtk.h"
++int mtk_phy_read_page(struct phy_device *phydev)
++{
++      return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
++}
++EXPORT_SYMBOL_GPL(mtk_phy_read_page);
++
++int mtk_phy_write_page(struct phy_device *phydev, int page)
++{
++      return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page);
++}
++EXPORT_SYMBOL_GPL(mtk_phy_write_page);
++
+ int mtk_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
+                               unsigned long rules,
+                               unsigned long supported_triggers)
+--- a/drivers/net/phy/mediatek/mtk.h
++++ b/drivers/net/phy/mediatek/mtk.h
+@@ -66,6 +66,9 @@ struct mtk_socphy_priv {
+       unsigned long           led_state;
+ };
++int mtk_phy_read_page(struct phy_device *phydev);
++int mtk_phy_write_page(struct phy_device *phydev, int page);
++
+ int mtk_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
+                               unsigned long rules,
+                               unsigned long supported_triggers);
diff --git a/target/linux/generic/backport-6.12/720-08-v6.13-net-phy-mediatek-add-MT7530-MT7531-s-PHY-ID-macros.patch b/target/linux/generic/backport-6.12/720-08-v6.13-net-phy-mediatek-add-MT7530-MT7531-s-PHY-ID-macros.patch
new file mode 100644 (file)
index 0000000..1c87384
--- /dev/null
@@ -0,0 +1,56 @@
+From e6579df175d5b1baa605c82f8e759542262637cf Mon Sep 17 00:00:00 2001
+From: "SkyLake.Huang" <skylake.huang@mediatek.com>
+Date: Sat, 9 Nov 2024 00:34:55 +0800
+Subject: [PATCH 08/20] net: phy: mediatek: add MT7530 & MT7531's PHY ID macros
+
+This patch adds MT7530 & MT7531's PHY ID macros in mtk-ge.c so that
+it follows the same rule of mtk-ge-soc.c.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: SkyLake.Huang <skylake.huang@mediatek.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/mediatek/mtk-ge.c | 11 +++++++----
+ 1 file changed, 7 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/phy/mediatek/mtk-ge.c
++++ b/drivers/net/phy/mediatek/mtk-ge.c
+@@ -5,6 +5,9 @@
+ #include "mtk.h"
++#define MTK_GPHY_ID_MT7530            0x03a29412
++#define MTK_GPHY_ID_MT7531            0x03a29441
++
+ #define MTK_EXT_PAGE_ACCESS           0x1f
+ #define MTK_PHY_PAGE_STANDARD         0x0000
+ #define MTK_PHY_PAGE_EXTENDED         0x0001
+@@ -59,7 +62,7 @@ static int mt7531_phy_config_init(struct
+ static struct phy_driver mtk_gephy_driver[] = {
+       {
+-              PHY_ID_MATCH_EXACT(0x03a29412),
++              PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7530),
+               .name           = "MediaTek MT7530 PHY",
+               .config_init    = mt7530_phy_config_init,
+               /* Interrupts are handled by the switch, not the PHY
+@@ -73,7 +76,7 @@ static struct phy_driver mtk_gephy_drive
+               .write_page     = mtk_phy_write_page,
+       },
+       {
+-              PHY_ID_MATCH_EXACT(0x03a29441),
++              PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7531),
+               .name           = "MediaTek MT7531 PHY",
+               .config_init    = mt7531_phy_config_init,
+               /* Interrupts are handled by the switch, not the PHY
+@@ -91,8 +94,8 @@ static struct phy_driver mtk_gephy_drive
+ module_phy_driver(mtk_gephy_driver);
+ static struct mdio_device_id __maybe_unused mtk_gephy_tbl[] = {
+-      { PHY_ID_MATCH_EXACT(0x03a29441) },
+-      { PHY_ID_MATCH_EXACT(0x03a29412) },
++      { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7530) },
++      { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7531) },
+       { }
+ };
diff --git a/target/linux/generic/backport-6.12/720-09-v6.14-net-phy-Constify-struct-mdio_device_id.patch b/target/linux/generic/backport-6.12/720-09-v6.14-net-phy-Constify-struct-mdio_device_id.patch
new file mode 100644 (file)
index 0000000..c0c840d
--- /dev/null
@@ -0,0 +1,700 @@
+From e127f7380aaf2cd1614961d826a4af7ab297d37f Mon Sep 17 00:00:00 2001
+From: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
+Date: Sun, 12 Jan 2025 15:14:50 +0100
+Subject: [PATCH 09/20] net: phy: Constify struct mdio_device_id
+
+'struct mdio_device_id' is not modified in these drivers.
+
+Constifying these structures moves some data to a read-only section, so
+increase overall security.
+
+On a x86_64, with allmodconfig, as an example:
+Before:
+======
+   text           data     bss     dec     hex filename
+  27014          12792       0   39806    9b7e drivers/net/phy/broadcom.o
+
+After:
+=====
+   text           data     bss     dec     hex filename
+  27206          12600       0   39806    9b7e drivers/net/phy/broadcom.o
+
+Signed-off-by: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/403c381b7d9156b67ad68ffc44b8eee70c5e86a9.1736691226.git.christophe.jaillet@wanadoo.fr
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/adin.c                   | 2 +-
+ drivers/net/phy/adin1100.c               | 2 +-
+ drivers/net/phy/air_en8811h.c            | 2 +-
+ drivers/net/phy/amd.c                    | 2 +-
+ drivers/net/phy/aquantia/aquantia_main.c | 2 +-
+ drivers/net/phy/ax88796b.c               | 2 +-
+ drivers/net/phy/bcm-cygnus.c             | 2 +-
+ drivers/net/phy/bcm54140.c               | 2 +-
+ drivers/net/phy/bcm63xx.c                | 2 +-
+ drivers/net/phy/bcm7xxx.c                | 2 +-
+ drivers/net/phy/bcm84881.c               | 2 +-
+ drivers/net/phy/broadcom.c               | 2 +-
+ drivers/net/phy/cicada.c                 | 2 +-
+ drivers/net/phy/cortina.c                | 2 +-
+ drivers/net/phy/davicom.c                | 2 +-
+ drivers/net/phy/dp83640.c                | 2 +-
+ drivers/net/phy/dp83822.c                | 2 +-
+ drivers/net/phy/dp83848.c                | 2 +-
+ drivers/net/phy/dp83867.c                | 2 +-
+ drivers/net/phy/dp83869.c                | 2 +-
+ drivers/net/phy/dp83tc811.c              | 2 +-
+ drivers/net/phy/dp83td510.c              | 2 +-
+ drivers/net/phy/dp83tg720.c              | 2 +-
+ drivers/net/phy/et1011c.c                | 2 +-
+ drivers/net/phy/icplus.c                 | 2 +-
+ drivers/net/phy/intel-xway.c             | 2 +-
+ drivers/net/phy/lxt.c                    | 2 +-
+ drivers/net/phy/marvell-88q2xxx.c        | 2 +-
+ drivers/net/phy/marvell-88x2222.c        | 2 +-
+ drivers/net/phy/marvell.c                | 2 +-
+ drivers/net/phy/marvell10g.c             | 2 +-
+ drivers/net/phy/mediatek/mtk-ge-soc.c    | 2 +-
+ drivers/net/phy/mediatek/mtk-ge.c        | 2 +-
+ drivers/net/phy/meson-gxl.c              | 2 +-
+ drivers/net/phy/micrel.c                 | 2 +-
+ drivers/net/phy/microchip.c              | 2 +-
+ drivers/net/phy/microchip_t1.c           | 2 +-
+ drivers/net/phy/microchip_t1s.c          | 2 +-
+ drivers/net/phy/mscc/mscc_main.c         | 2 +-
+ drivers/net/phy/mxl-gpy.c                | 2 +-
+ drivers/net/phy/national.c               | 2 +-
+ drivers/net/phy/ncn26000.c               | 2 +-
+ drivers/net/phy/nxp-c45-tja11xx.c        | 2 +-
+ drivers/net/phy/nxp-cbtx.c               | 2 +-
+ drivers/net/phy/nxp-tja11xx.c            | 2 +-
+ drivers/net/phy/qcom/at803x.c            | 2 +-
+ drivers/net/phy/qcom/qca807x.c           | 2 +-
+ drivers/net/phy/qcom/qca808x.c           | 2 +-
+ drivers/net/phy/qcom/qca83xx.c           | 2 +-
+ drivers/net/phy/qsemi.c                  | 2 +-
+ drivers/net/phy/rockchip.c               | 2 +-
+ drivers/net/phy/smsc.c                   | 2 +-
+ drivers/net/phy/ste10Xp.c                | 2 +-
+ drivers/net/phy/teranetics.c             | 2 +-
+ drivers/net/phy/uPD60620.c               | 2 +-
+ drivers/net/phy/vitesse.c                | 2 +-
+ 56 files changed, 56 insertions(+), 56 deletions(-)
+
+--- a/drivers/net/phy/adin.c
++++ b/drivers/net/phy/adin.c
+@@ -1040,7 +1040,7 @@ static struct phy_driver adin_driver[] =
+ module_phy_driver(adin_driver);
+-static struct mdio_device_id __maybe_unused adin_tbl[] = {
++static const struct mdio_device_id __maybe_unused adin_tbl[] = {
+       { PHY_ID_MATCH_MODEL(PHY_ID_ADIN1200) },
+       { PHY_ID_MATCH_MODEL(PHY_ID_ADIN1300) },
+       { }
+--- a/drivers/net/phy/adin1100.c
++++ b/drivers/net/phy/adin1100.c
+@@ -340,7 +340,7 @@ static struct phy_driver adin_driver[] =
+ module_phy_driver(adin_driver);
+-static struct mdio_device_id __maybe_unused adin_tbl[] = {
++static const struct mdio_device_id __maybe_unused adin_tbl[] = {
+       { PHY_ID_MATCH_MODEL(PHY_ID_ADIN1100) },
+       { PHY_ID_MATCH_MODEL(PHY_ID_ADIN1110) },
+       { PHY_ID_MATCH_MODEL(PHY_ID_ADIN2111) },
+--- a/drivers/net/phy/air_en8811h.c
++++ b/drivers/net/phy/air_en8811h.c
+@@ -1075,7 +1075,7 @@ static struct phy_driver en8811h_driver[
+ module_phy_driver(en8811h_driver);
+-static struct mdio_device_id __maybe_unused en8811h_tbl[] = {
++static const struct mdio_device_id __maybe_unused en8811h_tbl[] = {
+       { PHY_ID_MATCH_MODEL(EN8811H_PHY_ID) },
+       { }
+ };
+--- a/drivers/net/phy/amd.c
++++ b/drivers/net/phy/amd.c
+@@ -111,7 +111,7 @@ static struct phy_driver am79c_drivers[]
+ module_phy_driver(am79c_drivers);
+-static struct mdio_device_id __maybe_unused amd_tbl[] = {
++static const struct mdio_device_id __maybe_unused amd_tbl[] = {
+       { PHY_ID_AC101L, 0xfffffff0 },
+       { PHY_ID_AM79C874, 0xfffffff0 },
+       { }
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -1096,7 +1096,7 @@ static struct phy_driver aqr_driver[] =
+ module_phy_driver(aqr_driver);
+-static struct mdio_device_id __maybe_unused aqr_tbl[] = {
++static const 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) },
+--- a/drivers/net/phy/ax88796b.c
++++ b/drivers/net/phy/ax88796b.c
+@@ -121,7 +121,7 @@ static struct phy_driver asix_driver[] =
+ module_phy_driver(asix_driver);
+-static struct mdio_device_id __maybe_unused asix_tbl[] = {
++static const struct mdio_device_id __maybe_unused asix_tbl[] = {
+       { PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772A) },
+       { PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772C) },
+       { PHY_ID_ASIX_AX88796B, 0xfffffff0 },
+--- a/drivers/net/phy/bcm-cygnus.c
++++ b/drivers/net/phy/bcm-cygnus.c
+@@ -278,7 +278,7 @@ static struct phy_driver bcm_cygnus_phy_
+ }
+ };
+-static struct mdio_device_id __maybe_unused bcm_cygnus_phy_tbl[] = {
++static const struct mdio_device_id __maybe_unused bcm_cygnus_phy_tbl[] = {
+       { PHY_ID_BCM_CYGNUS, 0xfffffff0, },
+       { PHY_ID_BCM_OMEGA, 0xfffffff0, },
+       { }
+--- a/drivers/net/phy/bcm54140.c
++++ b/drivers/net/phy/bcm54140.c
+@@ -883,7 +883,7 @@ static struct phy_driver bcm54140_driver
+ };
+ module_phy_driver(bcm54140_drivers);
+-static struct mdio_device_id __maybe_unused bcm54140_tbl[] = {
++static const struct mdio_device_id __maybe_unused bcm54140_tbl[] = {
+       { PHY_ID_BCM54140, BCM54140_PHY_ID_MASK },
+       { }
+ };
+--- a/drivers/net/phy/bcm63xx.c
++++ b/drivers/net/phy/bcm63xx.c
+@@ -93,7 +93,7 @@ static struct phy_driver bcm63xx_driver[
+ module_phy_driver(bcm63xx_driver);
+-static struct mdio_device_id __maybe_unused bcm63xx_tbl[] = {
++static const struct mdio_device_id __maybe_unused bcm63xx_tbl[] = {
+       { 0x00406000, 0xfffffc00 },
+       { 0x002bdc00, 0xfffffc00 },
+       { }
+--- a/drivers/net/phy/bcm7xxx.c
++++ b/drivers/net/phy/bcm7xxx.c
+@@ -929,7 +929,7 @@ static struct phy_driver bcm7xxx_driver[
+       BCM7XXX_16NM_EPHY(PHY_ID_BCM7712, "Broadcom BCM7712"),
+ };
+-static struct mdio_device_id __maybe_unused bcm7xxx_tbl[] = {
++static const struct mdio_device_id __maybe_unused bcm7xxx_tbl[] = {
+       { PHY_ID_BCM72113, 0xfffffff0 },
+       { PHY_ID_BCM72116, 0xfffffff0, },
+       { PHY_ID_BCM72165, 0xfffffff0, },
+--- a/drivers/net/phy/bcm84881.c
++++ b/drivers/net/phy/bcm84881.c
+@@ -262,7 +262,7 @@ static struct phy_driver bcm84881_driver
+ module_phy_driver(bcm84881_drivers);
+ /* FIXME: module auto-loading for Clause 45 PHYs seems non-functional */
+-static struct mdio_device_id __maybe_unused bcm84881_tbl[] = {
++static const struct mdio_device_id __maybe_unused bcm84881_tbl[] = {
+       { 0xae025150, 0xfffffff0 },
+       { },
+ };
+--- a/drivers/net/phy/broadcom.c
++++ b/drivers/net/phy/broadcom.c
+@@ -1734,7 +1734,7 @@ static struct phy_driver broadcom_driver
+ module_phy_driver(broadcom_drivers);
+-static struct mdio_device_id __maybe_unused broadcom_tbl[] = {
++static const struct mdio_device_id __maybe_unused broadcom_tbl[] = {
+       { PHY_ID_BCM5411, 0xfffffff0 },
+       { PHY_ID_BCM5421, 0xfffffff0 },
+       { PHY_ID_BCM54210E, 0xfffffff0 },
+--- a/drivers/net/phy/cicada.c
++++ b/drivers/net/phy/cicada.c
+@@ -145,7 +145,7 @@ static struct phy_driver cis820x_driver[
+ module_phy_driver(cis820x_driver);
+-static struct mdio_device_id __maybe_unused cicada_tbl[] = {
++static const struct mdio_device_id __maybe_unused cicada_tbl[] = {
+       { 0x000fc410, 0x000ffff0 },
+       { 0x000fc440, 0x000fffc0 },
+       { }
+--- a/drivers/net/phy/cortina.c
++++ b/drivers/net/phy/cortina.c
+@@ -87,7 +87,7 @@ static struct phy_driver cortina_driver[
+ module_phy_driver(cortina_driver);
+-static struct mdio_device_id __maybe_unused cortina_tbl[] = {
++static const struct mdio_device_id __maybe_unused cortina_tbl[] = {
+       { PHY_ID_CS4340, 0xffffffff},
+       {},
+ };
+--- a/drivers/net/phy/davicom.c
++++ b/drivers/net/phy/davicom.c
+@@ -209,7 +209,7 @@ static struct phy_driver dm91xx_driver[]
+ module_phy_driver(dm91xx_driver);
+-static struct mdio_device_id __maybe_unused davicom_tbl[] = {
++static const struct mdio_device_id __maybe_unused davicom_tbl[] = {
+       { 0x0181b880, 0x0ffffff0 },
+       { 0x0181b8b0, 0x0ffffff0 },
+       { 0x0181b8a0, 0x0ffffff0 },
+--- a/drivers/net/phy/dp83640.c
++++ b/drivers/net/phy/dp83640.c
+@@ -1548,7 +1548,7 @@ MODULE_LICENSE("GPL");
+ module_init(dp83640_init);
+ module_exit(dp83640_exit);
+-static struct mdio_device_id __maybe_unused dp83640_tbl[] = {
++static const struct mdio_device_id __maybe_unused dp83640_tbl[] = {
+       { DP83640_PHY_ID, 0xfffffff0 },
+       { }
+ };
+--- a/drivers/net/phy/dp83822.c
++++ b/drivers/net/phy/dp83822.c
+@@ -825,7 +825,7 @@ static struct phy_driver dp83822_driver[
+ };
+ module_phy_driver(dp83822_driver);
+-static struct mdio_device_id __maybe_unused dp83822_tbl[] = {
++static const struct mdio_device_id __maybe_unused dp83822_tbl[] = {
+       { DP83822_PHY_ID, 0xfffffff0 },
+       { DP83825I_PHY_ID, 0xfffffff0 },
+       { DP83826C_PHY_ID, 0xfffffff0 },
+--- a/drivers/net/phy/dp83848.c
++++ b/drivers/net/phy/dp83848.c
+@@ -123,7 +123,7 @@ static int dp83848_config_init(struct ph
+       return 0;
+ }
+-static struct mdio_device_id __maybe_unused dp83848_tbl[] = {
++static const struct mdio_device_id __maybe_unused dp83848_tbl[] = {
+       { TI_DP83848C_PHY_ID, 0xfffffff0 },
+       { NS_DP83848C_PHY_ID, 0xfffffff0 },
+       { TI_DP83620_PHY_ID, 0xfffffff0 },
+--- a/drivers/net/phy/dp83867.c
++++ b/drivers/net/phy/dp83867.c
+@@ -1210,7 +1210,7 @@ static struct phy_driver dp83867_driver[
+ };
+ module_phy_driver(dp83867_driver);
+-static struct mdio_device_id __maybe_unused dp83867_tbl[] = {
++static const struct mdio_device_id __maybe_unused dp83867_tbl[] = {
+       { DP83867_PHY_ID, 0xfffffff0 },
+       { }
+ };
+--- a/drivers/net/phy/dp83869.c
++++ b/drivers/net/phy/dp83869.c
+@@ -928,7 +928,7 @@ static struct phy_driver dp83869_driver[
+ };
+ module_phy_driver(dp83869_driver);
+-static struct mdio_device_id __maybe_unused dp83869_tbl[] = {
++static const struct mdio_device_id __maybe_unused dp83869_tbl[] = {
+       { PHY_ID_MATCH_MODEL(DP83869_PHY_ID) },
+       { PHY_ID_MATCH_MODEL(DP83561_PHY_ID) },
+       { }
+--- a/drivers/net/phy/dp83tc811.c
++++ b/drivers/net/phy/dp83tc811.c
+@@ -403,7 +403,7 @@ static struct phy_driver dp83811_driver[
+ };
+ module_phy_driver(dp83811_driver);
+-static struct mdio_device_id __maybe_unused dp83811_tbl[] = {
++static const struct mdio_device_id __maybe_unused dp83811_tbl[] = {
+       { DP83TC811_PHY_ID, 0xfffffff0 },
+       { },
+ };
+--- a/drivers/net/phy/dp83td510.c
++++ b/drivers/net/phy/dp83td510.c
+@@ -605,7 +605,7 @@ static struct phy_driver dp83td510_drive
+ } };
+ module_phy_driver(dp83td510_driver);
+-static struct mdio_device_id __maybe_unused dp83td510_tbl[] = {
++static const struct mdio_device_id __maybe_unused dp83td510_tbl[] = {
+       { PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID) },
+       { }
+ };
+--- a/drivers/net/phy/dp83tg720.c
++++ b/drivers/net/phy/dp83tg720.c
+@@ -361,7 +361,7 @@ static struct phy_driver dp83tg720_drive
+ } };
+ module_phy_driver(dp83tg720_driver);
+-static struct mdio_device_id __maybe_unused dp83tg720_tbl[] = {
++static const struct mdio_device_id __maybe_unused dp83tg720_tbl[] = {
+       { PHY_ID_MATCH_MODEL(DP83TG720S_PHY_ID) },
+       { }
+ };
+--- a/drivers/net/phy/et1011c.c
++++ b/drivers/net/phy/et1011c.c
+@@ -94,7 +94,7 @@ static struct phy_driver et1011c_driver[
+ module_phy_driver(et1011c_driver);
+-static struct mdio_device_id __maybe_unused et1011c_tbl[] = {
++static const struct mdio_device_id __maybe_unused et1011c_tbl[] = {
+       { 0x0282f014, 0xfffffff0 },
+       { }
+ };
+--- a/drivers/net/phy/icplus.c
++++ b/drivers/net/phy/icplus.c
+@@ -624,7 +624,7 @@ static struct phy_driver icplus_driver[]
+ module_phy_driver(icplus_driver);
+-static struct mdio_device_id __maybe_unused icplus_tbl[] = {
++static const struct mdio_device_id __maybe_unused icplus_tbl[] = {
+       { PHY_ID_MATCH_MODEL(IP175C_PHY_ID) },
+       { PHY_ID_MATCH_MODEL(IP1001_PHY_ID) },
+       { PHY_ID_MATCH_EXACT(IP101A_PHY_ID) },
+--- a/drivers/net/phy/intel-xway.c
++++ b/drivers/net/phy/intel-xway.c
+@@ -456,7 +456,7 @@ static struct phy_driver xway_gphy[] = {
+ };
+ module_phy_driver(xway_gphy);
+-static struct mdio_device_id __maybe_unused xway_gphy_tbl[] = {
++static const struct mdio_device_id __maybe_unused xway_gphy_tbl[] = {
+       { PHY_ID_PHY11G_1_3, 0xffffffff },
+       { PHY_ID_PHY22F_1_3, 0xffffffff },
+       { PHY_ID_PHY11G_1_4, 0xffffffff },
+--- a/drivers/net/phy/lxt.c
++++ b/drivers/net/phy/lxt.c
+@@ -348,7 +348,7 @@ static struct phy_driver lxt97x_driver[]
+ module_phy_driver(lxt97x_driver);
+-static struct mdio_device_id __maybe_unused lxt_tbl[] = {
++static const struct mdio_device_id __maybe_unused lxt_tbl[] = {
+       { 0x78100000, 0xfffffff0 },
+       { 0x001378e0, 0xfffffff0 },
+       { 0x00137a10, 0xfffffff0 },
+--- a/drivers/net/phy/marvell-88q2xxx.c
++++ b/drivers/net/phy/marvell-88q2xxx.c
+@@ -940,7 +940,7 @@ static struct phy_driver mv88q2xxx_drive
+ module_phy_driver(mv88q2xxx_driver);
+-static struct mdio_device_id __maybe_unused mv88q2xxx_tbl[] = {
++static const struct mdio_device_id __maybe_unused mv88q2xxx_tbl[] = {
+       { MARVELL_PHY_ID_88Q2110, MARVELL_PHY_ID_MASK },
+       { MARVELL_PHY_ID_88Q2220, MARVELL_PHY_ID_MASK },
+       { /*sentinel*/ }
+--- a/drivers/net/phy/marvell-88x2222.c
++++ b/drivers/net/phy/marvell-88x2222.c
+@@ -613,7 +613,7 @@ static struct phy_driver mv2222_drivers[
+ };
+ module_phy_driver(mv2222_drivers);
+-static struct mdio_device_id __maybe_unused mv2222_tbl[] = {
++static const struct mdio_device_id __maybe_unused mv2222_tbl[] = {
+       { MARVELL_PHY_ID_88X2222, MARVELL_PHY_ID_MASK },
+       { }
+ };
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -4218,7 +4218,7 @@ static struct phy_driver marvell_drivers
+ module_phy_driver(marvell_drivers);
+-static struct mdio_device_id __maybe_unused marvell_tbl[] = {
++static const struct mdio_device_id __maybe_unused marvell_tbl[] = {
+       { MARVELL_PHY_ID_88E1101, MARVELL_PHY_ID_MASK },
+       { MARVELL_PHY_ID_88E3082, MARVELL_PHY_ID_MASK },
+       { MARVELL_PHY_ID_88E1112, MARVELL_PHY_ID_MASK },
+--- a/drivers/net/phy/marvell10g.c
++++ b/drivers/net/phy/marvell10g.c
+@@ -1484,7 +1484,7 @@ static struct phy_driver mv3310_drivers[
+ module_phy_driver(mv3310_drivers);
+-static struct mdio_device_id __maybe_unused mv3310_tbl[] = {
++static const struct mdio_device_id __maybe_unused mv3310_tbl[] = {
+       { MARVELL_PHY_ID_88X3310, MARVELL_PHY_ID_MASK },
+       { MARVELL_PHY_ID_88E2110, MARVELL_PHY_ID_MASK },
+       { },
+--- a/drivers/net/phy/mediatek/mtk-ge-soc.c
++++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
+@@ -1356,7 +1356,7 @@ static struct phy_driver mtk_socphy_driv
+ module_phy_driver(mtk_socphy_driver);
+-static struct mdio_device_id __maybe_unused mtk_socphy_tbl[] = {
++static const struct mdio_device_id __maybe_unused mtk_socphy_tbl[] = {
+       { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981) },
+       { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988) },
+       { }
+--- a/drivers/net/phy/mediatek/mtk-ge.c
++++ b/drivers/net/phy/mediatek/mtk-ge.c
+@@ -93,7 +93,7 @@ static struct phy_driver mtk_gephy_drive
+ module_phy_driver(mtk_gephy_driver);
+-static struct mdio_device_id __maybe_unused mtk_gephy_tbl[] = {
++static const struct mdio_device_id __maybe_unused mtk_gephy_tbl[] = {
+       { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7530) },
+       { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7531) },
+       { }
+--- a/drivers/net/phy/meson-gxl.c
++++ b/drivers/net/phy/meson-gxl.c
+@@ -221,7 +221,7 @@ static struct phy_driver meson_gxl_phy[]
+       },
+ };
+-static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
++static const struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
+       { PHY_ID_MATCH_VENDOR(0x01814400) },
+       { PHY_ID_MATCH_VENDOR(0x01803301) },
+       { }
+--- a/drivers/net/phy/micrel.c
++++ b/drivers/net/phy/micrel.c
+@@ -5835,7 +5835,7 @@ MODULE_DESCRIPTION("Micrel PHY driver");
+ MODULE_AUTHOR("David J. Choi");
+ MODULE_LICENSE("GPL");
+-static struct mdio_device_id __maybe_unused micrel_tbl[] = {
++static const struct mdio_device_id __maybe_unused micrel_tbl[] = {
+       { PHY_ID_KSZ9021, 0x000ffffe },
+       { PHY_ID_KSZ9031, MICREL_PHY_ID_MASK },
+       { PHY_ID_KSZ9131, MICREL_PHY_ID_MASK },
+--- a/drivers/net/phy/microchip.c
++++ b/drivers/net/phy/microchip.c
+@@ -509,7 +509,7 @@ static struct phy_driver microchip_phy_d
+ module_phy_driver(microchip_phy_driver);
+-static struct mdio_device_id __maybe_unused microchip_tbl[] = {
++static const struct mdio_device_id __maybe_unused microchip_tbl[] = {
+       { 0x0007c132, 0xfffffff2 },
+       { PHY_ID_MATCH_MODEL(PHY_ID_LAN937X_TX) },
+       { }
+--- a/drivers/net/phy/microchip_t1.c
++++ b/drivers/net/phy/microchip_t1.c
+@@ -1886,7 +1886,7 @@ static struct phy_driver microchip_t1_ph
+ module_phy_driver(microchip_t1_phy_driver);
+-static struct mdio_device_id __maybe_unused microchip_t1_tbl[] = {
++static const struct mdio_device_id __maybe_unused microchip_t1_tbl[] = {
+       { PHY_ID_MATCH_MODEL(PHY_ID_LAN87XX) },
+       { PHY_ID_MATCH_MODEL(PHY_ID_LAN937X) },
+       { PHY_ID_MATCH_MODEL(PHY_ID_LAN887X) },
+--- a/drivers/net/phy/microchip_t1s.c
++++ b/drivers/net/phy/microchip_t1s.c
+@@ -323,7 +323,7 @@ static struct phy_driver microchip_t1s_d
+ module_phy_driver(microchip_t1s_driver);
+-static struct mdio_device_id __maybe_unused tbl[] = {
++static const struct mdio_device_id __maybe_unused tbl[] = {
+       { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1) },
+       { PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB0) },
+       { }
+--- a/drivers/net/phy/mscc/mscc_main.c
++++ b/drivers/net/phy/mscc/mscc_main.c
+@@ -2710,7 +2710,7 @@ static struct phy_driver vsc85xx_driver[
+ module_phy_driver(vsc85xx_driver);
+-static struct mdio_device_id __maybe_unused vsc85xx_tbl[] = {
++static const struct mdio_device_id __maybe_unused vsc85xx_tbl[] = {
+       { PHY_ID_MATCH_VENDOR(PHY_VENDOR_MSCC) },
+       { }
+ };
+--- a/drivers/net/phy/mxl-gpy.c
++++ b/drivers/net/phy/mxl-gpy.c
+@@ -1047,7 +1047,7 @@ static struct phy_driver gpy_drivers[] =
+ };
+ module_phy_driver(gpy_drivers);
+-static struct mdio_device_id __maybe_unused gpy_tbl[] = {
++static const struct mdio_device_id __maybe_unused gpy_tbl[] = {
+       {PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx)},
+       {PHY_ID_GPY115B, PHY_ID_GPYx15B_MASK},
+       {PHY_ID_MATCH_MODEL(PHY_ID_GPY115C)},
+--- a/drivers/net/phy/national.c
++++ b/drivers/net/phy/national.c
+@@ -173,7 +173,7 @@ MODULE_DESCRIPTION("NatSemi PHY driver")
+ MODULE_AUTHOR("Stuart Menefy");
+ MODULE_LICENSE("GPL");
+-static struct mdio_device_id __maybe_unused ns_tbl[] = {
++static const struct mdio_device_id __maybe_unused ns_tbl[] = {
+       { DP83865_PHY_ID, 0xfffffff0 },
+       { }
+ };
+--- a/drivers/net/phy/ncn26000.c
++++ b/drivers/net/phy/ncn26000.c
+@@ -159,7 +159,7 @@ static struct phy_driver ncn26000_driver
+ module_phy_driver(ncn26000_driver);
+-static struct mdio_device_id __maybe_unused ncn26000_tbl[] = {
++static const struct mdio_device_id __maybe_unused ncn26000_tbl[] = {
+       { PHY_ID_MATCH_MODEL(PHY_ID_NCN26000) },
+       { }
+ };
+--- a/drivers/net/phy/nxp-c45-tja11xx.c
++++ b/drivers/net/phy/nxp-c45-tja11xx.c
+@@ -2102,7 +2102,7 @@ static struct phy_driver nxp_c45_driver[
+ module_phy_driver(nxp_c45_driver);
+-static struct mdio_device_id __maybe_unused nxp_c45_tbl[] = {
++static const struct mdio_device_id __maybe_unused nxp_c45_tbl[] = {
+       { PHY_ID_MATCH_MODEL(PHY_ID_TJA_1103) },
+       { PHY_ID_MATCH_MODEL(PHY_ID_TJA_1120) },
+       { /*sentinel*/ },
+--- a/drivers/net/phy/nxp-cbtx.c
++++ b/drivers/net/phy/nxp-cbtx.c
+@@ -215,7 +215,7 @@ static struct phy_driver cbtx_driver[] =
+ module_phy_driver(cbtx_driver);
+-static struct mdio_device_id __maybe_unused cbtx_tbl[] = {
++static const struct mdio_device_id __maybe_unused cbtx_tbl[] = {
+       { PHY_ID_MATCH_MODEL(PHY_ID_CBTX_SJA1110) },
+       { },
+ };
+--- a/drivers/net/phy/nxp-tja11xx.c
++++ b/drivers/net/phy/nxp-tja11xx.c
+@@ -888,7 +888,7 @@ static struct phy_driver tja11xx_driver[
+ module_phy_driver(tja11xx_driver);
+-static struct mdio_device_id __maybe_unused tja11xx_tbl[] = {
++static const struct mdio_device_id __maybe_unused tja11xx_tbl[] = {
+       { PHY_ID_MATCH_MODEL(PHY_ID_TJA1100) },
+       { PHY_ID_MATCH_MODEL(PHY_ID_TJA1101) },
+       { PHY_ID_MATCH_MODEL(PHY_ID_TJA1102) },
+--- a/drivers/net/phy/qcom/at803x.c
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -1071,7 +1071,7 @@ static struct phy_driver at803x_driver[]
+ module_phy_driver(at803x_driver);
+-static struct mdio_device_id __maybe_unused atheros_tbl[] = {
++static const 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) },
+--- a/drivers/net/phy/qcom/qca807x.c
++++ b/drivers/net/phy/qcom/qca807x.c
+@@ -828,7 +828,7 @@ static struct phy_driver qca807x_drivers
+ };
+ module_phy_driver(qca807x_drivers);
+-static struct mdio_device_id __maybe_unused qca807x_tbl[] = {
++static const struct mdio_device_id __maybe_unused qca807x_tbl[] = {
+       { PHY_ID_MATCH_EXACT(PHY_ID_QCA8072) },
+       { PHY_ID_MATCH_EXACT(PHY_ID_QCA8075) },
+       { }
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -655,7 +655,7 @@ static struct phy_driver qca808x_driver[
+ module_phy_driver(qca808x_driver);
+-static struct mdio_device_id __maybe_unused qca808x_tbl[] = {
++static const struct mdio_device_id __maybe_unused qca808x_tbl[] = {
+       { PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) },
+       { }
+ };
+--- a/drivers/net/phy/qcom/qca83xx.c
++++ b/drivers/net/phy/qcom/qca83xx.c
+@@ -261,7 +261,7 @@ static struct phy_driver qca83xx_driver[
+ module_phy_driver(qca83xx_driver);
+-static struct mdio_device_id __maybe_unused qca83xx_tbl[] = {
++static const 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) },
+--- a/drivers/net/phy/qsemi.c
++++ b/drivers/net/phy/qsemi.c
+@@ -155,7 +155,7 @@ static struct phy_driver qs6612_driver[]
+ module_phy_driver(qs6612_driver);
+-static struct mdio_device_id __maybe_unused qs6612_tbl[] = {
++static const struct mdio_device_id __maybe_unused qs6612_tbl[] = {
+       { 0x00181440, 0xfffffff0 },
+       { }
+ };
+--- a/drivers/net/phy/rockchip.c
++++ b/drivers/net/phy/rockchip.c
+@@ -188,7 +188,7 @@ static struct phy_driver rockchip_phy_dr
+ module_phy_driver(rockchip_phy_driver);
+-static struct mdio_device_id __maybe_unused rockchip_phy_tbl[] = {
++static const struct mdio_device_id __maybe_unused rockchip_phy_tbl[] = {
+       { INTERNAL_EPHY_ID, 0xfffffff0 },
+       { }
+ };
+--- a/drivers/net/phy/smsc.c
++++ b/drivers/net/phy/smsc.c
+@@ -885,7 +885,7 @@ MODULE_DESCRIPTION("SMSC PHY driver");
+ MODULE_AUTHOR("Herbert Valerio Riedel");
+ MODULE_LICENSE("GPL");
+-static struct mdio_device_id __maybe_unused smsc_tbl[] = {
++static const struct mdio_device_id __maybe_unused smsc_tbl[] = {
+       { 0x0007c0a0, 0xfffffff0 },
+       { 0x0007c0b0, 0xfffffff0 },
+       { 0x0007c0c0, 0xfffffff0 },
+--- a/drivers/net/phy/ste10Xp.c
++++ b/drivers/net/phy/ste10Xp.c
+@@ -124,7 +124,7 @@ static struct phy_driver ste10xp_pdriver
+ module_phy_driver(ste10xp_pdriver);
+-static struct mdio_device_id __maybe_unused ste10Xp_tbl[] = {
++static const struct mdio_device_id __maybe_unused ste10Xp_tbl[] = {
+       { STE101P_PHY_ID, 0xfffffff0 },
+       { STE100P_PHY_ID, 0xffffffff },
+       { }
+--- a/drivers/net/phy/teranetics.c
++++ b/drivers/net/phy/teranetics.c
+@@ -87,7 +87,7 @@ static struct phy_driver teranetics_driv
+ module_phy_driver(teranetics_driver);
+-static struct mdio_device_id __maybe_unused teranetics_tbl[] = {
++static const struct mdio_device_id __maybe_unused teranetics_tbl[] = {
+       { PHY_ID_TN2020, 0xffffffff },
+       { }
+ };
+--- a/drivers/net/phy/uPD60620.c
++++ b/drivers/net/phy/uPD60620.c
+@@ -90,7 +90,7 @@ static struct phy_driver upd60620_driver
+ module_phy_driver(upd60620_driver);
+-static struct mdio_device_id __maybe_unused upd60620_tbl[] = {
++static const struct mdio_device_id __maybe_unused upd60620_tbl[] = {
+       { UPD60620_PHY_ID, 0xfffffffe },
+       { }
+ };
+--- a/drivers/net/phy/vitesse.c
++++ b/drivers/net/phy/vitesse.c
+@@ -674,7 +674,7 @@ static struct phy_driver vsc82xx_driver[
+ module_phy_driver(vsc82xx_driver);
+-static struct mdio_device_id __maybe_unused vitesse_tbl[] = {
++static const struct mdio_device_id __maybe_unused vitesse_tbl[] = {
+       { PHY_ID_VSC8234, 0x000ffff0 },
+       { PHY_ID_VSC8244, 0x000fffc0 },
+       { PHY_ID_VSC8572, 0x000ffff0 },
diff --git a/target/linux/generic/backport-6.12/720-10-v6.15-net-phy-mediatek-Change-to-more-meaningful-macros.patch b/target/linux/generic/backport-6.12/720-10-v6.15-net-phy-mediatek-Change-to-more-meaningful-macros.patch
new file mode 100644 (file)
index 0000000..545e929
--- /dev/null
@@ -0,0 +1,146 @@
+From 7e06c3dbfa5f1e39eba92eb79d854fab2a7ad5fe Mon Sep 17 00:00:00 2001
+From: Sky Huang <skylake.huang@mediatek.com>
+Date: Thu, 13 Feb 2025 16:05:49 +0800
+Subject: [PATCH 10/20] net: phy: mediatek: Change to more meaningful macros
+
+Replace magic number with more meaningful macros in mtk-ge.c.
+Also, move some common macros into mtk-phy-lib.c.
+
+Signed-off-by: Sky Huang <skylake.huang@mediatek.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250213080553.921434-2-SkyLake.Huang@mediatek.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mediatek/mtk-ge-soc.c |  1 -
+ drivers/net/phy/mediatek/mtk-ge.c     | 71 +++++++++++++++++++++------
+ drivers/net/phy/mediatek/mtk.h        |  2 +
+ 3 files changed, 57 insertions(+), 17 deletions(-)
+
+--- a/drivers/net/phy/mediatek/mtk-ge-soc.c
++++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
+@@ -24,7 +24,6 @@
+ #define MTK_PHY_SMI_DET_ON_THRESH_MASK                GENMASK(13, 8)
+ #define MTK_PHY_PAGE_EXTENDED_2A30            0x2a30
+-#define MTK_PHY_PAGE_EXTENDED_52B5            0x52b5
+ #define ANALOG_INTERNAL_OPERATION_MAX_US      20
+ #define TXRESERVE_MIN                         0
+--- a/drivers/net/phy/mediatek/mtk-ge.c
++++ b/drivers/net/phy/mediatek/mtk-ge.c
+@@ -8,18 +8,38 @@
+ #define MTK_GPHY_ID_MT7530            0x03a29412
+ #define MTK_GPHY_ID_MT7531            0x03a29441
+-#define MTK_EXT_PAGE_ACCESS           0x1f
+-#define MTK_PHY_PAGE_STANDARD         0x0000
+-#define MTK_PHY_PAGE_EXTENDED         0x0001
+-#define MTK_PHY_PAGE_EXTENDED_2               0x0002
+-#define MTK_PHY_PAGE_EXTENDED_3               0x0003
+-#define MTK_PHY_PAGE_EXTENDED_2A30    0x2a30
+-#define MTK_PHY_PAGE_EXTENDED_52B5    0x52b5
++#define MTK_PHY_PAGE_EXTENDED_1                       0x0001
++#define MTK_PHY_AUX_CTRL_AND_STATUS           0x14
++#define   MTK_PHY_ENABLE_DOWNSHIFT            BIT(4)
++
++#define MTK_PHY_PAGE_EXTENDED_2                       0x0002
++#define MTK_PHY_PAGE_EXTENDED_3                       0x0003
++#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG11     0x11
++
++#define MTK_PHY_PAGE_EXTENDED_2A30            0x2a30
++
++/* Registers on MDIO_MMD_VEND1 */
++#define MTK_PHY_GBE_MODE_TX_DELAY_SEL         0x13
++#define MTK_PHY_TEST_MODE_TX_DELAY_SEL                0x14
++#define   MTK_TX_DELAY_PAIR_B_MASK            GENMASK(10, 8)
++#define   MTK_TX_DELAY_PAIR_D_MASK            GENMASK(2, 0)
++
++#define MTK_PHY_MCC_CTRL_AND_TX_POWER_CTRL    0xa6
++#define   MTK_MCC_NEARECHO_OFFSET_MASK                GENMASK(15, 8)
++
++#define MTK_PHY_RXADC_CTRL_RG7                        0xc6
++#define   MTK_PHY_DA_AD_BUF_BIAS_LP_MASK      GENMASK(9, 8)
++
++#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG123    0x123
++#define   MTK_PHY_LPI_NORM_MSE_LO_THRESH100_MASK      GENMASK(15, 8)
++#define   MTK_PHY_LPI_NORM_MSE_HI_THRESH100_MASK      GENMASK(7, 0)
+ static void mtk_gephy_config_init(struct phy_device *phydev)
+ {
+       /* Enable HW auto downshift */
+-      phy_modify_paged(phydev, MTK_PHY_PAGE_EXTENDED, 0x14, 0, BIT(4));
++      phy_modify_paged(phydev, MTK_PHY_PAGE_EXTENDED_1,
++                       MTK_PHY_AUX_CTRL_AND_STATUS,
++                       0, MTK_PHY_ENABLE_DOWNSHIFT);
+       /* Increase SlvDPSready time */
+       phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+@@ -29,10 +49,20 @@ static void mtk_gephy_config_init(struct
+       phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+       /* Adjust 100_mse_threshold */
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x123, 0xffff);
+-
+-      /* Disable mcc */
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xa6, 0x300);
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1,
++                     MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG123,
++                     MTK_PHY_LPI_NORM_MSE_LO_THRESH100_MASK |
++                     MTK_PHY_LPI_NORM_MSE_HI_THRESH100_MASK,
++                     FIELD_PREP(MTK_PHY_LPI_NORM_MSE_LO_THRESH100_MASK,
++                                0xff) |
++                     FIELD_PREP(MTK_PHY_LPI_NORM_MSE_HI_THRESH100_MASK,
++                                0xff));
++
++      /* If echo time is narrower than 0x3, it will be regarded as noise */
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1,
++                     MTK_PHY_MCC_CTRL_AND_TX_POWER_CTRL,
++                     MTK_MCC_NEARECHO_OFFSET_MASK,
++                     FIELD_PREP(MTK_MCC_NEARECHO_OFFSET_MASK, 0x3));
+ }
+ static int mt7530_phy_config_init(struct phy_device *phydev)
+@@ -40,7 +70,8 @@ static int mt7530_phy_config_init(struct
+       mtk_gephy_config_init(phydev);
+       /* Increase post_update_timer */
+-      phy_write_paged(phydev, MTK_PHY_PAGE_EXTENDED_3, 0x11, 0x4b);
++      phy_write_paged(phydev, MTK_PHY_PAGE_EXTENDED_3,
++                      MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG11, 0x4b);
+       return 0;
+ }
+@@ -51,11 +82,19 @@ static int mt7531_phy_config_init(struct
+       /* PHY link down power saving enable */
+       phy_set_bits(phydev, 0x17, BIT(4));
+-      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0xc6, 0x300);
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG7,
++                     MTK_PHY_DA_AD_BUF_BIAS_LP_MASK,
++                     FIELD_PREP(MTK_PHY_DA_AD_BUF_BIAS_LP_MASK, 0x3));
+       /* Set TX Pair delay selection */
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x13, 0x404);
+-      phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x14, 0x404);
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_GBE_MODE_TX_DELAY_SEL,
++                     MTK_TX_DELAY_PAIR_B_MASK | MTK_TX_DELAY_PAIR_D_MASK,
++                     FIELD_PREP(MTK_TX_DELAY_PAIR_B_MASK, 0x4) |
++                     FIELD_PREP(MTK_TX_DELAY_PAIR_D_MASK, 0x4));
++      phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TEST_MODE_TX_DELAY_SEL,
++                     MTK_TX_DELAY_PAIR_B_MASK | MTK_TX_DELAY_PAIR_D_MASK,
++                     FIELD_PREP(MTK_TX_DELAY_PAIR_B_MASK, 0x4) |
++                     FIELD_PREP(MTK_TX_DELAY_PAIR_D_MASK, 0x4));
+       return 0;
+ }
+--- a/drivers/net/phy/mediatek/mtk.h
++++ b/drivers/net/phy/mediatek/mtk.h
+@@ -9,6 +9,8 @@
+ #define _MTK_EPHY_H_
+ #define MTK_EXT_PAGE_ACCESS                   0x1f
++#define MTK_PHY_PAGE_STANDARD                 0x0000
++#define MTK_PHY_PAGE_EXTENDED_52B5            0x52b5
+ /* Registers on MDIO_MMD_VEND2 */
+ #define MTK_PHY_LED0_ON_CTRL                  0x24
diff --git a/target/linux/generic/backport-6.12/720-11-v6.15-net-phy-mediatek-Add-token-ring-access-helper-functi.patch b/target/linux/generic/backport-6.12/720-11-v6.15-net-phy-mediatek-Add-token-ring-access-helper-functi.patch
new file mode 100644 (file)
index 0000000..40ce29f
--- /dev/null
@@ -0,0 +1,448 @@
+From 6e7370079669b0d55c9464bb7c3fb8fb7368b912 Mon Sep 17 00:00:00 2001
+From: Sky Huang <skylake.huang@mediatek.com>
+Date: Thu, 13 Feb 2025 16:05:50 +0800
+Subject: [PATCH 11/20] net: phy: mediatek: Add token ring access helper
+ functions in mtk-phy-lib
+
+This patch adds TR(token ring) manipulations and adds correct
+macro names for those magic numbers. TR is a way to access
+proprietary registers on page 52b5. Use these helper functions
+so we can see which fields we're going to modify/set/clear.
+
+TR functions with __* prefix mean that the operations inside
+aren't wrapped by page select/restore functions.
+
+This patch doesn't really change registers' settings but just
+enhances readability and maintainability.
+
+Signed-off-by: Sky Huang <skylake.huang@mediatek.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250213080553.921434-3-SkyLake.Huang@mediatek.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mediatek/mtk-ge-soc.c  | 231 +++++++++++++++++--------
+ drivers/net/phy/mediatek/mtk-ge.c      |  11 +-
+ drivers/net/phy/mediatek/mtk-phy-lib.c |  63 +++++++
+ drivers/net/phy/mediatek/mtk.h         |   5 +
+ 4 files changed, 230 insertions(+), 80 deletions(-)
+
+--- a/drivers/net/phy/mediatek/mtk-ge-soc.c
++++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
+@@ -25,6 +25,90 @@
+ #define MTK_PHY_PAGE_EXTENDED_2A30            0x2a30
++/* Registers on Token Ring debug nodes */
++/* ch_addr = 0x0, node_addr = 0x7, data_addr = 0x15 */
++/* NormMseLoThresh */
++#define NORMAL_MSE_LO_THRESH_MASK             GENMASK(15, 8)
++
++/* ch_addr = 0x0, node_addr = 0xf, data_addr = 0x3c */
++/* RemAckCntLimitCtrl */
++#define REMOTE_ACK_COUNT_LIMIT_CTRL_MASK      GENMASK(2, 1)
++
++/* ch_addr = 0x1, node_addr = 0xd, data_addr = 0x20 */
++/* VcoSlicerThreshBitsHigh */
++#define VCO_SLICER_THRESH_HIGH_MASK           GENMASK(23, 0)
++
++/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x0 */
++/* DfeTailEnableVgaThresh1000 */
++#define DFE_TAIL_EANBLE_VGA_TRHESH_1000               GENMASK(5, 1)
++
++/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x1 */
++/* MrvlTrFix100Kp */
++#define MRVL_TR_FIX_100KP_MASK                        GENMASK(22, 20)
++/* MrvlTrFix100Kf */
++#define MRVL_TR_FIX_100KF_MASK                        GENMASK(19, 17)
++/* MrvlTrFix1000Kp */
++#define MRVL_TR_FIX_1000KP_MASK                       GENMASK(16, 14)
++/* MrvlTrFix1000Kf */
++#define MRVL_TR_FIX_1000KF_MASK                       GENMASK(13, 11)
++
++/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x12 */
++/* VgaDecRate */
++#define VGA_DECIMATION_RATE_MASK              GENMASK(8, 5)
++
++/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x17 */
++/* SlvDSPreadyTime */
++#define SLAVE_DSP_READY_TIME_MASK             GENMASK(22, 15)
++/* MasDSPreadyTime */
++#define MASTER_DSP_READY_TIME_MASK            GENMASK(14, 7)
++
++/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x20 */
++/* ResetSyncOffset */
++#define RESET_SYNC_OFFSET_MASK                        GENMASK(11, 8)
++
++/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x0 */
++/* FfeUpdGainForceVal */
++#define FFE_UPDATE_GAIN_FORCE_VAL_MASK                GENMASK(9, 7)
++/* FfeUpdGainForce */
++#define FFE_UPDATE_GAIN_FORCE                 BIT(6)
++
++/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x6 */
++/* SS: Steady-state, KP: Proportional Gain */
++/* SSTrKp100 */
++#define SS_TR_KP100_MASK                      GENMASK(21, 19)
++/* SSTrKf100 */
++#define SS_TR_KF100_MASK                      GENMASK(18, 16)
++/* SSTrKp1000Mas */
++#define SS_TR_KP1000_MASTER_MASK              GENMASK(15, 13)
++/* SSTrKf1000Mas */
++#define SS_TR_KF1000_MASTER_MASK              GENMASK(12, 10)
++/* SSTrKp1000Slv */
++#define SS_TR_KP1000_SLAVE_MASK                       GENMASK(9, 7)
++/* SSTrKf1000Slv */
++#define SS_TR_KF1000_SLAVE_MASK                       GENMASK(6, 4)
++
++/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0xd */
++/* RegEEE_st2TrKf1000 */
++#define EEE1000_STAGE2_TR_KF_MASK             GENMASK(13, 11)
++
++/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0xf */
++/* RegEEE_slv_waketr_timer_tar */
++#define SLAVE_WAKETR_TIMER_MASK                       GENMASK(20, 11)
++/* RegEEE_slv_remtx_timer_tar */
++#define SLAVE_REMTX_TIMER_MASK                        GENMASK(10, 1)
++
++/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x10 */
++/* RegEEE_slv_wake_int_timer_tar */
++#define SLAVE_WAKEINT_TIMER_MASK              GENMASK(10, 1)
++
++/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x14 */
++/* RegEEE_trfreeze_timer2 */
++#define TR_FREEZE_TIMER2_MASK                 GENMASK(9, 0)
++
++/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x1c */
++/* RegEEE100Stg1_tar */
++#define EEE100_LPSYNC_STAGE1_UPDATE_TIMER_MASK        GENMASK(8, 0)
++
+ #define ANALOG_INTERNAL_OPERATION_MAX_US      20
+ #define TXRESERVE_MIN                         0
+ #define TXRESERVE_MAX                         7
+@@ -700,40 +784,41 @@ restore:
+ static void mt798x_phy_common_finetune(struct phy_device *phydev)
+ {
+       phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+-      /* SlvDSPreadyTime = 24, MasDSPreadyTime = 24 */
+-      __phy_write(phydev, 0x11, 0xc71);
+-      __phy_write(phydev, 0x12, 0xc);
+-      __phy_write(phydev, 0x10, 0x8fae);
++      __mtk_tr_modify(phydev, 0x1, 0xf, 0x17,
++                      SLAVE_DSP_READY_TIME_MASK | MASTER_DSP_READY_TIME_MASK,
++                      FIELD_PREP(SLAVE_DSP_READY_TIME_MASK, 0x18) |
++                      FIELD_PREP(MASTER_DSP_READY_TIME_MASK, 0x18));
+       /* EnabRandUpdTrig = 1 */
+       __phy_write(phydev, 0x11, 0x2f00);
+       __phy_write(phydev, 0x12, 0xe);
+       __phy_write(phydev, 0x10, 0x8fb0);
+-      /* NormMseLoThresh = 85 */
+-      __phy_write(phydev, 0x11, 0x55a0);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x83aa);
+-
+-      /* FfeUpdGainForce = 1(Enable), FfeUpdGainForceVal = 4 */
+-      __phy_write(phydev, 0x11, 0x240);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x9680);
++      __mtk_tr_modify(phydev, 0x0, 0x7, 0x15,
++                      NORMAL_MSE_LO_THRESH_MASK,
++                      FIELD_PREP(NORMAL_MSE_LO_THRESH_MASK, 0x55));
++
++      __mtk_tr_modify(phydev, 0x2, 0xd, 0x0,
++                      FFE_UPDATE_GAIN_FORCE_VAL_MASK,
++                      FIELD_PREP(FFE_UPDATE_GAIN_FORCE_VAL_MASK, 0x4) |
++                                 FFE_UPDATE_GAIN_FORCE);
+       /* TrFreeze = 0 (mt7988 default) */
+       __phy_write(phydev, 0x11, 0x0);
+       __phy_write(phydev, 0x12, 0x0);
+       __phy_write(phydev, 0x10, 0x9686);
+-      /* SSTrKp100 = 5 */
+-      /* SSTrKf100 = 6 */
+-      /* SSTrKp1000Mas = 5 */
+-      /* SSTrKf1000Mas = 6 */
+-      /* SSTrKp1000Slv = 5 */
+-      /* SSTrKf1000Slv = 6 */
+-      __phy_write(phydev, 0x11, 0xbaef);
+-      __phy_write(phydev, 0x12, 0x2e);
+-      __phy_write(phydev, 0x10, 0x968c);
++      __mtk_tr_modify(phydev, 0x2, 0xd, 0x6,
++                      SS_TR_KP100_MASK | SS_TR_KF100_MASK |
++                      SS_TR_KP1000_MASTER_MASK | SS_TR_KF1000_MASTER_MASK |
++                      SS_TR_KP1000_SLAVE_MASK | SS_TR_KF1000_SLAVE_MASK,
++                      FIELD_PREP(SS_TR_KP100_MASK, 0x5) |
++                      FIELD_PREP(SS_TR_KF100_MASK, 0x6) |
++                      FIELD_PREP(SS_TR_KP1000_MASTER_MASK, 0x5) |
++                      FIELD_PREP(SS_TR_KF1000_MASTER_MASK, 0x6) |
++                      FIELD_PREP(SS_TR_KP1000_SLAVE_MASK, 0x5) |
++                      FIELD_PREP(SS_TR_KF1000_SLAVE_MASK, 0x6));
++
+       phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+ }
+@@ -756,27 +841,29 @@ static void mt7981_phy_finetune(struct p
+       }
+       phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+-      /* ResetSyncOffset = 6 */
+-      __phy_write(phydev, 0x11, 0x600);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x8fc0);
+-
+-      /* VgaDecRate = 1 */
+-      __phy_write(phydev, 0x11, 0x4c2a);
+-      __phy_write(phydev, 0x12, 0x3e);
+-      __phy_write(phydev, 0x10, 0x8fa4);
++      __mtk_tr_modify(phydev, 0x1, 0xf, 0x20,
++                      RESET_SYNC_OFFSET_MASK,
++                      FIELD_PREP(RESET_SYNC_OFFSET_MASK, 0x6));
++
++      __mtk_tr_modify(phydev, 0x1, 0xf, 0x12,
++                      VGA_DECIMATION_RATE_MASK,
++                      FIELD_PREP(VGA_DECIMATION_RATE_MASK, 0x1));
+       /* MrvlTrFix100Kp = 3, MrvlTrFix100Kf = 2,
+        * MrvlTrFix1000Kp = 3, MrvlTrFix1000Kf = 2
+        */
+-      __phy_write(phydev, 0x11, 0xd10a);
+-      __phy_write(phydev, 0x12, 0x34);
+-      __phy_write(phydev, 0x10, 0x8f82);
++      __mtk_tr_modify(phydev, 0x1, 0xf, 0x1,
++                      MRVL_TR_FIX_100KP_MASK | MRVL_TR_FIX_100KF_MASK |
++                      MRVL_TR_FIX_1000KP_MASK | MRVL_TR_FIX_1000KF_MASK,
++                      FIELD_PREP(MRVL_TR_FIX_100KP_MASK, 0x3) |
++                      FIELD_PREP(MRVL_TR_FIX_100KF_MASK, 0x2) |
++                      FIELD_PREP(MRVL_TR_FIX_1000KP_MASK, 0x3) |
++                      FIELD_PREP(MRVL_TR_FIX_1000KF_MASK, 0x2));
+       /* VcoSlicerThreshBitsHigh */
+-      __phy_write(phydev, 0x11, 0x5555);
+-      __phy_write(phydev, 0x12, 0x55);
+-      __phy_write(phydev, 0x10, 0x8ec0);
++      __mtk_tr_modify(phydev, 0x1, 0xd, 0x20,
++                      VCO_SLICER_THRESH_HIGH_MASK,
++                      FIELD_PREP(VCO_SLICER_THRESH_HIGH_MASK, 0x555555));
+       phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+       /* TR_OPEN_LOOP_EN = 1, lpf_x_average = 9 */
+@@ -828,25 +915,23 @@ static void mt7988_phy_finetune(struct p
+       phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_TX_FILTER, 0x5);
+       phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+-      /* ResetSyncOffset = 5 */
+-      __phy_write(phydev, 0x11, 0x500);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x8fc0);
++      __mtk_tr_modify(phydev, 0x1, 0xf, 0x20,
++                      RESET_SYNC_OFFSET_MASK,
++                      FIELD_PREP(RESET_SYNC_OFFSET_MASK, 0x5));
+       /* VgaDecRate is 1 at default on mt7988 */
+-      /* MrvlTrFix100Kp = 6, MrvlTrFix100Kf = 7,
+-       * MrvlTrFix1000Kp = 6, MrvlTrFix1000Kf = 7
+-       */
+-      __phy_write(phydev, 0x11, 0xb90a);
+-      __phy_write(phydev, 0x12, 0x6f);
+-      __phy_write(phydev, 0x10, 0x8f82);
+-
+-      /* RemAckCntLimitCtrl = 1 */
+-      __phy_write(phydev, 0x11, 0xfbba);
+-      __phy_write(phydev, 0x12, 0xc3);
+-      __phy_write(phydev, 0x10, 0x87f8);
+-
++      __mtk_tr_modify(phydev, 0x1, 0xf, 0x1,
++                      MRVL_TR_FIX_100KP_MASK | MRVL_TR_FIX_100KF_MASK |
++                      MRVL_TR_FIX_1000KP_MASK | MRVL_TR_FIX_1000KF_MASK,
++                      FIELD_PREP(MRVL_TR_FIX_100KP_MASK, 0x6) |
++                      FIELD_PREP(MRVL_TR_FIX_100KF_MASK, 0x7) |
++                      FIELD_PREP(MRVL_TR_FIX_1000KP_MASK, 0x6) |
++                      FIELD_PREP(MRVL_TR_FIX_1000KF_MASK, 0x7));
++
++      __mtk_tr_modify(phydev, 0x0, 0xf, 0x3c,
++                      REMOTE_ACK_COUNT_LIMIT_CTRL_MASK,
++                      FIELD_PREP(REMOTE_ACK_COUNT_LIMIT_CTRL_MASK, 0x1));
+       phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+       /* TR_OPEN_LOOP_EN = 1, lpf_x_average = 10 */
+@@ -927,40 +1012,36 @@ static void mt798x_phy_eee(struct phy_de
+       __phy_write(phydev, 0x12, 0x0);
+       __phy_write(phydev, 0x10, 0x9690);
+-      /* REG_EEE_st2TrKf1000 = 2 */
+-      __phy_write(phydev, 0x11, 0x114f);
+-      __phy_write(phydev, 0x12, 0x2);
+-      __phy_write(phydev, 0x10, 0x969a);
+-
+-      /* RegEEE_slv_wake_tr_timer_tar = 6, RegEEE_slv_remtx_timer_tar = 20 */
+-      __phy_write(phydev, 0x11, 0x3028);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x969e);
+-
+-      /* RegEEE_slv_wake_int_timer_tar = 8 */
+-      __phy_write(phydev, 0x11, 0x5010);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x96a0);
+-
+-      /* RegEEE_trfreeze_timer2 = 586 */
+-      __phy_write(phydev, 0x11, 0x24a);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x96a8);
+-
+-      /* RegEEE100Stg1_tar = 16 */
+-      __phy_write(phydev, 0x11, 0x3210);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x96b8);
++      __mtk_tr_modify(phydev, 0x2, 0xd, 0xd,
++                      EEE1000_STAGE2_TR_KF_MASK,
++                      FIELD_PREP(EEE1000_STAGE2_TR_KF_MASK, 0x2));
++
++      __mtk_tr_modify(phydev, 0x2, 0xd, 0xf,
++                      SLAVE_WAKETR_TIMER_MASK | SLAVE_REMTX_TIMER_MASK,
++                      FIELD_PREP(SLAVE_WAKETR_TIMER_MASK, 0x6) |
++                      FIELD_PREP(SLAVE_REMTX_TIMER_MASK, 0x14));
++
++      __mtk_tr_modify(phydev, 0x2, 0xd, 0x10,
++                      SLAVE_WAKEINT_TIMER_MASK,
++                      FIELD_PREP(SLAVE_WAKEINT_TIMER_MASK, 0x8));
++
++      __mtk_tr_modify(phydev, 0x2, 0xd, 0x14,
++                      TR_FREEZE_TIMER2_MASK,
++                      FIELD_PREP(TR_FREEZE_TIMER2_MASK, 0x24a));
++
++      __mtk_tr_modify(phydev, 0x2, 0xd, 0x1c,
++                      EEE100_LPSYNC_STAGE1_UPDATE_TIMER_MASK,
++                      FIELD_PREP(EEE100_LPSYNC_STAGE1_UPDATE_TIMER_MASK,
++                                 0x10));
+       /* REGEEE_wake_slv_tr_wait_dfesigdet_en = 0 */
+       __phy_write(phydev, 0x11, 0x1463);
+       __phy_write(phydev, 0x12, 0x0);
+       __phy_write(phydev, 0x10, 0x96ca);
+-      /* DfeTailEnableVgaThresh1000 = 27 */
+-      __phy_write(phydev, 0x11, 0x36);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x8f80);
++      __mtk_tr_modify(phydev, 0x1, 0xf, 0x0,
++                      DFE_TAIL_EANBLE_VGA_TRHESH_1000,
++                      FIELD_PREP(DFE_TAIL_EANBLE_VGA_TRHESH_1000, 0x1b));
+       phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+       phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_3);
+--- a/drivers/net/phy/mediatek/mtk-ge.c
++++ b/drivers/net/phy/mediatek/mtk-ge.c
+@@ -18,6 +18,10 @@
+ #define MTK_PHY_PAGE_EXTENDED_2A30            0x2a30
++/* Registers on Token Ring debug nodes */
++/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x17 */
++#define SLAVE_DSP_READY_TIME_MASK             GENMASK(22, 15)
++
+ /* Registers on MDIO_MMD_VEND1 */
+ #define MTK_PHY_GBE_MODE_TX_DELAY_SEL         0x13
+ #define MTK_PHY_TEST_MODE_TX_DELAY_SEL                0x14
+@@ -42,11 +46,8 @@ static void mtk_gephy_config_init(struct
+                        0, MTK_PHY_ENABLE_DOWNSHIFT);
+       /* Increase SlvDPSready time */
+-      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+-      __phy_write(phydev, 0x10, 0xafae);
+-      __phy_write(phydev, 0x12, 0x2f);
+-      __phy_write(phydev, 0x10, 0x8fae);
+-      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
++      mtk_tr_modify(phydev, 0x1, 0xf, 0x17, SLAVE_DSP_READY_TIME_MASK,
++                    FIELD_PREP(SLAVE_DSP_READY_TIME_MASK, 0x5e));
+       /* Adjust 100_mse_threshold */
+       phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+--- a/drivers/net/phy/mediatek/mtk-phy-lib.c
++++ b/drivers/net/phy/mediatek/mtk-phy-lib.c
+@@ -6,6 +6,69 @@
+ #include "mtk.h"
++/* Difference between functions with mtk_tr* and __mtk_tr* prefixes is
++ * mtk_tr* functions: wrapped by page switching operations
++ * __mtk_tr* functions: no page switching operations
++ */
++
++static void __mtk_tr_access(struct phy_device *phydev, bool read, u8 ch_addr,
++                          u8 node_addr, u8 data_addr)
++{
++      u16 tr_cmd = BIT(15); /* bit 14 & 0 are reserved */
++
++      if (read)
++              tr_cmd |= BIT(13);
++
++      tr_cmd |= (((ch_addr & 0x3) << 11) |
++                 ((node_addr & 0xf) << 7) |
++                 ((data_addr & 0x3f) << 1));
++      dev_dbg(&phydev->mdio.dev, "tr_cmd: 0x%x\n", tr_cmd);
++      __phy_write(phydev, 0x10, tr_cmd);
++}
++
++static void __mtk_tr_read(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
++                        u8 data_addr, u16 *tr_high, u16 *tr_low)
++{
++      __mtk_tr_access(phydev, true, ch_addr, node_addr, data_addr);
++      *tr_low = __phy_read(phydev, 0x11);
++      *tr_high = __phy_read(phydev, 0x12);
++      dev_dbg(&phydev->mdio.dev, "tr_high read: 0x%x, tr_low read: 0x%x\n",
++              *tr_high, *tr_low);
++}
++
++static void __mtk_tr_write(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
++                         u8 data_addr, u32 tr_data)
++{
++      __phy_write(phydev, 0x11, tr_data & 0xffff);
++      __phy_write(phydev, 0x12, tr_data >> 16);
++      dev_dbg(&phydev->mdio.dev, "tr_high write: 0x%x, tr_low write: 0x%x\n",
++              tr_data >> 16, tr_data & 0xffff);
++      __mtk_tr_access(phydev, false, ch_addr, node_addr, data_addr);
++}
++
++void __mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
++                   u8 data_addr, u32 mask, u32 set)
++{
++      u32 tr_data;
++      u16 tr_high;
++      u16 tr_low;
++
++      __mtk_tr_read(phydev, ch_addr, node_addr, data_addr, &tr_high, &tr_low);
++      tr_data = (tr_high << 16) | tr_low;
++      tr_data = (tr_data & ~mask) | set;
++      __mtk_tr_write(phydev, ch_addr, node_addr, data_addr, tr_data);
++}
++EXPORT_SYMBOL_GPL(__mtk_tr_modify);
++
++void mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
++                 u8 data_addr, u32 mask, u32 set)
++{
++      phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
++      __mtk_tr_modify(phydev, ch_addr, node_addr, data_addr, mask, set);
++      phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
++}
++EXPORT_SYMBOL_GPL(mtk_tr_modify);
++
+ int mtk_phy_read_page(struct phy_device *phydev)
+ {
+       return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
+--- a/drivers/net/phy/mediatek/mtk.h
++++ b/drivers/net/phy/mediatek/mtk.h
+@@ -68,6 +68,11 @@ struct mtk_socphy_priv {
+       unsigned long           led_state;
+ };
++void __mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
++                   u8 data_addr, u32 mask, u32 set);
++void mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
++                 u8 data_addr, u32 mask, u32 set);
++
+ int mtk_phy_read_page(struct phy_device *phydev);
+ int mtk_phy_write_page(struct phy_device *phydev, int page);
diff --git a/target/linux/generic/backport-6.12/720-12-v6.15-net-phy-mediatek-Add-token-ring-set-bit-operation-su.patch b/target/linux/generic/backport-6.12/720-12-v6.15-net-phy-mediatek-Add-token-ring-set-bit-operation-su.patch
new file mode 100644 (file)
index 0000000..972a9c0
--- /dev/null
@@ -0,0 +1,73 @@
+From c7e2fb3421ef5ebbb4c91f44bd735ab10edd755a Mon Sep 17 00:00:00 2001
+From: Sky Huang <skylake.huang@mediatek.com>
+Date: Thu, 13 Feb 2025 16:05:51 +0800
+Subject: [PATCH 12/20] net: phy: mediatek: Add token ring set bit operation
+ support
+
+Previously in mtk-ge-soc.c, we set some register bits via token
+ring, which were implemented in three __phy_write().
+Now we can do the same thing via __mtk_tr_set_bits() helper.
+
+Signed-off-by: Sky Huang <skylake.huang@mediatek.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250213080553.921434-4-SkyLake.Huang@mediatek.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mediatek/mtk-ge-soc.c  | 10 ++++++----
+ drivers/net/phy/mediatek/mtk-phy-lib.c |  7 +++++++
+ drivers/net/phy/mediatek/mtk.h         |  2 ++
+ 3 files changed, 15 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/phy/mediatek/mtk-ge-soc.c
++++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
+@@ -62,6 +62,10 @@
+ /* MasDSPreadyTime */
+ #define MASTER_DSP_READY_TIME_MASK            GENMASK(14, 7)
++/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x18 */
++/* EnabRandUpdTrig */
++#define ENABLE_RANDOM_UPDOWN_COUNTER_TRIGGER  BIT(8)
++
+ /* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x20 */
+ /* ResetSyncOffset */
+ #define RESET_SYNC_OFFSET_MASK                        GENMASK(11, 8)
+@@ -789,10 +793,8 @@ static void mt798x_phy_common_finetune(s
+                       FIELD_PREP(SLAVE_DSP_READY_TIME_MASK, 0x18) |
+                       FIELD_PREP(MASTER_DSP_READY_TIME_MASK, 0x18));
+-      /* EnabRandUpdTrig = 1 */
+-      __phy_write(phydev, 0x11, 0x2f00);
+-      __phy_write(phydev, 0x12, 0xe);
+-      __phy_write(phydev, 0x10, 0x8fb0);
++      __mtk_tr_set_bits(phydev, 0x1, 0xf, 0x18,
++                        ENABLE_RANDOM_UPDOWN_COUNTER_TRIGGER);
+       __mtk_tr_modify(phydev, 0x0, 0x7, 0x15,
+                       NORMAL_MSE_LO_THRESH_MASK,
+--- a/drivers/net/phy/mediatek/mtk-phy-lib.c
++++ b/drivers/net/phy/mediatek/mtk-phy-lib.c
+@@ -69,6 +69,13 @@ void mtk_tr_modify(struct phy_device *ph
+ }
+ EXPORT_SYMBOL_GPL(mtk_tr_modify);
++void __mtk_tr_set_bits(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
++                     u8 data_addr, u32 set)
++{
++      __mtk_tr_modify(phydev, ch_addr, node_addr, data_addr, 0, set);
++}
++EXPORT_SYMBOL_GPL(__mtk_tr_set_bits);
++
+ int mtk_phy_read_page(struct phy_device *phydev)
+ {
+       return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
+--- a/drivers/net/phy/mediatek/mtk.h
++++ b/drivers/net/phy/mediatek/mtk.h
+@@ -72,6 +72,8 @@ void __mtk_tr_modify(struct phy_device *
+                    u8 data_addr, u32 mask, u32 set);
+ void mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+                  u8 data_addr, u32 mask, u32 set);
++void __mtk_tr_set_bits(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
++                     u8 data_addr, u32 set);
+ int mtk_phy_read_page(struct phy_device *phydev);
+ int mtk_phy_write_page(struct phy_device *phydev, int page);
diff --git a/target/linux/generic/backport-6.12/720-13-v6.15-net-phy-mediatek-Add-token-ring-clear-bit-operation-.patch b/target/linux/generic/backport-6.12/720-13-v6.15-net-phy-mediatek-Add-token-ring-clear-bit-operation-.patch
new file mode 100644 (file)
index 0000000..47d891e
--- /dev/null
@@ -0,0 +1,122 @@
+From 7851c73a416b15aff6f9ada9c88affc5f48ff011 Mon Sep 17 00:00:00 2001
+From: Sky Huang <skylake.huang@mediatek.com>
+Date: Thu, 13 Feb 2025 16:05:52 +0800
+Subject: [PATCH 13/20] net: phy: mediatek: Add token ring clear bit operation
+ support
+
+Similar to __mtk_tr_set_bits() support. Previously in mtk-ge-soc.c,
+we clear some register bits via token ring, which were also implemented
+in three __phy_write(). Now we can do the same thing via
+__mtk_tr_clr_bits() helper.
+
+Signed-off-by: Sky Huang <skylake.huang@mediatek.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250213080553.921434-5-SkyLake.Huang@mediatek.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mediatek/mtk-ge-soc.c  | 30 +++++++++++++++-----------
+ drivers/net/phy/mediatek/mtk-phy-lib.c |  7 ++++++
+ drivers/net/phy/mediatek/mtk.h         |  2 ++
+ 3 files changed, 27 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/phy/mediatek/mtk-ge-soc.c
++++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
+@@ -76,6 +76,10 @@
+ /* FfeUpdGainForce */
+ #define FFE_UPDATE_GAIN_FORCE                 BIT(6)
++/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x3 */
++/* TrFreeze */
++#define TR_FREEZE_MASK                                GENMASK(11, 0)
++
+ /* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x6 */
+ /* SS: Steady-state, KP: Proportional Gain */
+ /* SSTrKp100 */
+@@ -91,6 +95,11 @@
+ /* SSTrKf1000Slv */
+ #define SS_TR_KF1000_SLAVE_MASK                       GENMASK(6, 4)
++/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x8 */
++/* clear this bit if wanna select from AFE */
++/* Regsigdet_sel_1000 */
++#define EEE1000_SELECT_SIGNAL_DETECTION_FROM_DFE      BIT(4)
++
+ /* ch_addr = 0x2, node_addr = 0xd, data_addr = 0xd */
+ /* RegEEE_st2TrKf1000 */
+ #define EEE1000_STAGE2_TR_KF_MASK             GENMASK(13, 11)
+@@ -113,6 +122,10 @@
+ /* RegEEE100Stg1_tar */
+ #define EEE100_LPSYNC_STAGE1_UPDATE_TIMER_MASK        GENMASK(8, 0)
++/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x25 */
++/* REGEEE_wake_slv_tr_wait_dfesigdet_en */
++#define WAKE_SLAVE_TR_WAIT_DFE_DETECTION_EN   BIT(11)
++
+ #define ANALOG_INTERNAL_OPERATION_MAX_US      20
+ #define TXRESERVE_MIN                         0
+ #define TXRESERVE_MAX                         7
+@@ -805,10 +818,7 @@ static void mt798x_phy_common_finetune(s
+                       FIELD_PREP(FFE_UPDATE_GAIN_FORCE_VAL_MASK, 0x4) |
+                                  FFE_UPDATE_GAIN_FORCE);
+-      /* TrFreeze = 0 (mt7988 default) */
+-      __phy_write(phydev, 0x11, 0x0);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x9686);
++      __mtk_tr_clr_bits(phydev, 0x2, 0xd, 0x3, TR_FREEZE_MASK);
+       __mtk_tr_modify(phydev, 0x2, 0xd, 0x6,
+                       SS_TR_KP100_MASK | SS_TR_KF100_MASK |
+@@ -1009,10 +1019,8 @@ static void mt798x_phy_eee(struct phy_de
+                        MTK_PHY_TR_READY_SKIP_AFE_WAKEUP);
+       phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+-      /* Regsigdet_sel_1000 = 0 */
+-      __phy_write(phydev, 0x11, 0xb);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x9690);
++      __mtk_tr_clr_bits(phydev, 0x2, 0xd, 0x8,
++                        EEE1000_SELECT_SIGNAL_DETECTION_FROM_DFE);
+       __mtk_tr_modify(phydev, 0x2, 0xd, 0xd,
+                       EEE1000_STAGE2_TR_KF_MASK,
+@@ -1036,10 +1044,8 @@ static void mt798x_phy_eee(struct phy_de
+                       FIELD_PREP(EEE100_LPSYNC_STAGE1_UPDATE_TIMER_MASK,
+                                  0x10));
+-      /* REGEEE_wake_slv_tr_wait_dfesigdet_en = 0 */
+-      __phy_write(phydev, 0x11, 0x1463);
+-      __phy_write(phydev, 0x12, 0x0);
+-      __phy_write(phydev, 0x10, 0x96ca);
++      __mtk_tr_clr_bits(phydev, 0x2, 0xd, 0x25,
++                        WAKE_SLAVE_TR_WAIT_DFE_DETECTION_EN);
+       __mtk_tr_modify(phydev, 0x1, 0xf, 0x0,
+                       DFE_TAIL_EANBLE_VGA_TRHESH_1000,
+--- a/drivers/net/phy/mediatek/mtk-phy-lib.c
++++ b/drivers/net/phy/mediatek/mtk-phy-lib.c
+@@ -76,6 +76,13 @@ void __mtk_tr_set_bits(struct phy_device
+ }
+ EXPORT_SYMBOL_GPL(__mtk_tr_set_bits);
++void __mtk_tr_clr_bits(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
++                     u8 data_addr, u32 clr)
++{
++      __mtk_tr_modify(phydev, ch_addr, node_addr, data_addr, clr, 0);
++}
++EXPORT_SYMBOL_GPL(__mtk_tr_clr_bits);
++
+ int mtk_phy_read_page(struct phy_device *phydev)
+ {
+       return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
+--- a/drivers/net/phy/mediatek/mtk.h
++++ b/drivers/net/phy/mediatek/mtk.h
+@@ -74,6 +74,8 @@ void mtk_tr_modify(struct phy_device *ph
+                  u8 data_addr, u32 mask, u32 set);
+ void __mtk_tr_set_bits(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+                      u8 data_addr, u32 set);
++void __mtk_tr_clr_bits(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
++                     u8 data_addr, u32 clr);
+ int mtk_phy_read_page(struct phy_device *phydev);
+ int mtk_phy_write_page(struct phy_device *phydev, int page);
diff --git a/target/linux/generic/backport-6.12/720-14-v6.15-net-phy-mediatek-Move-some-macros-to-phy-lib-for-lat.patch b/target/linux/generic/backport-6.12/720-14-v6.15-net-phy-mediatek-Move-some-macros-to-phy-lib-for-lat.patch
new file mode 100644 (file)
index 0000000..858de09
--- /dev/null
@@ -0,0 +1,45 @@
+From bae8c61522c4d5a5250a24dcb57d120ea593fab1 Mon Sep 17 00:00:00 2001
+From: Sky Huang <skylake.huang@mediatek.com>
+Date: Thu, 13 Feb 2025 16:05:53 +0800
+Subject: [PATCH 14/20] net: phy: mediatek: Move some macros to phy-lib for
+ later use
+
+Move some macros to phy-lib because MediaTek's 2.5G built-in
+ethernet PHY will also use them.
+
+Signed-off-by: Sky Huang <skylake.huang@mediatek.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250213080553.921434-6-SkyLake.Huang@mediatek.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mediatek/mtk-ge.c | 4 ----
+ drivers/net/phy/mediatek/mtk.h    | 4 ++++
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/phy/mediatek/mtk-ge.c
++++ b/drivers/net/phy/mediatek/mtk-ge.c
+@@ -8,10 +8,6 @@
+ #define MTK_GPHY_ID_MT7530            0x03a29412
+ #define MTK_GPHY_ID_MT7531            0x03a29441
+-#define MTK_PHY_PAGE_EXTENDED_1                       0x0001
+-#define MTK_PHY_AUX_CTRL_AND_STATUS           0x14
+-#define   MTK_PHY_ENABLE_DOWNSHIFT            BIT(4)
+-
+ #define MTK_PHY_PAGE_EXTENDED_2                       0x0002
+ #define MTK_PHY_PAGE_EXTENDED_3                       0x0003
+ #define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG11     0x11
+--- a/drivers/net/phy/mediatek/mtk.h
++++ b/drivers/net/phy/mediatek/mtk.h
+@@ -8,7 +8,11 @@
+ #ifndef _MTK_EPHY_H_
+ #define _MTK_EPHY_H_
++#define MTK_PHY_AUX_CTRL_AND_STATUS           0x14
++#define   MTK_PHY_ENABLE_DOWNSHIFT            BIT(4)
++
+ #define MTK_EXT_PAGE_ACCESS                   0x1f
++#define MTK_PHY_PAGE_EXTENDED_1                       0x0001
+ #define MTK_PHY_PAGE_STANDARD                 0x0000
+ #define MTK_PHY_PAGE_EXTENDED_52B5            0x52b5
diff --git a/target/linux/generic/backport-6.12/720-15-v6.16-net-phy-mediatek-permit-to-compile-test-GE-SOC-PHY-d.patch b/target/linux/generic/backport-6.12/720-15-v6.16-net-phy-mediatek-permit-to-compile-test-GE-SOC-PHY-d.patch
new file mode 100644 (file)
index 0000000..9778ea1
--- /dev/null
@@ -0,0 +1,37 @@
+From e5566162af8b9690e096d2e6089e4ed955a0d13d Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 10 Apr 2025 12:04:03 +0200
+Subject: [PATCH] net: phy: mediatek: permit to compile test GE SOC PHY driver
+
+When commit 462a3daad679 ("net: phy: mediatek: fix compile-test
+dependencies") fixed the dependency, it should have also introduced
+an or on COMPILE_TEST to permit this driver to be compile-tested even if
+NVMEM_MTK_EFUSE wasn't selected. The driver makes use of NVMEM API that
+are always compiled (return error) so the driver can actually be
+compiled even without that config.
+
+Fix and simplify the dependency condition of this kernel config.
+
+Fixes: 462a3daad679 ("net: phy: mediatek: fix compile-test dependencies")
+Acked-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Acked-by: Arnd Bergmann <arnd@arndb.de>
+Link: https://patch.msgid.link/20250410100410.348-1-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mediatek/Kconfig | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/net/phy/mediatek/Kconfig
++++ b/drivers/net/phy/mediatek/Kconfig
+@@ -15,8 +15,7 @@ config MEDIATEK_GE_PHY
+ config MEDIATEK_GE_SOC_PHY
+       tristate "MediaTek SoC Ethernet PHYs"
+-      depends on (ARM64 && ARCH_MEDIATEK) || COMPILE_TEST
+-      depends on NVMEM_MTK_EFUSE
++      depends on (ARM64 && ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || COMPILE_TEST
+       select MTK_NET_PHYLIB
+       help
+         Supports MediaTek SoC built-in Gigabit Ethernet PHYs.
diff --git a/target/linux/generic/backport-6.12/720-16-v6.16-net-phy-mediatek-add-Airoha-PHY-ID-to-SoC-driver.patch b/target/linux/generic/backport-6.12/720-16-v6.16-net-phy-mediatek-add-Airoha-PHY-ID-to-SoC-driver.patch
new file mode 100644 (file)
index 0000000..5498ecb
--- /dev/null
@@ -0,0 +1,129 @@
+From 4590c8bc10951feee3e439bf7fff1b458c2e6fad Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 10 Apr 2025 12:04:04 +0200
+Subject: [PATCH 17/20] net: phy: mediatek: add Airoha PHY ID to SoC driver
+
+Airoha AN7581 SoC ship with a Switch based on the MT753x Switch embedded
+in other SoC like the MT7581 and the MT7988. Similar to these they
+require configuring some pin to enable LED PHYs.
+
+Add support for the PHY ID for the Airoha embedded Switch and define a
+simple probe function to toggle these pins. Also fill the LED functions
+and add dedicated function to define LED polarity.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Link: https://patch.msgid.link/20250410100410.348-2-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mediatek/Kconfig      |  4 +-
+ drivers/net/phy/mediatek/mtk-ge-soc.c | 62 +++++++++++++++++++++++++++
+ 2 files changed, 65 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/mediatek/Kconfig
++++ b/drivers/net/phy/mediatek/Kconfig
+@@ -15,7 +15,9 @@ config MEDIATEK_GE_PHY
+ config MEDIATEK_GE_SOC_PHY
+       tristate "MediaTek SoC Ethernet PHYs"
+-      depends on (ARM64 && ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || COMPILE_TEST
++      depends on ARM64 || COMPILE_TEST
++      depends on ARCH_AIROHA || (ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || \
++                 COMPILE_TEST
+       select MTK_NET_PHYLIB
+       help
+         Supports MediaTek SoC built-in Gigabit Ethernet PHYs.
+--- a/drivers/net/phy/mediatek/mtk-ge-soc.c
++++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
+@@ -10,8 +10,11 @@
+ #include "mtk.h"
++#define MTK_PHY_MAX_LEDS                      2
++
+ #define MTK_GPHY_ID_MT7981                    0x03a29461
+ #define MTK_GPHY_ID_MT7988                    0x03a29481
++#define MTK_GPHY_ID_AN7581                    0x03a294c1
+ #define MTK_EXT_PAGE_ACCESS                   0x1f
+ #define MTK_PHY_PAGE_STANDARD                 0x0000
+@@ -1405,6 +1408,53 @@ static int mt7981_phy_probe(struct phy_d
+       return mt798x_phy_calibration(phydev);
+ }
++static int an7581_phy_probe(struct phy_device *phydev)
++{
++      struct mtk_socphy_priv *priv;
++      struct pinctrl *pinctrl;
++
++      /* Toggle pinctrl to enable PHY LED */
++      pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "gbe-led");
++      if (IS_ERR(pinctrl))
++              dev_err(&phydev->mdio.bus->dev,
++                      "Failed to setup PHY LED pinctrl\n");
++
++      priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
++      if (!priv)
++              return -ENOMEM;
++
++      phydev->priv = priv;
++
++      return 0;
++}
++
++static int an7581_phy_led_polarity_set(struct phy_device *phydev, int index,
++                                     unsigned long modes)
++{
++      u32 mode;
++      u16 val;
++
++      if (index >= MTK_PHY_MAX_LEDS)
++              return -EINVAL;
++
++      for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
++              switch (mode) {
++              case PHY_LED_ACTIVE_LOW:
++                      val = MTK_PHY_LED_ON_POLARITY;
++                      break;
++              case PHY_LED_ACTIVE_HIGH:
++                      val = 0;
++                      break;
++              default:
++                      return -EINVAL;
++              }
++      }
++
++      return phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
++                            MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL,
++                            MTK_PHY_LED_ON_POLARITY, val);
++}
++
+ static struct phy_driver mtk_socphy_driver[] = {
+       {
+               PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981),
+@@ -1440,6 +1490,17 @@ static struct phy_driver mtk_socphy_driv
+               .led_hw_control_set = mt798x_phy_led_hw_control_set,
+               .led_hw_control_get = mt798x_phy_led_hw_control_get,
+       },
++      {
++              PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7581),
++              .name           = "Airoha AN7581 PHY",
++              .probe          = an7581_phy_probe,
++              .led_blink_set  = mt798x_phy_led_blink_set,
++              .led_brightness_set = mt798x_phy_led_brightness_set,
++              .led_hw_is_supported = mt798x_phy_led_hw_is_supported,
++              .led_hw_control_set = mt798x_phy_led_hw_control_set,
++              .led_hw_control_get = mt798x_phy_led_hw_control_get,
++              .led_polarity_set = an7581_phy_led_polarity_set,
++      },
+ };
+ module_phy_driver(mtk_socphy_driver);
+@@ -1447,6 +1508,7 @@ module_phy_driver(mtk_socphy_driver);
+ static const struct mdio_device_id __maybe_unused mtk_socphy_tbl[] = {
+       { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981) },
+       { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988) },
++      { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7581) },
+       { }
+ };
diff --git a/target/linux/generic/backport-6.12/720-17-v6.16-net-phy-mediatek-init-val-in-.phy_led_polarity_set-f.patch b/target/linux/generic/backport-6.12/720-17-v6.16-net-phy-mediatek-init-val-in-.phy_led_polarity_set-f.patch
new file mode 100644 (file)
index 0000000..1b1dda2
--- /dev/null
@@ -0,0 +1,40 @@
+From 34501d047ac0a6cbb13285ba9d15f75c1deb7da7 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 15 Apr 2025 12:53:05 +0200
+Subject: [PATCH 18/20] net: phy: mediatek: init val in .phy_led_polarity_set
+ for AN7581
+
+Fix smatch warning for uninitialised val in .phy_led_polarity_set for
+AN7581 driver.
+
+Correctly init to 0 to set polarity high by default.
+
+Reported-by: Simon Horman <horms@kernel.org>
+Fixes: 6a325aed130b ("net: phy: mediatek: add Airoha PHY ID to SoC driver")
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Link: https://patch.msgid.link/20250415105313.3409-1-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mediatek/mtk-ge-soc.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/net/phy/mediatek/mtk-ge-soc.c
++++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
+@@ -1431,8 +1431,8 @@ static int an7581_phy_probe(struct phy_d
+ static int an7581_phy_led_polarity_set(struct phy_device *phydev, int index,
+                                      unsigned long modes)
+ {
++      u16 val = 0;
+       u32 mode;
+-      u16 val;
+       if (index >= MTK_PHY_MAX_LEDS)
+               return -EINVAL;
+@@ -1443,7 +1443,6 @@ static int an7581_phy_led_polarity_set(s
+                       val = MTK_PHY_LED_ON_POLARITY;
+                       break;
+               case PHY_LED_ACTIVE_HIGH:
+-                      val = 0;
+                       break;
+               default:
+                       return -EINVAL;
diff --git a/target/linux/generic/backport-6.12/721-01-v6.15-net-ethernet-mediatek-add-EEE-support.patch b/target/linux/generic/backport-6.12/721-01-v6.15-net-ethernet-mediatek-add-EEE-support.patch
new file mode 100644 (file)
index 0000000..7a94897
--- /dev/null
@@ -0,0 +1,157 @@
+From 952d7325362ffbefa6ce5619fb4e53c2159ec7a7 Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <dqfext@gmail.com>
+Date: Mon, 17 Feb 2025 17:40:21 +0800
+Subject: [PATCH] net: ethernet: mediatek: add EEE support
+
+Add EEE support to MediaTek SoC Ethernet. The register fields are
+similar to the ones in MT7531, except that the LPI threshold is in
+milliseconds.
+
+Signed-off-by: Qingfang Deng <dqfext@gmail.com>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 64 +++++++++++++++++++++
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 11 ++++
+ 2 files changed, 75 insertions(+)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -782,6 +782,7 @@ static void mtk_mac_link_up(struct phyli
+       mcr = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
+       mcr &= ~(MAC_MCR_SPEED_100 | MAC_MCR_SPEED_1000 |
++               MAC_MCR_EEE100M | MAC_MCR_EEE1G |
+                MAC_MCR_FORCE_DPX | MAC_MCR_FORCE_TX_FC |
+                MAC_MCR_FORCE_RX_FC);
+@@ -807,6 +808,15 @@ static void mtk_mac_link_up(struct phyli
+       if (rx_pause)
+               mcr |= MAC_MCR_FORCE_RX_FC;
++      if (mode == MLO_AN_PHY && phy && mac->tx_lpi_enabled && phy_init_eee(phy, false) >= 0) {
++              mcr |= MAC_MCR_EEE100M | MAC_MCR_EEE1G;
++              mtk_w32(mac->hw,
++                      FIELD_PREP(MAC_EEE_WAKEUP_TIME_1000, 17) |
++                      FIELD_PREP(MAC_EEE_WAKEUP_TIME_100, 36) |
++                      FIELD_PREP(MAC_EEE_LPI_TXIDLE_THD, mac->txidle_thd_ms),
++                      MTK_MAC_EEECR(mac->id));
++      }
++
+       mcr |= MAC_MCR_TX_EN | MAC_MCR_RX_EN | MAC_MCR_FORCE_LINK;
+       mtk_w32(mac->hw, mcr, MTK_MAC_MCR(mac->id));
+ }
+@@ -4523,6 +4533,61 @@ static int mtk_set_pauseparam(struct net
+       return phylink_ethtool_set_pauseparam(mac->phylink, pause);
+ }
++static int mtk_get_eee(struct net_device *dev, struct ethtool_keee *eee)
++{
++      struct mtk_mac *mac = netdev_priv(dev);
++      u32 reg;
++      int ret;
++
++      ret = phylink_ethtool_get_eee(mac->phylink, eee);
++      if (ret)
++              return ret;
++
++      reg = mtk_r32(mac->hw, MTK_MAC_EEECR(mac->id));
++      eee->tx_lpi_enabled = mac->tx_lpi_enabled;
++      eee->tx_lpi_timer = FIELD_GET(MAC_EEE_LPI_TXIDLE_THD, reg) * 1000;
++
++      return 0;
++}
++
++static int mtk_set_eee(struct net_device *dev, struct ethtool_keee *eee)
++{
++      struct mtk_mac *mac = netdev_priv(dev);
++      u32 txidle_thd_ms, reg;
++      int ret;
++
++      /* Tx idle timer in ms */
++      txidle_thd_ms = DIV_ROUND_UP(eee->tx_lpi_timer, 1000);
++      if (!FIELD_FIT(MAC_EEE_LPI_TXIDLE_THD, txidle_thd_ms))
++              return -EINVAL;
++
++      reg = FIELD_PREP(MAC_EEE_LPI_TXIDLE_THD, txidle_thd_ms);
++
++      /* PHY Wake-up time, this field does not have a reset value, so use the
++       * reset value from MT7531 (36us for 100BaseT and 17us for 1000BaseT).
++       */
++      reg |= FIELD_PREP(MAC_EEE_WAKEUP_TIME_1000, 17) |
++             FIELD_PREP(MAC_EEE_WAKEUP_TIME_100, 36);
++
++      if (!txidle_thd_ms)
++              /* Force LPI Mode without a delay */
++              reg |= MAC_EEE_LPI_MODE;
++
++      ret = phylink_ethtool_set_eee(mac->phylink, eee);
++      if (ret)
++              return ret;
++
++      mac->tx_lpi_enabled = eee->tx_lpi_enabled;
++      mac->txidle_thd_ms = txidle_thd_ms;
++      mtk_w32(mac->hw, reg, MTK_MAC_EEECR(mac->id));
++      if (eee->eee_enabled && eee->eee_active && eee->tx_lpi_enabled)
++              mtk_m32(mac->hw, 0, MAC_MCR_EEE100M | MAC_MCR_EEE1G, MTK_MAC_MCR(mac->id));
++      else
++              mtk_m32(mac->hw, MAC_MCR_EEE100M | MAC_MCR_EEE1G, 0, MTK_MAC_MCR(mac->id));
++
++      return 0;
++}
++
+ static u16 mtk_select_queue(struct net_device *dev, struct sk_buff *skb,
+                           struct net_device *sb_dev)
+ {
+@@ -4555,6 +4620,8 @@ static const struct ethtool_ops mtk_etht
+       .set_pauseparam         = mtk_set_pauseparam,
+       .get_rxnfc              = mtk_get_rxnfc,
+       .set_rxnfc              = mtk_set_rxnfc,
++      .get_eee                = mtk_get_eee,
++      .set_eee                = mtk_set_eee,
+ };
+ static const struct net_device_ops mtk_netdev_ops = {
+@@ -4615,6 +4682,8 @@ static int mtk_add_mac(struct mtk_eth *e
+       }
+       mac = netdev_priv(eth->netdev[id]);
+       eth->mac[id] = mac;
++      mac->tx_lpi_enabled = true;
++      mac->txidle_thd_ms = 1;
+       mac->id = id;
+       mac->hw = eth;
+       mac->of_node = np;
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -461,6 +461,8 @@
+ #define MAC_MCR_RX_FIFO_CLR_DIS       BIT(12)
+ #define MAC_MCR_BACKOFF_EN    BIT(9)
+ #define MAC_MCR_BACKPR_EN     BIT(8)
++#define MAC_MCR_EEE1G         BIT(7)
++#define MAC_MCR_EEE100M               BIT(6)
+ #define MAC_MCR_FORCE_RX_FC   BIT(5)
+ #define MAC_MCR_FORCE_TX_FC   BIT(4)
+ #define MAC_MCR_SPEED_1000    BIT(3)
+@@ -469,6 +471,15 @@
+ #define MAC_MCR_FORCE_LINK    BIT(0)
+ #define MAC_MCR_FORCE_LINK_DOWN       (MAC_MCR_FORCE_MODE)
++/* Mac EEE control registers */
++#define MTK_MAC_EEECR(x)              (0x10104 + (x * 0x100))
++#define MAC_EEE_WAKEUP_TIME_1000      GENMASK(31, 24)
++#define MAC_EEE_WAKEUP_TIME_100               GENMASK(23, 16)
++#define MAC_EEE_LPI_TXIDLE_THD                GENMASK(15, 8)
++#define MAC_EEE_CKG_TXIDLE            BIT(3)
++#define MAC_EEE_CKG_RXLPI             BIT(2)
++#define MAC_EEE_LPI_MODE              BIT(0)
++
+ /* Mac status registers */
+ #define MTK_MAC_MSR(x)                (0x10108 + (x * 0x100))
+ #define MAC_MSR_EEE1G         BIT(7)
+@@ -1316,6 +1327,8 @@ struct mtk_mac {
+       int                             id;
+       phy_interface_t                 interface;
+       u8                              ppe_idx;
++      bool                            tx_lpi_enabled;
++      u8                              txidle_thd_ms;
+       int                             speed;
+       struct device_node              *of_node;
+       struct phylink                  *phylink;
diff --git a/target/linux/generic/backport-6.12/730-01-v6.13-net-phy-aquantia-allow-forcing-order-of-MDI-pairs.patch b/target/linux/generic/backport-6.12/730-01-v6.13-net-phy-aquantia-allow-forcing-order-of-MDI-pairs.patch
new file mode 100644 (file)
index 0000000..aabaa33
--- /dev/null
@@ -0,0 +1,107 @@
+From a2e1ba275eae96a8171deb19e9c7c2f5978fee7b Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Fri, 4 Oct 2024 17:18:16 +0100
+Subject: [PATCH] net: phy: aquantia: allow forcing order of MDI pairs
+
+Despite supporting Auto MDI-X, it looks like Aquantia only supports
+swapping pair (1,2) with pair (3,6) like it used to be for MDI-X on
+100MBit/s networks.
+
+When all 4 pairs are in use (for 1000MBit/s or faster) the link does not
+come up with pair order is not configured correctly, either using
+MDI_CFG pin or using the "PMA Receive Reserved Vendor Provisioning 1"
+register.
+
+Normally, the order of MDI pairs being either ABCD or DCBA is configured
+by pulling the MDI_CFG pin.
+
+However, some hardware designs require overriding the value configured
+by that bootstrap pin. The PHY allows doing that by setting a bit in
+"PMA Receive Reserved Vendor Provisioning 1" register which allows
+ignoring the state of the MDI_CFG pin and another bit configuring
+whether the order of MDI pairs should be normal (ABCD) or reverse
+(DCBA). Pair polarity is not affected and remains identical in both
+settings.
+
+Introduce property "marvell,mdi-cfg-order" which allows forcing either
+normal or reverse order of the MDI pairs from DT.
+
+If the property isn't present, the behavior is unchanged and MDI pair
+order configuration is untouched (ie. either the result of MDI_CFG pin
+pull-up/pull-down, or pair order override already configured by the
+bootloader before Linux is started).
+
+Forcing normal pair order is required on the Adtran SDG-8733A Wi-Fi 7
+residential gateway.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/9ed760ff87d5fc456f31e407ead548bbb754497d.1728058550.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/aquantia/aquantia_main.c | 33 ++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -11,6 +11,7 @@
+ #include <linux/module.h>
+ #include <linux/delay.h>
+ #include <linux/bitfield.h>
++#include <linux/of.h>
+ #include <linux/phy.h>
+ #include "aquantia.h"
+@@ -71,6 +72,11 @@
+ #define MDIO_AN_TX_VEND_INT_MASK2             0xd401
+ #define MDIO_AN_TX_VEND_INT_MASK2_LINK                BIT(0)
++#define PMAPMD_RSVD_VEND_PROV                 0xe400
++#define PMAPMD_RSVD_VEND_PROV_MDI_CONF                GENMASK(1, 0)
++#define PMAPMD_RSVD_VEND_PROV_MDI_REVERSE     BIT(0)
++#define PMAPMD_RSVD_VEND_PROV_MDI_FORCE               BIT(1)
++
+ #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)
+@@ -485,6 +491,29 @@ static void aqr107_chip_info(struct phy_
+                  fw_major, fw_minor, build_id, prov_id);
+ }
++static int aqr107_config_mdi(struct phy_device *phydev)
++{
++      struct device_node *np = phydev->mdio.dev.of_node;
++      u32 mdi_conf;
++      int ret;
++
++      ret = of_property_read_u32(np, "marvell,mdi-cfg-order", &mdi_conf);
++
++      /* Do nothing in case property "marvell,mdi-cfg-order" is not present */
++      if (ret == -ENOENT)
++              return 0;
++
++      if (ret)
++              return ret;
++
++      if (mdi_conf & ~PMAPMD_RSVD_VEND_PROV_MDI_REVERSE)
++              return -EINVAL;
++
++      return phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, PMAPMD_RSVD_VEND_PROV,
++                            PMAPMD_RSVD_VEND_PROV_MDI_CONF,
++                            mdi_conf | PMAPMD_RSVD_VEND_PROV_MDI_FORCE);
++}
++
+ static int aqr107_config_init(struct phy_device *phydev)
+ {
+       struct aqr107_priv *priv = phydev->priv;
+@@ -514,6 +543,10 @@ static int aqr107_config_init(struct phy
+       if (ret)
+               return ret;
++      ret = aqr107_config_mdi(phydev);
++      if (ret)
++              return ret;
++
+       /* Restore LED polarity state after reset */
+       for_each_set_bit(led_active_low, &priv->leds_active_low, AQR_MAX_LEDS) {
+               ret = aqr_phy_led_active_low_set(phydev, led_active_low, true);
diff --git a/target/linux/generic/backport-6.12/730-02-v6.13-net-phy-aquantia-fix-return-value-check-in-aqr107_co.patch b/target/linux/generic/backport-6.12/730-02-v6.13-net-phy-aquantia-fix-return-value-check-in-aqr107_co.patch
new file mode 100644 (file)
index 0000000..565edbd
--- /dev/null
@@ -0,0 +1,31 @@
+From ce21b8fb255ebf0b49913fb4c62741d7eb05c6f6 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Fri, 11 Oct 2024 22:28:43 +0100
+Subject: [PATCH] net: phy: aquantia: fix return value check in
+ aqr107_config_mdi()
+
+of_property_read_u32() returns -EINVAL in case the property cannot be
+found rather than -ENOENT. Fix the check to not abort probing in case
+of the property being missing, and also in case CONFIG_OF is not set
+which will result in -ENOSYS.
+
+Fixes: a2e1ba275eae ("net: phy: aquantia: allow forcing order of MDI pairs")
+Reported-by: Jon Hunter <jonathanh@nvidia.com>
+Closes: https://lore.kernel.org/all/114b4c03-5d16-42ed-945d-cf78eabea12b@nvidia.com/
+Suggested-by: Hans-Frieder Vogt <hfdevel@gmx.net>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/phy/aquantia/aquantia_main.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -500,7 +500,7 @@ static int aqr107_config_mdi(struct phy_
+       ret = of_property_read_u32(np, "marvell,mdi-cfg-order", &mdi_conf);
+       /* Do nothing in case property "marvell,mdi-cfg-order" is not present */
+-      if (ret == -ENOENT)
++      if (ret == -EINVAL || ret == -ENOSYS)
+               return 0;
+       if (ret)
diff --git a/target/linux/generic/backport-6.12/730-03-v6.13-net-phy-support-active-high-property-for-PHY-LEDs.patch b/target/linux/generic/backport-6.12/730-03-v6.13-net-phy-support-active-high-property-for-PHY-LEDs.patch
new file mode 100644 (file)
index 0000000..6822dc0
--- /dev/null
@@ -0,0 +1,53 @@
+From a274465cc3bef2dfd9c9ea5100848dda0a8641e1 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 10 Oct 2024 13:54:19 +0100
+Subject: [PATCH 1/4] net: phy: support 'active-high' property for PHY LEDs
+
+In addition to 'active-low' and 'inactive-high-impedance' also
+support 'active-high' property for PHY LED pin configuration.
+As only either 'active-high' or 'active-low' can be set at the
+same time, WARN and return an error in case both are set.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/91598487773d768f254d5faf06cf65b13e972f0e.1728558223.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/phy_device.c | 6 ++++++
+ include/linux/phy.h          | 5 +++--
+ 2 files changed, 9 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -3387,11 +3387,17 @@ static int of_phy_led(struct phy_device
+       if (index > U8_MAX)
+               return -EINVAL;
++      if (of_property_read_bool(led, "active-high"))
++              set_bit(PHY_LED_ACTIVE_HIGH, &modes);
+       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 (WARN_ON(modes & BIT(PHY_LED_ACTIVE_LOW) &&
++                  modes & BIT(PHY_LED_ACTIVE_HIGH)))
++              return -EINVAL;
++
+       if (modes) {
+               /* Return error if asked to set polarity modes but not supported */
+               if (!phydev->drv->led_polarity_set)
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -902,8 +902,9 @@ struct phy_plca_status {
+ /* Modes for PHY LED configuration */
+ enum phy_led_modes {
+-      PHY_LED_ACTIVE_LOW = 0,
+-      PHY_LED_INACTIVE_HIGH_IMPEDANCE = 1,
++      PHY_LED_ACTIVE_HIGH = 0,
++      PHY_LED_ACTIVE_LOW = 1,
++      PHY_LED_INACTIVE_HIGH_IMPEDANCE = 2,
+       /* keep it last */
+       __PHY_LED_MODES_NUM,
diff --git a/target/linux/generic/backport-6.12/730-04-v6.13-net-phy-aquantia-correctly-describe-LED-polarity-ove.patch b/target/linux/generic/backport-6.12/730-04-v6.13-net-phy-aquantia-correctly-describe-LED-polarity-ove.patch
new file mode 100644 (file)
index 0000000..155f796
--- /dev/null
@@ -0,0 +1,108 @@
+From 9d55e68b19f222e6334ef4021c5527998f5ab537 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 10 Oct 2024 13:55:00 +0100
+Subject: [PATCH 2/4] net: phy: aquantia: correctly describe LED polarity
+ override
+
+Use newly defined 'active-high' property to set the
+VEND1_GLOBAL_LED_DRIVE_VDD bit and let 'active-low' clear that bit. This
+reflects the technical reality which was inverted in the previous
+description in which the 'active-low' property was used to actually set
+the VEND1_GLOBAL_LED_DRIVE_VDD bit, which means that VDD (ie. supply
+voltage) of the LED is driven rather than GND.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/86a413b4387c42dcb54f587cc2433a06f16aae83.1728558223.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/aquantia/aquantia.h      |  1 +
+ drivers/net/phy/aquantia/aquantia_leds.c | 19 ++++++++++++++-----
+ drivers/net/phy/aquantia/aquantia_main.c | 12 +++++++++---
+ 3 files changed, 24 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/phy/aquantia/aquantia.h
++++ b/drivers/net/phy/aquantia/aquantia.h
+@@ -177,6 +177,7 @@ static const struct aqr107_hw_stat aqr10
+ struct aqr107_priv {
+       u64 sgmii_stats[AQR107_SGMII_STAT_SZ];
+       unsigned long leds_active_low;
++      unsigned long leds_active_high;
+ };
+ #if IS_REACHABLE(CONFIG_HWMON)
+--- a/drivers/net/phy/aquantia/aquantia_leds.c
++++ b/drivers/net/phy/aquantia/aquantia_leds.c
+@@ -121,13 +121,13 @@ int aqr_phy_led_active_low_set(struct ph
+ {
+       return phy_modify_mmd(phydev, MDIO_MMD_VEND1, AQR_LED_DRIVE(index),
+                             VEND1_GLOBAL_LED_DRIVE_VDD,
+-                            enable ? VEND1_GLOBAL_LED_DRIVE_VDD : 0);
++                            enable ? 0 : VEND1_GLOBAL_LED_DRIVE_VDD);
+ }
+ int aqr_phy_led_polarity_set(struct phy_device *phydev, int index, unsigned long modes)
+ {
++      bool force_active_low = false, force_active_high = false;
+       struct aqr107_priv *priv = phydev->priv;
+-      bool active_low = false;
+       u32 mode;
+       if (index >= AQR_MAX_LEDS)
+@@ -136,7 +136,10 @@ int aqr_phy_led_polarity_set(struct phy_
+       for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
+               switch (mode) {
+               case PHY_LED_ACTIVE_LOW:
+-                      active_low = true;
++                      force_active_low = true;
++                      break;
++              case PHY_LED_ACTIVE_HIGH:
++                      force_active_high = true;
+                       break;
+               default:
+                       return -EINVAL;
+@@ -144,8 +147,14 @@ int aqr_phy_led_polarity_set(struct phy_
+       }
+       /* Save LED driver vdd state to restore on SW reset */
+-      if (active_low)
++      if (force_active_low)
+               priv->leds_active_low |= BIT(index);
+-      return aqr_phy_led_active_low_set(phydev, index, active_low);
++      if (force_active_high)
++              priv->leds_active_high |= BIT(index);
++
++      if (force_active_high || force_active_low)
++              return aqr_phy_led_active_low_set(phydev, index, force_active_low);
++
++      unreachable();
+ }
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -517,7 +517,7 @@ static int aqr107_config_mdi(struct phy_
+ static int aqr107_config_init(struct phy_device *phydev)
+ {
+       struct aqr107_priv *priv = phydev->priv;
+-      u32 led_active_low;
++      u32 led_idx;
+       int ret;
+       /* Check that the PHY interface type is compatible */
+@@ -548,8 +548,14 @@ static int aqr107_config_init(struct phy
+               return ret;
+       /* Restore LED polarity state after reset */
+-      for_each_set_bit(led_active_low, &priv->leds_active_low, AQR_MAX_LEDS) {
+-              ret = aqr_phy_led_active_low_set(phydev, led_active_low, true);
++      for_each_set_bit(led_idx, &priv->leds_active_low, AQR_MAX_LEDS) {
++              ret = aqr_phy_led_active_low_set(phydev, led_idx, true);
++              if (ret)
++                      return ret;
++      }
++
++      for_each_set_bit(led_idx, &priv->leds_active_high, AQR_MAX_LEDS) {
++              ret = aqr_phy_led_active_low_set(phydev, led_idx, false);
+               if (ret)
+                       return ret;
+       }
diff --git a/target/linux/generic/backport-6.12/730-05-v6.13-net-phy-mxl-gpy-add-basic-LED-support.patch b/target/linux/generic/backport-6.12/730-05-v6.13-net-phy-mxl-gpy-add-basic-LED-support.patch
new file mode 100644 (file)
index 0000000..c785f8a
--- /dev/null
@@ -0,0 +1,332 @@
+From 78997e9a5e4d8a4df561e083a92c91ae23010e07 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 1 Oct 2024 01:17:18 +0100
+Subject: [PATCH] net: phy: mxl-gpy: add basic LED support
+
+Add basic support for LEDs connected to MaxLinear GPY2xx and GPY115 PHYs.
+The PHYs allow up to 4 LEDs to be connected.
+Implement controlling LEDs in software as well as netdev trigger offloading
+and LED polarity setup.
+
+The hardware claims to support 16 PWM brightness levels but there is no
+documentation on how to use that feature, hence this is not supported.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/b6ec9050339f8244ff898898a1cecc33b13a48fc.1727741563.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mxl-gpy.c | 218 ++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 218 insertions(+)
+
+--- a/drivers/net/phy/mxl-gpy.c
++++ b/drivers/net/phy/mxl-gpy.c
+@@ -38,6 +38,7 @@
+ #define PHY_MIISTAT           0x18    /* MII state */
+ #define PHY_IMASK             0x19    /* interrupt mask */
+ #define PHY_ISTAT             0x1A    /* interrupt status */
++#define PHY_LED                       0x1B    /* LEDs */
+ #define PHY_FWV                       0x1E    /* firmware version */
+ #define PHY_MIISTAT_SPD_MASK  GENMASK(2, 0)
+@@ -61,6 +62,11 @@
+                                PHY_IMASK_ADSC | \
+                                PHY_IMASK_ANC)
++#define GPY_MAX_LEDS          4
++#define PHY_LED_POLARITY(idx) BIT(12 + (idx))
++#define PHY_LED_HWCONTROL(idx)        BIT(8 + (idx))
++#define PHY_LED_ON(idx)               BIT(idx)
++
+ #define PHY_FWV_REL_MASK      BIT(15)
+ #define PHY_FWV_MAJOR_MASK    GENMASK(11, 8)
+ #define PHY_FWV_MINOR_MASK    GENMASK(7, 0)
+@@ -72,6 +78,23 @@
+ #define PHY_MDI_MDI_X_CD      0x1
+ #define PHY_MDI_MDI_X_CROSS   0x0
++/* LED */
++#define VSPEC1_LED(idx)               (1 + (idx))
++#define VSPEC1_LED_BLINKS     GENMASK(15, 12)
++#define VSPEC1_LED_PULSE      GENMASK(11, 8)
++#define VSPEC1_LED_CON                GENMASK(7, 4)
++#define VSPEC1_LED_BLINKF     GENMASK(3, 0)
++
++#define VSPEC1_LED_LINK10     BIT(0)
++#define VSPEC1_LED_LINK100    BIT(1)
++#define VSPEC1_LED_LINK1000   BIT(2)
++#define VSPEC1_LED_LINK2500   BIT(3)
++
++#define VSPEC1_LED_TXACT      BIT(0)
++#define VSPEC1_LED_RXACT      BIT(1)
++#define VSPEC1_LED_COL                BIT(2)
++#define VSPEC1_LED_NO_CON     BIT(3)
++
+ /* SGMII */
+ #define VSPEC1_SGMII_CTRL     0x08
+ #define VSPEC1_SGMII_CTRL_ANEN        BIT(12)         /* Aneg enable */
+@@ -835,6 +858,156 @@ static int gpy115_loopback(struct phy_de
+       return genphy_soft_reset(phydev);
+ }
++static int gpy_led_brightness_set(struct phy_device *phydev,
++                                u8 index, enum led_brightness value)
++{
++      int ret;
++
++      if (index >= GPY_MAX_LEDS)
++              return -EINVAL;
++
++      /* clear HWCONTROL and set manual LED state */
++      ret = phy_modify(phydev, PHY_LED,
++                       ((value == LED_OFF) ? PHY_LED_HWCONTROL(index) : 0) |
++                       PHY_LED_ON(index),
++                       (value == LED_OFF) ? 0 : PHY_LED_ON(index));
++      if (ret)
++              return ret;
++
++      /* ToDo: set PWM brightness */
++
++      /* clear HW LED setup */
++      if (value == LED_OFF)
++              return phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index), 0);
++      else
++              return 0;
++}
++
++static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) |
++                                               BIT(TRIGGER_NETDEV_LINK_100) |
++                                               BIT(TRIGGER_NETDEV_LINK_1000) |
++                                               BIT(TRIGGER_NETDEV_LINK_2500) |
++                                               BIT(TRIGGER_NETDEV_RX) |
++                                               BIT(TRIGGER_NETDEV_TX));
++
++static int gpy_led_hw_is_supported(struct phy_device *phydev, u8 index,
++                                 unsigned long rules)
++{
++      if (index >= GPY_MAX_LEDS)
++              return -EINVAL;
++
++      /* All combinations of the supported triggers are allowed */
++      if (rules & ~supported_triggers)
++              return -EOPNOTSUPP;
++
++      return 0;
++}
++
++static int gpy_led_hw_control_get(struct phy_device *phydev, u8 index,
++                                unsigned long *rules)
++{
++      int val;
++
++      if (index >= GPY_MAX_LEDS)
++              return -EINVAL;
++
++      val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index));
++      if (val < 0)
++              return val;
++
++      if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK10)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_10);
++
++      if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK100)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_100);
++
++      if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK1000)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_1000);
++
++      if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK2500)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_2500);
++
++      if (FIELD_GET(VSPEC1_LED_CON, val) == (VSPEC1_LED_LINK10 |
++                                             VSPEC1_LED_LINK100 |
++                                             VSPEC1_LED_LINK1000 |
++                                             VSPEC1_LED_LINK2500))
++              *rules |= BIT(TRIGGER_NETDEV_LINK);
++
++      if (FIELD_GET(VSPEC1_LED_PULSE, val) & VSPEC1_LED_TXACT)
++              *rules |= BIT(TRIGGER_NETDEV_TX);
++
++      if (FIELD_GET(VSPEC1_LED_PULSE, val) & VSPEC1_LED_RXACT)
++              *rules |= BIT(TRIGGER_NETDEV_RX);
++
++      return 0;
++}
++
++static int gpy_led_hw_control_set(struct phy_device *phydev, u8 index,
++                                unsigned long rules)
++{
++      u16 val = 0;
++      int ret;
++
++      if (index >= GPY_MAX_LEDS)
++              return -EINVAL;
++
++      if (rules & BIT(TRIGGER_NETDEV_LINK) ||
++          rules & BIT(TRIGGER_NETDEV_LINK_10))
++              val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK10);
++
++      if (rules & BIT(TRIGGER_NETDEV_LINK) ||
++          rules & BIT(TRIGGER_NETDEV_LINK_100))
++              val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK100);
++
++      if (rules & BIT(TRIGGER_NETDEV_LINK) ||
++          rules & BIT(TRIGGER_NETDEV_LINK_1000))
++              val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK1000);
++
++      if (rules & BIT(TRIGGER_NETDEV_LINK) ||
++          rules & BIT(TRIGGER_NETDEV_LINK_2500))
++              val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK2500);
++
++      if (rules & BIT(TRIGGER_NETDEV_TX))
++              val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_TXACT);
++
++      if (rules & BIT(TRIGGER_NETDEV_RX))
++              val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_RXACT);
++
++      /* allow RX/TX pulse without link indication */
++      if ((rules & BIT(TRIGGER_NETDEV_TX) || rules & BIT(TRIGGER_NETDEV_RX)) &&
++          !(val & VSPEC1_LED_CON))
++              val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_NO_CON) | VSPEC1_LED_CON;
++
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index), val);
++      if (ret)
++              return ret;
++
++      return phy_set_bits(phydev, PHY_LED, PHY_LED_HWCONTROL(index));
++}
++
++static int gpy_led_polarity_set(struct phy_device *phydev, int index,
++                              unsigned long modes)
++{
++      bool active_low = false;
++      u32 mode;
++
++      if (index >= GPY_MAX_LEDS)
++              return -EINVAL;
++
++      for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
++              switch (mode) {
++              case PHY_LED_ACTIVE_LOW:
++                      active_low = true;
++                      break;
++              default:
++                      return -EINVAL;
++              }
++      }
++
++      return phy_modify(phydev, PHY_LED, PHY_LED_POLARITY(index),
++                        active_low ? 0 : PHY_LED_POLARITY(index));
++}
++
+ static struct phy_driver gpy_drivers[] = {
+       {
+               PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx),
+@@ -852,6 +1025,11 @@ static struct phy_driver gpy_drivers[] =
+               .set_wol        = gpy_set_wol,
+               .get_wol        = gpy_get_wol,
+               .set_loopback   = gpy_loopback,
++              .led_brightness_set = gpy_led_brightness_set,
++              .led_hw_is_supported = gpy_led_hw_is_supported,
++              .led_hw_control_get = gpy_led_hw_control_get,
++              .led_hw_control_set = gpy_led_hw_control_set,
++              .led_polarity_set = gpy_led_polarity_set,
+       },
+       {
+               .phy_id         = PHY_ID_GPY115B,
+@@ -870,6 +1048,11 @@ static struct phy_driver gpy_drivers[] =
+               .set_wol        = gpy_set_wol,
+               .get_wol        = gpy_get_wol,
+               .set_loopback   = gpy115_loopback,
++              .led_brightness_set = gpy_led_brightness_set,
++              .led_hw_is_supported = gpy_led_hw_is_supported,
++              .led_hw_control_get = gpy_led_hw_control_get,
++              .led_hw_control_set = gpy_led_hw_control_set,
++              .led_polarity_set = gpy_led_polarity_set,
+       },
+       {
+               PHY_ID_MATCH_MODEL(PHY_ID_GPY115C),
+@@ -887,6 +1070,11 @@ static struct phy_driver gpy_drivers[] =
+               .set_wol        = gpy_set_wol,
+               .get_wol        = gpy_get_wol,
+               .set_loopback   = gpy115_loopback,
++              .led_brightness_set = gpy_led_brightness_set,
++              .led_hw_is_supported = gpy_led_hw_is_supported,
++              .led_hw_control_get = gpy_led_hw_control_get,
++              .led_hw_control_set = gpy_led_hw_control_set,
++              .led_polarity_set = gpy_led_polarity_set,
+       },
+       {
+               .phy_id         = PHY_ID_GPY211B,
+@@ -905,6 +1093,11 @@ static struct phy_driver gpy_drivers[] =
+               .set_wol        = gpy_set_wol,
+               .get_wol        = gpy_get_wol,
+               .set_loopback   = gpy_loopback,
++              .led_brightness_set = gpy_led_brightness_set,
++              .led_hw_is_supported = gpy_led_hw_is_supported,
++              .led_hw_control_get = gpy_led_hw_control_get,
++              .led_hw_control_set = gpy_led_hw_control_set,
++              .led_polarity_set = gpy_led_polarity_set,
+       },
+       {
+               PHY_ID_MATCH_MODEL(PHY_ID_GPY211C),
+@@ -922,6 +1115,11 @@ static struct phy_driver gpy_drivers[] =
+               .set_wol        = gpy_set_wol,
+               .get_wol        = gpy_get_wol,
+               .set_loopback   = gpy_loopback,
++              .led_brightness_set = gpy_led_brightness_set,
++              .led_hw_is_supported = gpy_led_hw_is_supported,
++              .led_hw_control_get = gpy_led_hw_control_get,
++              .led_hw_control_set = gpy_led_hw_control_set,
++              .led_polarity_set = gpy_led_polarity_set,
+       },
+       {
+               .phy_id         = PHY_ID_GPY212B,
+@@ -940,6 +1138,11 @@ static struct phy_driver gpy_drivers[] =
+               .set_wol        = gpy_set_wol,
+               .get_wol        = gpy_get_wol,
+               .set_loopback   = gpy_loopback,
++              .led_brightness_set = gpy_led_brightness_set,
++              .led_hw_is_supported = gpy_led_hw_is_supported,
++              .led_hw_control_get = gpy_led_hw_control_get,
++              .led_hw_control_set = gpy_led_hw_control_set,
++              .led_polarity_set = gpy_led_polarity_set,
+       },
+       {
+               PHY_ID_MATCH_MODEL(PHY_ID_GPY212C),
+@@ -957,6 +1160,11 @@ static struct phy_driver gpy_drivers[] =
+               .set_wol        = gpy_set_wol,
+               .get_wol        = gpy_get_wol,
+               .set_loopback   = gpy_loopback,
++              .led_brightness_set = gpy_led_brightness_set,
++              .led_hw_is_supported = gpy_led_hw_is_supported,
++              .led_hw_control_get = gpy_led_hw_control_get,
++              .led_hw_control_set = gpy_led_hw_control_set,
++              .led_polarity_set = gpy_led_polarity_set,
+       },
+       {
+               .phy_id         = PHY_ID_GPY215B,
+@@ -975,6 +1183,11 @@ static struct phy_driver gpy_drivers[] =
+               .set_wol        = gpy_set_wol,
+               .get_wol        = gpy_get_wol,
+               .set_loopback   = gpy_loopback,
++              .led_brightness_set = gpy_led_brightness_set,
++              .led_hw_is_supported = gpy_led_hw_is_supported,
++              .led_hw_control_get = gpy_led_hw_control_get,
++              .led_hw_control_set = gpy_led_hw_control_set,
++              .led_polarity_set = gpy_led_polarity_set,
+       },
+       {
+               PHY_ID_MATCH_MODEL(PHY_ID_GPY215C),
+@@ -992,6 +1205,11 @@ static struct phy_driver gpy_drivers[] =
+               .set_wol        = gpy_set_wol,
+               .get_wol        = gpy_get_wol,
+               .set_loopback   = gpy_loopback,
++              .led_brightness_set = gpy_led_brightness_set,
++              .led_hw_is_supported = gpy_led_hw_is_supported,
++              .led_hw_control_get = gpy_led_hw_control_get,
++              .led_hw_control_set = gpy_led_hw_control_set,
++              .led_polarity_set = gpy_led_polarity_set,
+       },
+       {
+               PHY_ID_MATCH_MODEL(PHY_ID_GPY241B),
diff --git a/target/linux/generic/backport-6.12/730-06-v6.13-net-phy-mxl-gpy-add-missing-support-for-TRIGGER_NETD.patch b/target/linux/generic/backport-6.12/730-06-v6.13-net-phy-mxl-gpy-add-missing-support-for-TRIGGER_NETD.patch
new file mode 100644 (file)
index 0000000..39bef9b
--- /dev/null
@@ -0,0 +1,28 @@
+From f95b4725e796b12e5f347a0d161e1d3843142aa8 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Fri, 4 Oct 2024 16:56:35 +0100
+Subject: [PATCH] net: phy: mxl-gpy: add missing support for
+ TRIGGER_NETDEV_LINK_10
+
+The PHY also support 10MBit/s links as well as the corresponding link
+indication trigger to be offloaded. Add TRIGGER_NETDEV_LINK_10 to the
+supported triggers.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/cc5da0a989af8b0d49d823656d88053c4de2ab98.1728057367.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mxl-gpy.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/phy/mxl-gpy.c
++++ b/drivers/net/phy/mxl-gpy.c
+@@ -884,6 +884,7 @@ static int gpy_led_brightness_set(struct
+ }
+ static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) |
++                                               BIT(TRIGGER_NETDEV_LINK_10) |
+                                                BIT(TRIGGER_NETDEV_LINK_100) |
+                                                BIT(TRIGGER_NETDEV_LINK_1000) |
+                                                BIT(TRIGGER_NETDEV_LINK_2500) |
diff --git a/target/linux/generic/backport-6.12/730-07-v6.13-net-phy-mxl-gpy-correctly-describe-LED-polarity.patch b/target/linux/generic/backport-6.12/730-07-v6.13-net-phy-mxl-gpy-correctly-describe-LED-polarity.patch
new file mode 100644 (file)
index 0000000..5fd3dcc
--- /dev/null
@@ -0,0 +1,58 @@
+From eb89c79c1b8f17fc1611540768678e60df89ac42 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 10 Oct 2024 13:55:17 +0100
+Subject: [PATCH 3/4] net: phy: mxl-gpy: correctly describe LED polarity
+
+According the datasheet covering the LED (0x1b) register:
+0B Active High LEDx pin driven high when activated
+1B Active Low LEDx pin driven low when activated
+
+Make use of the now available 'active-high' property and correctly
+reflect the polarity setting which was previously inverted.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/180ccafa837f09908b852a8a874a3808c5ecd2d0.1728558223.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/mxl-gpy.c | 16 ++++++++++++----
+ 1 file changed, 12 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/phy/mxl-gpy.c
++++ b/drivers/net/phy/mxl-gpy.c
+@@ -989,7 +989,7 @@ static int gpy_led_hw_control_set(struct
+ static int gpy_led_polarity_set(struct phy_device *phydev, int index,
+                               unsigned long modes)
+ {
+-      bool active_low = false;
++      bool force_active_low = false, force_active_high = false;
+       u32 mode;
+       if (index >= GPY_MAX_LEDS)
+@@ -998,15 +998,23 @@ static int gpy_led_polarity_set(struct p
+       for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
+               switch (mode) {
+               case PHY_LED_ACTIVE_LOW:
+-                      active_low = true;
++                      force_active_low = true;
++                      break;
++              case PHY_LED_ACTIVE_HIGH:
++                      force_active_high = true;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+       }
+-      return phy_modify(phydev, PHY_LED, PHY_LED_POLARITY(index),
+-                        active_low ? 0 : PHY_LED_POLARITY(index));
++      if (force_active_low)
++              return phy_set_bits(phydev, PHY_LED, PHY_LED_POLARITY(index));
++
++      if (force_active_high)
++              return phy_clear_bits(phydev, PHY_LED, PHY_LED_POLARITY(index));
++
++      unreachable();
+ }
+ static struct phy_driver gpy_drivers[] = {
diff --git a/target/linux/generic/backport-6.12/730-08-v6.13-net-phy-intel-xway-add-support-for-PHY-LEDs.patch b/target/linux/generic/backport-6.12/730-08-v6.13-net-phy-intel-xway-add-support-for-PHY-LEDs.patch
new file mode 100644 (file)
index 0000000..c57b577
--- /dev/null
@@ -0,0 +1,379 @@
+From 1758af47b98c17da464cb45f476875150955dd48 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 10 Oct 2024 13:55:29 +0100
+Subject: [PATCH 4/4] net: phy: intel-xway: add support for PHY LEDs
+
+The intel-xway PHY driver predates the PHY LED framework and currently
+initializes all LED pins to equal default values.
+
+Add PHY LED functions to the drivers and don't set default values if
+LEDs are defined in device tree.
+
+According the datasheets 3 LEDs are supported on all Intel XWAY PHYs.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/81f4717ab9acf38f3239727a4540ae96fd01109b.1728558223.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/intel-xway.c | 253 +++++++++++++++++++++++++++++++++--
+ 1 file changed, 244 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/phy/intel-xway.c
++++ b/drivers/net/phy/intel-xway.c
+@@ -151,6 +151,13 @@
+ #define XWAY_MMD_LED3H                        0x01E8
+ #define XWAY_MMD_LED3L                        0x01E9
++#define XWAY_GPHY_MAX_LEDS            3
++#define XWAY_GPHY_LED_INV(idx)                BIT(12 + (idx))
++#define XWAY_GPHY_LED_EN(idx)         BIT(8 + (idx))
++#define XWAY_GPHY_LED_DA(idx)         BIT(idx)
++#define XWAY_MMD_LEDxH(idx)           (XWAY_MMD_LED0H + 2 * (idx))
++#define XWAY_MMD_LEDxL(idx)           (XWAY_MMD_LED0L + 2 * (idx))
++
+ #define PHY_ID_PHY11G_1_3             0x030260D1
+ #define PHY_ID_PHY22F_1_3             0x030260E1
+ #define PHY_ID_PHY11G_1_4             0xD565A400
+@@ -229,20 +236,12 @@ static int xway_gphy_rgmii_init(struct p
+                         XWAY_MDIO_MIICTRL_TXSKEW_MASK, val);
+ }
+-static int xway_gphy_config_init(struct phy_device *phydev)
++static int xway_gphy_init_leds(struct phy_device *phydev)
+ {
+       int err;
+       u32 ledxh;
+       u32 ledxl;
+-      /* Mask all interrupts */
+-      err = phy_write(phydev, XWAY_MDIO_IMASK, 0);
+-      if (err)
+-              return err;
+-
+-      /* Clear all pending interrupts */
+-      phy_read(phydev, XWAY_MDIO_ISTAT);
+-
+       /* Ensure that integrated led function is enabled for all leds */
+       err = phy_write(phydev, XWAY_MDIO_LED,
+                       XWAY_MDIO_LED_LED0_EN |
+@@ -276,6 +275,26 @@ static int xway_gphy_config_init(struct
+       phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED2H, ledxh);
+       phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED2L, ledxl);
++      return 0;
++}
++
++static int xway_gphy_config_init(struct phy_device *phydev)
++{
++      struct device_node *np = phydev->mdio.dev.of_node;
++      int err;
++
++      /* Mask all interrupts */
++      err = phy_write(phydev, XWAY_MDIO_IMASK, 0);
++      if (err)
++              return err;
++
++      /* Use default LED configuration if 'leds' node isn't defined */
++      if (!of_get_child_by_name(np, "leds"))
++              xway_gphy_init_leds(phydev);
++
++      /* Clear all pending interrupts */
++      phy_read(phydev, XWAY_MDIO_ISTAT);
++
+       err = xway_gphy_rgmii_init(phydev);
+       if (err)
+               return err;
+@@ -347,6 +366,172 @@ static irqreturn_t xway_gphy_handle_inte
+       return IRQ_HANDLED;
+ }
++static int xway_gphy_led_brightness_set(struct phy_device *phydev,
++                                      u8 index, enum led_brightness value)
++{
++      int ret;
++
++      if (index >= XWAY_GPHY_MAX_LEDS)
++              return -EINVAL;
++
++      /* clear EN and set manual LED state */
++      ret = phy_modify(phydev, XWAY_MDIO_LED,
++                       ((value == LED_OFF) ? XWAY_GPHY_LED_EN(index) : 0) |
++                       XWAY_GPHY_LED_DA(index),
++                       (value == LED_OFF) ? 0 : XWAY_GPHY_LED_DA(index));
++      if (ret)
++              return ret;
++
++      /* clear HW LED setup */
++      if (value == LED_OFF) {
++              ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index), 0);
++              if (ret)
++                      return ret;
++
++              return phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index), 0);
++      } else {
++              return 0;
++      }
++}
++
++static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) |
++                                               BIT(TRIGGER_NETDEV_LINK_10) |
++                                               BIT(TRIGGER_NETDEV_LINK_100) |
++                                               BIT(TRIGGER_NETDEV_LINK_1000) |
++                                               BIT(TRIGGER_NETDEV_RX) |
++                                               BIT(TRIGGER_NETDEV_TX));
++
++static int xway_gphy_led_hw_is_supported(struct phy_device *phydev, u8 index,
++                                       unsigned long rules)
++{
++      if (index >= XWAY_GPHY_MAX_LEDS)
++              return -EINVAL;
++
++      /* activity triggers are not possible without combination with a link
++       * trigger.
++       */
++      if (rules & (BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX)) &&
++          !(rules & (BIT(TRIGGER_NETDEV_LINK) |
++                     BIT(TRIGGER_NETDEV_LINK_10) |
++                     BIT(TRIGGER_NETDEV_LINK_100) |
++                     BIT(TRIGGER_NETDEV_LINK_1000))))
++              return -EOPNOTSUPP;
++
++      /* All other combinations of the supported triggers are allowed */
++      if (rules & ~supported_triggers)
++              return -EOPNOTSUPP;
++
++      return 0;
++}
++
++static int xway_gphy_led_hw_control_get(struct phy_device *phydev, u8 index,
++                                      unsigned long *rules)
++{
++      int lval, hval;
++
++      if (index >= XWAY_GPHY_MAX_LEDS)
++              return -EINVAL;
++
++      hval = phy_read_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index));
++      if (hval < 0)
++              return hval;
++
++      lval = phy_read_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index));
++      if (lval < 0)
++              return lval;
++
++      if (hval & XWAY_MMD_LEDxH_CON_LINK10)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_10);
++
++      if (hval & XWAY_MMD_LEDxH_CON_LINK100)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_100);
++
++      if (hval & XWAY_MMD_LEDxH_CON_LINK1000)
++              *rules |= BIT(TRIGGER_NETDEV_LINK_1000);
++
++      if ((hval & XWAY_MMD_LEDxH_CON_LINK10) &&
++          (hval & XWAY_MMD_LEDxH_CON_LINK100) &&
++          (hval & XWAY_MMD_LEDxH_CON_LINK1000))
++              *rules |= BIT(TRIGGER_NETDEV_LINK);
++
++      if (lval & XWAY_MMD_LEDxL_PULSE_TXACT)
++              *rules |= BIT(TRIGGER_NETDEV_TX);
++
++      if (lval & XWAY_MMD_LEDxL_PULSE_RXACT)
++              *rules |= BIT(TRIGGER_NETDEV_RX);
++
++      return 0;
++}
++
++static int xway_gphy_led_hw_control_set(struct phy_device *phydev, u8 index,
++                                      unsigned long rules)
++{
++      u16 hval = 0, lval = 0;
++      int ret;
++
++      if (index >= XWAY_GPHY_MAX_LEDS)
++              return -EINVAL;
++
++      if (rules & BIT(TRIGGER_NETDEV_LINK) ||
++          rules & BIT(TRIGGER_NETDEV_LINK_10))
++              hval |= XWAY_MMD_LEDxH_CON_LINK10;
++
++      if (rules & BIT(TRIGGER_NETDEV_LINK) ||
++          rules & BIT(TRIGGER_NETDEV_LINK_100))
++              hval |= XWAY_MMD_LEDxH_CON_LINK100;
++
++      if (rules & BIT(TRIGGER_NETDEV_LINK) ||
++          rules & BIT(TRIGGER_NETDEV_LINK_1000))
++              hval |= XWAY_MMD_LEDxH_CON_LINK1000;
++
++      if (rules & BIT(TRIGGER_NETDEV_TX))
++              lval |= XWAY_MMD_LEDxL_PULSE_TXACT;
++
++      if (rules & BIT(TRIGGER_NETDEV_RX))
++              lval |= XWAY_MMD_LEDxL_PULSE_RXACT;
++
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index), hval);
++      if (ret)
++              return ret;
++
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index), lval);
++      if (ret)
++              return ret;
++
++      return phy_set_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_EN(index));
++}
++
++static int xway_gphy_led_polarity_set(struct phy_device *phydev, int index,
++                                    unsigned long modes)
++{
++      bool force_active_low = false, force_active_high = false;
++      u32 mode;
++
++      if (index >= XWAY_GPHY_MAX_LEDS)
++              return -EINVAL;
++
++      for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
++              switch (mode) {
++              case PHY_LED_ACTIVE_LOW:
++                      force_active_low = true;
++                      break;
++              case PHY_LED_ACTIVE_HIGH:
++                      force_active_high = true;
++                      break;
++              default:
++                      return -EINVAL;
++              }
++      }
++
++      if (force_active_low)
++              return phy_set_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_INV(index));
++
++      if (force_active_high)
++              return phy_clear_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_INV(index));
++
++      unreachable();
++}
++
+ static struct phy_driver xway_gphy[] = {
+       {
+               .phy_id         = PHY_ID_PHY11G_1_3,
+@@ -359,6 +544,11 @@ static struct phy_driver xway_gphy[] = {
+               .config_intr    = xway_gphy_config_intr,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
++              .led_brightness_set = xway_gphy_led_brightness_set,
++              .led_hw_is_supported = xway_gphy_led_hw_is_supported,
++              .led_hw_control_get = xway_gphy_led_hw_control_get,
++              .led_hw_control_set = xway_gphy_led_hw_control_set,
++              .led_polarity_set = xway_gphy_led_polarity_set,
+       }, {
+               .phy_id         = PHY_ID_PHY22F_1_3,
+               .phy_id_mask    = 0xffffffff,
+@@ -370,6 +560,11 @@ static struct phy_driver xway_gphy[] = {
+               .config_intr    = xway_gphy_config_intr,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
++              .led_brightness_set = xway_gphy_led_brightness_set,
++              .led_hw_is_supported = xway_gphy_led_hw_is_supported,
++              .led_hw_control_get = xway_gphy_led_hw_control_get,
++              .led_hw_control_set = xway_gphy_led_hw_control_set,
++              .led_polarity_set = xway_gphy_led_polarity_set,
+       }, {
+               .phy_id         = PHY_ID_PHY11G_1_4,
+               .phy_id_mask    = 0xffffffff,
+@@ -381,6 +576,11 @@ static struct phy_driver xway_gphy[] = {
+               .config_intr    = xway_gphy_config_intr,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
++              .led_brightness_set = xway_gphy_led_brightness_set,
++              .led_hw_is_supported = xway_gphy_led_hw_is_supported,
++              .led_hw_control_get = xway_gphy_led_hw_control_get,
++              .led_hw_control_set = xway_gphy_led_hw_control_set,
++              .led_polarity_set = xway_gphy_led_polarity_set,
+       }, {
+               .phy_id         = PHY_ID_PHY22F_1_4,
+               .phy_id_mask    = 0xffffffff,
+@@ -392,6 +592,11 @@ static struct phy_driver xway_gphy[] = {
+               .config_intr    = xway_gphy_config_intr,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
++              .led_brightness_set = xway_gphy_led_brightness_set,
++              .led_hw_is_supported = xway_gphy_led_hw_is_supported,
++              .led_hw_control_get = xway_gphy_led_hw_control_get,
++              .led_hw_control_set = xway_gphy_led_hw_control_set,
++              .led_polarity_set = xway_gphy_led_polarity_set,
+       }, {
+               .phy_id         = PHY_ID_PHY11G_1_5,
+               .phy_id_mask    = 0xffffffff,
+@@ -402,6 +607,11 @@ static struct phy_driver xway_gphy[] = {
+               .config_intr    = xway_gphy_config_intr,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
++              .led_brightness_set = xway_gphy_led_brightness_set,
++              .led_hw_is_supported = xway_gphy_led_hw_is_supported,
++              .led_hw_control_get = xway_gphy_led_hw_control_get,
++              .led_hw_control_set = xway_gphy_led_hw_control_set,
++              .led_polarity_set = xway_gphy_led_polarity_set,
+       }, {
+               .phy_id         = PHY_ID_PHY22F_1_5,
+               .phy_id_mask    = 0xffffffff,
+@@ -412,6 +622,11 @@ static struct phy_driver xway_gphy[] = {
+               .config_intr    = xway_gphy_config_intr,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
++              .led_brightness_set = xway_gphy_led_brightness_set,
++              .led_hw_is_supported = xway_gphy_led_hw_is_supported,
++              .led_hw_control_get = xway_gphy_led_hw_control_get,
++              .led_hw_control_set = xway_gphy_led_hw_control_set,
++              .led_polarity_set = xway_gphy_led_polarity_set,
+       }, {
+               .phy_id         = PHY_ID_PHY11G_VR9_1_1,
+               .phy_id_mask    = 0xffffffff,
+@@ -422,6 +637,11 @@ static struct phy_driver xway_gphy[] = {
+               .config_intr    = xway_gphy_config_intr,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
++              .led_brightness_set = xway_gphy_led_brightness_set,
++              .led_hw_is_supported = xway_gphy_led_hw_is_supported,
++              .led_hw_control_get = xway_gphy_led_hw_control_get,
++              .led_hw_control_set = xway_gphy_led_hw_control_set,
++              .led_polarity_set = xway_gphy_led_polarity_set,
+       }, {
+               .phy_id         = PHY_ID_PHY22F_VR9_1_1,
+               .phy_id_mask    = 0xffffffff,
+@@ -432,6 +652,11 @@ static struct phy_driver xway_gphy[] = {
+               .config_intr    = xway_gphy_config_intr,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
++              .led_brightness_set = xway_gphy_led_brightness_set,
++              .led_hw_is_supported = xway_gphy_led_hw_is_supported,
++              .led_hw_control_get = xway_gphy_led_hw_control_get,
++              .led_hw_control_set = xway_gphy_led_hw_control_set,
++              .led_polarity_set = xway_gphy_led_polarity_set,
+       }, {
+               .phy_id         = PHY_ID_PHY11G_VR9_1_2,
+               .phy_id_mask    = 0xffffffff,
+@@ -442,6 +667,11 @@ static struct phy_driver xway_gphy[] = {
+               .config_intr    = xway_gphy_config_intr,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
++              .led_brightness_set = xway_gphy_led_brightness_set,
++              .led_hw_is_supported = xway_gphy_led_hw_is_supported,
++              .led_hw_control_get = xway_gphy_led_hw_control_get,
++              .led_hw_control_set = xway_gphy_led_hw_control_set,
++              .led_polarity_set = xway_gphy_led_polarity_set,
+       }, {
+               .phy_id         = PHY_ID_PHY22F_VR9_1_2,
+               .phy_id_mask    = 0xffffffff,
+@@ -452,6 +682,11 @@ static struct phy_driver xway_gphy[] = {
+               .config_intr    = xway_gphy_config_intr,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
++              .led_brightness_set = xway_gphy_led_brightness_set,
++              .led_hw_is_supported = xway_gphy_led_hw_is_supported,
++              .led_hw_control_get = xway_gphy_led_hw_control_get,
++              .led_hw_control_set = xway_gphy_led_hw_control_set,
++              .led_polarity_set = xway_gphy_led_polarity_set,
+       },
+ };
+ module_phy_driver(xway_gphy);
diff --git a/target/linux/generic/backport-6.12/730-09-v6.18-net-phy-mxl-gpy-fix-link-properties-on-USXGMII-and-i.patch b/target/linux/generic/backport-6.12/730-09-v6.18-net-phy-mxl-gpy-fix-link-properties-on-USXGMII-and-i.patch
new file mode 100644 (file)
index 0000000..6f3bbfc
--- /dev/null
@@ -0,0 +1,56 @@
+From 081156ce13f8fa4e97b5148dc54d8c0ddf02117b Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 20 Nov 2025 15:02:19 +0000
+Subject: [PATCH] net: phy: mxl-gpy: fix link properties on USXGMII and
+ internal PHYs
+
+gpy_update_interface() returns early in case the PHY is internal or
+connected via USXGMII. In this case the gigabit master/slave property
+as well as MDI/MDI-X status also won't be read which seems wrong.
+Always read those properties by moving the logic to retrieve them to
+gpy_read_status().
+
+Fixes: fd8825cd8c6fc ("net: phy: mxl-gpy: Add PHY Auto/MDI/MDI-X set driver for GPY211 chips")
+Fixes: 311abcdddc00a ("net: phy: add support to get Master-Slave configuration")
+Suggested-by: "Russell King (Oracle)" <linux@armlinux.org.uk>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/71fccf3f56742116eb18cc070d2a9810479ea7f9.1763650701.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/mxl-gpy.c | 18 +++++++++++-------
+ 1 file changed, 11 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/phy/mxl-gpy.c
++++ b/drivers/net/phy/mxl-gpy.c
+@@ -584,13 +584,7 @@ static int gpy_update_interface(struct p
+               break;
+       }
+-      if (phydev->speed == SPEED_2500 || phydev->speed == SPEED_1000) {
+-              ret = genphy_read_master_slave(phydev);
+-              if (ret < 0)
+-                      return ret;
+-      }
+-
+-      return gpy_update_mdix(phydev);
++      return 0;
+ }
+ static int gpy_read_status(struct phy_device *phydev)
+@@ -645,6 +639,16 @@ static int gpy_read_status(struct phy_de
+               ret = gpy_update_interface(phydev);
+               if (ret < 0)
+                       return ret;
++
++              if (phydev->speed == SPEED_2500 || phydev->speed == SPEED_1000) {
++                      ret = genphy_read_master_slave(phydev);
++                      if (ret < 0)
++                              return ret;
++              }
++
++              ret = gpy_update_mdix(phydev);
++              if (ret < 0)
++                      return ret;
+       }
+       return 0;
diff --git a/target/linux/generic/backport-6.12/730-10-v6.19-net-phy-mxl-gpy-add-support-for-MxL86211C.patch b/target/linux/generic/backport-6.12/730-10-v6.19-net-phy-mxl-gpy-add-support-for-MxL86211C.patch
new file mode 100644 (file)
index 0000000..7707337
--- /dev/null
@@ -0,0 +1,64 @@
+From 9d844da693d6d0813714d9b5b7a58ac05c4cf7f0 Mon Sep 17 00:00:00 2001
+From: Chad Monroe <chad@monroe.io>
+Date: Sat, 22 Nov 2025 13:32:15 +0000
+Subject: [PATCH] net: phy: mxl-gpy: add support for MxL86211C
+
+MxL86211C is a smaller and more efficient version of the GPY211C.
+Add the PHY ID and phy_driver instance to the mxl-gpy driver.
+
+Signed-off-by: Chad Monroe <chad@monroe.io>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/cabf3559d6511bed6b8a925f540e3162efc20f6b.1763818120.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mxl-gpy.c | 24 ++++++++++++++++++++++++
+ 1 file changed, 24 insertions(+)
+
+--- a/drivers/net/phy/mxl-gpy.c
++++ b/drivers/net/phy/mxl-gpy.c
+@@ -30,6 +30,7 @@
+ #define PHY_ID_GPY241B                0x67C9DE40
+ #define PHY_ID_GPY241BM               0x67C9DE80
+ #define PHY_ID_GPY245B                0x67C9DEC0
++#define PHY_ID_MXL86211C      0xC1335400
+ #define PHY_CTL1              0x13
+ #define PHY_CTL1_MDICD                BIT(3)
+@@ -1275,6 +1276,28 @@ static struct phy_driver gpy_drivers[] =
+               .get_wol        = gpy_get_wol,
+               .set_loopback   = gpy_loopback,
+       },
++      {
++              PHY_ID_MATCH_MODEL(PHY_ID_MXL86211C),
++              .name           = "Maxlinear Ethernet MxL86211C",
++              .get_features   = genphy_c45_pma_read_abilities,
++              .config_init    = gpy_config_init,
++              .probe          = gpy_probe,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++              .config_aneg    = gpy_config_aneg,
++              .aneg_done      = genphy_c45_aneg_done,
++              .read_status    = gpy_read_status,
++              .config_intr    = gpy_config_intr,
++              .handle_interrupt = gpy_handle_interrupt,
++              .set_wol        = gpy_set_wol,
++              .get_wol        = gpy_get_wol,
++              .set_loopback   = gpy_loopback,
++              .led_brightness_set = gpy_led_brightness_set,
++              .led_hw_is_supported = gpy_led_hw_is_supported,
++              .led_hw_control_get = gpy_led_hw_control_get,
++              .led_hw_control_set = gpy_led_hw_control_set,
++              .led_polarity_set = gpy_led_polarity_set,
++      },
+ };
+ module_phy_driver(gpy_drivers);
+@@ -1291,6 +1314,7 @@ static const struct mdio_device_id __may
+       {PHY_ID_MATCH_MODEL(PHY_ID_GPY241B)},
+       {PHY_ID_MATCH_MODEL(PHY_ID_GPY241BM)},
+       {PHY_ID_MATCH_MODEL(PHY_ID_GPY245B)},
++      {PHY_ID_MATCH_MODEL(PHY_ID_MXL86211C)},
+       { }
+ };
+ MODULE_DEVICE_TABLE(mdio, gpy_tbl);
diff --git a/target/linux/generic/backport-6.12/730-11-v7.0-net-phy-mxl-gpy-implement-SGMII-in-band-configuratio.patch b/target/linux/generic/backport-6.12/730-11-v7.0-net-phy-mxl-gpy-implement-SGMII-in-band-configuratio.patch
new file mode 100644 (file)
index 0000000..0e99bfe
--- /dev/null
@@ -0,0 +1,187 @@
+From 9da9633f2f02df7da67ab3b6f84eda4956ae1c5a Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 7 Jan 2026 15:39:16 +0000
+Subject: [PATCH] net: phy: mxl-gpy: implement SGMII in-band configuration
+
+SGMII in-band autonegotiation was previously kept untouched (and restored
+after switching back from 2500Base-X to SGMII). Now that the kernel offers
+a way to announce in-band capabilities and nable/disable in-band AN,
+implement the .inband_caps and .config_inband driver ops.
+This moves the responsibility to configure SGMII in-band AN from the PHY
+driver to phylink.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/70f07e46dd96e239a9711e6073e8c04c1d8672d4.1767800226.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mxl-gpy.c | 61 ++++++++++++++++++++++++++++++---------
+ 1 file changed, 47 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/phy/mxl-gpy.c
++++ b/drivers/net/phy/mxl-gpy.c
+@@ -568,20 +568,6 @@ static int gpy_update_interface(struct p
+       case SPEED_100:
+       case SPEED_10:
+               phydev->interface = PHY_INTERFACE_MODE_SGMII;
+-              if (gpy_sgmii_aneg_en(phydev))
+-                      break;
+-              /* Enable and restart SGMII ANEG for 10/100/1000Mbps link speed
+-               * if ANEG is disabled (in 2500-BaseX mode).
+-               */
+-              ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
+-                                   VSPEC1_SGMII_ANEN_ANRS,
+-                                   VSPEC1_SGMII_ANEN_ANRS);
+-              if (ret < 0) {
+-                      phydev_err(phydev,
+-                                 "Error: Enable of SGMII ANEG failed: %d\n",
+-                                 ret);
+-                      return ret;
+-              }
+               break;
+       }
+@@ -1022,6 +1008,27 @@ static int gpy_led_polarity_set(struct p
+       unreachable();
+ }
++static unsigned int gpy_inband_caps(struct phy_device *phydev,
++                                  phy_interface_t interface)
++{
++      switch (interface) {
++      case PHY_INTERFACE_MODE_SGMII:
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++      case PHY_INTERFACE_MODE_2500BASEX:
++              return LINK_INBAND_DISABLE;
++      default:
++              return 0;
++      }
++}
++
++static int gpy_config_inband(struct phy_device *phydev, unsigned int modes)
++{
++      return phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
++                            VSPEC1_SGMII_ANEN_ANRS,
++                            (modes == LINK_INBAND_DISABLE) ? 0 :
++                            VSPEC1_SGMII_ANEN_ANRS);
++}
++
+ static struct phy_driver gpy_drivers[] = {
+       {
+               PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx),
+@@ -1029,6 +1036,8 @@ static struct phy_driver gpy_drivers[] =
+               .get_features   = genphy_c45_pma_read_abilities,
+               .config_init    = gpy_config_init,
+               .probe          = gpy_probe,
++              .inband_caps    = gpy_inband_caps,
++              .config_inband  = gpy_config_inband,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+               .config_aneg    = gpy_config_aneg,
+@@ -1052,6 +1061,8 @@ static struct phy_driver gpy_drivers[] =
+               .get_features   = genphy_c45_pma_read_abilities,
+               .config_init    = gpy_config_init,
+               .probe          = gpy_probe,
++              .inband_caps    = gpy_inband_caps,
++              .config_inband  = gpy_config_inband,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+               .config_aneg    = gpy_config_aneg,
+@@ -1074,6 +1085,8 @@ static struct phy_driver gpy_drivers[] =
+               .get_features   = genphy_c45_pma_read_abilities,
+               .config_init    = gpy_config_init,
+               .probe          = gpy_probe,
++              .inband_caps    = gpy_inband_caps,
++              .config_inband  = gpy_config_inband,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+               .config_aneg    = gpy_config_aneg,
+@@ -1097,6 +1110,8 @@ static struct phy_driver gpy_drivers[] =
+               .get_features   = genphy_c45_pma_read_abilities,
+               .config_init    = gpy21x_config_init,
+               .probe          = gpy_probe,
++              .inband_caps    = gpy_inband_caps,
++              .config_inband  = gpy_config_inband,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+               .config_aneg    = gpy_config_aneg,
+@@ -1119,6 +1134,8 @@ static struct phy_driver gpy_drivers[] =
+               .get_features   = genphy_c45_pma_read_abilities,
+               .config_init    = gpy21x_config_init,
+               .probe          = gpy_probe,
++              .inband_caps    = gpy_inband_caps,
++              .config_inband  = gpy_config_inband,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+               .config_aneg    = gpy_config_aneg,
+@@ -1141,6 +1158,8 @@ static struct phy_driver gpy_drivers[] =
+               .name           = "Maxlinear Ethernet GPY212B",
+               .get_features   = genphy_c45_pma_read_abilities,
+               .config_init    = gpy21x_config_init,
++              .inband_caps    = gpy_inband_caps,
++              .config_inband  = gpy_config_inband,
+               .probe          = gpy_probe,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+@@ -1164,6 +1183,8 @@ static struct phy_driver gpy_drivers[] =
+               .get_features   = genphy_c45_pma_read_abilities,
+               .config_init    = gpy21x_config_init,
+               .probe          = gpy_probe,
++              .inband_caps    = gpy_inband_caps,
++              .config_inband  = gpy_config_inband,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+               .config_aneg    = gpy_config_aneg,
+@@ -1187,6 +1208,8 @@ static struct phy_driver gpy_drivers[] =
+               .get_features   = genphy_c45_pma_read_abilities,
+               .config_init    = gpy21x_config_init,
+               .probe          = gpy_probe,
++              .inband_caps    = gpy_inband_caps,
++              .config_inband  = gpy_config_inband,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+               .config_aneg    = gpy_config_aneg,
+@@ -1209,6 +1232,8 @@ static struct phy_driver gpy_drivers[] =
+               .get_features   = genphy_c45_pma_read_abilities,
+               .config_init    = gpy21x_config_init,
+               .probe          = gpy_probe,
++              .inband_caps    = gpy_inband_caps,
++              .config_inband  = gpy_config_inband,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+               .config_aneg    = gpy_config_aneg,
+@@ -1231,6 +1256,8 @@ static struct phy_driver gpy_drivers[] =
+               .get_features   = genphy_c45_pma_read_abilities,
+               .config_init    = gpy_config_init,
+               .probe          = gpy_probe,
++              .inband_caps    = gpy_inband_caps,
++              .config_inband  = gpy_config_inband,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+               .config_aneg    = gpy_config_aneg,
+@@ -1248,6 +1275,8 @@ static struct phy_driver gpy_drivers[] =
+               .get_features   = genphy_c45_pma_read_abilities,
+               .config_init    = gpy_config_init,
+               .probe          = gpy_probe,
++              .inband_caps    = gpy_inband_caps,
++              .config_inband  = gpy_config_inband,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+               .config_aneg    = gpy_config_aneg,
+@@ -1265,6 +1294,8 @@ static struct phy_driver gpy_drivers[] =
+               .get_features   = genphy_c45_pma_read_abilities,
+               .config_init    = gpy_config_init,
+               .probe          = gpy_probe,
++              .inband_caps    = gpy_inband_caps,
++              .config_inband  = gpy_config_inband,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+               .config_aneg    = gpy_config_aneg,
+@@ -1282,6 +1313,8 @@ static struct phy_driver gpy_drivers[] =
+               .get_features   = genphy_c45_pma_read_abilities,
+               .config_init    = gpy_config_init,
+               .probe          = gpy_probe,
++              .inband_caps    = gpy_inband_caps,
++              .config_inband  = gpy_config_inband,
+               .suspend        = genphy_suspend,
+               .resume         = genphy_resume,
+               .config_aneg    = gpy_config_aneg,
diff --git a/target/linux/generic/backport-6.12/731-v6.18-net-mediatek-wed-Introduce-MT7992-WED-support-to-MT7.patch b/target/linux/generic/backport-6.12/731-v6.18-net-mediatek-wed-Introduce-MT7992-WED-support-to-MT7.patch
new file mode 100644 (file)
index 0000000..8b1844d
--- /dev/null
@@ -0,0 +1,116 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 12 Aug 2025 06:57:23 +0200
+Subject: [PATCH] net: mediatek: wed: Introduce MT7992 WED support to MT7988
+ SoC
+
+Introduce the second WDMA RX ring in WED driver for MT7988 SoC since the
+Mediatek MT7992 WiFi chipset supports two separated WDMA rings.
+Add missing MT7988 configurations to properly support WED for MT7992 in
+MT76 driver.
+
+Co-developed-by: Rex Lu <rex.lu@mediatek.com>
+Signed-off-by: Rex Lu <rex.lu@mediatek.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20250812-mt7992-wed-support-v3-1-9ada78a819a4@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
+@@ -59,7 +59,9 @@ struct mtk_wed_flow_block_priv {
+ static const struct mtk_wed_soc_data mt7622_data = {
+       .regmap = {
+               .tx_bm_tkid             = 0x088,
+-              .wpdma_rx_ring0         = 0x770,
++              .wpdma_rx_ring = {
++                      0x770,
++              },
+               .reset_idx_tx_mask      = GENMASK(3, 0),
+               .reset_idx_rx_mask      = GENMASK(17, 16),
+       },
+@@ -70,7 +72,9 @@ static const struct mtk_wed_soc_data mt7
+ static const struct mtk_wed_soc_data mt7986_data = {
+       .regmap = {
+               .tx_bm_tkid             = 0x0c8,
+-              .wpdma_rx_ring0         = 0x770,
++              .wpdma_rx_ring = {
++                      0x770,
++              },
+               .reset_idx_tx_mask      = GENMASK(1, 0),
+               .reset_idx_rx_mask      = GENMASK(7, 6),
+       },
+@@ -81,7 +85,10 @@ static const struct mtk_wed_soc_data mt7
+ static const struct mtk_wed_soc_data mt7988_data = {
+       .regmap = {
+               .tx_bm_tkid             = 0x0c8,
+-              .wpdma_rx_ring0         = 0x7d0,
++              .wpdma_rx_ring = {
++                      0x7d0,
++                      0x7d8,
++              },
+               .reset_idx_tx_mask      = GENMASK(1, 0),
+               .reset_idx_rx_mask      = GENMASK(7, 6),
+       },
+@@ -621,8 +628,8 @@ mtk_wed_amsdu_init(struct mtk_wed_device
+               return ret;
+       }
+-      /* eagle E1 PCIE1 tx ring 22 flow control issue */
+-      if (dev->wlan.id == 0x7991)
++      /* Kite and Eagle E1 PCIE1 tx ring 22 flow control issue */
++      if (dev->wlan.id == 0x7991 || dev->wlan.id == 0x7992)
+               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);
+@@ -1239,7 +1246,11 @@ 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, dev->hw->soc->regmap.wpdma_rx_ring0, dev->wlan.wpdma_rx);
++      wed_w32(dev, dev->hw->soc->regmap.wpdma_rx_ring[0],
++              dev->wlan.wpdma_rx[0]);
++      if (mtk_wed_is_v3_or_greater(dev->hw))
++              wed_w32(dev, dev->hw->soc->regmap.wpdma_rx_ring[1],
++                      dev->wlan.wpdma_rx[1]);
+       if (!dev->wlan.hw_rro)
+               return;
+@@ -2335,6 +2346,16 @@ mtk_wed_start(struct mtk_wed_device *dev
+               if (!dev->rx_wdma[i].desc)
+                       mtk_wed_wdma_rx_ring_setup(dev, i, 16, false);
++      if (dev->wlan.hw_rro) {
++              for (i = 0; i < MTK_WED_RX_PAGE_QUEUES; i++) {
++                      u32 addr = MTK_WED_RRO_MSDU_PG_CTRL0(i) +
++                                 MTK_WED_RING_OFS_COUNT;
++
++                      if (!wed_r32(dev, addr))
++                              wed_w32(dev, addr, 1);
++              }
++      }
++
+       mtk_wed_hw_init(dev);
+       mtk_wed_configure_irq(dev, irq_mask);
+--- a/drivers/net/ethernet/mediatek/mtk_wed.h
++++ b/drivers/net/ethernet/mediatek/mtk_wed.h
+@@ -17,7 +17,7 @@ struct mtk_wed_wo;
+ struct mtk_wed_soc_data {
+       struct {
+               u32 tx_bm_tkid;
+-              u32 wpdma_rx_ring0;
++              u32 wpdma_rx_ring[MTK_WED_RX_QUEUES];
+               u32 reset_idx_tx_mask;
+               u32 reset_idx_rx_mask;
+       } regmap;
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -147,7 +147,7 @@ struct mtk_wed_device {
+               u32 wpdma_tx;
+               u32 wpdma_txfree;
+               u32 wpdma_rx_glo;
+-              u32 wpdma_rx;
++              u32 wpdma_rx[MTK_WED_RX_QUEUES];
+               u32 wpdma_rx_rro[MTK_WED_RX_QUEUES];
+               u32 wpdma_rx_pg;
diff --git a/target/linux/generic/backport-6.12/732-v6.18-wifi-mt76-wed-use-proper-wed-reference-in-mt76-wed-d.patch b/target/linux/generic/backport-6.12/732-v6.18-wifi-mt76-wed-use-proper-wed-reference-in-mt76-wed-d.patch
new file mode 100644 (file)
index 0000000..883de54
--- /dev/null
@@ -0,0 +1,79 @@
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Wed, 8 Oct 2025 12:41:48 +0200
+Subject: [PATCH] wifi: mt76: wed: use proper wed reference in mt76 wed driver
+ callabacks
+
+MT7996 driver can use both wed and wed_hif2 devices to offload traffic
+from/to the wireless NIC. In the current codebase we assume to always
+use the primary wed device in wed callbacks resulting in the following
+crash if the hw runs wed_hif2 (e.g. 6GHz link).
+
+[  297.455876] Unable to handle kernel read from unreadable memory at virtual address 000000000000080a
+[  297.464928] Mem abort info:
+[  297.467722]   ESR = 0x0000000096000005
+[  297.471461]   EC = 0x25: DABT (current EL), IL = 32 bits
+[  297.476766]   SET = 0, FnV = 0
+[  297.479809]   EA = 0, S1PTW = 0
+[  297.482940]   FSC = 0x05: level 1 translation fault
+[  297.487809] Data abort info:
+[  297.490679]   ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000
+[  297.496156]   CM = 0, WnR = 0, TnD = 0, TagAccess = 0
+[  297.501196]   GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
+[  297.506500] user pgtable: 4k pages, 39-bit VAs, pgdp=0000000107480000
+[  297.512927] [000000000000080a] pgd=08000001097fb003, p4d=08000001097fb003, pud=08000001097fb003, pmd=0000000000000000
+[  297.523532] Internal error: Oops: 0000000096000005 [#1] SMP
+[  297.715393] CPU: 2 UID: 0 PID: 45 Comm: kworker/u16:2 Tainted: G           O       6.12.50 #0
+[  297.723908] Tainted: [O]=OOT_MODULE
+[  297.727384] Hardware name: Banana Pi BPI-R4 (2x SFP+) (DT)
+[  297.732857] Workqueue: nf_ft_offload_del nf_flow_rule_route_ipv6 [nf_flow_table]
+[  297.740254] pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
+[  297.747205] pc : mt76_wed_offload_disable+0x64/0xa0 [mt76]
+[  297.752688] lr : mtk_wed_flow_remove+0x58/0x80
+[  297.757126] sp : ffffffc080fe3ae0
+[  297.760430] x29: ffffffc080fe3ae0 x28: ffffffc080fe3be0 x27: 00000000deadbef7
+[  297.767557] x26: ffffff80c5ebca00 x25: 0000000000000001 x24: ffffff80c85f4c00
+[  297.774683] x23: ffffff80c1875b78 x22: ffffffc080d42cd0 x21: ffffffc080660018
+[  297.781809] x20: ffffff80c6a076d0 x19: ffffff80c6a043c8 x18: 0000000000000000
+[  297.788935] x17: 0000000000000000 x16: 0000000000000001 x15: 0000000000000000
+[  297.796060] x14: 0000000000000019 x13: ffffff80c0ad8ec0 x12: 00000000fa83b2da
+[  297.803185] x11: ffffff80c02700c0 x10: ffffff80c0ad8ec0 x9 : ffffff81fef96200
+[  297.810311] x8 : ffffff80c02700c0 x7 : ffffff80c02700d0 x6 : 0000000000000002
+[  297.817435] x5 : 0000000000000400 x4 : 0000000000000000 x3 : 0000000000000000
+[  297.824561] x2 : 0000000000000001 x1 : 0000000000000800 x0 : ffffff80c6a063c8
+[  297.831686] Call trace:
+[  297.834123]  mt76_wed_offload_disable+0x64/0xa0 [mt76]
+[  297.839254]  mtk_wed_flow_remove+0x58/0x80
+[  297.843342]  mtk_flow_offload_cmd+0x434/0x574
+[  297.847689]  mtk_wed_setup_tc_block_cb+0x30/0x40
+[  297.852295]  nf_flow_offload_ipv6_hook+0x7f4/0x964 [nf_flow_table]
+[  297.858466]  nf_flow_rule_route_ipv6+0x438/0x4a4 [nf_flow_table]
+[  297.864463]  process_one_work+0x174/0x300
+[  297.868465]  worker_thread+0x278/0x430
+[  297.872204]  kthread+0xd8/0xdc
+[  297.875251]  ret_from_fork+0x10/0x20
+[  297.878820] Code: 928b5ae0 8b000273 91400a60 f943fa61 (79401421)
+[  297.884901] ---[ end trace 0000000000000000 ]---
+
+Fix the issue detecting the proper wed reference to use running wed
+callabacks.
+
+-- Partial backport for data structure change only (rest is in the mt76 package)
+
+Fixes: 83eafc9251d6 ("wifi: mt76: mt7996: add wed tx support")
+Tested-by: Daniel Pawlik <pawlik.dan@gmail.com>
+Tested-by: Matteo Croce <teknoraver@meta.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20251008-wed-fixes-v1-1-8f7678583385@kernel.org
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/include/linux/soc/mediatek/mtk_wed.h
++++ b/include/linux/soc/mediatek/mtk_wed.h
+@@ -154,6 +154,7 @@ struct mtk_wed_device {
+               bool wcid_512;
+               bool hw_rro;
+               bool msi;
++              bool hif2;
+               u16 token_start;
+               unsigned int nbuf;
diff --git a/target/linux/generic/backport-6.12/733-v6.18-net-mtk-wed-add-dma-mask-limitation-and-GFP_DMA32-fo.patch b/target/linux/generic/backport-6.12/733-v6.18-net-mtk-wed-add-dma-mask-limitation-and-GFP_DMA32-fo.patch
new file mode 100644 (file)
index 0000000..c1bb1b9
--- /dev/null
@@ -0,0 +1,50 @@
+From: Rex Lu <rex.lu@mediatek.com>
+Date: Thu, 9 Oct 2025 08:29:34 +0200
+Subject: [PATCH] net: mtk: wed: add dma mask limitation and GFP_DMA32 for
+ device with more than 4GB DRAM
+
+Limit tx/rx buffer address to 32-bit address space for board with more
+than 4GB DRAM.
+
+Fixes: 804775dfc2885 ("net: ethernet: mtk_eth_soc: add support for Wireless Ethernet Dispatch (WED)")
+Fixes: 6757d345dd7db ("net: ethernet: mtk_wed: introduce hw_rro support for MT7988")
+Tested-by: Daniel Pawlik <pawlik.dan@gmail.com>
+Tested-by: Matteo Croce <teknoraver@meta.com>
+Signed-off-by: Rex Lu <rex.lu@mediatek.com>
+Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org>
+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
+@@ -677,7 +677,7 @@ mtk_wed_tx_buffer_alloc(struct mtk_wed_d
+               void *buf;
+               int s;
+-              page = __dev_alloc_page(GFP_KERNEL);
++              page = __dev_alloc_page(GFP_KERNEL | GFP_DMA32);
+               if (!page)
+                       return -ENOMEM;
+@@ -800,7 +800,7 @@ mtk_wed_hwrro_buffer_alloc(struct mtk_we
+               struct page *page;
+               int s;
+-              page = __dev_alloc_page(GFP_KERNEL);
++              page = __dev_alloc_page(GFP_KERNEL | GFP_DMA32);
+               if (!page)
+                       return -ENOMEM;
+@@ -2438,6 +2438,10 @@ mtk_wed_attach(struct mtk_wed_device *de
+       dev->version = hw->version;
+       dev->hw->pcie_base = mtk_wed_get_pcie_base(dev);
++      ret = dma_set_mask_and_coherent(hw->dev, DMA_BIT_MASK(32));
++      if (ret)
++              goto out;
++
+       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);
diff --git a/target/linux/generic/backport-6.12/735-v6.13-net-phy-avoid-undefined-behavior-in-_led_polarity_se.patch b/target/linux/generic/backport-6.12/735-v6.13-net-phy-avoid-undefined-behavior-in-_led_polarity_se.patch
new file mode 100644 (file)
index 0000000..622d2c3
--- /dev/null
@@ -0,0 +1,64 @@
+From cff865c700711ecc3824b2dfe181637f3ed23c80 Mon Sep 17 00:00:00 2001
+From: Arnd Bergmann <arnd@arndb.de>
+Date: Tue, 17 Dec 2024 09:10:34 +0100
+Subject: net: phy: avoid undefined behavior in *_led_polarity_set()
+
+gcc runs into undefined behavior at the end of the three led_polarity_set()
+callback functions if it were called with a zero 'modes' argument and it
+just ends the function there without returning from it.
+
+This gets flagged by 'objtool' as a function that continues on
+to the next one:
+
+drivers/net/phy/aquantia/aquantia_leds.o: warning: objtool: aqr_phy_led_polarity_set+0xf: can't find jump dest instruction at .text+0x5d9
+drivers/net/phy/intel-xway.o: warning: objtool: xway_gphy_led_polarity_set() falls through to next function xway_gphy_config_init()
+drivers/net/phy/mxl-gpy.o: warning: objtool: gpy_led_polarity_set() falls through to next function gpy_led_hw_control_get()
+
+There is no point to micro-optimize the behavior here to save a single-digit
+number of bytes in the kernel, so just change this to a "return -EINVAL"
+as we do when any unexpected bits are set.
+
+Fixes: 1758af47b98c ("net: phy: intel-xway: add support for PHY LEDs")
+Fixes: 9d55e68b19f2 ("net: phy: aquantia: correctly describe LED polarity override")
+Fixes: eb89c79c1b8f ("net: phy: mxl-gpy: correctly describe LED polarity")
+Signed-off-by: Arnd Bergmann <arnd@arndb.de>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20241217081056.238792-1-arnd@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/aquantia/aquantia_leds.c | 2 +-
+ drivers/net/phy/intel-xway.c             | 2 +-
+ drivers/net/phy/mxl-gpy.c                | 2 +-
+ 3 files changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/aquantia/aquantia_leds.c
++++ b/drivers/net/phy/aquantia/aquantia_leds.c
+@@ -156,5 +156,5 @@ int aqr_phy_led_polarity_set(struct phy_
+       if (force_active_high || force_active_low)
+               return aqr_phy_led_active_low_set(phydev, index, force_active_low);
+-      unreachable();
++      return -EINVAL;
+ }
+--- a/drivers/net/phy/intel-xway.c
++++ b/drivers/net/phy/intel-xway.c
+@@ -529,7 +529,7 @@ static int xway_gphy_led_polarity_set(st
+       if (force_active_high)
+               return phy_clear_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_INV(index));
+-      unreachable();
++      return -EINVAL;
+ }
+ static struct phy_driver xway_gphy[] = {
+--- a/drivers/net/phy/mxl-gpy.c
++++ b/drivers/net/phy/mxl-gpy.c
+@@ -1005,7 +1005,7 @@ static int gpy_led_polarity_set(struct p
+       if (force_active_high)
+               return phy_clear_bits(phydev, PHY_LED, PHY_LED_POLARITY(index));
+-      unreachable();
++      return -EINVAL;
+ }
+ static unsigned int gpy_inband_caps(struct phy_device *phydev,
diff --git a/target/linux/generic/backport-6.12/740-v6.13-net-dsa-mv88e6xxx-Support-LED-control.patch b/target/linux/generic/backport-6.12/740-v6.13-net-dsa-mv88e6xxx-Support-LED-control.patch
new file mode 100644 (file)
index 0000000..19268cf
--- /dev/null
@@ -0,0 +1,1250 @@
+From 7b590490e3aa6bfa38bf6e2069a529017fd3c1d2 Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linus.walleij@linaro.org>
+Date: Fri, 13 Oct 2023 00:08:35 +0200
+Subject: [PATCH] net: dsa: mv88e6xxx: Support LED control
+
+This adds control over the hardware LEDs in the Marvell
+MV88E6xxx DSA switch and enables it for MV88E6352.
+
+This fixes an imminent problem on the Inteno XG6846 which
+has a WAN LED that simply do not work with hardware
+defaults: driver amendment is necessary.
+
+The patch is modeled after Christian Marangis LED support
+code for the QCA8k DSA switch, I got help with the register
+definitions from Tim Harvey.
+
+After this patch it is possible to activate hardware link
+indication like this (or with a similar script):
+
+  cd /sys/class/leds/Marvell\ 88E6352:05:00:green:wan/
+  echo netdev > trigger
+  echo 1 > link
+
+This makes the green link indicator come up on any link
+speed. It is also possible to be more elaborate, like this:
+
+  cd /sys/class/leds/Marvell\ 88E6352:05:00:green:wan/
+  echo netdev > trigger
+  echo 1 > link_1000
+  cd /sys/class/leds/Marvell\ 88E6352:05:01:amber:wan/
+  echo netdev > trigger
+  echo 1 > link_100
+
+Making the green LED come on for a gigabit link and the
+amber LED come on for a 100 mbit link.
+
+Each port has 2 LED slots (the hardware may use just one or
+none) and the hardware triggers are specified in four bits per
+LED, and some of the hardware triggers are only available on the
+SFP (fiber) uplink. The restrictions are described in the
+port.h header file where the registers are described. For
+example, selector 1 set for LED 1 on port 5 or 6 will indicate
+Fiber 1000 (gigabit) and activity with a blinking LED, but
+ONLY for an SFP connection. If port 5/6 is used with something
+not SFP, this selector is a noop: something else need to be
+selected.
+
+After the previous series rewriting the MV88E6xxx DT
+bindings to use YAML a "leds" subnode is already valid
+for each port, in my scratch device tree it looks like
+this:
+
+   leds {
+     #address-cells = <1>;
+     #size-cells = <0>;
+
+     led@0 {
+       reg = <0>;
+       color = <LED_COLOR_ID_GREEN>;
+       function = LED_FUNCTION_LAN;
+       default-state = "off";
+       linux,default-trigger = "netdev";
+     };
+     led@1 {
+       reg = <1>;
+       color = <LED_COLOR_ID_AMBER>;
+       function = LED_FUNCTION_LAN;
+       default-state = "off";
+     };
+   };
+
+This DT config is not yet configuring everything: when the netdev
+default trigger is assigned the hw acceleration callbacks are
+not called, and there is no way to set the netdev sub-trigger
+type (such as link_1000) from the device tree, such as if you want
+a gigabit link indicator. This has to be done from userspace at
+this point.
+
+We add LED operations to all switches in the 6352 family:
+6172, 6176, 6240 and 6352.
+
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+---
+ drivers/net/dsa/mv88e6xxx/Kconfig  |  10 +
+ drivers/net/dsa/mv88e6xxx/Makefile |   1 +
+ drivers/net/dsa/mv88e6xxx/chip.c   |  38 +-
+ drivers/net/dsa/mv88e6xxx/chip.h   |  11 +
+ drivers/net/dsa/mv88e6xxx/leds.c   | 839 +++++++++++++++++++++++++++++
+ drivers/net/dsa/mv88e6xxx/port.c   |   1 +
+ drivers/net/dsa/mv88e6xxx/port.h   | 133 +++++
+ 7 files changed, 1031 insertions(+), 2 deletions(-)
+ create mode 100644 drivers/net/dsa/mv88e6xxx/leds.c
+
+--- a/drivers/net/dsa/mv88e6xxx/Kconfig
++++ b/drivers/net/dsa/mv88e6xxx/Kconfig
+@@ -17,3 +17,13 @@ config NET_DSA_MV88E6XXX_PTP
+       help
+         Say Y to enable PTP hardware timestamping on Marvell 88E6xxx switch
+         chips that support it.
++
++config NET_DSA_MV88E6XXX_LEDS
++      bool "LED support for Marvell 88E6xxx"
++      default y
++      depends on NET_DSA_MV88E6XXX
++      depends on LEDS_CLASS=y || LEDS_CLASS=NET_DSA_MV88E6XXX
++      depends on LEDS_TRIGGERS
++      help
++        This enabled support for controlling the LEDs attached to the
++        Marvell 88E6xxx switch chips.
+--- a/drivers/net/dsa/mv88e6xxx/Makefile
++++ b/drivers/net/dsa/mv88e6xxx/Makefile
+@@ -9,6 +9,7 @@ mv88e6xxx-objs += global2.o
+ mv88e6xxx-objs += global2_avb.o
+ mv88e6xxx-objs += global2_scratch.o
+ mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += hwtstamp.o
++mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_LEDS) += leds.o
+ mv88e6xxx-objs += pcs-6185.o
+ mv88e6xxx-objs += pcs-6352.o
+ mv88e6xxx-objs += pcs-639x.o
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -27,6 +27,7 @@
+ #include <linux/of_irq.h>
+ #include <linux/of_mdio.h>
+ #include <linux/platform_data/mv88e6xxx.h>
++#include <linux/property.h>
+ #include <linux/netdevice.h>
+ #include <linux/gpio/consumer.h>
+ #include <linux/phylink.h>
+@@ -3412,14 +3413,43 @@ static int mv88e6xxx_setup_upstream_port
+ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
+ {
+       struct device_node *phy_handle = NULL;
++      struct fwnode_handle *ports_fwnode;
++      struct fwnode_handle *port_fwnode;
+       struct dsa_switch *ds = chip->ds;
++      struct mv88e6xxx_port *p;
+       struct dsa_port *dp;
+       int tx_amp;
+       int err;
+       u16 reg;
++      u32 val;
+-      chip->ports[port].chip = chip;
+-      chip->ports[port].port = port;
++      p = &chip->ports[port];
++      p->chip = chip;
++      p->port = port;
++
++      /* Look up corresponding fwnode if any */
++      ports_fwnode = device_get_named_child_node(chip->dev, "ethernet-ports");
++      if (!ports_fwnode)
++              ports_fwnode = device_get_named_child_node(chip->dev, "ports");
++      if (ports_fwnode) {
++              fwnode_for_each_child_node(ports_fwnode, port_fwnode) {
++                      if (fwnode_property_read_u32(port_fwnode, "reg", &val))
++                              continue;
++                      if (val == port) {
++                              p->fwnode = port_fwnode;
++                              p->fiber = fwnode_property_present(port_fwnode, "sfp");
++                              break;
++                      }
++              }
++      } else {
++              dev_dbg(chip->dev, "no ethernet ports node defined for the device\n");
++      }
++
++      if (chip->info->ops->port_setup_leds) {
++              err = chip->info->ops->port_setup_leds(chip, port);
++              if (err && err != -EOPNOTSUPP)
++                      return err;
++      }
+       err = mv88e6xxx_port_setup_mac(chip, port, LINK_UNFORCED,
+                                      SPEED_UNFORCED, DUPLEX_UNFORCED,
+@@ -4653,6 +4683,7 @@ static const struct mv88e6xxx_ops mv88e6
+       .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit,
+       .port_disable_pri_override = mv88e6xxx_port_disable_pri_override,
+       .port_get_cmode = mv88e6352_port_get_cmode,
++      .port_setup_leds = mv88e6xxx_port_setup_leds,
+       .port_setup_message_port = mv88e6xxx_setup_message_port,
+       .stats_snapshot = mv88e6320_g1_stats_snapshot,
+       .stats_set_histogram = mv88e6095_g1_stats_set_histogram,
+@@ -4755,6 +4786,7 @@ static const struct mv88e6xxx_ops mv88e6
+       .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit,
+       .port_disable_pri_override = mv88e6xxx_port_disable_pri_override,
+       .port_get_cmode = mv88e6352_port_get_cmode,
++      .port_setup_leds = mv88e6xxx_port_setup_leds,
+       .port_setup_message_port = mv88e6xxx_setup_message_port,
+       .stats_snapshot = mv88e6320_g1_stats_snapshot,
+       .stats_set_histogram = mv88e6095_g1_stats_set_histogram,
+@@ -5030,6 +5062,7 @@ static const struct mv88e6xxx_ops mv88e6
+       .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit,
+       .port_disable_pri_override = mv88e6xxx_port_disable_pri_override,
+       .port_get_cmode = mv88e6352_port_get_cmode,
++      .port_setup_leds = mv88e6xxx_port_setup_leds,
+       .port_setup_message_port = mv88e6xxx_setup_message_port,
+       .stats_snapshot = mv88e6320_g1_stats_snapshot,
+       .stats_set_histogram = mv88e6095_g1_stats_set_histogram,
+@@ -5460,6 +5493,7 @@ static const struct mv88e6xxx_ops mv88e6
+       .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit,
+       .port_disable_pri_override = mv88e6xxx_port_disable_pri_override,
+       .port_get_cmode = mv88e6352_port_get_cmode,
++      .port_setup_leds = mv88e6xxx_port_setup_leds,
+       .port_setup_message_port = mv88e6xxx_setup_message_port,
+       .stats_snapshot = mv88e6320_g1_stats_snapshot,
+       .stats_set_histogram = mv88e6095_g1_stats_set_histogram,
+--- a/drivers/net/dsa/mv88e6xxx/chip.h
++++ b/drivers/net/dsa/mv88e6xxx/chip.h
+@@ -13,7 +13,9 @@
+ #include <linux/irq.h>
+ #include <linux/gpio/consumer.h>
+ #include <linux/kthread.h>
++#include <linux/leds.h>
+ #include <linux/phy.h>
++#include <linux/property.h>
+ #include <linux/ptp_clock_kernel.h>
+ #include <linux/timecounter.h>
+ #include <net/dsa.h>
+@@ -276,6 +278,7 @@ struct mv88e6xxx_vlan {
+ struct mv88e6xxx_port {
+       struct mv88e6xxx_chip *chip;
+       int port;
++      struct fwnode_handle *fwnode;
+       struct mv88e6xxx_vlan bridge_pvid;
+       u64 serdes_stats[2];
+       u64 atu_member_violation;
+@@ -290,6 +293,11 @@ struct mv88e6xxx_port {
+       struct devlink_region *region;
+       void *pcs_private;
++      /* LED related information */
++      bool fiber;
++      struct led_classdev led0;
++      struct led_classdev led1;
++
+       /* MacAuth Bypass control flag */
+       bool mab;
+ };
+@@ -574,6 +582,9 @@ struct mv88e6xxx_ops {
+                             phy_interface_t mode);
+       int (*port_get_cmode)(struct mv88e6xxx_chip *chip, int port, u8 *cmode);
++      /* LED control */
++      int (*port_setup_leds)(struct mv88e6xxx_chip *chip, int port);
++
+       /* Some devices have a per port register indicating what is
+        * the upstream port this port should forward to.
+        */
+--- /dev/null
++++ b/drivers/net/dsa/mv88e6xxx/leds.c
+@@ -0,0 +1,839 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++#include <linux/bitfield.h>
++#include <linux/leds.h>
++#include <linux/property.h>
++
++#include "chip.h"
++#include "global2.h"
++#include "port.h"
++
++/* Offset 0x16: LED control */
++
++static int mv88e6xxx_port_led_write(struct mv88e6xxx_chip *chip, int port, u16 reg)
++{
++      reg |= MV88E6XXX_PORT_LED_CONTROL_UPDATE;
++
++      return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_LED_CONTROL, reg);
++}
++
++static int mv88e6xxx_port_led_read(struct mv88e6xxx_chip *chip, int port,
++                                 u16 ptr, u16 *val)
++{
++      int err;
++
++      err = mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_LED_CONTROL, ptr);
++      if (err)
++              return err;
++
++      err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_LED_CONTROL, val);
++      *val &= 0x3ff;
++
++      return err;
++}
++
++static int mv88e6xxx_led_brightness_set(struct mv88e6xxx_port *p, int led,
++                                      int brightness)
++{
++      u16 reg;
++      int err;
++
++      err = mv88e6xxx_port_led_read(p->chip, p->port,
++                                    MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL,
++                                    &reg);
++      if (err)
++              return err;
++
++      if (led == 1)
++              reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK;
++      else
++              reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK;
++
++      if (brightness) {
++              /* Selector 0x0f == Force LED ON */
++              if (led == 1)
++                      reg |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELF;
++              else
++                      reg |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELF;
++      } else {
++              /* Selector 0x0e == Force LED OFF */
++              if (led == 1)
++                      reg |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELE;
++              else
++                      reg |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELE;
++      }
++
++      reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL;
++
++      return mv88e6xxx_port_led_write(p->chip, p->port, reg);
++}
++
++static int mv88e6xxx_led0_brightness_set_blocking(struct led_classdev *ldev,
++                                                enum led_brightness brightness)
++{
++      struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
++      int err;
++
++      mv88e6xxx_reg_lock(p->chip);
++      err = mv88e6xxx_led_brightness_set(p, 0, brightness);
++      mv88e6xxx_reg_unlock(p->chip);
++
++      return err;
++}
++
++static int mv88e6xxx_led1_brightness_set_blocking(struct led_classdev *ldev,
++                                                enum led_brightness brightness)
++{
++      struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
++      int err;
++
++      mv88e6xxx_reg_lock(p->chip);
++      err = mv88e6xxx_led_brightness_set(p, 1, brightness);
++      mv88e6xxx_reg_unlock(p->chip);
++
++      return err;
++}
++
++struct mv88e6xxx_led_hwconfig {
++      int led;
++      u8 portmask;
++      unsigned long rules;
++      bool fiber;
++      bool blink_activity;
++      u16 selector;
++};
++
++/* The following is a lookup table to check what rules we can support on a
++ * certain LED given restrictions such as that some rules only work with fiber
++ * (SFP) connections and some blink on activity by default.
++ */
++#define MV88E6XXX_PORTS_0_3 (BIT(0) | BIT(1) | BIT(2) | BIT(3))
++#define MV88E6XXX_PORTS_4_5 (BIT(4) | BIT(5))
++#define MV88E6XXX_PORT_4 BIT(4)
++#define MV88E6XXX_PORT_5 BIT(5)
++
++/* Entries are listed in selector order.
++ *
++ * These configurations vary across different switch families, list
++ * different tables per-family here.
++ */
++static const struct mv88e6xxx_led_hwconfig mv88e6352_led_hwconfigs[] = {
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORT_4,
++              .rules = BIT(TRIGGER_NETDEV_LINK),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL0,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORT_5,
++              .rules = BIT(TRIGGER_NETDEV_LINK_1000),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL0,
++      },
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL1,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_100),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL1,
++      },
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORTS_4_5,
++              .rules = BIT(TRIGGER_NETDEV_LINK_100),
++              .blink_activity = true,
++              .fiber = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL1,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORTS_4_5,
++              .rules = BIT(TRIGGER_NETDEV_LINK_1000),
++              .blink_activity = true,
++              .fiber = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL1,
++      },
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_1000),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL2,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_100),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL2,
++      },
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORTS_4_5,
++              .rules = BIT(TRIGGER_NETDEV_LINK_1000),
++              .blink_activity = true,
++              .fiber = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL2,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORTS_4_5,
++              .rules = BIT(TRIGGER_NETDEV_LINK_100),
++              .blink_activity = true,
++              .fiber = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL2,
++      },
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL3,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_1000),
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL3,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORTS_4_5,
++              .rules = BIT(TRIGGER_NETDEV_LINK),
++              .fiber = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL3,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORT_4,
++              .rules = BIT(TRIGGER_NETDEV_LINK),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL4,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORT_5,
++              .rules = BIT(TRIGGER_NETDEV_LINK),
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL5,
++      },
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL6,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_1000),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL6,
++      },
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORT_4,
++              .rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL6,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORT_5,
++              .rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL6,
++      },
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_1000),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL7,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_1000),
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL7,
++      },
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK),
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL8,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL8,
++      },
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORT_5,
++              .rules = BIT(TRIGGER_NETDEV_LINK),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL8,
++      },
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_10),
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL9,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_100),
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL9,
++      },
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_10),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SELA,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_100),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SELA,
++      },
++      {
++              .led = 0,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000),
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SELB,
++      },
++      {
++              .led = 1,
++              .portmask = MV88E6XXX_PORTS_0_3,
++              .rules = BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000),
++              .blink_activity = true,
++              .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SELB,
++      },
++};
++
++/* mv88e6xxx_led_match_selector() - look up the appropriate LED mode selector
++ * @p: port state container
++ * @led: LED number, 0 or 1
++ * @blink_activity: blink the LED (usually blink on indicated activity)
++ * @fiber: the link is connected to fiber such as SFP
++ * @rules: LED status flags from the LED classdev core
++ * @selector: fill in the selector in this parameter with an OR operation
++ */
++static int mv88e6xxx_led_match_selector(struct mv88e6xxx_port *p, int led, bool blink_activity,
++                                      bool fiber, unsigned long rules, u16 *selector)
++{
++      const struct mv88e6xxx_led_hwconfig *conf;
++      int i;
++
++      /* No rules means we turn the LED off */
++      if (!rules) {
++              if (led == 1)
++                      *selector |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELE;
++              else
++                      *selector |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELE;
++              return 0;
++      }
++
++      /* TODO: these rules are for MV88E6352, when adding other families,
++       * think about making sure you select the table that match the
++       * specific switch family.
++       */
++      for (i = 0; i < ARRAY_SIZE(mv88e6352_led_hwconfigs); i++) {
++              conf = &mv88e6352_led_hwconfigs[i];
++
++              if (conf->led != led)
++                      continue;
++
++              if (!(conf->portmask & BIT(p->port)))
++                      continue;
++
++              if (conf->blink_activity != blink_activity)
++                      continue;
++
++              if (conf->fiber != fiber)
++                      continue;
++
++              if (conf->rules == rules) {
++                      dev_dbg(p->chip->dev, "port%d LED %d set selector %04x for rules %08lx\n",
++                              p->port, led, conf->selector, rules);
++                      *selector |= conf->selector;
++                      return 0;
++              }
++      }
++
++      return -EOPNOTSUPP;
++}
++
++/* mv88e6xxx_led_match_selector() - find Linux netdev rules from a selector value
++ * @p: port state container
++ * @selector: the selector value from the LED actity register
++ * @led: LED number, 0 or 1
++ * @rules: Linux netdev activity rules found from selector
++ */
++static int
++mv88e6xxx_led_match_rule(struct mv88e6xxx_port *p, u16 selector, int led, unsigned long *rules)
++{
++      const struct mv88e6xxx_led_hwconfig *conf;
++      int i;
++
++      /* Find the selector in the table, we just look for the right selector
++       * and ignore if the activity has special properties such as blinking
++       * or is fiber-only.
++       */
++      for (i = 0; i < ARRAY_SIZE(mv88e6352_led_hwconfigs); i++) {
++              conf = &mv88e6352_led_hwconfigs[i];
++
++              if (conf->led != led)
++                      continue;
++
++              if (!(conf->portmask & BIT(p->port)))
++                      continue;
++
++              if (conf->selector == selector) {
++                      dev_dbg(p->chip->dev, "port%d LED %d has selector %04x, rules %08lx\n",
++                              p->port, led, selector, conf->rules);
++                      *rules = conf->rules;
++                      return 0;
++              }
++      }
++
++      return -EINVAL;
++}
++
++/* mv88e6xxx_led_get_selector() - get the appropriate LED mode selector
++ * @p: port state container
++ * @led: LED number, 0 or 1
++ * @fiber: the link is connected to fiber such as SFP
++ * @rules: LED status flags from the LED classdev core
++ * @selector: fill in the selector in this parameter with an OR operation
++ */
++static int mv88e6xxx_led_get_selector(struct mv88e6xxx_port *p, int led,
++                                    bool fiber, unsigned long rules, u16 *selector)
++{
++      int err;
++
++      /* What happens here is that we first try to locate a trigger with solid
++       * indicator (such as LED is on for a 1000 link) else we try a second
++       * sweep to find something suitable with a trigger that will blink on
++       * activity.
++       */
++      err = mv88e6xxx_led_match_selector(p, led, false, fiber, rules, selector);
++      if (err)
++              return mv88e6xxx_led_match_selector(p, led, true, fiber, rules, selector);
++
++      return 0;
++}
++
++/* Sets up the hardware blinking period */
++static int mv88e6xxx_led_set_blinking_period(struct mv88e6xxx_port *p, int led,
++                                           unsigned long delay_on, unsigned long delay_off)
++{
++      unsigned long period;
++      u16 reg;
++
++      period = delay_on + delay_off;
++
++      reg = 0;
++
++      switch (period) {
++      case 21:
++              reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_21MS;
++              break;
++      case 42:
++              reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_42MS;
++              break;
++      case 84:
++              reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_84MS;
++              break;
++      case 168:
++              reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_168MS;
++              break;
++      case 336:
++              reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_336MS;
++              break;
++      case 672:
++              reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_672MS;
++              break;
++      default:
++              /* Fall back to software blinking */
++              return -EINVAL;
++      }
++
++      /* This is essentially PWM duty cycle: how long time of the period
++       * will the LED be on. Zero isn't great in most cases.
++       */
++      switch (delay_on) {
++      case 0:
++              /* This is usually pretty useless and will make the LED look OFF */
++              reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_NONE;
++              break;
++      case 21:
++              reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_21MS;
++              break;
++      case 42:
++              reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_42MS;
++              break;
++      case 84:
++              reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_84MS;
++              break;
++      case 168:
++              reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_168MS;
++              break;
++      default:
++              /* Just use something non-zero */
++              reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_21MS;
++              break;
++      }
++
++      /* Set up blink rate */
++      reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_STRETCH_BLINK;
++
++      return mv88e6xxx_port_led_write(p->chip, p->port, reg);
++}
++
++static int mv88e6xxx_led_blink_set(struct mv88e6xxx_port *p, int led,
++                                 unsigned long *delay_on, unsigned long *delay_off)
++{
++      u16 reg;
++      int err;
++
++      /* Choose a sensible default 336 ms (~3 Hz) */
++      if ((*delay_on == 0) && (*delay_off == 0)) {
++              *delay_on = 168;
++              *delay_off = 168;
++      }
++
++      /* No off delay is just on */
++      if (*delay_off == 0)
++              return mv88e6xxx_led_brightness_set(p, led, 1);
++
++      err = mv88e6xxx_led_set_blinking_period(p, led, *delay_on, *delay_off);
++      if (err)
++              return err;
++
++      err = mv88e6xxx_port_led_read(p->chip, p->port,
++                                    MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL,
++                                    &reg);
++      if (err)
++              return err;
++
++      if (led == 1)
++              reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK;
++      else
++              reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK;
++
++      /* This will select the forced blinking status */
++      if (led == 1)
++              reg |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELD;
++      else
++              reg |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELD;
++
++      reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL;
++
++      return mv88e6xxx_port_led_write(p->chip, p->port, reg);
++}
++
++static int mv88e6xxx_led0_blink_set(struct led_classdev *ldev,
++                                  unsigned long *delay_on,
++                                  unsigned long *delay_off)
++{
++      struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
++      int err;
++
++      mv88e6xxx_reg_lock(p->chip);
++      err = mv88e6xxx_led_blink_set(p, 0, delay_on, delay_off);
++      mv88e6xxx_reg_unlock(p->chip);
++
++      return err;
++}
++
++static int mv88e6xxx_led1_blink_set(struct led_classdev *ldev,
++                                  unsigned long *delay_on,
++                                  unsigned long *delay_off)
++{
++      struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
++      int err;
++
++      mv88e6xxx_reg_lock(p->chip);
++      err = mv88e6xxx_led_blink_set(p, 1, delay_on, delay_off);
++      mv88e6xxx_reg_unlock(p->chip);
++
++      return err;
++}
++
++static int
++mv88e6xxx_led0_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules)
++{
++      struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
++      u16 selector = 0;
++
++      return mv88e6xxx_led_get_selector(p, 0, p->fiber, rules, &selector);
++}
++
++static int
++mv88e6xxx_led1_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules)
++{
++      struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
++      u16 selector = 0;
++
++      return mv88e6xxx_led_get_selector(p, 1, p->fiber, rules, &selector);
++}
++
++static int mv88e6xxx_led_hw_control_set(struct mv88e6xxx_port *p,
++                                      int led, unsigned long rules)
++{
++      u16 reg;
++      int err;
++
++      err = mv88e6xxx_port_led_read(p->chip, p->port,
++                                    MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL,
++                                    &reg);
++      if (err)
++              return err;
++
++      if (led == 1)
++              reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK;
++      else
++              reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK;
++
++      err = mv88e6xxx_led_get_selector(p, led, p->fiber, rules, &reg);
++      if (err)
++              return err;
++
++      reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL;
++
++      if (led == 0)
++              dev_dbg(p->chip->dev, "LED 0 hw control on port %d trigger selector 0x%02x\n",
++                      p->port,
++                      (unsigned int)(reg & MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK));
++      else
++              dev_dbg(p->chip->dev, "LED 1 hw control on port %d trigger selector 0x%02x\n",
++                      p->port,
++                      (unsigned int)(reg & MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK) >> 4);
++
++      return mv88e6xxx_port_led_write(p->chip, p->port, reg);
++}
++
++static int
++mv88e6xxx_led_hw_control_get(struct mv88e6xxx_port *p, int led, unsigned long *rules)
++{
++      u16 val;
++      int err;
++
++      mv88e6xxx_reg_lock(p->chip);
++      err = mv88e6xxx_port_led_read(p->chip, p->port,
++                                    MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL, &val);
++      mv88e6xxx_reg_unlock(p->chip);
++      if (err)
++              return err;
++
++      /* Mask out the selector bits for this port */
++      if (led == 1) {
++              val &= MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK;
++              /* It's forced blinking/OFF/ON */
++              if (val == MV88E6XXX_PORT_LED_CONTROL_LED1_SELD ||
++                  val == MV88E6XXX_PORT_LED_CONTROL_LED1_SELE ||
++                  val == MV88E6XXX_PORT_LED_CONTROL_LED1_SELF) {
++                      *rules = 0;
++                      return 0;
++              }
++      } else {
++              val &= MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK;
++              /* It's forced blinking/OFF/ON */
++              if (val == MV88E6XXX_PORT_LED_CONTROL_LED0_SELD ||
++                  val == MV88E6XXX_PORT_LED_CONTROL_LED0_SELE ||
++                  val == MV88E6XXX_PORT_LED_CONTROL_LED0_SELF) {
++                      *rules = 0;
++                      return 0;
++              }
++      }
++
++      err = mv88e6xxx_led_match_rule(p, val, led, rules);
++      if (!err)
++              return 0;
++
++      dev_dbg(p->chip->dev, "couldn't find matching selector for %04x\n", val);
++      *rules = 0;
++      return 0;
++}
++
++static int
++mv88e6xxx_led0_hw_control_set(struct led_classdev *ldev, unsigned long rules)
++{
++      struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
++      int err;
++
++      mv88e6xxx_reg_lock(p->chip);
++      err = mv88e6xxx_led_hw_control_set(p, 0, rules);
++      mv88e6xxx_reg_unlock(p->chip);
++
++      return err;
++}
++
++static int
++mv88e6xxx_led1_hw_control_set(struct led_classdev *ldev, unsigned long rules)
++{
++      struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
++      int err;
++
++      mv88e6xxx_reg_lock(p->chip);
++      err = mv88e6xxx_led_hw_control_set(p, 1, rules);
++      mv88e6xxx_reg_unlock(p->chip);
++
++      return err;
++}
++
++static int
++mv88e6xxx_led0_hw_control_get(struct led_classdev *ldev, unsigned long *rules)
++{
++      struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
++
++      return mv88e6xxx_led_hw_control_get(p, 0, rules);
++}
++
++static int
++mv88e6xxx_led1_hw_control_get(struct led_classdev *ldev, unsigned long *rules)
++{
++      struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
++
++      return mv88e6xxx_led_hw_control_get(p, 1, rules);
++}
++
++static struct device *mv88e6xxx_led_hw_control_get_device(struct mv88e6xxx_port *p)
++{
++      struct dsa_port *dp;
++
++      dp = dsa_to_port(p->chip->ds, p->port);
++      if (!dp)
++              return NULL;
++      if (dp->user)
++              return &dp->user->dev;
++      return NULL;
++}
++
++static struct device *
++mv88e6xxx_led0_hw_control_get_device(struct led_classdev *ldev)
++{
++      struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
++
++      return mv88e6xxx_led_hw_control_get_device(p);
++}
++
++static struct device *
++mv88e6xxx_led1_hw_control_get_device(struct led_classdev *ldev)
++{
++      struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
++
++      return mv88e6xxx_led_hw_control_get_device(p);
++}
++
++int mv88e6xxx_port_setup_leds(struct mv88e6xxx_chip *chip, int port)
++{
++      struct fwnode_handle *led = NULL, *leds = NULL;
++      struct led_init_data init_data = { };
++      enum led_default_state state;
++      struct mv88e6xxx_port *p;
++      struct led_classdev *l;
++      struct device *dev;
++      u32 led_num;
++      int ret;
++
++      /* LEDs are on ports 1,2,3,4, 5 and 6 (index 0..5), no more */
++      if (port > 5)
++              return -EOPNOTSUPP;
++
++      p = &chip->ports[port];
++      if (!p->fwnode)
++              return 0;
++
++      dev = chip->dev;
++
++      leds = fwnode_get_named_child_node(p->fwnode, "leds");
++      if (!leds) {
++              dev_dbg(dev, "No Leds node specified in device tree for port %d!\n",
++                      port);
++              return 0;
++      }
++
++      fwnode_for_each_child_node(leds, led) {
++              /* Reg represent the led number of the port, max 2
++               * LEDs can be connected to each port, in some designs
++               * only one LED is connected.
++               */
++              if (fwnode_property_read_u32(led, "reg", &led_num))
++                      continue;
++              if (led_num > 1) {
++                      dev_err(dev, "invalid LED specified port %d\n", port);
++                      return -EINVAL;
++              }
++
++              if (led_num == 0)
++                      l = &p->led0;
++              else
++                      l = &p->led1;
++
++              state = led_init_default_state_get(led);
++              switch (state) {
++              case LEDS_DEFSTATE_ON:
++                      l->brightness = 1;
++                      mv88e6xxx_led_brightness_set(p, led_num, 1);
++                      break;
++              case LEDS_DEFSTATE_KEEP:
++                      break;
++              default:
++                      l->brightness = 0;
++                      mv88e6xxx_led_brightness_set(p, led_num, 0);
++              }
++
++              l->max_brightness = 1;
++              if (led_num == 0) {
++                      l->brightness_set_blocking = mv88e6xxx_led0_brightness_set_blocking;
++                      l->blink_set = mv88e6xxx_led0_blink_set;
++                      l->hw_control_is_supported = mv88e6xxx_led0_hw_control_is_supported;
++                      l->hw_control_set = mv88e6xxx_led0_hw_control_set;
++                      l->hw_control_get = mv88e6xxx_led0_hw_control_get;
++                      l->hw_control_get_device = mv88e6xxx_led0_hw_control_get_device;
++              } else {
++                      l->brightness_set_blocking = mv88e6xxx_led1_brightness_set_blocking;
++                      l->blink_set = mv88e6xxx_led1_blink_set;
++                      l->hw_control_is_supported = mv88e6xxx_led1_hw_control_is_supported;
++                      l->hw_control_set = mv88e6xxx_led1_hw_control_set;
++                      l->hw_control_get = mv88e6xxx_led1_hw_control_get;
++                      l->hw_control_get_device = mv88e6xxx_led1_hw_control_get_device;
++              }
++              l->hw_control_trigger = "netdev";
++
++              init_data.default_label = ":port";
++              init_data.fwnode = led;
++              init_data.devname_mandatory = true;
++              init_data.devicename = kasprintf(GFP_KERNEL, "%s:0%d:0%d", chip->info->name,
++                                               port, led_num);
++              if (!init_data.devicename)
++                      return -ENOMEM;
++
++              ret = devm_led_classdev_register_ext(dev, l, &init_data);
++              kfree(init_data.devicename);
++
++              if (ret) {
++                      dev_err(dev, "Failed to init LED %d for port %d", led_num, port);
++                      return ret;
++              }
++      }
++
++      return 0;
++}
+--- a/drivers/net/dsa/mv88e6xxx/port.c
++++ b/drivers/net/dsa/mv88e6xxx/port.c
+@@ -12,6 +12,7 @@
+ #include <linux/if_bridge.h>
+ #include <linux/phy.h>
+ #include <linux/phylink.h>
++#include <linux/property.h>
+ #include "chip.h"
+ #include "global2.h"
+--- a/drivers/net/dsa/mv88e6xxx/port.h
++++ b/drivers/net/dsa/mv88e6xxx/port.h
+@@ -309,6 +309,130 @@
+ /* Offset 0x13: OutFiltered Counter */
+ #define MV88E6XXX_PORT_OUT_FILTERED   0x13
++/* Offset 0x16: LED Control */
++#define MV88E6XXX_PORT_LED_CONTROL                            0x16
++#define MV88E6XXX_PORT_LED_CONTROL_UPDATE                     BIT(15)
++#define MV88E6XXX_PORT_LED_CONTROL_POINTER_MASK                       GENMASK(14, 12)
++#define MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL         (0x00 << 12) /* Control for LED 0 and 1 */
++#define MV88E6XXX_PORT_LED_CONTROL_POINTER_STRETCH_BLINK      (0x06 << 12) /* Stetch and Blink Rate */
++#define MV88E6XXX_PORT_LED_CONTROL_POINTER_CNTL_SPECIAL               (0x07 << 12) /* Control for the Port's Special LED */
++#define MV88E6XXX_PORT_LED_CONTROL_DATA_MASK                  GENMASK(10, 0)
++/* Selection masks valid for either port 1,2,3,4 or 5 */
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK              GENMASK(3, 0)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK              GENMASK(7, 4)
++/* Selection control for LED 0 and 1, ports 5 and 6 only has LED 0
++ * Bits  Function
++ * 0..3  LED 0 control selector on ports 1-5
++ * 4..7  LED 1 control selector on ports 1-4 on port 5 this controls LED 0 of port 6
++ *
++ * Sel Port LED Function for the 6352 family:
++ * 0   1-4  0   Link/Act/Speed by Blink Rate (off=no link, on=link, blink=activity, blink speed=link speed)
++ *     1-4  1   Port 2's Special LED
++ *     5-6  0   Port 5 Link/Act (off=no link, on=link, blink=activity)
++ *     5-6  1   Port 6 Link/Act (off=no link, on=link 1000, blink=activity)
++ * 1   1-4  0   100/1000 Link/Act (off=no link, on=100 or 1000 link, blink=activity)
++ *     1-4  1   10/100 Link Act (off=no link, on=10 or 100 link, blink=activity)
++ *     5-6  0   Fiber 100 Link/Act (off=no link, on=link 100, blink=activity)
++ *     5-6  1   Fiber 1000 Link/Act (off=no link, on=link 1000, blink=activity)
++ * 2   1-4  0   1000 Link/Act (off=no link, on=link 1000, blink=activity)
++ *     1-4  1   10/100 Link/Act (off=no link, on=10 or 100 link, blink=activity)
++ *     5-6  0   Fiber 1000 Link/Act (off=no link, on=link 1000, blink=activity)
++ *     5-6  1   Fiber 100 Link/Act (off=no link, on=link 100, blink=activity)
++ * 3   1-4  0   Link/Act (off=no link, on=link, blink=activity)
++ *     1-4  1   1000 Link (off=no link, on=1000 link)
++ *     5-6  0   Port 0's Special LED
++ *     5-6  1   Fiber Link (off=no link, on=link)
++ * 4   1-4  0   Port 0's Special LED
++ *     1-4  1   Port 1's Special LED
++ *     5-6  0   Port 1's Special LED
++ *     5-6  1   Port 5 Link/Act (off=no link, on=link, blink=activity)
++ * 5   1-4  0   Reserved
++ *     1-4  1   Reserved
++ *     5-6  0   Port 2's Special LED
++ *     5-6  1   Port 6 Link (off=no link, on=link)
++ * 6   1-4  0   Duplex/Collision (off=half-duplex,on=full-duplex,blink=collision)
++ *     1-4  1   10/1000 Link/Act (off=no link, on=10 or 1000 link, blink=activity)
++ *     5-6  0   Port 5 Duplex/Collision (off=half-duplex, on=full-duplex, blink=col)
++ *     5-6  1   Port 6 Duplex/Collision (off=half-duplex, on=full-duplex, blink=col)
++ * 7   1-4  0   10/1000 Link/Act (off=no link, on=10 or 1000 link, blink=activity)
++ *     1-4  1   10/1000 Link (off=no link, on=10 or 1000 link)
++ *     5-6  0   Port 5 Link/Act/Speed by Blink rate (off=no link, on=link, blink=activity, blink speed=link speed)
++ *     5-6  1   Port 6 Link/Act/Speed by Blink rate (off=no link, on=link, blink=activity,  blink speed=link speed)
++ * 8   1-4  0   Link (off=no link, on=link)
++ *     1-4  1   Activity (off=no link, blink on=activity)
++ *     5-6  0   Port 6 Link/Act (off=no link, on=link, blink=activity)
++ *     5-6  1   Port 0's Special LED
++ * 9   1-4  0   10 Link (off=no link, on=10 link)
++ *     1-4  1   100 Link (off=no link, on=100 link)
++ *     5-6  0   Reserved
++ *     5-6  1   Port 1's Special LED
++ * a   1-4  0   10 Link/Act (off=no link, on=10 link, blink=activity)
++ *     1-4  1   100 Link/Act (off=no link, on=100 link, blink=activity)
++ *     5-6  0   Reserved
++ *     5-6  1   Port 2's Special LED
++ * b   1-4  0   100/1000 Link (off=no link, on=100 or 1000 link)
++ *     1-4  1   10/100 Link (off=no link, on=100 link, blink=activity)
++ *     5-6  0   Reserved
++ *     5-6  1   Reserved
++ * c     *  *   PTP Act (blink on=PTP activity)
++ * d     *  *   Force Blink
++ * e     *  *   Force Off
++ * f     *  *   Force On
++ */
++/* Select LED0 output */
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL0                  0x0
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL1                  0x1
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL2                  0x2
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL3                  0x3
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL4                  0x4
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL5                  0x5
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL6                  0x6
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL7                  0x7
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL8                  0x8
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL9                  0x9
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELA                  0xa
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELB                  0xb
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELC                  0xc
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELD                  0xd
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELE                  0xe
++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELF                  0xf
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL0                  (0x0 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL1                  (0x1 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL2                  (0x2 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL3                  (0x3 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL4                  (0x4 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL5                  (0x5 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL6                  (0x6 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL7                  (0x7 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL8                  (0x8 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL9                  (0x9 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELA                  (0xa << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELB                  (0xb << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELC                  (0xc << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELD                  (0xd << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELE                  (0xe << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELF                  (0xf << 4)
++/* Stretch and Blink Rate Control (Index 0x06 of LED Control) */
++/* Pulse Stretch Selection for all LED's on this port */
++#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_NONE    (0 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_21MS    (1 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_42MS    (2 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_84MS    (3 << 4)
++#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_168MS   (4 << 4)
++/* Blink Rate Selection for all LEDs on this port */
++#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_21MS               0
++#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_42MS               1
++#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_84MS               2
++#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_168MS      3
++#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_336MS      4
++#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_672MS      5
++ /* Control for Special LED (Index 0x7 of LED Control on Port0) */
++#define MV88E6XXX_PORT_LED_CONTROL_0x07_P0_LAN_LINKACT_SHIFT  0 /* bits 6:0 LAN Link Activity LED */
++/* Control for Special LED (Index 0x7 of LED Control on Port 1) */
++#define MV88E6XXX_PORT_LED_CONTROL_0x07_P1_WAN_LINKACT_SHIFT  0 /* bits 6:0 WAN Link Activity LED */
++/* Control for Special LED (Index 0x7 of LED Control on Port 2) */
++#define MV88E6XXX_PORT_LED_CONTROL_0x07_P2_PTP_ACT            0 /* bits 6:0 PTP Activity */
++
+ /* Offset 0x18: IEEE Priority Mapping Table */
+ #define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE                    0x18
+ #define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_UPDATE             0x8000
+@@ -457,6 +581,15 @@ int mv88e6393x_port_set_cmode(struct mv8
+                             phy_interface_t mode);
+ int mv88e6185_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode);
+ int mv88e6352_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode);
++#ifdef CONFIG_NET_DSA_MV88E6XXX_LEDS
++int mv88e6xxx_port_setup_leds(struct mv88e6xxx_chip *chip, int port);
++#else
++static inline int mv88e6xxx_port_setup_leds(struct mv88e6xxx_chip *chip,
++                                          int port)
++{
++      return 0;
++}
++#endif
+ int mv88e6xxx_port_drop_untagged(struct mv88e6xxx_chip *chip, int port,
+                                bool drop_untagged);
+ int mv88e6xxx_port_set_map_da(struct mv88e6xxx_chip *chip, int port, bool map);
diff --git a/target/linux/generic/backport-6.12/741-v6.13-net-dsa-mv88e6xxx-fix-unreleased-fwnode_handle-in-se.patch b/target/linux/generic/backport-6.12/741-v6.13-net-dsa-mv88e6xxx-fix-unreleased-fwnode_handle-in-se.patch
new file mode 100644 (file)
index 0000000..2c81983
--- /dev/null
@@ -0,0 +1,31 @@
+From b8ee7a11c75436b85fa1641aa5f970de0f8a575c Mon Sep 17 00:00:00 2001
+From: Javier Carrasco <javier.carrasco.cruz@gmail.com>
+Date: Sat, 19 Oct 2024 22:16:49 +0200
+Subject: net: dsa: mv88e6xxx: fix unreleased fwnode_handle in setup_port()
+
+'ports_fwnode' is initialized via device_get_named_child_node(), which
+requires a call to fwnode_handle_put() when the variable is no longer
+required to avoid leaking memory.
+
+Add the missing fwnode_handle_put() after 'ports_fwnode' has been used
+and is no longer required.
+
+Fixes: 94a2a84f5e9e ("net: dsa: mv88e6xxx: Support LED control")
+Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/mv88e6xxx/chip.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -3441,6 +3441,7 @@ static int mv88e6xxx_setup_port(struct m
+                               break;
+                       }
+               }
++              fwnode_handle_put(ports_fwnode);
+       } else {
+               dev_dbg(chip->dev, "no ethernet ports node defined for the device\n");
+       }
diff --git a/target/linux/generic/backport-6.12/742-v6.17-net-dsa-mv88e6xxx-Fix-fwnode-reference-leaks-in-mv88.patch b/target/linux/generic/backport-6.12/742-v6.17-net-dsa-mv88e6xxx-Fix-fwnode-reference-leaks-in-mv88.patch
new file mode 100644 (file)
index 0000000..6e0fbb8
--- /dev/null
@@ -0,0 +1,68 @@
+From f63e7c8a83892781f6ceb55566f9497639c44555 Mon Sep 17 00:00:00 2001
+From: Miaoqian Lin <linmq006@gmail.com>
+Date: Mon, 1 Sep 2025 15:32:23 +0800
+Subject: net: dsa: mv88e6xxx: Fix fwnode reference leaks in
+ mv88e6xxx_port_setup_leds
+
+Fix multiple fwnode reference leaks:
+
+1. The function calls fwnode_get_named_child_node() to get the "leds" node,
+   but never calls fwnode_handle_put(leds) to release this reference.
+
+2. Within the fwnode_for_each_child_node() loop, the early return
+   paths that don't properly release the "led" fwnode reference.
+
+This fix follows the same pattern as commit d029edefed39
+("net dsa: qca8k: fix usages of device_get_named_child_node()")
+
+Fixes: 94a2a84f5e9e ("net: dsa: mv88e6xxx: Support LED control")
+Cc: stable@vger.kernel.org
+Signed-off-by: Miaoqian Lin <linmq006@gmail.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Link: https://patch.msgid.link/20250901073224.2273103-1-linmq006@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mv88e6xxx/leds.c | 17 +++++++++++++----
+ 1 file changed, 13 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/dsa/mv88e6xxx/leds.c
++++ b/drivers/net/dsa/mv88e6xxx/leds.c
+@@ -779,7 +779,8 @@ int mv88e6xxx_port_setup_leds(struct mv8
+                       continue;
+               if (led_num > 1) {
+                       dev_err(dev, "invalid LED specified port %d\n", port);
+-                      return -EINVAL;
++                      ret = -EINVAL;
++                      goto err_put_led;
+               }
+               if (led_num == 0)
+@@ -823,17 +824,25 @@ int mv88e6xxx_port_setup_leds(struct mv8
+               init_data.devname_mandatory = true;
+               init_data.devicename = kasprintf(GFP_KERNEL, "%s:0%d:0%d", chip->info->name,
+                                                port, led_num);
+-              if (!init_data.devicename)
+-                      return -ENOMEM;
++              if (!init_data.devicename) {
++                      ret = -ENOMEM;
++                      goto err_put_led;
++              }
+               ret = devm_led_classdev_register_ext(dev, l, &init_data);
+               kfree(init_data.devicename);
+               if (ret) {
+                       dev_err(dev, "Failed to init LED %d for port %d", led_num, port);
+-                      return ret;
++                      goto err_put_led;
+               }
+       }
++      fwnode_handle_put(leds);
+       return 0;
++
++err_put_led:
++      fwnode_handle_put(led);
++      fwnode_handle_put(leds);
++      return ret;
+ }
diff --git a/target/linux/generic/backport-6.12/750-v7.0-net-phy-move-mmd_phy_read-and-mmd_phy_write-to-phyli.patch b/target/linux/generic/backport-6.12/750-v7.0-net-phy-move-mmd_phy_read-and-mmd_phy_write-to-phyli.patch
new file mode 100644 (file)
index 0000000..50c9155
--- /dev/null
@@ -0,0 +1,65 @@
+From 65de36f5eae1c540115bf8f705f853718e9b6451 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 5 Jan 2026 16:38:29 +0000
+Subject: [PATCH 3/5] net: phy: move mmd_phy_read and mmd_phy_write to phylib.h
+
+Helper functions mmd_phy_read and mmd_phy_write are useful for PHYs
+which require custom MMD access functions for some but not all MMDs.
+Move mmd_phy_read and mmd_phy_write function prototypes from
+phylib-internal.h to phylib.h to make them available for PHY drivers.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/79169cd624a3572d426e42c7b13cd2654a35d0cb.1767630451.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+
+Note that this patch replaces the commit above as neither phylink.h nor
+phylink-internal.h exist in Linux 6.12.
+--- a/drivers/net/phy/phy-core.c
++++ b/drivers/net/phy/phy-core.c
+@@ -542,8 +542,8 @@ 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)
++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);
+@@ -552,9 +552,10 @@ static int mmd_phy_read(struct mii_bus *
+       /* Read the content of the MMD's selected register */
+       return __mdiobus_read(bus, phy_addr, MII_MMD_DATA);
+ }
++EXPORT_SYMBOL_GPL(mmd_phy_read);
+-static int mmd_phy_write(struct mii_bus *bus, int phy_addr, bool is_c45,
+-                       int devad, u32 regnum, u16 val)
++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);
+@@ -563,6 +564,7 @@ static int mmd_phy_write(struct mii_bus
+       /* Write the data into MMD's selected register */
+       return __mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);
+ }
++EXPORT_SYMBOL_GPL(mmd_phy_write);
+ /**
+  * __phy_read_mmd - Convenience function for reading a register
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -2032,6 +2032,11 @@ extern struct phy_driver genphy_c45_driv
+ /* The gen10g_* functions are the old Clause 45 stub */
+ int gen10g_config_aneg(struct phy_device *phydev);
++int mmd_phy_read(struct mii_bus *bus, int phy_addr, bool is_c45,
++               int devad, u32 regnum);
++int mmd_phy_write(struct mii_bus *bus, int phy_addr, bool is_c45,
++                int devad, u32 regnum, u16 val);
++
+ static inline int phy_read_status(struct phy_device *phydev)
+ {
+       if (!phydev->drv)
diff --git a/target/linux/generic/backport-6.12/760-v6.18-net-phy-introduce-phy_id_compare_model-PHY-ID-helper.patch b/target/linux/generic/backport-6.12/760-v6.18-net-phy-introduce-phy_id_compare_model-PHY-ID-helper.patch
new file mode 100644 (file)
index 0000000..a367f66
--- /dev/null
@@ -0,0 +1,39 @@
+From ae1c658b33d4bec20c037aebba583a68375d4773 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 11 Sep 2025 15:08:31 +0200
+Subject: [PATCH] net: phy: introduce phy_id_compare_model() PHY ID helper
+
+Similar to phy_id_compare_vendor(), introduce the equivalent
+phy_id_compare_model() helper for the generic PHY ID Model mask.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Link: https://patch.msgid.link/20250911130840.23569-1-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ include/linux/phy.h | 13 +++++++++++++
+ 1 file changed, 13 insertions(+)
+
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1284,6 +1284,19 @@ static inline bool phy_id_compare(u32 id
+ }
+ /**
++ * phy_id_compare_model - compare @id with @model mask
++ * @id: PHY ID
++ * @model_mask: PHY Model mask
++ *
++ * Return: true if the bits from @id match @model using the
++ *       generic PHY Model mask.
++ */
++static inline bool phy_id_compare_model(u32 id, u32 model_mask)
++{
++      return phy_id_compare(id, model_mask, PHY_ID_MATCH_MODEL_MASK);
++}
++
++/**
+  * phydev_id_compare - compare @id with the PHY's Clause 22 ID
+  * @phydev: the PHY device
+  * @id: the PHY ID to be matched
diff --git a/target/linux/generic/backport-6.12/761-v6.19-net-phy-mxl-gpy-add-support-for-mxl86252-and-mxl86282.patch b/target/linux/generic/backport-6.12/761-v6.19-net-phy-mxl-gpy-add-support-for-mxl86252-and-mxl86282.patch
new file mode 100644 (file)
index 0000000..87b3e36
--- /dev/null
@@ -0,0 +1,163 @@
+From de1e5c9333f426348571f7a3b034f99490d3f926 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sat, 22 Nov 2025 13:33:47 +0000
+Subject: [PATCH] net: phy: mxl-gpy: add support for MxL86252 and MxL86282
+
+Add PHY driver support for Maxlinear MxL86252 and MxL86282 switches.
+The PHYs built-into those switches are just like any other GPY 2.5G PHYs
+with the exception of the temperature sensor data being encoded in a
+different way.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/a6cd7fe461b011cec2b59dffaf34e9c8b0819059.1763818120.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/mxl-gpy.c | 91 ++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 89 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/mxl-gpy.c
++++ b/drivers/net/phy/mxl-gpy.c
+@@ -31,6 +31,8 @@
+ #define PHY_ID_GPY241BM               0x67C9DE80
+ #define PHY_ID_GPY245B                0x67C9DEC0
+ #define PHY_ID_MXL86211C      0xC1335400
++#define PHY_ID_MXL86252               0xC1335520
++#define PHY_ID_MXL86282               0xC1335500
+ #define PHY_CTL1              0x13
+ #define PHY_CTL1_MDICD                BIT(3)
+@@ -200,6 +202,29 @@ static int gpy_hwmon_read(struct device
+       return 0;
+ }
++static int mxl862x2_hwmon_read(struct device *dev,
++                             enum hwmon_sensor_types type,
++                             u32 attr, int channel, long *value)
++{
++      struct phy_device *phydev = dev_get_drvdata(dev);
++      long tmp;
++      int ret;
++
++      ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_TEMP_STA);
++      if (ret < 0)
++              return ret;
++      if (!ret)
++              return -ENODATA;
++
++      tmp = (s16)ret;
++      tmp *= 78125;
++      tmp /= 10000;
++
++      *value = tmp;
++
++      return 0;
++}
++
+ static umode_t gpy_hwmon_is_visible(const void *data,
+                                   enum hwmon_sensor_types type,
+                                   u32 attr, int channel)
+@@ -217,14 +242,25 @@ static const struct hwmon_ops gpy_hwmon_
+       .read           = gpy_hwmon_read,
+ };
++static const struct hwmon_ops mxl862x2_hwmon_hwmon_ops = {
++      .is_visible     = gpy_hwmon_is_visible,
++      .read           = mxl862x2_hwmon_read,
++};
++
+ static const struct hwmon_chip_info gpy_hwmon_chip_info = {
+       .ops            = &gpy_hwmon_hwmon_ops,
+       .info           = gpy_hwmon_info,
+ };
++static const struct hwmon_chip_info mxl862x2_hwmon_chip_info = {
++      .ops            = &mxl862x2_hwmon_hwmon_ops,
++      .info           = gpy_hwmon_info,
++};
++
+ static int gpy_hwmon_register(struct phy_device *phydev)
+ {
+       struct device *dev = &phydev->mdio.dev;
++      const struct hwmon_chip_info *info;
+       struct device *hwmon_dev;
+       char *hwmon_name;
+@@ -232,10 +268,15 @@ static int gpy_hwmon_register(struct phy
+       if (IS_ERR(hwmon_name))
+               return PTR_ERR(hwmon_name);
++      if (phy_id_compare_model(phydev->phy_id, PHY_ID_MXL86252) ||
++          phy_id_compare_model(phydev->phy_id, PHY_ID_MXL86282))
++              info = &mxl862x2_hwmon_chip_info;
++      else
++              info = &gpy_hwmon_chip_info;
++
+       hwmon_dev = devm_hwmon_device_register_with_info(dev, hwmon_name,
+                                                        phydev,
+-                                                       &gpy_hwmon_chip_info,
+-                                                       NULL);
++                                                       info, NULL);
+       return PTR_ERR_OR_ZERO(hwmon_dev);
+ }
+@@ -1331,6 +1372,50 @@ static struct phy_driver gpy_drivers[] =
+               .led_hw_control_set = gpy_led_hw_control_set,
+               .led_polarity_set = gpy_led_polarity_set,
+       },
++      {
++              PHY_ID_MATCH_MODEL(PHY_ID_MXL86252),
++              .name           = "MaxLinear Ethernet MxL86252",
++              .get_features   = genphy_c45_pma_read_abilities,
++              .config_init    = gpy_config_init,
++              .probe          = gpy_probe,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++              .config_aneg    = gpy_config_aneg,
++              .aneg_done      = genphy_c45_aneg_done,
++              .read_status    = gpy_read_status,
++              .config_intr    = gpy_config_intr,
++              .handle_interrupt = gpy_handle_interrupt,
++              .set_wol        = gpy_set_wol,
++              .get_wol        = gpy_get_wol,
++              .set_loopback   = gpy_loopback,
++              .led_brightness_set = gpy_led_brightness_set,
++              .led_hw_is_supported = gpy_led_hw_is_supported,
++              .led_hw_control_get = gpy_led_hw_control_get,
++              .led_hw_control_set = gpy_led_hw_control_set,
++              .led_polarity_set = gpy_led_polarity_set,
++      },
++      {
++              PHY_ID_MATCH_MODEL(PHY_ID_MXL86282),
++              .name           = "MaxLinear Ethernet MxL86282",
++              .get_features   = genphy_c45_pma_read_abilities,
++              .config_init    = gpy_config_init,
++              .probe          = gpy_probe,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++              .config_aneg    = gpy_config_aneg,
++              .aneg_done      = genphy_c45_aneg_done,
++              .read_status    = gpy_read_status,
++              .config_intr    = gpy_config_intr,
++              .handle_interrupt = gpy_handle_interrupt,
++              .set_wol        = gpy_set_wol,
++              .get_wol        = gpy_get_wol,
++              .set_loopback   = gpy_loopback,
++              .led_brightness_set = gpy_led_brightness_set,
++              .led_hw_is_supported = gpy_led_hw_is_supported,
++              .led_hw_control_get = gpy_led_hw_control_get,
++              .led_hw_control_set = gpy_led_hw_control_set,
++              .led_polarity_set = gpy_led_polarity_set,
++      },
+ };
+ module_phy_driver(gpy_drivers);
+@@ -1348,6 +1433,8 @@ static const struct mdio_device_id __may
+       {PHY_ID_MATCH_MODEL(PHY_ID_GPY241BM)},
+       {PHY_ID_MATCH_MODEL(PHY_ID_GPY245B)},
+       {PHY_ID_MATCH_MODEL(PHY_ID_MXL86211C)},
++      {PHY_ID_MATCH_MODEL(PHY_ID_MXL86252)},
++      {PHY_ID_MATCH_MODEL(PHY_ID_MXL86282)},
+       { }
+ };
+ MODULE_DEVICE_TABLE(mdio, gpy_tbl);
diff --git a/target/linux/generic/backport-6.12/762-v7.0-net-dsa-add-tag-format-for-MxL862xx-switches.patch b/target/linux/generic/backport-6.12/762-v7.0-net-dsa-add-tag-format-for-MxL862xx-switches.patch
new file mode 100644 (file)
index 0000000..94fb211
--- /dev/null
@@ -0,0 +1,191 @@
+From 1ecc2ebd1298d5c0eaa238e71b7d2109d7d77538 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sat, 7 Feb 2026 03:07:11 +0000
+Subject: [PATCH 01/35] net: dsa: add tag format for MxL862xx switches
+
+Add proprietary special tag format for the MaxLinear MXL862xx family of
+switches. While using the same Ethertype as MaxLinear's GSW1xx switches,
+the actual tag format differs significantly, hence we need a dedicated
+tag driver for that.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/c64e6ddb6c93a4fac39f9ab9b2d8bf551a2b118d.1770433307.git.daniel@makrotopia.org
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ include/net/dsa.h      |   2 +
+ net/dsa/Kconfig        |   7 +++
+ net/dsa/Makefile       |   1 +
+ net/dsa/tag_mxl862xx.c | 110 +++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 120 insertions(+)
+ create mode 100644 net/dsa/tag_mxl862xx.c
+
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -55,6 +55,7 @@ struct tc_action;
+ #define DSA_TAG_PROTO_LAN937X_VALUE           27
+ #define DSA_TAG_PROTO_VSC73XX_8021Q_VALUE     28
+ #define DSA_TAG_PROTO_BRCM_LEGACY_FCS_VALUE   29
++#define DSA_TAG_PROTO_MXL862_VALUE            30
+ enum dsa_tag_protocol {
+       DSA_TAG_PROTO_NONE              = DSA_TAG_PROTO_NONE_VALUE,
+@@ -87,6 +88,7 @@ enum dsa_tag_protocol {
+       DSA_TAG_PROTO_RZN1_A5PSW        = DSA_TAG_PROTO_RZN1_A5PSW_VALUE,
+       DSA_TAG_PROTO_LAN937X           = DSA_TAG_PROTO_LAN937X_VALUE,
+       DSA_TAG_PROTO_VSC73XX_8021Q     = DSA_TAG_PROTO_VSC73XX_8021Q_VALUE,
++      DSA_TAG_PROTO_MXL862            = DSA_TAG_PROTO_MXL862_VALUE,
+ };
+ struct dsa_switch;
+--- a/net/dsa/Kconfig
++++ b/net/dsa/Kconfig
+@@ -104,6 +104,13 @@ config NET_DSA_TAG_MTK
+         Say Y or M if you want to enable support for tagging frames for
+         Mediatek switches.
++config NET_DSA_TAG_MXL_862XX
++      tristate "Tag driver for MaxLinear MxL862xx switches"
++      help
++        Say Y or M if you want to enable support for tagging frames for the
++        MaxLinear MxL86252 and MxL86282 switches using their native 8-byte
++        tagging protocol.
++
+ config NET_DSA_TAG_KSZ
+       tristate "Tag driver for Microchip 8795/937x/9477/9893 families of switches"
+       help
+--- a/net/dsa/Makefile
++++ b/net/dsa/Makefile
+@@ -28,6 +28,7 @@ obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += t
+ obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o
+ obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
+ obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o
++obj-$(CONFIG_NET_DSA_TAG_MXL_862XX) += tag_mxl862xx.o
+ obj-$(CONFIG_NET_DSA_TAG_NONE) += tag_none.o
+ obj-$(CONFIG_NET_DSA_TAG_OCELOT) += tag_ocelot.o
+ obj-$(CONFIG_NET_DSA_TAG_OCELOT_8021Q) += tag_ocelot_8021q.o
+--- /dev/null
++++ b/net/dsa/tag_mxl862xx.c
+@@ -0,0 +1,110 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * DSA Special Tag for MaxLinear 862xx switch chips
++ *
++ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
++ * Copyright (C) 2024 MaxLinear Inc.
++ */
++
++#include <linux/bitops.h>
++#include <linux/etherdevice.h>
++#include <linux/skbuff.h>
++#include <net/dsa.h>
++#include "tag.h"
++
++#define MXL862_NAME   "mxl862xx"
++
++#define MXL862_HEADER_LEN     8
++
++/* Word 0 -> EtherType */
++
++/* Word 2 */
++#define MXL862_SUBIF_ID               GENMASK(4, 0)
++
++/* Word 3 */
++#define MXL862_IGP_EGP                GENMASK(3, 0)
++
++static struct sk_buff *mxl862_tag_xmit(struct sk_buff *skb,
++                                     struct net_device *dev)
++{
++      struct dsa_port *dp = dsa_user_to_port(dev);
++      struct dsa_port *cpu_dp = dp->cpu_dp;
++      unsigned int cpu_port, sub_interface;
++      __be16 *mxl862_tag;
++
++      cpu_port = cpu_dp->index;
++
++      /* target port sub-interface ID relative to the CPU port */
++      sub_interface = dp->index + 16 - cpu_port;
++
++      /* provide additional space 'MXL862_HEADER_LEN' bytes */
++      skb_push(skb, MXL862_HEADER_LEN);
++
++      /* shift MAC address to the beginning of the enlarged buffer,
++       * releasing the space required for DSA tag (between MAC address and
++       * Ethertype)
++       */
++      dsa_alloc_etype_header(skb, MXL862_HEADER_LEN);
++
++      /* special tag ingress (from the perspective of the switch) */
++      mxl862_tag = dsa_etype_header_pos_tx(skb);
++      mxl862_tag[0] = htons(ETH_P_MXLGSW);
++      mxl862_tag[1] = 0;
++      mxl862_tag[2] = htons(FIELD_PREP(MXL862_SUBIF_ID, sub_interface));
++      mxl862_tag[3] = htons(FIELD_PREP(MXL862_IGP_EGP, cpu_port));
++
++      return skb;
++}
++
++static struct sk_buff *mxl862_tag_rcv(struct sk_buff *skb,
++                                    struct net_device *dev)
++{
++      __be16 *mxl862_tag;
++      int port;
++
++      if (unlikely(!pskb_may_pull(skb, MXL862_HEADER_LEN))) {
++              dev_warn_ratelimited(&dev->dev, "Cannot pull SKB, packet dropped\n");
++              return NULL;
++      }
++
++      mxl862_tag = dsa_etype_header_pos_rx(skb);
++
++      if (unlikely(mxl862_tag[0] != htons(ETH_P_MXLGSW))) {
++              dev_warn_ratelimited(&dev->dev,
++                                   "Invalid special tag marker, packet dropped, tag: %8ph\n",
++                                   mxl862_tag);
++              return NULL;
++      }
++
++      /* Get source port information */
++      port = FIELD_GET(MXL862_IGP_EGP, ntohs(mxl862_tag[3]));
++      skb->dev = dsa_conduit_find_user(dev, 0, port);
++      if (unlikely(!skb->dev)) {
++              dev_warn_ratelimited(&dev->dev,
++                                   "Invalid source port, packet dropped, tag: %8ph\n",
++                                   mxl862_tag);
++              return NULL;
++      }
++
++      /* remove the MxL862xx special tag between the MAC addresses and the
++       * current ethertype field.
++       */
++      skb_pull_rcsum(skb, MXL862_HEADER_LEN);
++      dsa_strip_etype_header(skb, MXL862_HEADER_LEN);
++
++      return skb;
++}
++
++static const struct dsa_device_ops mxl862_netdev_ops = {
++      .name = MXL862_NAME,
++      .proto = DSA_TAG_PROTO_MXL862,
++      .xmit = mxl862_tag_xmit,
++      .rcv = mxl862_tag_rcv,
++      .needed_headroom = MXL862_HEADER_LEN,
++};
++
++MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_MXL862, MXL862_NAME);
++MODULE_DESCRIPTION("DSA tag driver for MaxLinear MxL862xx switches");
++MODULE_LICENSE("GPL");
++
++module_dsa_tag_driver(mxl862_netdev_ops);
+--- a/include/uapi/linux/if_ether.h
++++ b/include/uapi/linux/if_ether.h
+@@ -92,6 +92,9 @@
+ #define ETH_P_ETHERCAT        0x88A4          /* EtherCAT                     */
+ #define ETH_P_8021AD  0x88A8          /* 802.1ad Service VLAN         */
+ #define ETH_P_802_EX1 0x88B5          /* 802.1 Local Experimental 1.  */
++#define ETH_P_MXLGSW  0x88C3          /* Infineon Technologies Corporate Research ST
++                                       * Used by MaxLinear GSW DSA
++                                       */
+ #define ETH_P_PREAUTH 0x88C7          /* 802.11 Preauthentication */
+ #define ETH_P_TIPC    0x88CA          /* TIPC                         */
+ #define ETH_P_LLDP    0x88CC          /* Link Layer Discovery Protocol */
diff --git a/target/linux/generic/backport-6.12/763-v7.0-net-mdio-add-unlocked-mdiodev-C45-bus-accessors.patch b/target/linux/generic/backport-6.12/763-v7.0-net-mdio-add-unlocked-mdiodev-C45-bus-accessors.patch
new file mode 100644 (file)
index 0000000..e05dd1f
--- /dev/null
@@ -0,0 +1,41 @@
+From 1111454d5a637e039a46b867088b524c73159da4 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sat, 7 Feb 2026 03:07:18 +0000
+Subject: [PATCH 02/35] net: mdio: add unlocked mdiodev C45 bus accessors
+
+Add helper inline functions __mdiodev_c45_read() and
+__mdiodev_c45_write(), which are the C45 equivalents of the existing
+__mdiodev_read() and __mdiodev_write() added by commit e6a45700e7e1
+("net: mdio: add unlocked mdiobus and mdiodev bus accessors")
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/8d1d55949a75a871d2a3b90e421de4bd58d77685.1770433307.git.daniel@makrotopia.org
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ include/linux/mdio.h | 13 +++++++++++++
+ 1 file changed, 13 insertions(+)
+
+--- a/include/linux/mdio.h
++++ b/include/linux/mdio.h
+@@ -668,6 +668,19 @@ static inline int mdiodev_modify_changed
+                                     mask, set);
+ }
++static inline int __mdiodev_c45_read(struct mdio_device *mdiodev, int devad,
++                                   u16 regnum)
++{
++      return __mdiobus_c45_read(mdiodev->bus, mdiodev->addr, devad, regnum);
++}
++
++static inline int __mdiodev_c45_write(struct mdio_device *mdiodev, u32 devad,
++                                    u16 regnum, u16 val)
++{
++      return __mdiobus_c45_write(mdiodev->bus, mdiodev->addr, devad, regnum,
++                                 val);
++}
++
+ static inline int mdiodev_c45_modify(struct mdio_device *mdiodev, int devad,
+                                    u32 regnum, u16 mask, u16 set)
+ {
diff --git a/target/linux/generic/backport-6.12/764-v7.0-net-dsa-add-basic-initial-driver-for-MxL862xx-switch.patch b/target/linux/generic/backport-6.12/764-v7.0-net-dsa-add-basic-initial-driver-for-MxL862xx-switch.patch
new file mode 100644 (file)
index 0000000..247c698
--- /dev/null
@@ -0,0 +1,1583 @@
+From c764b51397f5f5919b07fdfbb9b70082059e1c16 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sat, 7 Feb 2026 03:07:27 +0000
+Subject: [PATCH 03/35] net: dsa: add basic initial driver for MxL862xx
+ switches
+
+Add very basic DSA driver for MaxLinear's MxL862xx switches.
+
+In contrast to previous MaxLinear switches the MxL862xx has a built-in
+processor that runs a sophisticated firmware based on Zephyr RTOS.
+Interaction between the host and the switch hence is organized using a
+software API of that firmware rather than accessing hardware registers
+directly.
+
+Add descriptions of the most basic firmware API calls to access the
+built-in MDIO bus hosting the 2.5GE PHYs, basic port control as well as
+setting up the CPU port.
+
+Implement a very basic DSA driver using that API which is sufficient to
+get packets flowing between the user ports and the CPU port.
+
+The firmware offers all features one would expect from a modern switch
+hardware, they are going to be added one by one in follow-up patch
+series.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/ccde07e8cf33d8ae243000013b57cfaa2695e0a9.1770433307.git.daniel@makrotopia.org
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/dsa/Kconfig                  |   2 +
+ drivers/net/dsa/Makefile                 |   1 +
+ drivers/net/dsa/mxl862xx/Kconfig         |  12 +
+ drivers/net/dsa/mxl862xx/Makefile        |   3 +
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h  | 675 +++++++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h  |  49 ++
+ drivers/net/dsa/mxl862xx/mxl862xx-host.c | 245 ++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-host.h |  12 +
+ drivers/net/dsa/mxl862xx/mxl862xx.c      | 476 ++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx.h      |  16 +
+ 10 files changed, 1491 insertions(+)
+ create mode 100644 drivers/net/dsa/mxl862xx/Kconfig
+ create mode 100644 drivers/net/dsa/mxl862xx/Makefile
+ create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-api.h
+ create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+ create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-host.c
+ create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-host.h
+ create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx.c
+ create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx.h
+
+--- a/drivers/net/dsa/Kconfig
++++ b/drivers/net/dsa/Kconfig
+@@ -79,6 +79,8 @@ source "drivers/net/dsa/microchip/Kconfi
+ source "drivers/net/dsa/mv88e6xxx/Kconfig"
++source "drivers/net/dsa/mxl862xx/Kconfig"
++
+ source "drivers/net/dsa/ocelot/Kconfig"
+ source "drivers/net/dsa/qca/Kconfig"
+--- a/drivers/net/dsa/Makefile
++++ b/drivers/net/dsa/Makefile
+@@ -21,6 +21,7 @@ obj-y                                += b53/
+ obj-y                         += hirschmann/
+ obj-y                         += microchip/
+ obj-y                         += mv88e6xxx/
++obj-y                         += mxl862xx/
+ obj-y                         += ocelot/
+ obj-y                         += qca/
+ obj-y                         += realtek/
+--- /dev/null
++++ b/drivers/net/dsa/mxl862xx/Kconfig
+@@ -0,0 +1,12 @@
++# SPDX-License-Identifier: GPL-2.0-only
++config NET_DSA_MXL862
++      tristate "MaxLinear MxL862xx"
++      depends on NET_DSA
++      select MAXLINEAR_GPHY
++      select NET_DSA_TAG_MXL_862XX
++      help
++        This enables support for the MaxLinear MxL862xx switch family.
++        These switches have two 10GE SerDes interfaces, one typically
++        used as CPU port.
++         - MxL86282 has eight 2.5 Gigabit PHYs
++         - MxL86252 has five 2.5 Gigabit PHYs
+--- /dev/null
++++ b/drivers/net/dsa/mxl862xx/Makefile
+@@ -0,0 +1,3 @@
++# SPDX-License-Identifier: GPL-2.0
++obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o
++mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o
+--- /dev/null
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+@@ -0,0 +1,675 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++
++#ifndef __MXL862XX_API_H
++#define __MXL862XX_API_H
++
++#include <linux/if_ether.h>
++
++/**
++ * struct mdio_relay_data - relayed access to the switch internal MDIO bus
++ * @data: data to be read or written
++ * @phy: PHY index
++ * @mmd: MMD device
++ * @reg: register index
++ */
++struct mdio_relay_data {
++      __le16 data;
++      u8 phy;
++      u8 mmd;
++      __le16 reg;
++} __packed;
++
++/**
++ * struct mxl862xx_register_mod - Register access parameter to directly
++ *                                modify internal registers
++ * @addr: Register address offset for modification
++ * @data: Value to write to the register address
++ * @mask: Mask of bits to be modified (1 to modify, 0 to ignore)
++ *
++ * Used for direct register modification operations.
++ */
++struct mxl862xx_register_mod {
++      __le16 addr;
++      __le16 data;
++      __le16 mask;
++} __packed;
++
++/**
++ * enum mxl862xx_mac_clear_type - MAC table clear type
++ * @MXL862XX_MAC_CLEAR_PHY_PORT: clear dynamic entries based on port_id
++ * @MXL862XX_MAC_CLEAR_DYNAMIC: clear all dynamic entries
++ */
++enum mxl862xx_mac_clear_type {
++      MXL862XX_MAC_CLEAR_PHY_PORT = 0,
++      MXL862XX_MAC_CLEAR_DYNAMIC,
++};
++
++/**
++ * struct mxl862xx_mac_table_clear - MAC table clear
++ * @type: see &enum mxl862xx_mac_clear_type
++ * @port_id: physical port id
++ */
++struct mxl862xx_mac_table_clear {
++      u8 type;
++      u8 port_id;
++} __packed;
++
++/**
++ * enum mxl862xx_age_timer - Aging Timer Value.
++ * @MXL862XX_AGETIMER_1_SEC: 1 second aging time
++ * @MXL862XX_AGETIMER_10_SEC: 10 seconds aging time
++ * @MXL862XX_AGETIMER_300_SEC: 300 seconds aging time
++ * @MXL862XX_AGETIMER_1_HOUR: 1 hour aging time
++ * @MXL862XX_AGETIMER_1_DAY: 24 hours aging time
++ * @MXL862XX_AGETIMER_CUSTOM: Custom aging time in seconds
++ */
++enum mxl862xx_age_timer {
++      MXL862XX_AGETIMER_1_SEC = 1,
++      MXL862XX_AGETIMER_10_SEC,
++      MXL862XX_AGETIMER_300_SEC,
++      MXL862XX_AGETIMER_1_HOUR,
++      MXL862XX_AGETIMER_1_DAY,
++      MXL862XX_AGETIMER_CUSTOM,
++};
++
++/**
++ * struct mxl862xx_bridge_alloc - Bridge Allocation
++ * @bridge_id: If the bridge allocation is successful, a valid ID will be
++ *             returned in this field. Otherwise, INVALID_HANDLE is
++ *             returned. For bridge free, this field should contain a
++ *             valid ID returned by the bridge allocation. ID 0 is not
++ *             used for historic reasons.
++ *
++ * Used by MXL862XX_BRIDGE_ALLOC and MXL862XX_BRIDGE_FREE.
++ */
++struct mxl862xx_bridge_alloc {
++      __le16 bridge_id;
++};
++
++/**
++ * enum mxl862xx_bridge_config_mask - Bridge configuration mask
++ * @MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNING_LIMIT:
++ *     Mask for mac_learning_limit_enable and mac_learning_limit.
++ * @MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNED_COUNT:
++ *     Mask for mac_learning_count
++ * @MXL862XX_BRIDGE_CONFIG_MASK_MAC_DISCARD_COUNT:
++ *     Mask for learning_discard_event
++ * @MXL862XX_BRIDGE_CONFIG_MASK_SUB_METER:
++ *     Mask for sub_metering_enable and traffic_sub_meter_id
++ * @MXL862XX_BRIDGE_CONFIG_MASK_FORWARDING_MODE:
++ *     Mask for forward_broadcast, forward_unknown_multicast_ip,
++ *     forward_unknown_multicast_non_ip and forward_unknown_unicast.
++ * @MXL862XX_BRIDGE_CONFIG_MASK_ALL: Enable all
++ * @MXL862XX_BRIDGE_CONFIG_MASK_FORCE: Bypass any check for debug purpose
++ */
++enum mxl862xx_bridge_config_mask {
++      MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNING_LIMIT = BIT(0),
++      MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNED_COUNT = BIT(1),
++      MXL862XX_BRIDGE_CONFIG_MASK_MAC_DISCARD_COUNT = BIT(2),
++      MXL862XX_BRIDGE_CONFIG_MASK_SUB_METER = BIT(3),
++      MXL862XX_BRIDGE_CONFIG_MASK_FORWARDING_MODE = BIT(4),
++      MXL862XX_BRIDGE_CONFIG_MASK_ALL = 0x7FFFFFFF,
++      MXL862XX_BRIDGE_CONFIG_MASK_FORCE = BIT(31)
++};
++
++/**
++ * enum mxl862xx_bridge_port_egress_meter - Meters for egress traffic type
++ * @MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST:
++ *     Index of broadcast traffic meter
++ * @MXL862XX_BRIDGE_PORT_EGRESS_METER_MULTICAST:
++ *     Index of known multicast traffic meter
++ * @MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP:
++ *     Index of unknown multicast IP traffic meter
++ * @MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP:
++ *     Index of unknown multicast non-IP traffic meter
++ * @MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC:
++ *     Index of unknown unicast traffic meter
++ * @MXL862XX_BRIDGE_PORT_EGRESS_METER_OTHERS:
++ *     Index of traffic meter for other types
++ * @MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX: Number of index
++ */
++enum mxl862xx_bridge_port_egress_meter {
++      MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST = 0,
++      MXL862XX_BRIDGE_PORT_EGRESS_METER_MULTICAST,
++      MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP,
++      MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP,
++      MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC,
++      MXL862XX_BRIDGE_PORT_EGRESS_METER_OTHERS,
++      MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX,
++};
++
++/**
++ * enum mxl862xx_bridge_forward_mode - Bridge forwarding type of packet
++ * @MXL862XX_BRIDGE_FORWARD_FLOOD: Packet is flooded to port members of
++ *                                 ingress bridge port
++ * @MXL862XX_BRIDGE_FORWARD_DISCARD: Packet is discarded
++ */
++enum mxl862xx_bridge_forward_mode {
++      MXL862XX_BRIDGE_FORWARD_FLOOD = 0,
++      MXL862XX_BRIDGE_FORWARD_DISCARD,
++};
++
++/**
++ * struct mxl862xx_bridge_config - Bridge Configuration
++ * @bridge_id: Bridge ID (FID)
++ * @mask: See &enum mxl862xx_bridge_config_mask
++ * @mac_learning_limit_enable: Enable MAC learning limitation
++ * @mac_learning_limit: Max number of MAC addresses that can be learned in
++ *                      this bridge (all bridge ports)
++ * @mac_learning_count: Number of MAC addresses learned from this bridge
++ * @learning_discard_event: Number of learning discard events due to
++ *                          hardware resource not available
++ * @sub_metering_enable: Traffic metering on type of traffic (such as
++ *                       broadcast, multicast, unknown unicast, etc) applies
++ * @traffic_sub_meter_id: Meter for bridge process with specific type (such
++ *                        as broadcast, multicast, unknown unicast, etc)
++ * @forward_broadcast: Forwarding mode of broadcast traffic. See
++ *                     &enum mxl862xx_bridge_forward_mode
++ * @forward_unknown_multicast_ip: Forwarding mode of unknown multicast IP
++ *                                traffic.
++ *                                See &enum mxl862xx_bridge_forward_mode
++ * @forward_unknown_multicast_non_ip: Forwarding mode of unknown multicast
++ *                                    non-IP traffic.
++ *                                    See &enum mxl862xx_bridge_forward_mode
++ * @forward_unknown_unicast: Forwarding mode of unknown unicast traffic. See
++ *                           &enum mxl862xx_bridge_forward_mode
++ */
++struct mxl862xx_bridge_config {
++      __le16 bridge_id;
++      __le32 mask; /* enum mxl862xx_bridge_config_mask */
++      u8 mac_learning_limit_enable;
++      __le16 mac_learning_limit;
++      __le16 mac_learning_count;
++      __le32 learning_discard_event;
++      u8 sub_metering_enable[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX];
++      __le16 traffic_sub_meter_id[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX];
++      __le32 forward_broadcast; /* enum mxl862xx_bridge_forward_mode */
++      __le32 forward_unknown_multicast_ip; /* enum mxl862xx_bridge_forward_mode */
++      __le32 forward_unknown_multicast_non_ip; /* enum mxl862xx_bridge_forward_mode */
++      __le32 forward_unknown_unicast; /* enum mxl862xx_bridge_forward_mode */
++} __packed;
++
++/**
++ * struct mxl862xx_bridge_port_alloc - Bridge Port Allocation
++ * @bridge_port_id: If the bridge port allocation is successful, a valid ID
++ *                  will be returned in this field. Otherwise, INVALID_HANDLE
++ *                  is returned. For bridge port free, this field should
++ *                  contain a valid ID returned by the bridge port allocation.
++ *
++ * Used by MXL862XX_BRIDGE_PORT_ALLOC and MXL862XX_BRIDGE_PORT_FREE.
++ */
++struct mxl862xx_bridge_port_alloc {
++      __le16 bridge_port_id;
++};
++
++/**
++ * enum mxl862xx_bridge_port_config_mask - Bridge Port configuration mask
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID:
++ *     Mask for bridge_id
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN:
++ *     Mask for ingress_extended_vlan_enable,
++ *     ingress_extended_vlan_block_id and ingress_extended_vlan_block_size
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN:
++ *     Mask for egress_extended_vlan_enable, egress_extended_vlan_block_id
++ *     and egress_extended_vlan_block_size
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_MARKING:
++ *     Mask for ingress_marking_mode
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_REMARKING:
++ *     Mask for egress_remarking_mode
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_METER:
++ *     Mask for ingress_metering_enable and ingress_traffic_meter_id
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER:
++ *     Mask for egress_sub_metering_enable and egress_traffic_sub_meter_id
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING:
++ *     Mask for dest_logical_port_id, pmapper_enable, dest_sub_if_id_group,
++ *     pmapper_mapping_mode, pmapper_id_valid and pmapper
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP:
++ *     Mask for bridge_port_map
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_IP_LOOKUP:
++ *     Mask for mc_dest_ip_lookup_disable
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_IP_LOOKUP:
++ *     Mask for mc_src_ip_lookup_enable
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_MAC_LOOKUP:
++ *     Mask for dest_mac_lookup_disable
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING:
++ *     Mask for src_mac_learning_disable
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_SPOOFING:
++ *     Mask for mac_spoofing_detect_enable
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_PORT_LOCK:
++ *     Mask for port_lock_enable
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNING_LIMIT:
++ *     Mask for mac_learning_limit_enable and mac_learning_limit
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNED_COUNT:
++ *     Mask for mac_learning_count
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN_FILTER:
++ *     Mask for ingress_vlan_filter_enable, ingress_vlan_filter_block_id
++ *     and ingress_vlan_filter_block_size
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER1:
++ *     Mask for bypass_egress_vlan_filter1, egress_vlan_filter1enable,
++ *     egress_vlan_filter1block_id and egress_vlan_filter1block_size
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER2:
++ *     Mask for egress_vlan_filter2enable, egress_vlan_filter2block_id and
++ *     egress_vlan_filter2block_size
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING:
++ *     Mask for vlan_tag_selection, vlan_src_mac_priority_enable,
++ *     vlan_src_mac_dei_enable, vlan_src_mac_vid_enable,
++ *     vlan_dst_mac_priority_enable, vlan_dst_mac_dei_enable and
++ *     vlan_dst_mac_vid_enable
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MULTICAST_LOOKUP:
++ *     Mask for vlan_multicast_priority_enable,
++ *     vlan_multicast_dei_enable and vlan_multicast_vid_enable
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_LOOP_VIOLATION_COUNTER:
++ *     Mask for loop_violation_count
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_ALL: Enable all
++ * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_FORCE: Bypass any check for debug purpose
++ */
++enum mxl862xx_bridge_port_config_mask {
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID = BIT(0),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN = BIT(1),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN = BIT(2),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_MARKING = BIT(3),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_REMARKING = BIT(4),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_METER = BIT(5),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER = BIT(6),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING = BIT(7),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP = BIT(8),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_IP_LOOKUP = BIT(9),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_IP_LOOKUP = BIT(10),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_MAC_LOOKUP = BIT(11),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING = BIT(12),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_SPOOFING = BIT(13),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_PORT_LOCK = BIT(14),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNING_LIMIT = BIT(15),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNED_COUNT = BIT(16),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN_FILTER = BIT(17),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER1 = BIT(18),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER2 = BIT(19),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING = BIT(20),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MULTICAST_LOOKUP = BIT(21),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_LOOP_VIOLATION_COUNTER = BIT(22),
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_ALL = 0x7FFFFFFF,
++      MXL862XX_BRIDGE_PORT_CONFIG_MASK_FORCE = BIT(31)
++};
++
++/**
++ * enum mxl862xx_color_marking_mode - Color Marking Mode
++ * @MXL862XX_MARKING_ALL_GREEN: mark packets (except critical) to green
++ * @MXL862XX_MARKING_INTERNAL_MARKING: do not change color and priority
++ * @MXL862XX_MARKING_DEI: DEI mark mode
++ * @MXL862XX_MARKING_PCP_8P0D: PCP 8P0D mark mode
++ * @MXL862XX_MARKING_PCP_7P1D: PCP 7P1D mark mode
++ * @MXL862XX_MARKING_PCP_6P2D: PCP 6P2D mark mode
++ * @MXL862XX_MARKING_PCP_5P3D: PCP 5P3D mark mode
++ * @MXL862XX_MARKING_DSCP_AF: DSCP AF class
++ */
++enum mxl862xx_color_marking_mode {
++      MXL862XX_MARKING_ALL_GREEN = 0,
++      MXL862XX_MARKING_INTERNAL_MARKING,
++      MXL862XX_MARKING_DEI,
++      MXL862XX_MARKING_PCP_8P0D,
++      MXL862XX_MARKING_PCP_7P1D,
++      MXL862XX_MARKING_PCP_6P2D,
++      MXL862XX_MARKING_PCP_5P3D,
++      MXL862XX_MARKING_DSCP_AF,
++};
++
++/**
++ * enum mxl862xx_color_remarking_mode - Color Remarking Mode
++ * @MXL862XX_REMARKING_NONE: values from last process stage
++ * @MXL862XX_REMARKING_DEI: DEI mark mode
++ * @MXL862XX_REMARKING_PCP_8P0D: PCP 8P0D mark mode
++ * @MXL862XX_REMARKING_PCP_7P1D: PCP 7P1D mark mode
++ * @MXL862XX_REMARKING_PCP_6P2D: PCP 6P2D mark mode
++ * @MXL862XX_REMARKING_PCP_5P3D: PCP 5P3D mark mode
++ * @MXL862XX_REMARKING_DSCP_AF: DSCP AF class
++ */
++enum mxl862xx_color_remarking_mode {
++      MXL862XX_REMARKING_NONE = 0,
++      MXL862XX_REMARKING_DEI = 2,
++      MXL862XX_REMARKING_PCP_8P0D,
++      MXL862XX_REMARKING_PCP_7P1D,
++      MXL862XX_REMARKING_PCP_6P2D,
++      MXL862XX_REMARKING_PCP_5P3D,
++      MXL862XX_REMARKING_DSCP_AF,
++};
++
++/**
++ * enum mxl862xx_pmapper_mapping_mode - P-mapper Mapping Mode
++ * @MXL862XX_PMAPPER_MAPPING_PCP: Use PCP for VLAN tagged packets to derive
++ *                                sub interface ID group
++ * @MXL862XX_PMAPPER_MAPPING_LAG: Use LAG Index for Pmapper access
++ *                                regardless of IP and VLAN packet
++ * @MXL862XX_PMAPPER_MAPPING_DSCP: Use DSCP for VLAN tagged IP packets to
++ *                                 derive sub interface ID group
++ */
++enum mxl862xx_pmapper_mapping_mode {
++      MXL862XX_PMAPPER_MAPPING_PCP = 0,
++      MXL862XX_PMAPPER_MAPPING_LAG,
++      MXL862XX_PMAPPER_MAPPING_DSCP,
++};
++
++/**
++ * struct mxl862xx_pmapper - P-mapper Configuration
++ * @pmapper_id: Index of P-mapper (0-31)
++ * @dest_sub_if_id_group: Sub interface ID group. Entry 0 is for non-IP and
++ *                        non-VLAN tagged packets.
++ *                        Entries 1-8 are PCP mapping entries for VLAN tagged
++ *                        packets.
++ *                        Entries 9-72 are DSCP or LAG mapping entries.
++ *
++ * Used by CTP port config and bridge port config. In case of LAG, it is
++ * user's responsibility to provide the mapped entries in given P-mapper
++ * table. In other modes the entries are auto mapped from input packet.
++ */
++struct mxl862xx_pmapper {
++      __le16 pmapper_id;
++      u8 dest_sub_if_id_group[73];
++} __packed;
++
++/**
++ * struct mxl862xx_bridge_port_config - Bridge Port Configuration
++ * @bridge_port_id: Bridge Port ID allocated by bridge port allocation
++ * @mask: See &enum mxl862xx_bridge_port_config_mask
++ * @bridge_id: Bridge ID (FID) to which this bridge port is associated
++ * @ingress_extended_vlan_enable: Enable extended VLAN processing for
++ *                                ingress traffic
++ * @ingress_extended_vlan_block_id: Extended VLAN block allocated for
++ *                                  ingress traffic
++ * @ingress_extended_vlan_block_size: Extended VLAN block size for ingress
++ *                                    traffic
++ * @egress_extended_vlan_enable: Enable extended VLAN processing for egress
++ *                               traffic
++ * @egress_extended_vlan_block_id: Extended VLAN block allocated for egress
++ *                                 traffic
++ * @egress_extended_vlan_block_size: Extended VLAN block size for egress
++ *                                   traffic
++ * @ingress_marking_mode: Ingress color marking mode. See
++ *                        &enum mxl862xx_color_marking_mode
++ * @egress_remarking_mode: Color remarking for egress traffic. See
++ *                         &enum mxl862xx_color_remarking_mode
++ * @ingress_metering_enable: Traffic metering on ingress traffic applies
++ * @ingress_traffic_meter_id: Meter for ingress Bridge Port process
++ * @egress_sub_metering_enable: Traffic metering on various types of egress
++ *                              traffic
++ * @egress_traffic_sub_meter_id: Meter for egress Bridge Port process with
++ *                               specific type
++ * @dest_logical_port_id: Destination logical port
++ * @pmapper_enable: Enable P-mapper
++ * @dest_sub_if_id_group: Destination sub interface ID group when
++ *                        pmapper_enable is false
++ * @pmapper_mapping_mode: P-mapper mapping mode. See
++ *                        &enum mxl862xx_pmapper_mapping_mode
++ * @pmapper_id_valid: When true, P-mapper is re-used; when false,
++ *                    allocation is handled by API
++ * @pmapper: P-mapper configuration used when pmapper_enable is true
++ * @bridge_port_map: Port map defining broadcast domain. Each bit
++ *                   represents one bridge port. Bridge port ID is
++ *                   index * 16 + bit offset.
++ * @mc_dest_ip_lookup_disable: Disable multicast IP destination table
++ *                             lookup
++ * @mc_src_ip_lookup_enable: Enable multicast IP source table lookup
++ * @dest_mac_lookup_disable: Disable destination MAC lookup; packet treated
++ *                           as unknown
++ * @src_mac_learning_disable: Disable source MAC address learning
++ * @mac_spoofing_detect_enable: Enable MAC spoofing detection
++ * @port_lock_enable: Enable port locking
++ * @mac_learning_limit_enable: Enable MAC learning limitation
++ * @mac_learning_limit: Maximum number of MAC addresses that can be learned
++ *                      from this bridge port
++ * @loop_violation_count: Number of loop violation events from this bridge
++ *                        port
++ * @mac_learning_count: Number of MAC addresses learned from this bridge
++ *                      port
++ * @ingress_vlan_filter_enable: Enable ingress VLAN filter
++ * @ingress_vlan_filter_block_id: VLAN filter block of ingress traffic
++ * @ingress_vlan_filter_block_size: VLAN filter block size for ingress
++ *                                  traffic
++ * @bypass_egress_vlan_filter1: For ingress traffic, bypass VLAN filter 1
++ *                              at egress bridge port processing
++ * @egress_vlan_filter1enable: Enable egress VLAN filter 1
++ * @egress_vlan_filter1block_id: VLAN filter block 1 of egress traffic
++ * @egress_vlan_filter1block_size: VLAN filter block 1 size
++ * @egress_vlan_filter2enable: Enable egress VLAN filter 2
++ * @egress_vlan_filter2block_id: VLAN filter block 2 of egress traffic
++ * @egress_vlan_filter2block_size: VLAN filter block 2 size
++ * @vlan_tag_selection: VLAN tag selection for MAC address/multicast
++ *                      learning, lookup and filtering.
++ *                      0 - Intermediate outer VLAN tag is used.
++ *                      1 - Original outer VLAN tag is used.
++ * @vlan_src_mac_priority_enable: Enable VLAN Priority field for source MAC
++ *                                learning and filtering
++ * @vlan_src_mac_dei_enable: Enable VLAN DEI/CFI field for source MAC
++ *                           learning and filtering
++ * @vlan_src_mac_vid_enable: Enable VLAN ID field for source MAC learning
++ *                           and filtering
++ * @vlan_dst_mac_priority_enable: Enable VLAN Priority field for destination
++ *                                MAC lookup and filtering
++ * @vlan_dst_mac_dei_enable: Enable VLAN CFI/DEI field for destination MAC
++ *                           lookup and filtering
++ * @vlan_dst_mac_vid_enable: Enable VLAN ID field for destination MAC lookup
++ *                           and filtering
++ * @vlan_multicast_priority_enable: Enable VLAN Priority field for IP
++ *                                  multicast lookup
++ * @vlan_multicast_dei_enable: Enable VLAN CFI/DEI field for IP multicast
++ *                             lookup
++ * @vlan_multicast_vid_enable: Enable VLAN ID field for IP multicast lookup
++ */
++struct mxl862xx_bridge_port_config {
++      __le16 bridge_port_id;
++      __le32 mask; /* enum mxl862xx_bridge_port_config_mask  */
++      __le16 bridge_id;
++      u8 ingress_extended_vlan_enable;
++      __le16 ingress_extended_vlan_block_id;
++      __le16 ingress_extended_vlan_block_size;
++      u8 egress_extended_vlan_enable;
++      __le16 egress_extended_vlan_block_id;
++      __le16 egress_extended_vlan_block_size;
++      __le32 ingress_marking_mode; /* enum mxl862xx_color_marking_mode */
++      __le32 egress_remarking_mode; /* enum mxl862xx_color_remarking_mode */
++      u8 ingress_metering_enable;
++      __le16 ingress_traffic_meter_id;
++      u8 egress_sub_metering_enable[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX];
++      __le16 egress_traffic_sub_meter_id[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX];
++      u8 dest_logical_port_id;
++      u8 pmapper_enable;
++      __le16 dest_sub_if_id_group;
++      __le32 pmapper_mapping_mode; /* enum mxl862xx_pmapper_mapping_mode */
++      u8 pmapper_id_valid;
++      struct mxl862xx_pmapper pmapper;
++      __le16 bridge_port_map[8];
++      u8 mc_dest_ip_lookup_disable;
++      u8 mc_src_ip_lookup_enable;
++      u8 dest_mac_lookup_disable;
++      u8 src_mac_learning_disable;
++      u8 mac_spoofing_detect_enable;
++      u8 port_lock_enable;
++      u8 mac_learning_limit_enable;
++      __le16 mac_learning_limit;
++      __le16 loop_violation_count;
++      __le16 mac_learning_count;
++      u8 ingress_vlan_filter_enable;
++      __le16 ingress_vlan_filter_block_id;
++      __le16 ingress_vlan_filter_block_size;
++      u8 bypass_egress_vlan_filter1;
++      u8 egress_vlan_filter1enable;
++      __le16 egress_vlan_filter1block_id;
++      __le16 egress_vlan_filter1block_size;
++      u8 egress_vlan_filter2enable;
++      __le16 egress_vlan_filter2block_id;
++      __le16 egress_vlan_filter2block_size;
++      u8 vlan_tag_selection;
++      u8 vlan_src_mac_priority_enable;
++      u8 vlan_src_mac_dei_enable;
++      u8 vlan_src_mac_vid_enable;
++      u8 vlan_dst_mac_priority_enable;
++      u8 vlan_dst_mac_dei_enable;
++      u8 vlan_dst_mac_vid_enable;
++      u8 vlan_multicast_priority_enable;
++      u8 vlan_multicast_dei_enable;
++      u8 vlan_multicast_vid_enable;
++} __packed;
++
++/**
++ * struct mxl862xx_cfg -  Global Switch configuration Attributes
++ * @mac_table_age_timer: See &enum mxl862xx_age_timer
++ * @age_timer: Custom MAC table aging timer in seconds
++ * @max_packet_len: Maximum Ethernet packet length
++ * @learning_limit_action: Automatic MAC address table learning limitation
++ *                         consecutive action
++ * @mac_locking_action: Accept or discard MAC port locking violation
++ *                      packets
++ * @mac_spoofing_action: Accept or discard MAC spoofing and port MAC locking
++ *                       violation packets
++ * @pause_mac_mode_src: Pause frame MAC source address mode
++ * @pause_mac_src: Pause frame MAC source address
++ */
++struct mxl862xx_cfg {
++      __le32 mac_table_age_timer; /* enum mxl862xx_age_timer */
++      __le32 age_timer;
++      __le16 max_packet_len;
++      u8 learning_limit_action;
++      u8 mac_locking_action;
++      u8 mac_spoofing_action;
++      u8 pause_mac_mode_src;
++      u8 pause_mac_src[ETH_ALEN];
++} __packed;
++
++/**
++ * enum mxl862xx_ss_sp_tag_mask - Special tag valid field indicator bits
++ * @MXL862XX_SS_SP_TAG_MASK_RX: valid RX special tag mode
++ * @MXL862XX_SS_SP_TAG_MASK_TX: valid TX special tag mode
++ * @MXL862XX_SS_SP_TAG_MASK_RX_PEN: valid RX special tag info over preamble
++ * @MXL862XX_SS_SP_TAG_MASK_TX_PEN: valid TX special tag info over preamble
++ */
++enum mxl862xx_ss_sp_tag_mask {
++      MXL862XX_SS_SP_TAG_MASK_RX = BIT(0),
++      MXL862XX_SS_SP_TAG_MASK_TX = BIT(1),
++      MXL862XX_SS_SP_TAG_MASK_RX_PEN = BIT(2),
++      MXL862XX_SS_SP_TAG_MASK_TX_PEN = BIT(3),
++};
++
++/**
++ * enum mxl862xx_ss_sp_tag_rx - RX special tag mode
++ * @MXL862XX_SS_SP_TAG_RX_NO_TAG_NO_INSERT: packet does NOT have special
++ *                                          tag and special tag is NOT inserted
++ * @MXL862XX_SS_SP_TAG_RX_NO_TAG_INSERT: packet does NOT have special tag
++ *                                       and special tag is inserted
++ * @MXL862XX_SS_SP_TAG_RX_TAG_NO_INSERT: packet has special tag and special
++ *                                       tag is NOT inserted
++ */
++enum mxl862xx_ss_sp_tag_rx {
++      MXL862XX_SS_SP_TAG_RX_NO_TAG_NO_INSERT = 0,
++      MXL862XX_SS_SP_TAG_RX_NO_TAG_INSERT = 1,
++      MXL862XX_SS_SP_TAG_RX_TAG_NO_INSERT = 2,
++};
++
++/**
++ * enum mxl862xx_ss_sp_tag_tx - TX special tag mode
++ * @MXL862XX_SS_SP_TAG_TX_NO_TAG_NO_REMOVE: packet does NOT have special
++ *                                          tag and special tag is NOT removed
++ * @MXL862XX_SS_SP_TAG_TX_TAG_REPLACE: packet has special tag and special
++ *                                     tag is replaced
++ * @MXL862XX_SS_SP_TAG_TX_TAG_NO_REMOVE: packet has special tag and special
++ *                                       tag is NOT removed
++ * @MXL862XX_SS_SP_TAG_TX_TAG_REMOVE: packet has special tag and special
++ *                                    tag is removed
++ */
++enum mxl862xx_ss_sp_tag_tx {
++      MXL862XX_SS_SP_TAG_TX_NO_TAG_NO_REMOVE = 0,
++      MXL862XX_SS_SP_TAG_TX_TAG_REPLACE = 1,
++      MXL862XX_SS_SP_TAG_TX_TAG_NO_REMOVE = 2,
++      MXL862XX_SS_SP_TAG_TX_TAG_REMOVE = 3,
++};
++
++/**
++ * enum mxl862xx_ss_sp_tag_rx_pen - RX special tag info over preamble
++ * @MXL862XX_SS_SP_TAG_RX_PEN_ALL_0: special tag info inserted from byte 2
++ *                                   to 7 are all 0
++ * @MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_IS_16: special tag byte 5 is 16, other
++ *                                          bytes from 2 to 7 are 0
++ * @MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_FROM_PREAMBLE: special tag byte 5 is
++ *                                                  from preamble field, others
++ *                                                  are 0
++ * @MXL862XX_SS_SP_TAG_RX_PEN_BYTE_2_TO_7_FROM_PREAMBLE: special tag byte 2
++ *                                                       to 7 are from preamble
++ *                                                       field
++ */
++enum mxl862xx_ss_sp_tag_rx_pen {
++      MXL862XX_SS_SP_TAG_RX_PEN_ALL_0 = 0,
++      MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_IS_16 = 1,
++      MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_FROM_PREAMBLE = 2,
++      MXL862XX_SS_SP_TAG_RX_PEN_BYTE_2_TO_7_FROM_PREAMBLE = 3,
++};
++
++/**
++ * struct mxl862xx_ss_sp_tag - Special tag port settings
++ * @pid: port ID (1~16)
++ * @mask: See &enum mxl862xx_ss_sp_tag_mask
++ * @rx: See &enum mxl862xx_ss_sp_tag_rx
++ * @tx: See &enum mxl862xx_ss_sp_tag_tx
++ * @rx_pen: See &enum mxl862xx_ss_sp_tag_rx_pen
++ * @tx_pen: TX special tag info over preamble
++ *    0 - disabled
++ *    1 - enabled
++ */
++struct mxl862xx_ss_sp_tag {
++      u8 pid;
++      u8 mask; /* enum mxl862xx_ss_sp_tag_mask */
++      u8 rx; /* enum mxl862xx_ss_sp_tag_rx */
++      u8 tx; /* enum mxl862xx_ss_sp_tag_tx */
++      u8 rx_pen; /* enum mxl862xx_ss_sp_tag_rx_pen */
++      u8 tx_pen; /* boolean */
++} __packed;
++
++/**
++ * enum mxl862xx_logical_port_mode - Logical port mode
++ * @MXL862XX_LOGICAL_PORT_8BIT_WLAN: WLAN with 8-bit station ID
++ * @MXL862XX_LOGICAL_PORT_9BIT_WLAN: WLAN with 9-bit station ID
++ * @MXL862XX_LOGICAL_PORT_ETHERNET: Ethernet port
++ * @MXL862XX_LOGICAL_PORT_OTHER: Others
++ */
++enum mxl862xx_logical_port_mode {
++      MXL862XX_LOGICAL_PORT_8BIT_WLAN = 0,
++      MXL862XX_LOGICAL_PORT_9BIT_WLAN,
++      MXL862XX_LOGICAL_PORT_ETHERNET,
++      MXL862XX_LOGICAL_PORT_OTHER = 0xFF,
++};
++
++/**
++ * struct mxl862xx_ctp_port_assignment - CTP Port Assignment/association
++ *                                       with logical port
++ * @logical_port_id: Logical Port Id. The valid range is hardware dependent
++ * @first_ctp_port_id: First CTP (Connectivity Termination Port) ID mapped
++ *                     to above logical port ID
++ * @number_of_ctp_port: Total number of CTP Ports mapped above logical port
++ *                      ID
++ * @mode: Logical port mode to define sub interface ID format. See
++ *        &enum mxl862xx_logical_port_mode
++ * @bridge_port_id: Bridge Port ID (not FID). For allocation, each CTP
++ *                  allocated is mapped to the Bridge Port given by this field.
++ *                  The Bridge Port will be configured to use first CTP as
++ *                  egress CTP.
++ */
++struct mxl862xx_ctp_port_assignment {
++      u8 logical_port_id;
++      __le16 first_ctp_port_id;
++      __le16 number_of_ctp_port;
++      __le32 mode; /* enum mxl862xx_logical_port_mode */
++      __le16 bridge_port_id;
++} __packed;
++
++/**
++ * struct mxl862xx_sys_fw_image_version - Firmware version information
++ * @iv_major: firmware major version
++ * @iv_minor: firmware minor version
++ * @iv_revision: firmware revision
++ * @iv_build_num: firmware build number
++ */
++struct mxl862xx_sys_fw_image_version {
++      u8 iv_major;
++      u8 iv_minor;
++      __le16 iv_revision;
++      __le32 iv_build_num;
++} __packed;
++
++#endif /* __MXL862XX_API_H */
+--- /dev/null
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+@@ -0,0 +1,49 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++
++#ifndef __MXL862XX_CMD_H
++#define __MXL862XX_CMD_H
++
++#define MXL862XX_MMD_DEV              30
++#define MXL862XX_MMD_REG_CTRL         0
++#define MXL862XX_MMD_REG_LEN_RET      1
++#define MXL862XX_MMD_REG_DATA_FIRST   2
++#define MXL862XX_MMD_REG_DATA_LAST    95
++#define MXL862XX_MMD_REG_DATA_MAX_SIZE \
++      (MXL862XX_MMD_REG_DATA_LAST - MXL862XX_MMD_REG_DATA_FIRST + 1)
++
++#define MXL862XX_COMMON_MAGIC         0x100
++#define MXL862XX_BRDG_MAGIC           0x300
++#define MXL862XX_BRDGPORT_MAGIC               0x400
++#define MXL862XX_CTP_MAGIC            0x500
++#define MXL862XX_SWMAC_MAGIC          0xa00
++#define MXL862XX_SS_MAGIC             0x1600
++#define GPY_GPY2XX_MAGIC              0x1800
++#define SYS_MISC_MAGIC                        0x1900
++
++#define MXL862XX_COMMON_CFGGET                (MXL862XX_COMMON_MAGIC + 0x9)
++#define MXL862XX_COMMON_REGISTERMOD   (MXL862XX_COMMON_MAGIC + 0x11)
++
++#define MXL862XX_BRIDGE_ALLOC         (MXL862XX_BRDG_MAGIC + 0x1)
++#define MXL862XX_BRIDGE_CONFIGSET     (MXL862XX_BRDG_MAGIC + 0x2)
++#define MXL862XX_BRIDGE_CONFIGGET     (MXL862XX_BRDG_MAGIC + 0x3)
++#define MXL862XX_BRIDGE_FREE          (MXL862XX_BRDG_MAGIC + 0x4)
++
++#define MXL862XX_BRIDGEPORT_ALLOC     (MXL862XX_BRDGPORT_MAGIC + 0x1)
++#define MXL862XX_BRIDGEPORT_CONFIGSET (MXL862XX_BRDGPORT_MAGIC + 0x2)
++#define MXL862XX_BRIDGEPORT_CONFIGGET (MXL862XX_BRDGPORT_MAGIC + 0x3)
++#define MXL862XX_BRIDGEPORT_FREE      (MXL862XX_BRDGPORT_MAGIC + 0x4)
++
++#define MXL862XX_CTP_PORTASSIGNMENTSET        (MXL862XX_CTP_MAGIC + 0x3)
++
++#define MXL862XX_MAC_TABLECLEARCOND   (MXL862XX_SWMAC_MAGIC + 0x8)
++
++#define MXL862XX_SS_SPTAG_SET         (MXL862XX_SS_MAGIC + 0x02)
++
++#define INT_GPHY_READ                 (GPY_GPY2XX_MAGIC + 0x01)
++#define INT_GPHY_WRITE                        (GPY_GPY2XX_MAGIC + 0x02)
++
++#define SYS_MISC_FW_VERSION           (SYS_MISC_MAGIC + 0x02)
++
++#define MMD_API_MAXIMUM_ID            0x7fff
++
++#endif /* __MXL862XX_CMD_H */
+--- /dev/null
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+@@ -0,0 +1,245 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Based upon the MaxLinear SDK driver
++ *
++ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
++ * Copyright (C) 2025 John Crispin <john@phrozen.org>
++ * Copyright (C) 2024 MaxLinear Inc.
++ */
++
++#include <linux/bits.h>
++#include <linux/iopoll.h>
++#include <linux/limits.h>
++#include <net/dsa.h>
++#include "mxl862xx.h"
++#include "mxl862xx-host.h"
++
++#define CTRL_BUSY_MASK                        BIT(15)
++
++#define MXL862XX_MMD_REG_CTRL         0
++#define MXL862XX_MMD_REG_LEN_RET      1
++#define MXL862XX_MMD_REG_DATA_FIRST   2
++#define MXL862XX_MMD_REG_DATA_LAST    95
++#define MXL862XX_MMD_REG_DATA_MAX_SIZE \
++              (MXL862XX_MMD_REG_DATA_LAST - MXL862XX_MMD_REG_DATA_FIRST + 1)
++
++#define MMD_API_SET_DATA_0            2
++#define MMD_API_GET_DATA_0            5
++#define MMD_API_RST_DATA              8
++
++#define MXL862XX_SWITCH_RESET 0x9907
++
++static int mxl862xx_reg_read(struct mxl862xx_priv *priv, u32 addr)
++{
++      return __mdiodev_c45_read(priv->mdiodev, MDIO_MMD_VEND1, addr);
++}
++
++static int mxl862xx_reg_write(struct mxl862xx_priv *priv, u32 addr, u16 data)
++{
++      return __mdiodev_c45_write(priv->mdiodev, MDIO_MMD_VEND1, addr, data);
++}
++
++static int mxl862xx_ctrl_read(struct mxl862xx_priv *priv)
++{
++      return mxl862xx_reg_read(priv, MXL862XX_MMD_REG_CTRL);
++}
++
++static int mxl862xx_busy_wait(struct mxl862xx_priv *priv)
++{
++      int val;
++
++      return readx_poll_timeout(mxl862xx_ctrl_read, priv, val,
++                                !(val & CTRL_BUSY_MASK), 15, 500000);
++}
++
++static int mxl862xx_set_data(struct mxl862xx_priv *priv, u16 words)
++{
++      int ret;
++      u16 cmd;
++
++      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET,
++                               MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16));
++      if (ret < 0)
++              return ret;
++
++      cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE - 1;
++      if (!(cmd < 2))
++              return -EINVAL;
++
++      cmd += MMD_API_SET_DATA_0;
++      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL,
++                               cmd | CTRL_BUSY_MASK);
++      if (ret < 0)
++              return ret;
++
++      return mxl862xx_busy_wait(priv);
++}
++
++static int mxl862xx_get_data(struct mxl862xx_priv *priv, u16 words)
++{
++      int ret;
++      u16 cmd;
++
++      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET,
++                               MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16));
++      if (ret < 0)
++              return ret;
++
++      cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE;
++      if (!(cmd > 0 && cmd < 3))
++              return -EINVAL;
++
++      cmd += MMD_API_GET_DATA_0;
++      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL,
++                               cmd | CTRL_BUSY_MASK);
++      if (ret < 0)
++              return ret;
++
++      return mxl862xx_busy_wait(priv);
++}
++
++static int mxl862xx_firmware_return(int ret)
++{
++      /* Only 16-bit values are valid. */
++      if (WARN_ON(ret & GENMASK(31, 16)))
++              return -EINVAL;
++
++      /* Interpret value as signed 16-bit integer. */
++      return (s16)ret;
++}
++
++static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size,
++                           bool quiet)
++{
++      int ret;
++
++      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, size);
++      if (ret)
++              return ret;
++
++      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL,
++                               cmd | CTRL_BUSY_MASK);
++      if (ret)
++              return ret;
++
++      ret = mxl862xx_busy_wait(priv);
++      if (ret)
++              return ret;
++
++      ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_LEN_RET);
++      if (ret < 0)
++              return ret;
++
++      /* handle errors returned by the firmware as -EIO
++       * The firmware is based on Zephyr OS and uses the errors as
++       * defined in errno.h of Zephyr OS. See
++       * https://github.com/zephyrproject-rtos/zephyr/blob/v3.7.0/lib/libc/minimal/include/errno.h
++       */
++      ret = mxl862xx_firmware_return(ret);
++      if (ret < 0) {
++              if (!quiet)
++                      dev_err(&priv->mdiodev->dev,
++                              "CMD %04x returned error %d\n", cmd, ret);
++              return -EIO;
++      }
++
++      return ret;
++}
++
++int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data,
++                    u16 size, bool read, bool quiet)
++{
++      __le16 *data = _data;
++      int ret, cmd_ret;
++      u16 max, i;
++
++      dev_dbg(&priv->mdiodev->dev, "CMD %04x DATA %*ph\n", cmd, size, data);
++
++      mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
++
++      max = (size + 1) / 2;
++
++      ret = mxl862xx_busy_wait(priv);
++      if (ret < 0)
++              goto out;
++
++      for (i = 0; i < max; i++) {
++              u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE;
++
++              if (i && off == 0) {
++                      /* Send command to set data when every
++                       * MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are written.
++                       */
++                      ret = mxl862xx_set_data(priv, i);
++                      if (ret < 0)
++                              goto out;
++              }
++
++              ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_DATA_FIRST + off,
++                                       le16_to_cpu(data[i]));
++              if (ret < 0)
++                      goto out;
++      }
++
++      ret = mxl862xx_send_cmd(priv, cmd, size, quiet);
++      if (ret < 0 || !read)
++              goto out;
++
++      /* store result of mxl862xx_send_cmd() */
++      cmd_ret = ret;
++
++      for (i = 0; i < max; i++) {
++              u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE;
++
++              if (i && off == 0) {
++                      /* Send command to fetch next batch of data when every
++                       * MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are read.
++                       */
++                      ret = mxl862xx_get_data(priv, i);
++                      if (ret < 0)
++                              goto out;
++              }
++
++              ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_DATA_FIRST + off);
++              if (ret < 0)
++                      goto out;
++
++              if ((i * 2 + 1) == size) {
++                      /* Special handling for last BYTE if it's not WORD
++                       * aligned to avoid writing beyond the allocated data
++                       * structure.
++                       */
++                      *(uint8_t *)&data[i] = ret & 0xff;
++              } else {
++                      data[i] = cpu_to_le16((u16)ret);
++              }
++      }
++
++      /* on success return the result of the mxl862xx_send_cmd() */
++      ret = cmd_ret;
++
++      dev_dbg(&priv->mdiodev->dev, "RET %d DATA %*ph\n", ret, size, data);
++
++out:
++      mutex_unlock(&priv->mdiodev->bus->mdio_lock);
++
++      return ret;
++}
++
++int mxl862xx_reset(struct mxl862xx_priv *priv)
++{
++      int ret;
++
++      mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
++
++      /* Software reset */
++      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, 0);
++      if (ret)
++              goto out;
++
++      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, MXL862XX_SWITCH_RESET);
++out:
++      mutex_unlock(&priv->mdiodev->bus->mdio_lock);
++
++      return ret;
++}
+--- /dev/null
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h
+@@ -0,0 +1,12 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++
++#ifndef __MXL862XX_HOST_H
++#define __MXL862XX_HOST_H
++
++#include "mxl862xx.h"
++
++int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size,
++                    bool read, bool quiet);
++int mxl862xx_reset(struct mxl862xx_priv *priv);
++
++#endif /* __MXL862XX_HOST_H */
+--- /dev/null
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -0,0 +1,476 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Driver for MaxLinear MxL862xx switch family
++ *
++ * Copyright (C) 2024 MaxLinear Inc.
++ * Copyright (C) 2025 John Crispin <john@phrozen.org>
++ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
++ */
++
++#include <linux/module.h>
++#include <linux/delay.h>
++#include <linux/of_device.h>
++#include <linux/of_mdio.h>
++#include <linux/phy.h>
++#include <linux/phylink.h>
++#include <net/dsa.h>
++
++#include "mxl862xx.h"
++#include "mxl862xx-api.h"
++#include "mxl862xx-cmd.h"
++#include "mxl862xx-host.h"
++
++#define MXL862XX_API_WRITE(dev, cmd, data) \
++      mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false)
++#define MXL862XX_API_READ(dev, cmd, data) \
++      mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, false)
++#define MXL862XX_API_READ_QUIET(dev, cmd, data) \
++      mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
++
++#define MXL862XX_SDMA_PCTRLP(p)               (0xbc0 + ((p) * 0x6))
++#define MXL862XX_SDMA_PCTRL_EN                BIT(0)
++
++#define MXL862XX_FDMA_PCTRLP(p)               (0xa80 + ((p) * 0x6))
++#define MXL862XX_FDMA_PCTRL_EN                BIT(0)
++
++#define MXL862XX_READY_TIMEOUT_MS     10000
++#define MXL862XX_READY_POLL_MS                100
++
++static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds,
++                                                     int port,
++                                                     enum dsa_tag_protocol m)
++{
++      return DSA_TAG_PROTO_MXL862;
++}
++
++/* PHY access via firmware relay */
++static int mxl862xx_phy_read_mmd(struct mxl862xx_priv *priv, int port,
++                               int devadd, int reg)
++{
++      struct mdio_relay_data param = {
++              .phy = port,
++              .mmd = devadd,
++              .reg = cpu_to_le16(reg),
++      };
++      int ret;
++
++      ret = MXL862XX_API_READ(priv, INT_GPHY_READ, param);
++      if (ret)
++              return ret;
++
++      return le16_to_cpu(param.data);
++}
++
++static int mxl862xx_phy_write_mmd(struct mxl862xx_priv *priv, int port,
++                                int devadd, int reg, u16 data)
++{
++      struct mdio_relay_data param = {
++              .phy = port,
++              .mmd = devadd,
++              .reg = cpu_to_le16(reg),
++              .data = cpu_to_le16(data),
++      };
++
++      return MXL862XX_API_WRITE(priv, INT_GPHY_WRITE, param);
++}
++
++static int mxl862xx_phy_read_mii_bus(struct mii_bus *bus, int port, int regnum)
++{
++      return mxl862xx_phy_read_mmd(bus->priv, port, 0, regnum);
++}
++
++static int mxl862xx_phy_write_mii_bus(struct mii_bus *bus, int port,
++                                    int regnum, u16 val)
++{
++      return mxl862xx_phy_write_mmd(bus->priv, port, 0, regnum, val);
++}
++
++static int mxl862xx_phy_read_c45_mii_bus(struct mii_bus *bus, int port,
++                                       int devadd, int regnum)
++{
++      return mxl862xx_phy_read_mmd(bus->priv, port, devadd, regnum);
++}
++
++static int mxl862xx_phy_write_c45_mii_bus(struct mii_bus *bus, int port,
++                                        int devadd, int regnum, u16 val)
++{
++      return mxl862xx_phy_write_mmd(bus->priv, port, devadd, regnum, val);
++}
++
++static int mxl862xx_wait_ready(struct dsa_switch *ds)
++{
++      struct mxl862xx_sys_fw_image_version ver = {};
++      unsigned long start = jiffies, timeout;
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_cfg cfg = {};
++      int ret;
++
++      timeout = start + msecs_to_jiffies(MXL862XX_READY_TIMEOUT_MS);
++      msleep(2000); /* it always takes at least 2 seconds */
++      do {
++              ret = MXL862XX_API_READ_QUIET(priv, SYS_MISC_FW_VERSION, ver);
++              if (ret || !ver.iv_major)
++                      goto not_ready_yet;
++
++              /* being able to perform CFGGET indicates that
++               * the firmware is ready
++               */
++              ret = MXL862XX_API_READ_QUIET(priv,
++                                            MXL862XX_COMMON_CFGGET,
++                                            cfg);
++              if (ret)
++                      goto not_ready_yet;
++
++              dev_info(ds->dev, "switch ready after %ums, firmware %u.%u.%u (build %u)\n",
++                       jiffies_to_msecs(jiffies - start),
++                       ver.iv_major, ver.iv_minor,
++                       le16_to_cpu(ver.iv_revision),
++                       le32_to_cpu(ver.iv_build_num));
++              return 0;
++
++not_ready_yet:
++              msleep(MXL862XX_READY_POLL_MS);
++      } while (time_before(jiffies, timeout));
++
++      dev_err(ds->dev, "switch not responding after reset\n");
++      return -ETIMEDOUT;
++}
++
++static int mxl862xx_setup_mdio(struct dsa_switch *ds)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct device *dev = ds->dev;
++      struct device_node *mdio_np;
++      struct mii_bus *bus;
++      int ret;
++
++      bus = devm_mdiobus_alloc(dev);
++      if (!bus)
++              return -ENOMEM;
++
++      bus->priv = priv;
++      ds->user_mii_bus = bus;
++      bus->name = KBUILD_MODNAME "-mii";
++      snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev));
++      bus->read_c45 = mxl862xx_phy_read_c45_mii_bus;
++      bus->write_c45 = mxl862xx_phy_write_c45_mii_bus;
++      bus->read = mxl862xx_phy_read_mii_bus;
++      bus->write = mxl862xx_phy_write_mii_bus;
++      bus->parent = dev;
++      bus->phy_mask = ~ds->phys_mii_mask;
++
++      mdio_np = of_get_child_by_name(dev->of_node, "mdio");
++      if (!mdio_np)
++              return -ENODEV;
++
++      ret = devm_of_mdiobus_register(dev, bus, mdio_np);
++      of_node_put(mdio_np);
++
++      return ret;
++}
++
++static int mxl862xx_setup(struct dsa_switch *ds)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      int ret;
++
++      ret = mxl862xx_reset(priv);
++      if (ret)
++              return ret;
++
++      ret = mxl862xx_wait_ready(ds);
++      if (ret)
++              return ret;
++
++      return mxl862xx_setup_mdio(ds);
++}
++
++static int mxl862xx_port_state(struct dsa_switch *ds, int port, bool enable)
++{
++      struct mxl862xx_register_mod sdma = {
++              .addr = cpu_to_le16(MXL862XX_SDMA_PCTRLP(port)),
++              .data = cpu_to_le16(enable ? MXL862XX_SDMA_PCTRL_EN : 0),
++              .mask = cpu_to_le16(MXL862XX_SDMA_PCTRL_EN),
++      };
++      struct mxl862xx_register_mod fdma = {
++              .addr = cpu_to_le16(MXL862XX_FDMA_PCTRLP(port)),
++              .data = cpu_to_le16(enable ? MXL862XX_FDMA_PCTRL_EN : 0),
++              .mask = cpu_to_le16(MXL862XX_FDMA_PCTRL_EN),
++      };
++      int ret;
++
++      ret = MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_REGISTERMOD, sdma);
++      if (ret)
++              return ret;
++
++      return MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_REGISTERMOD, fdma);
++}
++
++static int mxl862xx_port_enable(struct dsa_switch *ds, int port,
++                              struct phy_device *phydev)
++{
++      return mxl862xx_port_state(ds, port, true);
++}
++
++static void mxl862xx_port_disable(struct dsa_switch *ds, int port)
++{
++      if (mxl862xx_port_state(ds, port, false))
++              dev_err(ds->dev, "failed to disable port %d\n", port);
++}
++
++static void mxl862xx_port_fast_age(struct dsa_switch *ds, int port)
++{
++      struct mxl862xx_mac_table_clear param = {
++              .type = MXL862XX_MAC_CLEAR_PHY_PORT,
++              .port_id = port,
++      };
++
++      if (MXL862XX_API_WRITE(ds->priv, MXL862XX_MAC_TABLECLEARCOND, param))
++              dev_err(ds->dev, "failed to clear fdb on port %d\n", port);
++}
++
++static int mxl862xx_configure_ctp_port(struct dsa_switch *ds, int port,
++                                     u16 first_ctp_port_id,
++                                     u16 number_of_ctp_ports)
++{
++      struct mxl862xx_ctp_port_assignment ctp_assign = {
++              .logical_port_id = port,
++              .first_ctp_port_id = cpu_to_le16(first_ctp_port_id),
++              .number_of_ctp_port = cpu_to_le16(number_of_ctp_ports),
++              .mode = cpu_to_le32(MXL862XX_LOGICAL_PORT_ETHERNET),
++      };
++
++      return MXL862XX_API_WRITE(ds->priv, MXL862XX_CTP_PORTASSIGNMENTSET,
++                                ctp_assign);
++}
++
++static int mxl862xx_configure_sp_tag_proto(struct dsa_switch *ds, int port,
++                                         bool enable)
++{
++      struct mxl862xx_ss_sp_tag tag = {
++              .pid = port,
++              .mask = MXL862XX_SS_SP_TAG_MASK_RX | MXL862XX_SS_SP_TAG_MASK_TX,
++              .rx = enable ? MXL862XX_SS_SP_TAG_RX_TAG_NO_INSERT :
++                             MXL862XX_SS_SP_TAG_RX_NO_TAG_INSERT,
++              .tx = enable ? MXL862XX_SS_SP_TAG_TX_TAG_NO_REMOVE :
++                             MXL862XX_SS_SP_TAG_TX_TAG_REMOVE,
++      };
++
++      return MXL862XX_API_WRITE(ds->priv, MXL862XX_SS_SPTAG_SET, tag);
++}
++
++static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port)
++{
++      struct mxl862xx_bridge_port_config br_port_cfg = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      u16 bridge_port_map = 0;
++      struct dsa_port *dp;
++
++      /* CPU port bridge setup */
++      br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING);
++
++      br_port_cfg.bridge_port_id = cpu_to_le16(port);
++      br_port_cfg.src_mac_learning_disable = false;
++      br_port_cfg.vlan_src_mac_vid_enable = true;
++      br_port_cfg.vlan_dst_mac_vid_enable = true;
++
++      /* include all assigned user ports in the CPU portmap */
++      dsa_switch_for_each_user_port(dp, ds) {
++              /* it's safe to rely on cpu_dp being valid for user ports */
++              if (dp->cpu_dp->index != port)
++                      continue;
++
++              bridge_port_map |= BIT(dp->index);
++      }
++      br_port_cfg.bridge_port_map[0] |= cpu_to_le16(bridge_port_map);
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
++}
++
++static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
++{
++      struct mxl862xx_bridge_port_config br_port_cfg = {};
++      struct dsa_port *dp = dsa_to_port(ds, port);
++      struct mxl862xx_bridge_alloc br_alloc = {};
++      int ret;
++
++      ret = MXL862XX_API_READ(ds->priv, MXL862XX_BRIDGE_ALLOC, br_alloc);
++      if (ret) {
++              dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
++              return ret;
++      }
++
++      br_port_cfg.bridge_id = br_alloc.bridge_id;
++      br_port_cfg.bridge_port_id = cpu_to_le16(port);
++      br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING);
++      br_port_cfg.src_mac_learning_disable = true;
++      br_port_cfg.vlan_src_mac_vid_enable = false;
++      br_port_cfg.vlan_dst_mac_vid_enable = false;
++      /* As this function is only called for user ports it is safe to rely on
++       * cpu_dp being valid
++       */
++      br_port_cfg.bridge_port_map[0] = cpu_to_le16(BIT(dp->cpu_dp->index));
++
++      return MXL862XX_API_WRITE(ds->priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
++}
++
++static int mxl862xx_port_setup(struct dsa_switch *ds, int port)
++{
++      struct dsa_port *dp = dsa_to_port(ds, port);
++      bool is_cpu_port = dsa_port_is_cpu(dp);
++      int ret;
++
++      /* disable port and flush MAC entries */
++      ret = mxl862xx_port_state(ds, port, false);
++      if (ret)
++              return ret;
++
++      mxl862xx_port_fast_age(ds, port);
++
++      /* skip setup for unused and DSA ports */
++      if (dsa_port_is_unused(dp) ||
++          dsa_port_is_dsa(dp))
++              return 0;
++
++      /* configure tag protocol */
++      ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);
++      if (ret)
++              return ret;
++
++      /* assign CTP port IDs */
++      ret = mxl862xx_configure_ctp_port(ds, port, port,
++                                        is_cpu_port ? 32 - port : 1);
++      if (ret)
++              return ret;
++
++      if (is_cpu_port)
++              /* assign user ports to CPU port bridge */
++              return mxl862xx_setup_cpu_bridge(ds, port);
++
++      /* setup single-port bridge for user ports */
++      return mxl862xx_add_single_port_bridge(ds, port);
++}
++
++static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
++                                    struct phylink_config *config)
++{
++      config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 |
++                                 MAC_100 | MAC_1000 | MAC_2500FD;
++
++      __set_bit(PHY_INTERFACE_MODE_INTERNAL,
++                config->supported_interfaces);
++}
++
++static const struct dsa_switch_ops mxl862xx_switch_ops = {
++      .get_tag_protocol = mxl862xx_get_tag_protocol,
++      .setup = mxl862xx_setup,
++      .port_setup = mxl862xx_port_setup,
++      .phylink_get_caps = mxl862xx_phylink_get_caps,
++      .port_enable = mxl862xx_port_enable,
++      .port_disable = mxl862xx_port_disable,
++      .port_fast_age = mxl862xx_port_fast_age,
++};
++
++static void mxl862xx_phylink_mac_config(struct phylink_config *config,
++                                      unsigned int mode,
++                                      const struct phylink_link_state *state)
++{
++}
++
++static void mxl862xx_phylink_mac_link_down(struct phylink_config *config,
++                                         unsigned int mode,
++                                         phy_interface_t interface)
++{
++}
++
++static void mxl862xx_phylink_mac_link_up(struct phylink_config *config,
++                                       struct phy_device *phydev,
++                                       unsigned int mode,
++                                       phy_interface_t interface,
++                                       int speed, int duplex,
++                                       bool tx_pause, bool rx_pause)
++{
++}
++
++static const struct phylink_mac_ops mxl862xx_phylink_mac_ops = {
++      .mac_config = mxl862xx_phylink_mac_config,
++      .mac_link_down = mxl862xx_phylink_mac_link_down,
++      .mac_link_up = mxl862xx_phylink_mac_link_up,
++};
++
++static int mxl862xx_probe(struct mdio_device *mdiodev)
++{
++      struct device *dev = &mdiodev->dev;
++      struct mxl862xx_priv *priv;
++      struct dsa_switch *ds;
++
++      priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++      if (!priv)
++              return -ENOMEM;
++
++      priv->mdiodev = mdiodev;
++
++      ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);
++      if (!ds)
++              return -ENOMEM;
++
++      priv->ds = ds;
++      ds->dev = dev;
++      ds->priv = priv;
++      ds->ops = &mxl862xx_switch_ops;
++      ds->phylink_mac_ops = &mxl862xx_phylink_mac_ops;
++      ds->num_ports = MXL862XX_MAX_PORTS;
++
++      dev_set_drvdata(dev, ds);
++
++      return dsa_register_switch(ds);
++}
++
++static void mxl862xx_remove(struct mdio_device *mdiodev)
++{
++      struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
++
++      if (!ds)
++              return;
++
++      dsa_unregister_switch(ds);
++}
++
++static void mxl862xx_shutdown(struct mdio_device *mdiodev)
++{
++      struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
++
++      if (!ds)
++              return;
++
++      dsa_switch_shutdown(ds);
++
++      dev_set_drvdata(&mdiodev->dev, NULL);
++}
++
++static const struct of_device_id mxl862xx_of_match[] = {
++      { .compatible = "maxlinear,mxl86282" },
++      { .compatible = "maxlinear,mxl86252" },
++      { /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, mxl862xx_of_match);
++
++static struct mdio_driver mxl862xx_driver = {
++      .probe  = mxl862xx_probe,
++      .remove = mxl862xx_remove,
++      .shutdown = mxl862xx_shutdown,
++      .mdiodrv.driver = {
++              .name = "mxl862xx",
++              .of_match_table = mxl862xx_of_match,
++      },
++};
++
++mdio_module_driver(mxl862xx_driver);
++
++MODULE_DESCRIPTION("Driver for MaxLinear MxL862xx switch family");
++MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -0,0 +1,16 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++
++#ifndef __MXL862XX_H
++#define __MXL862XX_H
++
++#include <linux/mdio.h>
++#include <net/dsa.h>
++
++#define MXL862XX_MAX_PORTS            17
++
++struct mxl862xx_priv {
++      struct dsa_switch *ds;
++      struct mdio_device *mdiodev;
++};
++
++#endif /* __MXL862XX_H */
diff --git a/target/linux/generic/backport-6.12/765-v7.0-net-dsa-mxl862xx-rename-MDIO-op-arguments.patch b/target/linux/generic/backport-6.12/765-v7.0-net-dsa-mxl862xx-rename-MDIO-op-arguments.patch
new file mode 100644 (file)
index 0000000..b478806
--- /dev/null
@@ -0,0 +1,94 @@
+From b5f8b39d22ab93cada5c88dc2cb6495b95f44c70 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 3 Mar 2026 03:17:43 +0000
+Subject: [PATCH 04/35] net: dsa: mxl862xx: rename MDIO op arguments
+
+The use of the 'port' argument name for functions implementing the MDIO
+bus operations is misleading as the port address isn't equal to the
+PHY address.
+
+Rename the MDIO operation argument name to match the prototypes of
+mdiobus_write, mdiobus_read, mdiobus_c45_read and mdiobus_c45_write.
+
+Suggested-by: Vladimir Oltean <olteanv@gmail.com>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/e1f4cb3bcffc7df9af0f2c9b673b14c7e1201c9a.1772507674.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 32 ++++++++++++++---------------
+ 1 file changed, 16 insertions(+), 16 deletions(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -44,13 +44,13 @@ static enum dsa_tag_protocol mxl862xx_ge
+ }
+ /* PHY access via firmware relay */
+-static int mxl862xx_phy_read_mmd(struct mxl862xx_priv *priv, int port,
+-                               int devadd, int reg)
++static int mxl862xx_phy_read_mmd(struct mxl862xx_priv *priv, int addr,
++                               int devadd, int regnum)
+ {
+       struct mdio_relay_data param = {
+-              .phy = port,
++              .phy = addr,
+               .mmd = devadd,
+-              .reg = cpu_to_le16(reg),
++              .reg = cpu_to_le16(regnum),
+       };
+       int ret;
+@@ -61,40 +61,40 @@ static int mxl862xx_phy_read_mmd(struct
+       return le16_to_cpu(param.data);
+ }
+-static int mxl862xx_phy_write_mmd(struct mxl862xx_priv *priv, int port,
+-                                int devadd, int reg, u16 data)
++static int mxl862xx_phy_write_mmd(struct mxl862xx_priv *priv, int addr,
++                                int devadd, int regnum, u16 data)
+ {
+       struct mdio_relay_data param = {
+-              .phy = port,
++              .phy = addr,
+               .mmd = devadd,
+-              .reg = cpu_to_le16(reg),
++              .reg = cpu_to_le16(regnum),
+               .data = cpu_to_le16(data),
+       };
+       return MXL862XX_API_WRITE(priv, INT_GPHY_WRITE, param);
+ }
+-static int mxl862xx_phy_read_mii_bus(struct mii_bus *bus, int port, int regnum)
++static int mxl862xx_phy_read_mii_bus(struct mii_bus *bus, int addr, int regnum)
+ {
+-      return mxl862xx_phy_read_mmd(bus->priv, port, 0, regnum);
++      return mxl862xx_phy_read_mmd(bus->priv, addr, 0, regnum);
+ }
+-static int mxl862xx_phy_write_mii_bus(struct mii_bus *bus, int port,
++static int mxl862xx_phy_write_mii_bus(struct mii_bus *bus, int addr,
+                                     int regnum, u16 val)
+ {
+-      return mxl862xx_phy_write_mmd(bus->priv, port, 0, regnum, val);
++      return mxl862xx_phy_write_mmd(bus->priv, addr, 0, regnum, val);
+ }
+-static int mxl862xx_phy_read_c45_mii_bus(struct mii_bus *bus, int port,
++static int mxl862xx_phy_read_c45_mii_bus(struct mii_bus *bus, int addr,
+                                        int devadd, int regnum)
+ {
+-      return mxl862xx_phy_read_mmd(bus->priv, port, devadd, regnum);
++      return mxl862xx_phy_read_mmd(bus->priv, addr, devadd, regnum);
+ }
+-static int mxl862xx_phy_write_c45_mii_bus(struct mii_bus *bus, int port,
++static int mxl862xx_phy_write_c45_mii_bus(struct mii_bus *bus, int addr,
+                                         int devadd, int regnum, u16 val)
+ {
+-      return mxl862xx_phy_write_mmd(bus->priv, port, devadd, regnum, val);
++      return mxl862xx_phy_write_mmd(bus->priv, addr, devadd, regnum, val);
+ }
+ static int mxl862xx_wait_ready(struct dsa_switch *ds)
diff --git a/target/linux/generic/backport-6.12/766-v7.0-net-dsa-mxl862xx-don-t-set-user_mii_bus.patch b/target/linux/generic/backport-6.12/766-v7.0-net-dsa-mxl862xx-don-t-set-user_mii_bus.patch
new file mode 100644 (file)
index 0000000..69c8ecc
--- /dev/null
@@ -0,0 +1,31 @@
+From d0341efa8f5182cafe16506b9bef98184f4951fe Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 10 Mar 2026 00:41:56 +0000
+Subject: [PATCH 05/35] net: dsa: mxl862xx: don't set user_mii_bus
+
+The PHY addresses in the MII bus are not equal to the port addresses,
+so the bus cannot be assigned as user_mii_bus. Falling back on the
+user_mii_bus in case a PHY isn't declared in device tree will result in
+using the wrong (in this case: off-by-+1) PHY.
+Remove the wrong assignment.
+
+Fixes: 23794bec1cb60 ("net: dsa: add basic initial driver for MxL862xx switches")
+Suggested-by: Vladimir Oltean <olteanv@gmail.com>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Link: https://patch.msgid.link/0f0df310fd8cab57e0e5e3d0831dd057fd05bcd5.1773103271.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -149,7 +149,6 @@ static int mxl862xx_setup_mdio(struct ds
+               return -ENOMEM;
+       bus->priv = priv;
+-      ds->user_mii_bus = bus;
+       bus->name = KBUILD_MODNAME "-mii";
+       snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev));
+       bus->read_c45 = mxl862xx_phy_read_c45_mii_bus;
diff --git a/target/linux/generic/backport-6.12/767-v7.0-net-dsa-MxL862xx-don-t-force-enable-MAXLINEAR_GPHY.patch b/target/linux/generic/backport-6.12/767-v7.0-net-dsa-MxL862xx-don-t-force-enable-MAXLINEAR_GPHY.patch
new file mode 100644 (file)
index 0000000..724a9d3
--- /dev/null
@@ -0,0 +1,54 @@
+From c0402837642625ef13ade862e20e229f4a5810f5 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 18 Mar 2026 03:07:52 +0000
+Subject: [PATCH 06/35] net: dsa: mxl862xx: don't read out-of-bounds
+
+The write loop in mxl862xx_api_wrap() computes the word count as
+(size + 1) / 2, rounding up for odd-sized structs.
+
+On the last iteration of an odd-sized buffer it reads a full __le16
+from data[i], accessing one byte past the end of the caller's struct.
+KASAN catches this as a stack-out-of-bounds read during probe (e.g.
+from mxl862xx_bridge_config_fwd() because of the odd length of
+sizeof(struct mxl862xx_bridge_config) == 49).
+
+The read-back loop already handles this case, it writes only a single
+byte when (i * 2 + 1) == size. The write loop lacked the same guard.
+
+In practice the over-read is harmless: the extra stack byte is sent to
+the firmware which ignores trailing data beyond the command's declared
+payload size.
+
+Apply the same odd-size last-byte handling to the write path: when the
+final word contains only one valid byte, send *(u8 *)&data[i] instead
+of le16_to_cpu(data[i]). This is endian-safe because data is
+__le16-encoded and the low byte is always at the lowest address
+regardless of host byte order.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/83356ad9c9a4470dd49b6b3d661c2a8dd85cc6a1.1773803190.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-host.c | 10 ++++++++--
+ 1 file changed, 8 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+@@ -175,8 +175,14 @@ int mxl862xx_api_wrap(struct mxl862xx_pr
+                               goto out;
+               }
+-              ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_DATA_FIRST + off,
+-                                       le16_to_cpu(data[i]));
++              if ((i * 2 + 1) == size)
++                      ret = mxl862xx_reg_write(priv,
++                                               MXL862XX_MMD_REG_DATA_FIRST + off,
++                                               *(u8 *)&data[i]);
++              else
++                      ret = mxl862xx_reg_write(priv,
++                                               MXL862XX_MMD_REG_DATA_FIRST + off,
++                                               le16_to_cpu(data[i]));
+               if (ret < 0)
+                       goto out;
+       }
diff --git a/target/linux/generic/backport-6.12/768-v7.1-net-dsa-MxL862xx-don-t-force-enable-MAXLINEAR_GPHY.patch b/target/linux/generic/backport-6.12/768-v7.1-net-dsa-MxL862xx-don-t-force-enable-MAXLINEAR_GPHY.patch
new file mode 100644 (file)
index 0000000..a2252d3
--- /dev/null
@@ -0,0 +1,39 @@
+From 06cdf1bf5ba80e90bc54e7fe0c096b47d5ab3d8d Mon Sep 17 00:00:00 2001
+From: Arnd Bergmann <arnd@arndb.de>
+Date: Mon, 16 Feb 2026 11:55:17 +0100
+Subject: [PATCH 07/35] net: dsa: MxL862xx: don't force-enable MAXLINEAR_GPHY
+
+The newly added dsa driver attempts to enable the corresponding PHY driver,
+but that one has additional dependencies that may not be available:
+
+WARNING: unmet direct dependencies detected for MAXLINEAR_GPHY
+  Depends on [m]: NETDEVICES [=y] && PHYLIB [=y] && (HWMON [=m] || HWMON [=m]=n [=n])
+  Selected by [y]:
+  - NET_DSA_MXL862 [=y] && NETDEVICES [=y] && NET_DSA [=y]
+aarch64-linux-ld: drivers/net/phy/mxl-gpy.o: in function `gpy_probe':
+mxl-gpy.c:(.text.gpy_probe+0x13c): undefined reference to `devm_hwmon_device_register_with_info'
+aarch64-linux-ld: drivers/net/phy/mxl-gpy.o: in function `gpy_hwmon_read':
+mxl-gpy.c:(.text.gpy_hwmon_read+0x48): undefined reference to `polynomial_calc'
+
+There is actually no compile-time dependency, as DSA correctly uses the
+PHY abstractions. Remove the 'select' statement to reduce the complexity.
+
+Fixes: 23794bec1cb6 ("net: dsa: add basic initial driver for MxL862xx switches")
+Signed-off-by: Arnd Bergmann <arnd@arndb.de>
+Reviewed-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/20260216105522.2382373-1-arnd@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mxl862xx/Kconfig | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/drivers/net/dsa/mxl862xx/Kconfig
++++ b/drivers/net/dsa/mxl862xx/Kconfig
+@@ -2,7 +2,6 @@
+ config NET_DSA_MXL862
+       tristate "MaxLinear MxL862xx"
+       depends on NET_DSA
+-      select MAXLINEAR_GPHY
+       select NET_DSA_TAG_MXL_862XX
+       help
+         This enables support for the MaxLinear MxL862xx switch family.
diff --git a/target/linux/generic/backport-6.12/769-v7.1-net-dsa-mxl862xx-add-CRC-for-MDIO-communication.patch b/target/linux/generic/backport-6.12/769-v7.1-net-dsa-mxl862xx-add-CRC-for-MDIO-communication.patch
new file mode 100644 (file)
index 0000000..8f65bea
--- /dev/null
@@ -0,0 +1,585 @@
+From d48001906168be3088f9cd7aa8d1ad8dbc53e4f4 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 22 Mar 2026 13:27:20 +0000
+Subject: [PATCH 08/35] net: dsa: mxl862xx: add CRC for MDIO communication
+
+Enable the firmware's opt-in CRC validation on the MDIO/MMD command
+interface to detect bit errors on the bus. The firmware bundles CRC-6
+and CRC-16 under a single enable flag, so both are implemented
+together.
+
+CRC-6 protects the ctrl and len_ret command registers using a table-
+driven 3GPP algorithm. It is applied to every command exchange
+including SET_DATA/GET_DATA batch transfers. With CRC enabled, the
+firmware encodes its return value as a signed 11-bit integer within
+the CRC- protected register fields, replacing the previous 16-bit
+interpretation.
+
+CRC-16 protects the data payload using the kernel's crc16() library.
+The driver appends a CRC-16 checksum to outgoing data and verifies the
+firmware-appended checksum on responses. The checksum is placed at the
+exact byte offset where the struct data ends, correctly handling
+packed structs with odd sizes by splitting the checksum across word
+boundaries. SET_DATA/GET_DATA sub-commands carry only CRC-6.
+
+Upon detection of a CRC error on either side all conduit interfaces
+are taken down, triggering all user ports to go down as well. This is
+the most feasible option: CRC errors are likely caused either by
+broken hardware, or are symptom of overheating. In either case, trying
+to resume normal operation isn't reasonable.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/620453b9a150bbe5b7ea4224331cb5dc5e57263b.1774185953.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mxl862xx/Kconfig         |   1 +
+ drivers/net/dsa/mxl862xx/mxl862xx-host.c | 345 ++++++++++++++++++-----
+ drivers/net/dsa/mxl862xx/mxl862xx-host.h |   2 +
+ drivers/net/dsa/mxl862xx/mxl862xx.c      |  11 +
+ drivers/net/dsa/mxl862xx/mxl862xx.h      |   2 +
+ 5 files changed, 296 insertions(+), 65 deletions(-)
+
+--- a/drivers/net/dsa/mxl862xx/Kconfig
++++ b/drivers/net/dsa/mxl862xx/Kconfig
+@@ -2,6 +2,7 @@
+ config NET_DSA_MXL862
+       tristate "MaxLinear MxL862xx"
+       depends on NET_DSA
++      select CRC16
+       select NET_DSA_TAG_MXL_862XX
+       help
+         This enables support for the MaxLinear MxL862xx switch family.
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+@@ -7,7 +7,9 @@
+  * Copyright (C) 2024 MaxLinear Inc.
+  */
++#include <linux/bitops.h>
+ #include <linux/bits.h>
++#include <linux/crc16.h>
+ #include <linux/iopoll.h>
+ #include <linux/limits.h>
+ #include <net/dsa.h>
+@@ -15,6 +17,9 @@
+ #include "mxl862xx-host.h"
+ #define CTRL_BUSY_MASK                        BIT(15)
++#define CTRL_CRC_FLAG                 BIT(14)
++
++#define LEN_RET_LEN_MASK              GENMASK(9, 0)
+ #define MXL862XX_MMD_REG_CTRL         0
+ #define MXL862XX_MMD_REG_LEN_RET      1
+@@ -27,7 +32,159 @@
+ #define MMD_API_GET_DATA_0            5
+ #define MMD_API_RST_DATA              8
+-#define MXL862XX_SWITCH_RESET 0x9907
++#define MXL862XX_SWITCH_RESET         0x9907
++
++static void mxl862xx_crc_err_work_fn(struct work_struct *work)
++{
++      struct mxl862xx_priv *priv = container_of(work, struct mxl862xx_priv,
++                                                crc_err_work);
++      struct dsa_port *dp;
++
++      dev_warn(&priv->mdiodev->dev,
++               "MDIO CRC error detected, shutting down all ports\n");
++
++      rtnl_lock();
++      dsa_switch_for_each_cpu_port(dp, priv->ds)
++              dev_close(dp->conduit);
++      rtnl_unlock();
++
++      clear_bit(0, &priv->crc_err);
++}
++
++/* Firmware CRC error codes (outside normal Zephyr errno range). */
++#define MXL862XX_FW_CRC6_ERR          (-1024)
++#define MXL862XX_FW_CRC16_ERR         (-1023)
++
++/* 3GPP CRC-6 lookup table (polynomial 0x6F).
++ * Matches the firmware's default CRC-6 implementation.
++ */
++static const u8 mxl862xx_crc6_table[256] = {
++      0x00, 0x2f, 0x31, 0x1e, 0x0d, 0x22, 0x3c, 0x13,
++      0x1a, 0x35, 0x2b, 0x04, 0x17, 0x38, 0x26, 0x09,
++      0x34, 0x1b, 0x05, 0x2a, 0x39, 0x16, 0x08, 0x27,
++      0x2e, 0x01, 0x1f, 0x30, 0x23, 0x0c, 0x12, 0x3d,
++      0x07, 0x28, 0x36, 0x19, 0x0a, 0x25, 0x3b, 0x14,
++      0x1d, 0x32, 0x2c, 0x03, 0x10, 0x3f, 0x21, 0x0e,
++      0x33, 0x1c, 0x02, 0x2d, 0x3e, 0x11, 0x0f, 0x20,
++      0x29, 0x06, 0x18, 0x37, 0x24, 0x0b, 0x15, 0x3a,
++      0x0e, 0x21, 0x3f, 0x10, 0x03, 0x2c, 0x32, 0x1d,
++      0x14, 0x3b, 0x25, 0x0a, 0x19, 0x36, 0x28, 0x07,
++      0x3a, 0x15, 0x0b, 0x24, 0x37, 0x18, 0x06, 0x29,
++      0x20, 0x0f, 0x11, 0x3e, 0x2d, 0x02, 0x1c, 0x33,
++      0x09, 0x26, 0x38, 0x17, 0x04, 0x2b, 0x35, 0x1a,
++      0x13, 0x3c, 0x22, 0x0d, 0x1e, 0x31, 0x2f, 0x00,
++      0x3d, 0x12, 0x0c, 0x23, 0x30, 0x1f, 0x01, 0x2e,
++      0x27, 0x08, 0x16, 0x39, 0x2a, 0x05, 0x1b, 0x34,
++      0x1c, 0x33, 0x2d, 0x02, 0x11, 0x3e, 0x20, 0x0f,
++      0x06, 0x29, 0x37, 0x18, 0x0b, 0x24, 0x3a, 0x15,
++      0x28, 0x07, 0x19, 0x36, 0x25, 0x0a, 0x14, 0x3b,
++      0x32, 0x1d, 0x03, 0x2c, 0x3f, 0x10, 0x0e, 0x21,
++      0x1b, 0x34, 0x2a, 0x05, 0x16, 0x39, 0x27, 0x08,
++      0x01, 0x2e, 0x30, 0x1f, 0x0c, 0x23, 0x3d, 0x12,
++      0x2f, 0x00, 0x1e, 0x31, 0x22, 0x0d, 0x13, 0x3c,
++      0x35, 0x1a, 0x04, 0x2b, 0x38, 0x17, 0x09, 0x26,
++      0x12, 0x3d, 0x23, 0x0c, 0x1f, 0x30, 0x2e, 0x01,
++      0x08, 0x27, 0x39, 0x16, 0x05, 0x2a, 0x34, 0x1b,
++      0x26, 0x09, 0x17, 0x38, 0x2b, 0x04, 0x1a, 0x35,
++      0x3c, 0x13, 0x0d, 0x22, 0x31, 0x1e, 0x00, 0x2f,
++      0x15, 0x3a, 0x24, 0x0b, 0x18, 0x37, 0x29, 0x06,
++      0x0f, 0x20, 0x3e, 0x11, 0x02, 0x2d, 0x33, 0x1c,
++      0x21, 0x0e, 0x10, 0x3f, 0x2c, 0x03, 0x1d, 0x32,
++      0x3b, 0x14, 0x0a, 0x25, 0x36, 0x19, 0x07, 0x28,
++};
++
++/* Compute 3GPP CRC-6 over the ctrl register (16 bits) and the lower
++ * 10 bits of the len_ret register. The 26-bit input is packed as
++ * { len_ret[9:0], ctrl[15:0] } and processed LSB-first through the
++ * lookup table.
++ */
++static u8 mxl862xx_crc6(u16 ctrl, u16 len_ret)
++{
++      u32 data = ((u32)(len_ret & LEN_RET_LEN_MASK) << 16) | ctrl;
++      u8 crc = 0;
++      int i;
++
++      for (i = 0; i < sizeof(data); i++, data >>= 8)
++              crc = mxl862xx_crc6_table[(crc << 2) ^ (data & 0xff)] & 0x3f;
++
++      return crc;
++}
++
++/* Encode CRC-6 into the ctrl and len_ret registers before writing them
++ * to MDIO. The caller must set ctrl = API_ID | CTRL_BUSY_MASK |
++ * CTRL_CRC_FLAG, and len_ret = parameter length (bits 0-9 only).
++ *
++ * After encoding:
++ *   ctrl[12:0]     = API ID (unchanged)
++ *   ctrl[14:13]    = CRC-6 bits 5-4
++ *   ctrl[15]       = busy flag (unchanged)
++ *   len_ret[9:0]   = parameter length (unchanged)
++ *   len_ret[13:10] = CRC-6 bits 3-0
++ *   len_ret[14]    = original ctrl[14] (CRC check flag, forwarded to FW)
++ *   len_ret[15]    = original ctrl[13] (magic bit, always 1)
++ */
++static void mxl862xx_crc6_encode(u16 *pctrl, u16 *plen_ret)
++{
++      u16 crc, ctrl, len_ret;
++
++      /* Set magic bit before CRC computation */
++      *pctrl |= BIT(13);
++
++      crc = mxl862xx_crc6(*pctrl, *plen_ret);
++
++      /* Place CRC MSB (bits 5-4) into ctrl bits 13-14 */
++      ctrl = (*pctrl & ~GENMASK(14, 13));
++      ctrl |= (crc & 0x30) << 9;
++
++      /* Place CRC LSB (bits 3-0) into len_ret bits 10-13 */
++      len_ret = *plen_ret | ((crc & 0x0f) << 10);
++
++      /* Forward ctrl[14] (CRC check flag) to len_ret[14],
++       * and ctrl[13] (magic, always 1) to len_ret[15].
++       */
++      len_ret |= (*pctrl & BIT(14)) | ((*pctrl & BIT(13)) << 2);
++
++      *pctrl = ctrl;
++      *plen_ret = len_ret;
++}
++
++/* Verify CRC-6 on a firmware response and extract the return value.
++ *
++ * The firmware encodes the return value as a signed 11-bit integer:
++ *   - Sign bit (bit 10) in ctrl[14]
++ *   - Magnitude (bits 9-0) in len_ret[9:0]
++ * These are recoverable after CRC-6 verification by restoring the
++ * original ctrl from the auxiliary copies in len_ret[15:14].
++ *
++ * Return: 0 on CRC match (with *result set), or -EIO on mismatch.
++ */
++static int mxl862xx_crc6_verify(u16 ctrl, u16 len_ret, int *result)
++{
++      u16 crc_recv, crc_calc;
++
++      /* Extract the received CRC-6 */
++      crc_recv = ((ctrl >> 9) & 0x30) | ((len_ret >> 10) & 0x0f);
++
++      /* Reconstruct the original ctrl for re-computation:
++       *   ctrl[14] = len_ret[14] (sign bit / CRC check flag)
++       *   ctrl[13] = len_ret[15] >> 2 (magic bit)
++       */
++      ctrl &= ~GENMASK(14, 13);
++      ctrl |= len_ret & BIT(14);
++      ctrl |= (len_ret & BIT(15)) >> 2;
++
++      crc_calc = mxl862xx_crc6(ctrl, len_ret);
++      if (crc_recv != crc_calc)
++              return -EIO;
++
++      /* Extract signed 11-bit return value:
++       *   bit 10 (sign) from ctrl[14], bits 9-0 from len_ret[9:0]
++       */
++      *result = sign_extend32((len_ret & LEN_RET_LEN_MASK) |
++                              ((ctrl & CTRL_CRC_FLAG) >> 4), 10);
++
++      return 0;
++}
+ static int mxl862xx_reg_read(struct mxl862xx_priv *priv, u32 addr)
+ {
+@@ -52,60 +209,78 @@ static int mxl862xx_busy_wait(struct mxl
+                                 !(val & CTRL_BUSY_MASK), 15, 500000);
+ }
+-static int mxl862xx_set_data(struct mxl862xx_priv *priv, u16 words)
++/* Issue a firmware command with CRC-6 protection on the ctrl and len_ret
++ * registers, wait for completion, and verify the response CRC-6.
++ *
++ * Return: firmware result value (>= 0) on success, or negative errno.
++ */
++static int mxl862xx_issue_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 len)
+ {
+-      int ret;
+-      u16 cmd;
++      u16 ctrl_enc, len_enc;
++      int ret, fw_result;
++
++      ctrl_enc = cmd | CTRL_BUSY_MASK | CTRL_CRC_FLAG;
++      len_enc = len;
++      mxl862xx_crc6_encode(&ctrl_enc, &len_enc);
+-      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET,
+-                               MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16));
++      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, len_enc);
++      if (ret < 0)
++              return ret;
++
++      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, ctrl_enc);
++      if (ret < 0)
++              return ret;
++
++      ret = mxl862xx_busy_wait(priv);
++      if (ret < 0)
++              return ret;
++
++      ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_CTRL);
++      if (ret < 0)
++              return ret;
++      ctrl_enc = ret;
++
++      ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_LEN_RET);
+       if (ret < 0)
+               return ret;
++      len_enc = ret;
++
++      ret = mxl862xx_crc6_verify(ctrl_enc, len_enc, &fw_result);
++      if (ret) {
++              if (!test_and_set_bit(0, &priv->crc_err))
++                      schedule_work(&priv->crc_err_work);
++              return -EIO;
++      }
++
++      return fw_result;
++}
++
++static int mxl862xx_set_data(struct mxl862xx_priv *priv, u16 words)
++{
++      u16 cmd;
+       cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE - 1;
+       if (!(cmd < 2))
+               return -EINVAL;
+       cmd += MMD_API_SET_DATA_0;
+-      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL,
+-                               cmd | CTRL_BUSY_MASK);
+-      if (ret < 0)
+-              return ret;
+-      return mxl862xx_busy_wait(priv);
++      return mxl862xx_issue_cmd(priv, cmd,
++                                MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16));
+ }
+ static int mxl862xx_get_data(struct mxl862xx_priv *priv, u16 words)
+ {
+-      int ret;
+       u16 cmd;
+-      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET,
+-                               MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16));
+-      if (ret < 0)
+-              return ret;
+-
+       cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE;
+       if (!(cmd > 0 && cmd < 3))
+               return -EINVAL;
+       cmd += MMD_API_GET_DATA_0;
+-      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL,
+-                               cmd | CTRL_BUSY_MASK);
+-      if (ret < 0)
+-              return ret;
+-
+-      return mxl862xx_busy_wait(priv);
+-}
+-
+-static int mxl862xx_firmware_return(int ret)
+-{
+-      /* Only 16-bit values are valid. */
+-      if (WARN_ON(ret & GENMASK(31, 16)))
+-              return -EINVAL;
+-      /* Interpret value as signed 16-bit integer. */
+-      return (s16)ret;
++      return mxl862xx_issue_cmd(priv, cmd,
++                                MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16));
+ }
+ static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size,
+@@ -113,30 +288,23 @@ static int mxl862xx_send_cmd(struct mxl8
+ {
+       int ret;
+-      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, size);
+-      if (ret)
+-              return ret;
++      ret = mxl862xx_issue_cmd(priv, cmd, size);
+-      ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL,
+-                               cmd | CTRL_BUSY_MASK);
+-      if (ret)
+-              return ret;
+-
+-      ret = mxl862xx_busy_wait(priv);
+-      if (ret)
+-              return ret;
+-
+-      ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_LEN_RET);
+-      if (ret < 0)
+-              return ret;
+-
+-      /* handle errors returned by the firmware as -EIO
++      /* Handle errors returned by the firmware as -EIO.
+        * The firmware is based on Zephyr OS and uses the errors as
+        * defined in errno.h of Zephyr OS. See
+        * https://github.com/zephyrproject-rtos/zephyr/blob/v3.7.0/lib/libc/minimal/include/errno.h
++       *
++       * The firmware signals CRC validation failures with dedicated
++       * error codes outside the normal Zephyr errno range:
++       *   -1024: CRC-6 mismatch on ctrl/len_ret registers
++       *   -1023: CRC-16 mismatch on data payload
+        */
+-      ret = mxl862xx_firmware_return(ret);
+       if (ret < 0) {
++              if ((ret == MXL862XX_FW_CRC6_ERR ||
++                   ret == MXL862XX_FW_CRC16_ERR) &&
++                  !test_and_set_bit(0, &priv->crc_err))
++                      schedule_work(&priv->crc_err_work);
+               if (!quiet)
+                       dev_err(&priv->mdiodev->dev,
+                               "CMD %04x returned error %d\n", cmd, ret);
+@@ -151,7 +319,7 @@ int mxl862xx_api_wrap(struct mxl862xx_pr
+ {
+       __le16 *data = _data;
+       int ret, cmd_ret;
+-      u16 max, i;
++      u16 max, crc, i;
+       dev_dbg(&priv->mdiodev->dev, "CMD %04x DATA %*ph\n", cmd, size, data);
+@@ -163,26 +331,45 @@ int mxl862xx_api_wrap(struct mxl862xx_pr
+       if (ret < 0)
+               goto out;
+-      for (i = 0; i < max; i++) {
++      /* Compute CRC-16 over the data payload; written as an extra word
++       * after the data so the firmware can verify the transfer.
++       */
++      crc = crc16(0xffff, (const u8 *)data, size);
++
++      for (i = 0; i < max + 1; i++) {
+               u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE;
++              u16 val;
+               if (i && off == 0) {
+                       /* Send command to set data when every
+                        * MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are written.
+-                       */
++                        */
+                       ret = mxl862xx_set_data(priv, i);
+                       if (ret < 0)
+                               goto out;
+               }
+-              if ((i * 2 + 1) == size)
+-                      ret = mxl862xx_reg_write(priv,
+-                                               MXL862XX_MMD_REG_DATA_FIRST + off,
+-                                               *(u8 *)&data[i]);
+-              else
+-                      ret = mxl862xx_reg_write(priv,
+-                                               MXL862XX_MMD_REG_DATA_FIRST + off,
+-                                               le16_to_cpu(data[i]));
++              if (i == max) {
++                      /* Even size: full CRC word.
++                       * Odd size: only CRC high byte remains (low byte
++                       * was packed into the previous word).
++                       */
++                      val = (size & 1) ? crc >> 8 : crc;
++              } else if ((i * 2 + 1) == size) {
++                      /* Special handling for last BYTE if it's not WORD
++                       * aligned to avoid reading beyond the allocated data
++                       * structure.  Pack the CRC low byte into the high
++                       * byte of this word so it sits at byte offset 'size'
++                       * in the firmware's contiguous buffer.
++                       */
++                      val = *(u8 *)&data[i] | ((crc & 0xff) << 8);
++              } else {
++                      val = le16_to_cpu(data[i]);
++              }
++
++              ret = mxl862xx_reg_write(priv,
++                                       MXL862XX_MMD_REG_DATA_FIRST + off,
++                                       val);
+               if (ret < 0)
+                       goto out;
+       }
+@@ -194,13 +381,13 @@ int mxl862xx_api_wrap(struct mxl862xx_pr
+       /* store result of mxl862xx_send_cmd() */
+       cmd_ret = ret;
+-      for (i = 0; i < max; i++) {
++      for (i = 0; i < max + 1; i++) {
+               u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE;
+               if (i && off == 0) {
+                       /* Send command to fetch next batch of data when every
+                        * MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are read.
+-                       */
++                        */
+                       ret = mxl862xx_get_data(priv, i);
+                       if (ret < 0)
+                               goto out;
+@@ -210,17 +397,35 @@ int mxl862xx_api_wrap(struct mxl862xx_pr
+               if (ret < 0)
+                       goto out;
+-              if ((i * 2 + 1) == size) {
++              if (i == max) {
++                      /* Even size: full CRC word.
++                       * Odd size: only CRC high byte remains (low byte
++                       * was in the previous word).
++                       */
++                      if (size & 1)
++                              crc = (crc & 0x00ff) |
++                                    (((u16)ret & 0xff) << 8);
++                      else
++                              crc = (u16)ret;
++              } else if ((i * 2 + 1) == size) {
+                       /* Special handling for last BYTE if it's not WORD
+                        * aligned to avoid writing beyond the allocated data
+-                       * structure.
++                       * structure.  The high byte carries the CRC low byte.
+                        */
+                       *(uint8_t *)&data[i] = ret & 0xff;
++                      crc = (ret >> 8) & 0xff;
+               } else {
+                       data[i] = cpu_to_le16((u16)ret);
+               }
+       }
++      if (crc16(0xffff, (const u8 *)data, size) != crc) {
++              if (!test_and_set_bit(0, &priv->crc_err))
++                      schedule_work(&priv->crc_err_work);
++              ret = -EIO;
++              goto out;
++      }
++
+       /* on success return the result of the mxl862xx_send_cmd() */
+       ret = cmd_ret;
+@@ -249,3 +454,13 @@ out:
+       return ret;
+ }
++
++void mxl862xx_host_init(struct mxl862xx_priv *priv)
++{
++      INIT_WORK(&priv->crc_err_work, mxl862xx_crc_err_work_fn);
++}
++
++void mxl862xx_host_shutdown(struct mxl862xx_priv *priv)
++{
++      cancel_work_sync(&priv->crc_err_work);
++}
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h
+@@ -5,6 +5,8 @@
+ #include "mxl862xx.h"
++void mxl862xx_host_init(struct mxl862xx_priv *priv);
++void mxl862xx_host_shutdown(struct mxl862xx_priv *priv);
+ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size,
+                     bool read, bool quiet);
+ int mxl862xx_reset(struct mxl862xx_priv *priv);
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -424,6 +424,7 @@ static int mxl862xx_probe(struct mdio_de
+       ds->ops = &mxl862xx_switch_ops;
+       ds->phylink_mac_ops = &mxl862xx_phylink_mac_ops;
+       ds->num_ports = MXL862XX_MAX_PORTS;
++      mxl862xx_host_init(priv);
+       dev_set_drvdata(dev, ds);
+@@ -433,22 +434,32 @@ static int mxl862xx_probe(struct mdio_de
+ static void mxl862xx_remove(struct mdio_device *mdiodev)
+ {
+       struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
++      struct mxl862xx_priv *priv;
+       if (!ds)
+               return;
++      priv = ds->priv;
++
+       dsa_unregister_switch(ds);
++
++      mxl862xx_host_shutdown(priv);
+ }
+ static void mxl862xx_shutdown(struct mdio_device *mdiodev)
+ {
+       struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
++      struct mxl862xx_priv *priv;
+       if (!ds)
+               return;
++      priv = ds->priv;
++
+       dsa_switch_shutdown(ds);
++      mxl862xx_host_shutdown(priv);
++
+       dev_set_drvdata(&mdiodev->dev, NULL);
+ }
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -11,6 +11,8 @@
+ struct mxl862xx_priv {
+       struct dsa_switch *ds;
+       struct mdio_device *mdiodev;
++      struct work_struct crc_err_work;
++      unsigned long crc_err;
+ };
+ #endif /* __MXL862XX_H */
diff --git a/target/linux/generic/backport-6.12/770-v7.1-net-dsa-mxl862xx-use-RST_DATA-to-skip-writing-zero-w.patch b/target/linux/generic/backport-6.12/770-v7.1-net-dsa-mxl862xx-use-RST_DATA-to-skip-writing-zero-w.patch
new file mode 100644 (file)
index 0000000..dc21f10
--- /dev/null
@@ -0,0 +1,93 @@
+From 4a296a038c0ea3ad20afe8df00eb083232317646 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 22 Mar 2026 13:27:26 +0000
+Subject: [PATCH 09/35] net: dsa: mxl862xx: use RST_DATA to skip writing zero
+ words
+
+Issue the firmware's RST_DATA command before writing data payloads that
+contain many zero words. RST_DATA zeroes both the firmware's internal
+buffer and the MMD data registers in a single command, allowing the
+driver to skip individual MDIO writes for zero-valued words. This
+reduces bus traffic for the common case where API structs have many
+unused or default-zero fields.
+
+The optimization is applied when at least 5 zero words are found in the
+payload, roughly the break-even point against the cost of the extra
+RST_DATA command round-trip.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/d10bd6ad5df062d0da342c3e0d330550b3d2432b.1774185953.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-host.c | 38 ++++++++++++++++++++++++
+ 1 file changed, 38 insertions(+)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+@@ -283,6 +283,17 @@ static int mxl862xx_get_data(struct mxl8
+                                 MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16));
+ }
++static int mxl862xx_rst_data(struct mxl862xx_priv *priv)
++{
++      return mxl862xx_issue_cmd(priv, MMD_API_RST_DATA, 0);
++}
++
++/* Minimum number of zero words in the data payload before issuing a
++ * RST_DATA command is worthwhile.  RST_DATA costs one full command
++ * round-trip (~5 MDIO transactions), so the threshold must offset that.
++ */
++#define RST_DATA_THRESHOLD    5
++
+ static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size,
+                            bool quiet)
+ {
+@@ -318,6 +329,8 @@ int mxl862xx_api_wrap(struct mxl862xx_pr
+                     u16 size, bool read, bool quiet)
+ {
+       __le16 *data = _data;
++      bool use_rst = false;
++      unsigned int zeros;
+       int ret, cmd_ret;
+       u16 max, crc, i;
+@@ -331,6 +344,24 @@ int mxl862xx_api_wrap(struct mxl862xx_pr
+       if (ret < 0)
+               goto out;
++      /* If the data contains enough zero words, issue RST_DATA to zero
++       * both the firmware buffer and MMD registers, then skip writing
++       * zero words individually.
++       */
++      for (i = 0, zeros = 0; i < size / 2 && zeros < RST_DATA_THRESHOLD; i++)
++              if (!data[i])
++                      zeros++;
++
++      if (zeros < RST_DATA_THRESHOLD && (size & 1) && !*(u8 *)&data[i])
++              zeros++;
++
++      if (zeros >= RST_DATA_THRESHOLD) {
++              ret = mxl862xx_rst_data(priv);
++              if (ret < 0)
++                      goto out;
++              use_rst = true;
++      }
++
+       /* Compute CRC-16 over the data payload; written as an extra word
+        * after the data so the firmware can verify the transfer.
+        */
+@@ -367,6 +398,13 @@ int mxl862xx_api_wrap(struct mxl862xx_pr
+                       val = le16_to_cpu(data[i]);
+               }
++              /* After RST_DATA, skip zero data words as the registers
++               * already contain zeros, but never skip the CRC word at the
++               * final word.
++               */
++              if (use_rst && i < max && val == 0)
++                      continue;
++
+               ret = mxl862xx_reg_write(priv,
+                                        MXL862XX_MMD_REG_DATA_FIRST + off,
+                                        val);
diff --git a/target/linux/generic/backport-6.12/780-01-v6.13-r8169-remove-original-workaround-for-RTL8125-broken-.patch b/target/linux/generic/backport-6.12/780-01-v6.13-r8169-remove-original-workaround-for-RTL8125-broken-.patch
new file mode 100644 (file)
index 0000000..bd10a96
--- /dev/null
@@ -0,0 +1,33 @@
+From 854d71c555dfc3383c1fde7d9989b6046e21093d Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Wed, 9 Oct 2024 07:48:05 +0200
+Subject: [PATCH] r8169: remove original workaround for RTL8125 broken rx issue
+
+Now that we have b9c7ac4fe22c ("r8169: disable ALDPS per default for
+RTL8125"), the first attempt to fix the issue shouldn't be needed
+any longer. So let's effectively revert 621735f59064 ("r8169: fix
+rare issue with broken rx after link-down on RTL8125") and see
+whether anybody complains.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/382d8c88-cbce-400f-ad62-fda0181c7e38@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 4 ----
+ 1 file changed, 4 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -4810,11 +4810,7 @@ static void r8169_phylink_handler(struct
+       if (netif_carrier_ok(ndev)) {
+               rtl_link_chg_patch(tp);
+               pm_request_resume(d);
+-              netif_wake_queue(tp->dev);
+       } else {
+-              /* In few cases rx is broken after link-down otherwise */
+-              if (rtl_is_8125(tp))
+-                      rtl_schedule_task(tp, RTL_FLAG_TASK_RESET_NO_QUEUE_WAKE);
+               pm_runtime_idle(d);
+       }
diff --git a/target/linux/generic/backport-6.12/780-02-v6.13-r8169-enable-SG-TSO-on-selected-chip-versions-per-de.patch b/target/linux/generic/backport-6.12/780-02-v6.13-r8169-enable-SG-TSO-on-selected-chip-versions-per-de.patch
new file mode 100644 (file)
index 0000000..7ea1a11
--- /dev/null
@@ -0,0 +1,52 @@
+From b8bf38440ba94e8ed8e2ae55c5dfb0276d30e843 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Thu, 10 Oct 2024 12:58:02 +0200
+Subject: [PATCH] r8169: enable SG/TSO on selected chip versions per default
+
+Due to problem reports in the past SG and TSO/TSO6 are disabled per
+default. It's not fully clear which chip versions are affected, so we
+may impact also users of unaffected chip versions, unless they know
+how to use ethtool for enabling SG/TSO/TSO6.
+Vendor drivers r8168/r8125 enable SG/TSO/TSO6 for selected chip
+versions per default, I'd interpret this as confirmation that these
+chip versions are unaffected. So let's do the same here.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 16 +++++++++++-----
+ 1 file changed, 11 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -5524,11 +5524,6 @@ static int rtl_init_one(struct pci_dev *
+       dev->features |= dev->hw_features;
+-      /* There has been a number of reports that using SG/TSO results in
+-       * tx timeouts. However for a lot of people SG/TSO works fine.
+-       * Therefore disable both features by default, but allow users to
+-       * enable them. Use at own risk!
+-       */
+       if (rtl_chip_supports_csum_v2(tp)) {
+               dev->hw_features |= NETIF_F_SG | NETIF_F_TSO | NETIF_F_TSO6;
+               netif_set_tso_max_size(dev, RTL_GSO_MAX_SIZE_V2);
+@@ -5539,6 +5534,17 @@ static int rtl_init_one(struct pci_dev *
+               netif_set_tso_max_segs(dev, RTL_GSO_MAX_SEGS_V1);
+       }
++      /* There has been a number of reports that using SG/TSO results in
++       * tx timeouts. However for a lot of people SG/TSO works fine.
++       * It's not fully clear which chip versions are affected. Vendor
++       * drivers enable SG/TSO for certain chip versions per default,
++       * let's mimic this here. On other chip versions users can
++       * use ethtool to enable SG/TSO, use at own risk!
++       */
++      if (tp->mac_version >= RTL_GIGA_MAC_VER_46 &&
++          tp->mac_version != RTL_GIGA_MAC_VER_61)
++              dev->features |= dev->hw_features;
++
+       dev->hw_features |= NETIF_F_RXALL;
+       dev->hw_features |= NETIF_F_RXFCS;
diff --git a/target/linux/generic/backport-6.12/780-03-v6.13-r8169-implement-additional-ethtool-stats-ops.patch b/target/linux/generic/backport-6.12/780-03-v6.13-r8169-implement-additional-ethtool-stats-ops.patch
new file mode 100644 (file)
index 0000000..594a8fa
--- /dev/null
@@ -0,0 +1,130 @@
+From e3fc5139bd8ffaa1498adc21be4e8ecbc6aed508 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Sun, 13 Oct 2024 11:17:39 +0200
+Subject: [PATCH] r8169: implement additional ethtool stats ops
+
+This adds support for ethtool standard statistics, and makes use of the
+extended hardware statistics being available from RTl8125.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/58e0da73-a7dd-4be3-82ae-d5b3f9069bde@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 82 +++++++++++++++++++++++
+ 1 file changed, 82 insertions(+)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -2160,6 +2160,19 @@ static void rtl8169_get_ringparam(struct
+       data->tx_pending = NUM_TX_DESC;
+ }
++static void rtl8169_get_pause_stats(struct net_device *dev,
++                                  struct ethtool_pause_stats *pause_stats)
++{
++      struct rtl8169_private *tp = netdev_priv(dev);
++
++      if (!rtl_is_8125(tp))
++              return;
++
++      rtl8169_update_counters(tp);
++      pause_stats->tx_pause_frames = le32_to_cpu(tp->counters->tx_pause_on);
++      pause_stats->rx_pause_frames = le32_to_cpu(tp->counters->rx_pause_on);
++}
++
+ static void rtl8169_get_pauseparam(struct net_device *dev,
+                                  struct ethtool_pauseparam *data)
+ {
+@@ -2186,6 +2199,69 @@ static int rtl8169_set_pauseparam(struct
+       return 0;
+ }
++static void rtl8169_get_eth_mac_stats(struct net_device *dev,
++                                    struct ethtool_eth_mac_stats *mac_stats)
++{
++      struct rtl8169_private *tp = netdev_priv(dev);
++
++      rtl8169_update_counters(tp);
++
++      mac_stats->FramesTransmittedOK =
++              le64_to_cpu(tp->counters->tx_packets);
++      mac_stats->SingleCollisionFrames =
++              le32_to_cpu(tp->counters->tx_one_collision);
++      mac_stats->MultipleCollisionFrames =
++              le32_to_cpu(tp->counters->tx_multi_collision);
++      mac_stats->FramesReceivedOK =
++              le64_to_cpu(tp->counters->rx_packets);
++      mac_stats->AlignmentErrors =
++              le16_to_cpu(tp->counters->align_errors);
++      mac_stats->FramesLostDueToIntMACXmitError =
++              le64_to_cpu(tp->counters->tx_errors);
++      mac_stats->BroadcastFramesReceivedOK =
++              le64_to_cpu(tp->counters->rx_broadcast);
++      mac_stats->MulticastFramesReceivedOK =
++              le32_to_cpu(tp->counters->rx_multicast);
++
++      if (!rtl_is_8125(tp))
++              return;
++
++      mac_stats->AlignmentErrors =
++              le32_to_cpu(tp->counters->align_errors32);
++      mac_stats->OctetsTransmittedOK =
++              le64_to_cpu(tp->counters->tx_octets);
++      mac_stats->LateCollisions =
++              le32_to_cpu(tp->counters->tx_late_collision);
++      mac_stats->FramesAbortedDueToXSColls =
++              le32_to_cpu(tp->counters->tx_aborted32);
++      mac_stats->OctetsReceivedOK =
++              le64_to_cpu(tp->counters->rx_octets);
++      mac_stats->FramesLostDueToIntMACRcvError =
++              le32_to_cpu(tp->counters->rx_mac_error);
++      mac_stats->MulticastFramesXmittedOK =
++              le64_to_cpu(tp->counters->tx_multicast64);
++      mac_stats->BroadcastFramesXmittedOK =
++              le64_to_cpu(tp->counters->tx_broadcast64);
++      mac_stats->MulticastFramesReceivedOK =
++              le64_to_cpu(tp->counters->rx_multicast64);
++       mac_stats->FrameTooLongErrors =
++              le32_to_cpu(tp->counters->rx_frame_too_long);
++}
++
++static void rtl8169_get_eth_ctrl_stats(struct net_device *dev,
++                                     struct ethtool_eth_ctrl_stats *ctrl_stats)
++{
++      struct rtl8169_private *tp = netdev_priv(dev);
++
++      if (!rtl_is_8125(tp))
++              return;
++
++      rtl8169_update_counters(tp);
++
++      ctrl_stats->UnsupportedOpcodesReceived =
++              le32_to_cpu(tp->counters->rx_unknown_opcode);
++}
++
+ static const struct ethtool_ops rtl8169_ethtool_ops = {
+       .supported_coalesce_params = ETHTOOL_COALESCE_USECS |
+                                    ETHTOOL_COALESCE_MAX_FRAMES,
+@@ -2207,8 +2283,11 @@ static const struct ethtool_ops rtl8169_
+       .get_link_ksettings     = phy_ethtool_get_link_ksettings,
+       .set_link_ksettings     = phy_ethtool_set_link_ksettings,
+       .get_ringparam          = rtl8169_get_ringparam,
++      .get_pause_stats        = rtl8169_get_pause_stats,
+       .get_pauseparam         = rtl8169_get_pauseparam,
+       .set_pauseparam         = rtl8169_set_pauseparam,
++      .get_eth_mac_stats      = rtl8169_get_eth_mac_stats,
++      .get_eth_ctrl_stats     = rtl8169_get_eth_ctrl_stats,
+ };
+ static enum mac_version rtl8169_get_mac_version(u16 xid, bool gmii)
+@@ -3926,6 +4005,9 @@ static void rtl_hw_start_8125(struct rtl
+               break;
+       }
++      /* enable extended tally counter */
++      r8168_mac_ocp_modify(tp, 0xea84, 0, BIT(1) | BIT(0));
++
+       rtl_hw_config(tp);
+ }
diff --git a/target/linux/generic/backport-6.12/780-04-v6.13-r8169-don-t-take-RTNL-lock-in-rtl_task.patch b/target/linux/generic/backport-6.12/780-04-v6.13-r8169-don-t-take-RTNL-lock-in-rtl_task.patch
new file mode 100644 (file)
index 0000000..a125ebb
--- /dev/null
@@ -0,0 +1,50 @@
+From ac48430368c1a4f4e6c2fa92243b4b93fd25bee4 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Wed, 16 Oct 2024 22:05:57 +0200
+Subject: [PATCH] r8169: don't take RTNL lock in rtl_task()
+
+There's not really a benefit here in taking the RTNL lock. The task
+handler does exception handling only, so we're in trouble anyway when
+we come here, and there's no need to protect against e.g. a parallel
+ethtool call.
+A benefit of removing the RTNL lock here is that we now can
+synchronously cancel the workqueue from a context holding the RTNL mutex.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 8 ++------
+ 1 file changed, 2 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -4833,10 +4833,8 @@ static void rtl_task(struct work_struct
+               container_of(work, struct rtl8169_private, wk.work);
+       int ret;
+-      rtnl_lock();
+-
+       if (!test_bit(RTL_FLAG_TASK_ENABLED, tp->wk.flags))
+-              goto out_unlock;
++              return;
+       if (test_and_clear_bit(RTL_FLAG_TASK_TX_TIMEOUT, tp->wk.flags)) {
+               /* if chip isn't accessible, reset bus to revive it */
+@@ -4845,7 +4843,7 @@ static void rtl_task(struct work_struct
+                       if (ret < 0) {
+                               netdev_err(tp->dev, "Can't reset secondary PCI bus, detach NIC\n");
+                               netif_device_detach(tp->dev);
+-                              goto out_unlock;
++                              return;
+                       }
+               }
+@@ -4864,8 +4862,6 @@ reset:
+       } else if (test_and_clear_bit(RTL_FLAG_TASK_RESET_NO_QUEUE_WAKE, tp->wk.flags)) {
+               rtl_reset_work(tp);
+       }
+-out_unlock:
+-      rtnl_unlock();
+ }
+ static int rtl8169_poll(struct napi_struct *napi, int budget)
diff --git a/target/linux/generic/backport-6.12/780-05-v6.13-replace-custom-flag-with-disable_work-et-al.patch b/target/linux/generic/backport-6.12/780-05-v6.13-replace-custom-flag-with-disable_work-et-al.patch
new file mode 100644 (file)
index 0000000..49e274a
--- /dev/null
@@ -0,0 +1,94 @@
+From e2015942e90a021151a5751776f35830ba063be7 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Wed, 16 Oct 2024 22:06:53 +0200
+Subject: [PATCH] r8169: replace custom flag with disable_work() et al
+
+So far we use a custom flag to define when a task can be scheduled and
+when not. Let's use the standard mechanism with disable_work() et al
+instead.
+Note that in rtl8169_close() we can remove the call to cancel_work()
+because we now call disable_work_sync() in rtl8169_down() already.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 18 ++++++------------
+ 1 file changed, 6 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -619,7 +619,6 @@ struct rtl8169_tc_offsets {
+ };
+ enum rtl_flag {
+-      RTL_FLAG_TASK_ENABLED = 0,
+       RTL_FLAG_TASK_RESET_PENDING,
+       RTL_FLAG_TASK_RESET_NO_QUEUE_WAKE,
+       RTL_FLAG_TASK_TX_TIMEOUT,
+@@ -2505,11 +2504,9 @@ u16 rtl8168h_2_get_adc_bias_ioffset(stru
+ static void rtl_schedule_task(struct rtl8169_private *tp, enum rtl_flag flag)
+ {
+-      if (!test_bit(RTL_FLAG_TASK_ENABLED, tp->wk.flags))
+-              return;
+-
+       set_bit(flag, tp->wk.flags);
+-      schedule_work(&tp->wk.work);
++      if (!schedule_work(&tp->wk.work))
++              clear_bit(flag, tp->wk.flags);
+ }
+ static void rtl8169_init_phy(struct rtl8169_private *tp)
+@@ -4833,9 +4830,6 @@ static void rtl_task(struct work_struct
+               container_of(work, struct rtl8169_private, wk.work);
+       int ret;
+-      if (!test_bit(RTL_FLAG_TASK_ENABLED, tp->wk.flags))
+-              return;
+-
+       if (test_and_clear_bit(RTL_FLAG_TASK_TX_TIMEOUT, tp->wk.flags)) {
+               /* if chip isn't accessible, reset bus to revive it */
+               if (RTL_R32(tp, TxConfig) == ~0) {
+@@ -4919,6 +4913,7 @@ static int r8169_phy_connect(struct rtl8
+ static void rtl8169_down(struct rtl8169_private *tp)
+ {
++      disable_work_sync(&tp->wk.work);
+       /* Clear all task flags */
+       bitmap_zero(tp->wk.flags, RTL_FLAG_MAX);
+@@ -4947,7 +4942,7 @@ static void rtl8169_up(struct rtl8169_pr
+       phy_resume(tp->phydev);
+       rtl8169_init_phy(tp);
+       napi_enable(&tp->napi);
+-      set_bit(RTL_FLAG_TASK_ENABLED, tp->wk.flags);
++      enable_work(&tp->wk.work);
+       rtl_reset_work(tp);
+       phy_start(tp->phydev);
+@@ -4964,8 +4959,6 @@ static int rtl8169_close(struct net_devi
+       rtl8169_down(tp);
+       rtl8169_rx_clear(tp);
+-      cancel_work(&tp->wk.work);
+-
+       free_irq(tp->irq, tp);
+       phy_disconnect(tp->phydev);
+@@ -5199,7 +5192,7 @@ static void rtl_remove_one(struct pci_de
+       if (pci_dev_run_wake(pdev))
+               pm_runtime_get_noresume(&pdev->dev);
+-      cancel_work_sync(&tp->wk.work);
++      disable_work_sync(&tp->wk.work);
+       if (IS_ENABLED(CONFIG_R8169_LEDS))
+               r8169_remove_leds(tp->leds);
+@@ -5577,6 +5570,7 @@ static int rtl_init_one(struct pci_dev *
+       tp->irq = pci_irq_vector(pdev, 0);
+       INIT_WORK(&tp->wk.work, rtl_task);
++      disable_work(&tp->wk.work);
+       rtl_init_mac_address(tp);
diff --git a/target/linux/generic/backport-6.12/780-06-v6.13-r8169-avoid-duplicated-messages-if-loading-firmware-.patch b/target/linux/generic/backport-6.12/780-06-v6.13-r8169-avoid-duplicated-messages-if-loading-firmware-.patch
new file mode 100644 (file)
index 0000000..6430d32
--- /dev/null
@@ -0,0 +1,41 @@
+From 1c105bacb160b5918e917ab811552b7be69fc69c Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Wed, 16 Oct 2024 22:29:39 +0200
+Subject: [PATCH] r8169: avoid duplicated messages if loading firmware fails
+ and switch to warn level
+
+In case of a problem with firmware loading we inform at the driver level,
+in addition the firmware load code itself issues warnings. Therefore
+switch to firmware_request_nowarn() to avoid duplicated error messages.
+In addition switch to warn level because the firmware is optional and
+typically just fixes compatibility issues.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Message-ID: <d9c5094c-89a6-40e2-b5fe-8df7df4624ef@gmail.com>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ drivers/net/ethernet/realtek/r8169_firmware.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_firmware.c
++++ b/drivers/net/ethernet/realtek/r8169_firmware.c
+@@ -215,7 +215,7 @@ int rtl_fw_request_firmware(struct rtl_f
+ {
+       int rc;
+-      rc = request_firmware(&rtl_fw->fw, rtl_fw->fw_name, rtl_fw->dev);
++      rc = firmware_request_nowarn(&rtl_fw->fw, rtl_fw->fw_name, rtl_fw->dev);
+       if (rc < 0)
+               goto out;
+@@ -227,7 +227,7 @@ int rtl_fw_request_firmware(struct rtl_f
+       return 0;
+ out:
+-      dev_err(rtl_fw->dev, "Unable to load firmware %s (%d)\n",
+-              rtl_fw->fw_name, rc);
++      dev_warn(rtl_fw->dev, "Unable to load firmware %s (%d)\n",
++               rtl_fw->fw_name, rc);
+       return rc;
+ }
diff --git a/target/linux/generic/backport-6.12/780-07-v6.13-r8169-remove-rtl_dash_loop_wait_high-low.patch b/target/linux/generic/backport-6.12/780-07-v6.13-r8169-remove-rtl_dash_loop_wait_high-low.patch
new file mode 100644 (file)
index 0000000..bc8f979
--- /dev/null
@@ -0,0 +1,82 @@
+From d64113c6bb5ea5a70b7c9c3a6bcadef307638187 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Wed, 16 Oct 2024 22:31:10 +0200
+Subject: [PATCH] r8169: remove rtl_dash_loop_wait_high/low
+
+Remove rtl_dash_loop_wait_high/low to simplify the code.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Message-ID: <fb8c490c-2d92-48f5-8bbf-1fc1f2ee1649@gmail.com>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 35 ++++++-----------------
+ 1 file changed, 8 insertions(+), 27 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -1348,40 +1348,19 @@ static void rtl8168ep_stop_cmac(struct r
+       RTL_W8(tp, IBCR0, RTL_R8(tp, IBCR0) & ~0x01);
+ }
+-static void rtl_dash_loop_wait(struct rtl8169_private *tp,
+-                             const struct rtl_cond *c,
+-                             unsigned long usecs, int n, bool high)
+-{
+-      if (!tp->dash_enabled)
+-              return;
+-      rtl_loop_wait(tp, c, usecs, n, high);
+-}
+-
+-static void rtl_dash_loop_wait_high(struct rtl8169_private *tp,
+-                                  const struct rtl_cond *c,
+-                                  unsigned long d, int n)
+-{
+-      rtl_dash_loop_wait(tp, c, d, n, true);
+-}
+-
+-static void rtl_dash_loop_wait_low(struct rtl8169_private *tp,
+-                                 const struct rtl_cond *c,
+-                                 unsigned long d, int n)
+-{
+-      rtl_dash_loop_wait(tp, c, d, n, false);
+-}
+-
+ static void rtl8168dp_driver_start(struct rtl8169_private *tp)
+ {
+       r8168dp_oob_notify(tp, OOB_CMD_DRIVER_START);
+-      rtl_dash_loop_wait_high(tp, &rtl_dp_ocp_read_cond, 10000, 10);
++      if (tp->dash_enabled)
++              rtl_loop_wait_high(tp, &rtl_dp_ocp_read_cond, 10000, 10);
+ }
+ static void rtl8168ep_driver_start(struct rtl8169_private *tp)
+ {
+       r8168ep_ocp_write(tp, 0x01, 0x180, OOB_CMD_DRIVER_START);
+       r8168ep_ocp_write(tp, 0x01, 0x30, r8168ep_ocp_read(tp, 0x30) | 0x01);
+-      rtl_dash_loop_wait_high(tp, &rtl_ep_ocp_read_cond, 10000, 30);
++      if (tp->dash_enabled)
++              rtl_loop_wait_high(tp, &rtl_ep_ocp_read_cond, 10000, 30);
+ }
+ static void rtl8168_driver_start(struct rtl8169_private *tp)
+@@ -1395,7 +1374,8 @@ static void rtl8168_driver_start(struct
+ static void rtl8168dp_driver_stop(struct rtl8169_private *tp)
+ {
+       r8168dp_oob_notify(tp, OOB_CMD_DRIVER_STOP);
+-      rtl_dash_loop_wait_low(tp, &rtl_dp_ocp_read_cond, 10000, 10);
++      if (tp->dash_enabled)
++              rtl_loop_wait_low(tp, &rtl_dp_ocp_read_cond, 10000, 10);
+ }
+ static void rtl8168ep_driver_stop(struct rtl8169_private *tp)
+@@ -1403,7 +1383,8 @@ static void rtl8168ep_driver_stop(struct
+       rtl8168ep_stop_cmac(tp);
+       r8168ep_ocp_write(tp, 0x01, 0x180, OOB_CMD_DRIVER_STOP);
+       r8168ep_ocp_write(tp, 0x01, 0x30, r8168ep_ocp_read(tp, 0x30) | 0x01);
+-      rtl_dash_loop_wait_low(tp, &rtl_ep_ocp_read_cond, 10000, 10);
++      if (tp->dash_enabled)
++              rtl_loop_wait_low(tp, &rtl_ep_ocp_read_cond, 10000, 10);
+ }
+ static void rtl8168_driver_stop(struct rtl8169_private *tp)
diff --git a/target/linux/generic/backport-6.12/780-08-v6.13-r8169-enable-EEE-at-2.5G-per-default-on-RTL8125B.patch b/target/linux/generic/backport-6.12/780-08-v6.13-r8169-enable-EEE-at-2.5G-per-default-on-RTL8125B.patch
new file mode 100644 (file)
index 0000000..4ec06cc
--- /dev/null
@@ -0,0 +1,28 @@
+From c4e64095c00cb2de413cd6b90be047c273bcd491 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Thu, 17 Oct 2024 22:27:44 +0200
+Subject: [PATCH] r8169: enable EEE at 2.5G per default on RTL8125B
+
+Register a6d/12 is shadowing register MDIO_AN_EEE_ADV2. So this line
+disables advertisement of EEE at 2.5G. Latest vendor driver r8125
+doesn't do this (any longer?), so this mode seems to be safe.
+EEE saves quite some energy, therefore enable this mode per default.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Message-ID: <95dd5a0c-09ea-4847-94d9-b7aa3063e8ff@gmail.com>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ drivers/net/ethernet/realtek/r8169_phy_config.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -99,7 +99,6 @@ static void rtl8125a_config_eee_phy(stru
+ static void rtl8125b_config_eee_phy(struct phy_device *phydev)
+ {
+-      phy_modify_paged(phydev, 0xa6d, 0x12, 0x0001, 0x0000);
+       phy_modify_paged(phydev, 0xa6d, 0x14, 0x0010, 0x0000);
+       phy_modify_paged(phydev, 0xa42, 0x14, 0x0080, 0x0000);
+       phy_modify_paged(phydev, 0xa4a, 0x11, 0x0200, 0x0000);
diff --git a/target/linux/generic/backport-6.12/780-10-v6.13-r8169-fix-inconsistent-indenting-in-rtl8169_get_eth_.patch b/target/linux/generic/backport-6.12/780-10-v6.13-r8169-fix-inconsistent-indenting-in-rtl8169_get_eth_.patch
new file mode 100644 (file)
index 0000000..41bab55
--- /dev/null
@@ -0,0 +1,30 @@
+From b8bd8c44a266c9a7dcb907eab10fbb119e3f6494 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Thu, 24 Oct 2024 22:48:59 +0200
+Subject: [PATCH] r8169: fix inconsistent indenting in
+ rtl8169_get_eth_mac_stats
+
+This fixes an inconsistent indenting introduced with e3fc5139bd8f
+("r8169: implement additional ethtool stats ops").
+
+Reported-by: kernel test robot <lkp@intel.com>
+Closes: https://lore.kernel.org/oe-kbuild-all/202410220413.1gAxIJ4t-lkp@intel.com/
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/20fd6f39-3c1b-4af0-9adc-7d1f49728fad@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -2224,7 +2224,7 @@ static void rtl8169_get_eth_mac_stats(st
+               le64_to_cpu(tp->counters->tx_broadcast64);
+       mac_stats->MulticastFramesReceivedOK =
+               le64_to_cpu(tp->counters->rx_multicast64);
+-       mac_stats->FrameTooLongErrors =
++      mac_stats->FrameTooLongErrors =
+               le32_to_cpu(tp->counters->rx_frame_too_long);
+ }
diff --git a/target/linux/generic/backport-6.12/780-11-v6.13-r8169-align-RTL8125-EEE-config-with-vendor-driver.patch b/target/linux/generic/backport-6.12/780-11-v6.13-r8169-align-RTL8125-EEE-config-with-vendor-driver.patch
new file mode 100644 (file)
index 0000000..de2be01
--- /dev/null
@@ -0,0 +1,49 @@
+From eb90f876b7961d702d7fc549e14614860f531e60 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Thu, 31 Oct 2024 22:42:52 +0100
+Subject: [PATCH] r8169: align RTL8125 EEE config with vendor driver
+
+Align the EEE config for RTL8125A/RTL8125B with vendor driver r8125.
+This should help to avoid compatibility issues.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/044c925e-8669-4b98-87df-95b4056f4f5f@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ .../net/ethernet/realtek/r8169_phy_config.c    | 18 ++++++++++++------
+ 1 file changed, 12 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -89,19 +89,25 @@ static void rtl8168h_config_eee_phy(stru
+       phy_modify_paged(phydev, 0xa42, 0x14, 0x0000, 0x0080);
+ }
+-static void rtl8125a_config_eee_phy(struct phy_device *phydev)
++static void rtl8125_common_config_eee_phy(struct phy_device *phydev)
+ {
+-      rtl8168h_config_eee_phy(phydev);
++      phy_modify_paged(phydev, 0xa6d, 0x14, 0x0010, 0x0000);
++      phy_modify_paged(phydev, 0xa42, 0x14, 0x0080, 0x0000);
++      phy_modify_paged(phydev, 0xa4a, 0x11, 0x0200, 0x0000);
++}
++static void rtl8125a_config_eee_phy(struct phy_device *phydev)
++{
++      rtl8168g_config_eee_phy(phydev);
++      /* disable EEE at 2.5Gbps */
+       phy_modify_paged(phydev, 0xa6d, 0x12, 0x0001, 0x0000);
+-      phy_modify_paged(phydev, 0xa6d, 0x14, 0x0010, 0x0000);
++      rtl8125_common_config_eee_phy(phydev);
+ }
+ static void rtl8125b_config_eee_phy(struct phy_device *phydev)
+ {
+-      phy_modify_paged(phydev, 0xa6d, 0x14, 0x0010, 0x0000);
+-      phy_modify_paged(phydev, 0xa42, 0x14, 0x0080, 0x0000);
+-      phy_modify_paged(phydev, 0xa4a, 0x11, 0x0200, 0x0000);
++      rtl8168g_config_eee_phy(phydev);
++      rtl8125_common_config_eee_phy(phydev);
+ }
+ static void rtl8169s_hw_phy_config(struct rtl8169_private *tp,
diff --git a/target/linux/generic/backport-6.12/780-12-v6.13-r8169-align-RTL8125-RTL8126-PHY-config-with-vendor-d.patch b/target/linux/generic/backport-6.12/780-12-v6.13-r8169-align-RTL8125-RTL8126-PHY-config-with-vendor-d.patch
new file mode 100644 (file)
index 0000000..a546c42
--- /dev/null
@@ -0,0 +1,46 @@
+From 4af2f60bf7378bd5c92b15a528d8c6c7d02bed6c Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Thu, 31 Oct 2024 22:43:45 +0100
+Subject: [PATCH] r8169: align RTL8125/RTL8126 PHY config with vendor driver
+
+This aligns some parameters with vendor driver r8125/r8126 to avoid
+compatibility issues. Note that for RTL8125B there's no functional
+change, just the open-coded version of the function is replaced.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/a8a9d896-fbe6-41f2-bf87-666567d3cdb3@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_phy_config.c | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -1073,8 +1073,8 @@ static void rtl8125b_hw_phy_config(struc
+                                  struct phy_device *phydev)
+ {
+       r8169_apply_firmware(tp);
++      rtl8168g_enable_gphy_10m(phydev);
+-      phy_modify_paged(phydev, 0xa44, 0x11, 0x0000, 0x0800);
+       phy_modify_paged(phydev, 0xac4, 0x13, 0x00f0, 0x0090);
+       phy_modify_paged(phydev, 0xad3, 0x10, 0x0003, 0x0001);
+@@ -1113,6 +1113,7 @@ static void rtl8125d_hw_phy_config(struc
+                                  struct phy_device *phydev)
+ {
+       r8169_apply_firmware(tp);
++      rtl8168g_enable_gphy_10m(phydev);
+       rtl8125_legacy_force_mode(phydev);
+       rtl8168g_disable_aldps(phydev);
+       rtl8125b_config_eee_phy(phydev);
+@@ -1122,6 +1123,9 @@ static void rtl8126a_hw_phy_config(struc
+                                  struct phy_device *phydev)
+ {
+       r8169_apply_firmware(tp);
++      rtl8168g_enable_gphy_10m(phydev);
++      rtl8125_legacy_force_mode(phydev);
++      rtl8168g_disable_aldps(phydev);
+ }
+ void r8169_hw_phy_config(struct rtl8169_private *tp, struct phy_device *phydev,
diff --git a/target/linux/generic/backport-6.12/780-13-v6.13-r8169-align-RTL8126-EEE-config-with-vendor-driver.patch b/target/linux/generic/backport-6.12/780-13-v6.13-r8169-align-RTL8126-EEE-config-with-vendor-driver.patch
new file mode 100644 (file)
index 0000000..36c8041
--- /dev/null
@@ -0,0 +1,25 @@
+From a3d8520e6a19ab018da6c7fc22512c913697a829 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Thu, 31 Oct 2024 22:44:36 +0100
+Subject: [PATCH] r8169: align RTL8126 EEE config with vendor driver
+
+Align the EEE config for RTL8126A with vendor driver r8126 to avoid
+compatibility issues.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/71e4859e-4cd0-4b6b-b7fa-621d7721992f@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_phy_config.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -1126,6 +1126,7 @@ static void rtl8126a_hw_phy_config(struc
+       rtl8168g_enable_gphy_10m(phydev);
+       rtl8125_legacy_force_mode(phydev);
+       rtl8168g_disable_aldps(phydev);
++      rtl8125_common_config_eee_phy(phydev);
+ }
+ void r8169_hw_phy_config(struct rtl8169_private *tp, struct phy_device *phydev,
diff --git a/target/linux/generic/backport-6.12/780-14-v6.13-r8169-improve-initialization-of-RSS-registers-on-RTL.patch b/target/linux/generic/backport-6.12/780-14-v6.13-r8169-improve-initialization-of-RSS-registers-on-RTL.patch
new file mode 100644 (file)
index 0000000..d00b08e
--- /dev/null
@@ -0,0 +1,38 @@
+From 2cd02f2fdd8a92e5b6b85ff64eab0fc549b30c07 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Sat, 2 Nov 2024 14:49:01 +0100
+Subject: [PATCH] r8169: improve initialization of RSS registers on
+ RTL8125/RTL8126
+
+Replace the register addresses with the names used in r8125/r8126
+vendor driver, and consider that RSS_CTRL_8125 is a 32 bit register.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/3bf2f340-b369-4174-97bf-fd38d4217492@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -346,6 +346,8 @@ enum rtl8125_registers {
+       TxPoll_8125             = 0x90,
+       LEDSEL3                 = 0x96,
+       MAC0_BKP                = 0x19e0,
++      RSS_CTRL_8125           = 0x4500,
++      Q_NUM_CTRL_8125         = 0x4800,
+       EEE_TXIDLE_TIMER_8125   = 0x6048,
+ };
+@@ -3788,8 +3790,8 @@ static void rtl_hw_start_8125_common(str
+       rtl_pcie_state_l2l3_disable(tp);
+       RTL_W16(tp, 0x382, 0x221b);
+-      RTL_W8(tp, 0x4500, 0);
+-      RTL_W16(tp, 0x4800, 0);
++      RTL_W32(tp, RSS_CTRL_8125, 0);
++      RTL_W16(tp, Q_NUM_CTRL_8125, 0);
+       /* disable UPS */
+       r8168_mac_ocp_modify(tp, 0xd40a, 0x0010, 0x0000);
diff --git a/target/linux/generic/backport-6.12/780-15-v6.13-r8169-remove-leftover-locks-after-reverted-change.patch b/target/linux/generic/backport-6.12/780-15-v6.13-r8169-remove-leftover-locks-after-reverted-change.patch
new file mode 100644 (file)
index 0000000..19bcf08
--- /dev/null
@@ -0,0 +1,113 @@
+From 83cb4b470c66b37b19a347a35cea01e0cbdd258d Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Mon, 4 Nov 2024 23:16:20 +0100
+Subject: [PATCH] r8169: remove leftover locks after reverted change
+
+After e31a9fedc7d8 ("Revert "r8169: disable ASPM during NAPI poll"")
+these locks aren't needed any longer.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/680f2606-ac7d-4ced-8694-e5033855da9b@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 29 ++---------------------
+ 1 file changed, 2 insertions(+), 27 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -661,13 +661,9 @@ struct rtl8169_private {
+               struct work_struct work;
+       } wk;
+-      raw_spinlock_t config25_lock;
+       raw_spinlock_t mac_ocp_lock;
+       struct mutex led_lock;  /* serialize LED ctrl RMW access */
+-      raw_spinlock_t cfg9346_usage_lock;
+-      int cfg9346_usage_count;
+-
+       unsigned supports_gmii:1;
+       unsigned aspm_manageable:1;
+       unsigned dash_enabled:1;
+@@ -721,22 +717,12 @@ static inline struct device *tp_to_dev(s
+ static void rtl_lock_config_regs(struct rtl8169_private *tp)
+ {
+-      unsigned long flags;
+-
+-      raw_spin_lock_irqsave(&tp->cfg9346_usage_lock, flags);
+-      if (!--tp->cfg9346_usage_count)
+-              RTL_W8(tp, Cfg9346, Cfg9346_Lock);
+-      raw_spin_unlock_irqrestore(&tp->cfg9346_usage_lock, flags);
++      RTL_W8(tp, Cfg9346, Cfg9346_Lock);
+ }
+ static void rtl_unlock_config_regs(struct rtl8169_private *tp)
+ {
+-      unsigned long flags;
+-
+-      raw_spin_lock_irqsave(&tp->cfg9346_usage_lock, flags);
+-      if (!tp->cfg9346_usage_count++)
+-              RTL_W8(tp, Cfg9346, Cfg9346_Unlock);
+-      raw_spin_unlock_irqrestore(&tp->cfg9346_usage_lock, flags);
++      RTL_W8(tp, Cfg9346, Cfg9346_Unlock);
+ }
+ static void rtl_pci_commit(struct rtl8169_private *tp)
+@@ -747,24 +733,18 @@ static void rtl_pci_commit(struct rtl816
+ static void rtl_mod_config2(struct rtl8169_private *tp, u8 clear, u8 set)
+ {
+-      unsigned long flags;
+       u8 val;
+-      raw_spin_lock_irqsave(&tp->config25_lock, flags);
+       val = RTL_R8(tp, Config2);
+       RTL_W8(tp, Config2, (val & ~clear) | set);
+-      raw_spin_unlock_irqrestore(&tp->config25_lock, flags);
+ }
+ static void rtl_mod_config5(struct rtl8169_private *tp, u8 clear, u8 set)
+ {
+-      unsigned long flags;
+       u8 val;
+-      raw_spin_lock_irqsave(&tp->config25_lock, flags);
+       val = RTL_R8(tp, Config5);
+       RTL_W8(tp, Config5, (val & ~clear) | set);
+-      raw_spin_unlock_irqrestore(&tp->config25_lock, flags);
+ }
+ static bool rtl_is_8125(struct rtl8169_private *tp)
+@@ -1570,7 +1550,6 @@ static void __rtl8169_set_wol(struct rtl
+               { WAKE_MAGIC, Config3, MagicPacket }
+       };
+       unsigned int i, tmp = ARRAY_SIZE(cfg);
+-      unsigned long flags;
+       u8 options;
+       rtl_unlock_config_regs(tp);
+@@ -1589,14 +1568,12 @@ static void __rtl8169_set_wol(struct rtl
+                       r8168_mac_ocp_modify(tp, 0xc0b6, BIT(0), 0);
+       }
+-      raw_spin_lock_irqsave(&tp->config25_lock, flags);
+       for (i = 0; i < tmp; i++) {
+               options = RTL_R8(tp, cfg[i].reg) & ~cfg[i].mask;
+               if (wolopts & cfg[i].opt)
+                       options |= cfg[i].mask;
+               RTL_W8(tp, cfg[i].reg, options);
+       }
+-      raw_spin_unlock_irqrestore(&tp->config25_lock, flags);
+       switch (tp->mac_version) {
+       case RTL_GIGA_MAC_VER_02 ... RTL_GIGA_MAC_VER_06:
+@@ -5477,8 +5454,6 @@ static int rtl_init_one(struct pci_dev *
+       tp->supports_gmii = ent->driver_data == RTL_CFG_NO_GBIT ? 0 : 1;
+       tp->ocp_base = OCP_STD_PHY_BASE;
+-      raw_spin_lock_init(&tp->cfg9346_usage_lock);
+-      raw_spin_lock_init(&tp->config25_lock);
+       raw_spin_lock_init(&tp->mac_ocp_lock);
+       mutex_init(&tp->led_lock);
diff --git a/target/linux/generic/backport-6.12/780-16-v6.13-r8169-improve-__rtl8169_set_wol.patch b/target/linux/generic/backport-6.12/780-16-v6.13-r8169-improve-__rtl8169_set_wol.patch
new file mode 100644 (file)
index 0000000..3158ac5
--- /dev/null
@@ -0,0 +1,108 @@
+From c507e96b5763b36b63ad50ad804341f72ea000e4 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Wed, 6 Nov 2024 17:55:45 +0100
+Subject: [PATCH] r8169: improve __rtl8169_set_wol
+
+Add helper r8169_mod_reg8_cond() what allows to significantly simplify
+__rtl8169_set_wol().
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/697b197a-8eac-40c6-8847-27093cacec36@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 55 ++++++++++-------------
+ 1 file changed, 24 insertions(+), 31 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -747,6 +747,20 @@ static void rtl_mod_config5(struct rtl81
+       RTL_W8(tp, Config5, (val & ~clear) | set);
+ }
++static void r8169_mod_reg8_cond(struct rtl8169_private *tp, int reg,
++                              u8 bits, bool cond)
++{
++      u8 val, old_val;
++
++      old_val = RTL_R8(tp, reg);
++      if (cond)
++              val = old_val | bits;
++      else
++              val = old_val & ~bits;
++      if (val != old_val)
++              RTL_W8(tp, reg, val);
++}
++
+ static bool rtl_is_8125(struct rtl8169_private *tp)
+ {
+       return tp->mac_version >= RTL_GIGA_MAC_VER_61;
+@@ -1537,58 +1551,37 @@ static void rtl8169_get_wol(struct net_d
+ static void __rtl8169_set_wol(struct rtl8169_private *tp, u32 wolopts)
+ {
+-      static const struct {
+-              u32 opt;
+-              u16 reg;
+-              u8  mask;
+-      } cfg[] = {
+-              { WAKE_PHY,   Config3, LinkUp },
+-              { WAKE_UCAST, Config5, UWF },
+-              { WAKE_BCAST, Config5, BWF },
+-              { WAKE_MCAST, Config5, MWF },
+-              { WAKE_ANY,   Config5, LanWake },
+-              { WAKE_MAGIC, Config3, MagicPacket }
+-      };
+-      unsigned int i, tmp = ARRAY_SIZE(cfg);
+-      u8 options;
+-
+       rtl_unlock_config_regs(tp);
+       if (rtl_is_8168evl_up(tp)) {
+-              tmp--;
+               if (wolopts & WAKE_MAGIC)
+                       rtl_eri_set_bits(tp, 0x0dc, MagicPacket_v2);
+               else
+                       rtl_eri_clear_bits(tp, 0x0dc, MagicPacket_v2);
+       } else if (rtl_is_8125(tp)) {
+-              tmp--;
+               if (wolopts & WAKE_MAGIC)
+                       r8168_mac_ocp_modify(tp, 0xc0b6, 0, BIT(0));
+               else
+                       r8168_mac_ocp_modify(tp, 0xc0b6, BIT(0), 0);
++      } else {
++              r8169_mod_reg8_cond(tp, Config3, MagicPacket,
++                                  wolopts & WAKE_MAGIC);
+       }
+-      for (i = 0; i < tmp; i++) {
+-              options = RTL_R8(tp, cfg[i].reg) & ~cfg[i].mask;
+-              if (wolopts & cfg[i].opt)
+-                      options |= cfg[i].mask;
+-              RTL_W8(tp, cfg[i].reg, options);
+-      }
++      r8169_mod_reg8_cond(tp, Config3, LinkUp, wolopts & WAKE_PHY);
++      r8169_mod_reg8_cond(tp, Config5, UWF, wolopts & WAKE_UCAST);
++      r8169_mod_reg8_cond(tp, Config5, BWF, wolopts & WAKE_BCAST);
++      r8169_mod_reg8_cond(tp, Config5, MWF, wolopts & WAKE_MCAST);
++      r8169_mod_reg8_cond(tp, Config5, LanWake, wolopts);
+       switch (tp->mac_version) {
+       case RTL_GIGA_MAC_VER_02 ... RTL_GIGA_MAC_VER_06:
+-              options = RTL_R8(tp, Config1) & ~PMEnable;
+-              if (wolopts)
+-                      options |= PMEnable;
+-              RTL_W8(tp, Config1, options);
++              r8169_mod_reg8_cond(tp, Config1, PMEnable, wolopts);
+               break;
+       case RTL_GIGA_MAC_VER_34:
+       case RTL_GIGA_MAC_VER_37:
+       case RTL_GIGA_MAC_VER_39 ... RTL_GIGA_MAC_VER_66:
+-              if (wolopts)
+-                      rtl_mod_config2(tp, 0, PME_SIGNAL);
+-              else
+-                      rtl_mod_config2(tp, PME_SIGNAL, 0);
++              r8169_mod_reg8_cond(tp, Config2, PME_SIGNAL, wolopts);
+               break;
+       default:
+               break;
diff --git a/target/linux/generic/backport-6.12/780-17-v6.13-r8169-improve-rtl_set_d3_pll_down.patch b/target/linux/generic/backport-6.12/780-17-v6.13-r8169-improve-rtl_set_d3_pll_down.patch
new file mode 100644 (file)
index 0000000..ede589e
--- /dev/null
@@ -0,0 +1,44 @@
+From 330dc2297c82953dff402e0b4176a5383a618538 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Wed, 6 Nov 2024 17:56:28 +0100
+Subject: [PATCH] r8169: improve rtl_set_d3_pll_down
+
+Make use of new helper r8169_mod_reg8_cond() and move from a switch()
+to an if() clause. Benefit is that we don't have to touch this piece of
+code each time support for a new chip version is added.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/e1ccdb85-a4ed-4800-89c2-89770ff06452@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 18 +++++-------------
+ 1 file changed, 5 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -1430,19 +1430,11 @@ static enum rtl_dash_type rtl_get_dash_t
+ static void rtl_set_d3_pll_down(struct rtl8169_private *tp, bool enable)
+ {
+-      switch (tp->mac_version) {
+-      case RTL_GIGA_MAC_VER_25 ... RTL_GIGA_MAC_VER_26:
+-      case RTL_GIGA_MAC_VER_29 ... RTL_GIGA_MAC_VER_30:
+-      case RTL_GIGA_MAC_VER_32 ... RTL_GIGA_MAC_VER_37:
+-      case RTL_GIGA_MAC_VER_39 ... RTL_GIGA_MAC_VER_66:
+-              if (enable)
+-                      RTL_W8(tp, PMCH, RTL_R8(tp, PMCH) & ~D3_NO_PLL_DOWN);
+-              else
+-                      RTL_W8(tp, PMCH, RTL_R8(tp, PMCH) | D3_NO_PLL_DOWN);
+-              break;
+-      default:
+-              break;
+-      }
++      if (tp->mac_version >= RTL_GIGA_MAC_VER_25 &&
++          tp->mac_version != RTL_GIGA_MAC_VER_28 &&
++          tp->mac_version != RTL_GIGA_MAC_VER_31 &&
++          tp->mac_version != RTL_GIGA_MAC_VER_38)
++              r8169_mod_reg8_cond(tp, PMCH, D3_NO_PLL_DOWN, !enable);
+ }
+ static void rtl_reset_packet_filter(struct rtl8169_private *tp)
diff --git a/target/linux/generic/backport-6.12/780-18-v6.13-r8169-align-WAKE_PHY-handling-with-r8125-r8126-vendo.patch b/target/linux/generic/backport-6.12/780-18-v6.13-r8169-align-WAKE_PHY-handling-with-r8125-r8126-vendo.patch
new file mode 100644 (file)
index 0000000..ceb8c12
--- /dev/null
@@ -0,0 +1,29 @@
+From e3e9e9039fa6ae885c7d5c954d7b9f105fa23e8f Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Wed, 6 Nov 2024 17:57:08 +0100
+Subject: [PATCH] r8169: align WAKE_PHY handling with r8125/r8126 vendor
+ drivers
+
+Vendor drivers r8125/r8126 apply this additional magic setting when
+enabling WAKE_PHY, so do the same here.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/51130715-45be-4db5-abb7-05d87e1f5df9@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -1561,6 +1561,9 @@ static void __rtl8169_set_wol(struct rtl
+       }
+       r8169_mod_reg8_cond(tp, Config3, LinkUp, wolopts & WAKE_PHY);
++      if (rtl_is_8125(tp))
++              r8168_mac_ocp_modify(tp, 0xe0c6, 0x3f,
++                                   wolopts & WAKE_PHY ? 0x13 : 0);
+       r8169_mod_reg8_cond(tp, Config5, UWF, wolopts & WAKE_UCAST);
+       r8169_mod_reg8_cond(tp, Config5, BWF, wolopts & WAKE_BCAST);
+       r8169_mod_reg8_cond(tp, Config5, MWF, wolopts & WAKE_MCAST);
diff --git a/target/linux/generic/backport-6.12/780-19-v6.13-r8169-use-helper-r8169_mod_reg8_cond-to-simplify-rtl.patch b/target/linux/generic/backport-6.12/780-19-v6.13-r8169-use-helper-r8169_mod_reg8_cond-to-simplify-rtl.patch
new file mode 100644 (file)
index 0000000..d6332df
--- /dev/null
@@ -0,0 +1,117 @@
+From 7a3bcd39ae1f0e3ab896d9df62339ab4297a0bfd Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Sat, 9 Nov 2024 23:12:12 +0100
+Subject: [PATCH] r8169: use helper r8169_mod_reg8_cond to simplify
+ rtl_jumbo_config
+
+Use recently added helper r8169_mod_reg8_cond() to simplify jumbo
+mode configuration.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/3df1d484-a02e-46e7-8f75-db5b428e422e@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 77 ++++-------------------
+ 1 file changed, 11 insertions(+), 66 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -2542,86 +2542,31 @@ static void rtl8169_init_ring_indexes(st
+       tp->dirty_tx = tp->cur_tx = tp->cur_rx = 0;
+ }
+-static void r8168c_hw_jumbo_enable(struct rtl8169_private *tp)
+-{
+-      RTL_W8(tp, Config3, RTL_R8(tp, Config3) | Jumbo_En0);
+-      RTL_W8(tp, Config4, RTL_R8(tp, Config4) | Jumbo_En1);
+-}
+-
+-static void r8168c_hw_jumbo_disable(struct rtl8169_private *tp)
+-{
+-      RTL_W8(tp, Config3, RTL_R8(tp, Config3) & ~Jumbo_En0);
+-      RTL_W8(tp, Config4, RTL_R8(tp, Config4) & ~Jumbo_En1);
+-}
+-
+-static void r8168dp_hw_jumbo_enable(struct rtl8169_private *tp)
+-{
+-      RTL_W8(tp, Config3, RTL_R8(tp, Config3) | Jumbo_En0);
+-}
+-
+-static void r8168dp_hw_jumbo_disable(struct rtl8169_private *tp)
+-{
+-      RTL_W8(tp, Config3, RTL_R8(tp, Config3) & ~Jumbo_En0);
+-}
+-
+-static void r8168e_hw_jumbo_enable(struct rtl8169_private *tp)
+-{
+-      RTL_W8(tp, MaxTxPacketSize, 0x24);
+-      RTL_W8(tp, Config3, RTL_R8(tp, Config3) | Jumbo_En0);
+-      RTL_W8(tp, Config4, RTL_R8(tp, Config4) | 0x01);
+-}
+-
+-static void r8168e_hw_jumbo_disable(struct rtl8169_private *tp)
+-{
+-      RTL_W8(tp, MaxTxPacketSize, 0x3f);
+-      RTL_W8(tp, Config3, RTL_R8(tp, Config3) & ~Jumbo_En0);
+-      RTL_W8(tp, Config4, RTL_R8(tp, Config4) & ~0x01);
+-}
+-
+-static void r8168b_1_hw_jumbo_enable(struct rtl8169_private *tp)
+-{
+-      RTL_W8(tp, Config4, RTL_R8(tp, Config4) | (1 << 0));
+-}
+-
+-static void r8168b_1_hw_jumbo_disable(struct rtl8169_private *tp)
+-{
+-      RTL_W8(tp, Config4, RTL_R8(tp, Config4) & ~(1 << 0));
+-}
+-
+ static void rtl_jumbo_config(struct rtl8169_private *tp)
+ {
+       bool jumbo = tp->dev->mtu > ETH_DATA_LEN;
+       int readrq = 4096;
++      if (jumbo && tp->mac_version >= RTL_GIGA_MAC_VER_17 &&
++          tp->mac_version <= RTL_GIGA_MAC_VER_26)
++              readrq = 512;
++
+       rtl_unlock_config_regs(tp);
+       switch (tp->mac_version) {
+       case RTL_GIGA_MAC_VER_17:
+-              if (jumbo) {
+-                      readrq = 512;
+-                      r8168b_1_hw_jumbo_enable(tp);
+-              } else {
+-                      r8168b_1_hw_jumbo_disable(tp);
+-              }
++              r8169_mod_reg8_cond(tp, Config4, BIT(0), jumbo);
+               break;
+       case RTL_GIGA_MAC_VER_18 ... RTL_GIGA_MAC_VER_26:
+-              if (jumbo) {
+-                      readrq = 512;
+-                      r8168c_hw_jumbo_enable(tp);
+-              } else {
+-                      r8168c_hw_jumbo_disable(tp);
+-              }
++              r8169_mod_reg8_cond(tp, Config3, Jumbo_En0, jumbo);
++              r8169_mod_reg8_cond(tp, Config4, Jumbo_En1, jumbo);
+               break;
+       case RTL_GIGA_MAC_VER_28:
+-              if (jumbo)
+-                      r8168dp_hw_jumbo_enable(tp);
+-              else
+-                      r8168dp_hw_jumbo_disable(tp);
++              r8169_mod_reg8_cond(tp, Config3, Jumbo_En0, jumbo);
+               break;
+       case RTL_GIGA_MAC_VER_31 ... RTL_GIGA_MAC_VER_33:
+-              if (jumbo)
+-                      r8168e_hw_jumbo_enable(tp);
+-              else
+-                      r8168e_hw_jumbo_disable(tp);
++              RTL_W8(tp, MaxTxPacketSize, jumbo ? 0x24 : 0x3f);
++              r8169_mod_reg8_cond(tp, Config3, Jumbo_En0, jumbo);
++              r8169_mod_reg8_cond(tp, Config4, BIT(0), jumbo);
+               break;
+       default:
+               break;
diff --git a/target/linux/generic/backport-6.12/780-20-v6.13-r8169-copy-vendor-driver-2.5G-5G-EEE-advertisement-c.patch b/target/linux/generic/backport-6.12/780-20-v6.13-r8169-copy-vendor-driver-2.5G-5G-EEE-advertisement-c.patch
new file mode 100644 (file)
index 0000000..fee9d95
--- /dev/null
@@ -0,0 +1,82 @@
+From e340bff27e63ed61a1e9895bed546107859e48a7 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Fri, 8 Nov 2024 08:08:24 +0100
+Subject: [PATCH] r8169: copy vendor driver 2.5G/5G EEE advertisement
+ constraints
+
+Vendor driver r8125 doesn't advertise 2.5G EEE on RTL8125A, and r8126
+doesn't advertise 5G EEE. Likely there are compatibility issues,
+therefore do the same in r8169.
+With this change we don't have to disable 2.5G EEE advertisement in
+rtl8125a_config_eee_phy() any longer.
+We use new phylib accessor phy_set_eee_broken() to mark the respective
+EEE modes as broken.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/ce185e10-8a2f-4cf8-a49b-fd8fb3c3c8a1@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c       |  6 ++++++
+ drivers/net/ethernet/realtek/r8169_phy_config.c | 16 ++++------------
+ 2 files changed, 10 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -5253,6 +5253,11 @@ static int r8169_mdio_register(struct rt
+               phy_support_eee(tp->phydev);
+       phy_support_asym_pause(tp->phydev);
++      /* mimic behavior of r8125/r8126 vendor drivers */
++      if (tp->mac_version == RTL_GIGA_MAC_VER_61)
++              tp->phydev->eee_broken_modes |= MDIO_EEE_2_5GT;
++      tp->phydev->eee_broken_modes |= MDIO_EEE_5GT;
++
+       /* PHY will be woken up in rtl_open() */
+       phy_suspend(tp->phydev);
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -96,15 +96,7 @@ static void rtl8125_common_config_eee_ph
+       phy_modify_paged(phydev, 0xa4a, 0x11, 0x0200, 0x0000);
+ }
+-static void rtl8125a_config_eee_phy(struct phy_device *phydev)
+-{
+-      rtl8168g_config_eee_phy(phydev);
+-      /* disable EEE at 2.5Gbps */
+-      phy_modify_paged(phydev, 0xa6d, 0x12, 0x0001, 0x0000);
+-      rtl8125_common_config_eee_phy(phydev);
+-}
+-
+-static void rtl8125b_config_eee_phy(struct phy_device *phydev)
++static void rtl8125_config_eee_phy(struct phy_device *phydev)
+ {
+       rtl8168g_config_eee_phy(phydev);
+       rtl8125_common_config_eee_phy(phydev);
+@@ -1066,7 +1058,7 @@ static void rtl8125a_2_hw_phy_config(str
+       rtl8168g_enable_gphy_10m(phydev);
+       rtl8168g_disable_aldps(phydev);
+-      rtl8125a_config_eee_phy(phydev);
++      rtl8125_config_eee_phy(phydev);
+ }
+ static void rtl8125b_hw_phy_config(struct rtl8169_private *tp,
+@@ -1106,7 +1098,7 @@ static void rtl8125b_hw_phy_config(struc
+       rtl8125_legacy_force_mode(phydev);
+       rtl8168g_disable_aldps(phydev);
+-      rtl8125b_config_eee_phy(phydev);
++      rtl8125_config_eee_phy(phydev);
+ }
+ static void rtl8125d_hw_phy_config(struct rtl8169_private *tp,
+@@ -1116,7 +1108,7 @@ static void rtl8125d_hw_phy_config(struc
+       rtl8168g_enable_gphy_10m(phydev);
+       rtl8125_legacy_force_mode(phydev);
+       rtl8168g_disable_aldps(phydev);
+-      rtl8125b_config_eee_phy(phydev);
++      rtl8125_config_eee_phy(phydev);
+ }
+ static void rtl8126a_hw_phy_config(struct rtl8169_private *tp,
diff --git a/target/linux/generic/backport-6.12/780-21-v6.14-r8169-remove-unused-flag-RTL_FLAG_TASK_RESET_NO_QUEU.patch b/target/linux/generic/backport-6.12/780-21-v6.14-r8169-remove-unused-flag-RTL_FLAG_TASK_RESET_NO_QUEU.patch
new file mode 100644 (file)
index 0000000..182858a
--- /dev/null
@@ -0,0 +1,35 @@
+From 2e20bf8cc05766dcd0357cdfcada49e1bc45512b Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Mon, 2 Dec 2024 21:14:35 +0100
+Subject: [PATCH] r8169: remove unused flag RTL_FLAG_TASK_RESET_NO_QUEUE_WAKE
+
+After 854d71c555dfc3 ("r8169: remove original workaround for RTL8125
+broken rx issue") this flag isn't used any longer. So remove it.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Michal Swiatkowski <michal.swiatkowski@linux.intel.com>
+Link: https://patch.msgid.link/d9dd214b-3027-4f60-b0e8-6f34a0c76582@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 3 ---
+ 1 file changed, 3 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -622,7 +622,6 @@ struct rtl8169_tc_offsets {
+ enum rtl_flag {
+       RTL_FLAG_TASK_RESET_PENDING,
+-      RTL_FLAG_TASK_RESET_NO_QUEUE_WAKE,
+       RTL_FLAG_TASK_TX_TIMEOUT,
+       RTL_FLAG_MAX
+ };
+@@ -4746,8 +4745,6 @@ static void rtl_task(struct work_struct
+ reset:
+               rtl_reset_work(tp);
+               netif_wake_queue(tp->dev);
+-      } else if (test_and_clear_bit(RTL_FLAG_TASK_RESET_NO_QUEUE_WAKE, tp->wk.flags)) {
+-              rtl_reset_work(tp);
+       }
+ }
diff --git a/target/linux/generic/backport-6.12/780-22-v6.14-r8169-remove-support-for-chip-version-11.patch b/target/linux/generic/backport-6.12/780-22-v6.14-r8169-remove-support-for-chip-version-11.patch
new file mode 100644 (file)
index 0000000..055e0f0
--- /dev/null
@@ -0,0 +1,114 @@
+From bb18265c3aba92b91a1355609769f3e967b65dee Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Mon, 2 Dec 2024 21:20:02 +0100
+Subject: [PATCH] r8169: remove support for chip version 11
+
+This is a follow-up to 982300c115d2 ("r8169: remove detection of chip
+version 11 (early RTL8168b)"). Nobody complained yet, so remove
+support for this chip version.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/b689ab6d-20b5-4b64-bd7e-531a0a972ba3@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169.h            |  2 +-
+ drivers/net/ethernet/realtek/r8169_main.c       | 14 +-------------
+ drivers/net/ethernet/realtek/r8169_phy_config.c | 10 ----------
+ 3 files changed, 2 insertions(+), 24 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169.h
++++ b/drivers/net/ethernet/realtek/r8169.h
+@@ -23,7 +23,7 @@ enum mac_version {
+       RTL_GIGA_MAC_VER_08,
+       RTL_GIGA_MAC_VER_09,
+       RTL_GIGA_MAC_VER_10,
+-      RTL_GIGA_MAC_VER_11,
++      /* support for RTL_GIGA_MAC_VER_11 has been removed */
+       /* RTL_GIGA_MAC_VER_12 was handled the same as VER_17 */
+       /* RTL_GIGA_MAC_VER_13 was merged with VER_10 */
+       RTL_GIGA_MAC_VER_14,
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -103,7 +103,6 @@ static const struct {
+       [RTL_GIGA_MAC_VER_08] = {"RTL8102e"                             },
+       [RTL_GIGA_MAC_VER_09] = {"RTL8102e/RTL8103e"                    },
+       [RTL_GIGA_MAC_VER_10] = {"RTL8101e/RTL8100e"                    },
+-      [RTL_GIGA_MAC_VER_11] = {"RTL8168b/8111b"                       },
+       [RTL_GIGA_MAC_VER_14] = {"RTL8401"                              },
+       [RTL_GIGA_MAC_VER_17] = {"RTL8168b/8111b"                       },
+       [RTL_GIGA_MAC_VER_18] = {"RTL8168cp/8111cp"                     },
+@@ -2334,7 +2333,7 @@ static enum mac_version rtl8169_get_mac_
+               /* 8168B family. */
+               { 0x7c8, 0x380, RTL_GIGA_MAC_VER_17 },
+-              /* This one is very old and rare, let's see if anybody complains.
++              /* This one is very old and rare, support has been removed.
+                * { 0x7c8, 0x300,      RTL_GIGA_MAC_VER_11 },
+                */
+@@ -3826,7 +3825,6 @@ static void rtl_hw_config(struct rtl8169
+               [RTL_GIGA_MAC_VER_08] = rtl_hw_start_8102e_3,
+               [RTL_GIGA_MAC_VER_09] = rtl_hw_start_8102e_2,
+               [RTL_GIGA_MAC_VER_10] = NULL,
+-              [RTL_GIGA_MAC_VER_11] = rtl_hw_start_8168b,
+               [RTL_GIGA_MAC_VER_14] = rtl_hw_start_8401,
+               [RTL_GIGA_MAC_VER_17] = rtl_hw_start_8168b,
+               [RTL_GIGA_MAC_VER_18] = rtl_hw_start_8168cp_1,
+@@ -4702,12 +4700,6 @@ static irqreturn_t rtl8169_interrupt(int
+       if (status & LinkChg)
+               phy_mac_interrupt(tp->phydev);
+-      if (unlikely(status & RxFIFOOver &&
+-          tp->mac_version == RTL_GIGA_MAC_VER_11)) {
+-              netif_stop_queue(tp->dev);
+-              rtl_schedule_task(tp, RTL_FLAG_TASK_RESET_PENDING);
+-      }
+-
+       rtl_irq_disable(tp);
+       napi_schedule(&tp->napi);
+ out:
+@@ -5124,9 +5116,6 @@ static void rtl_set_irq_mask(struct rtl8
+       if (tp->mac_version <= RTL_GIGA_MAC_VER_06)
+               tp->irq_mask |= SYSErr | RxFIFOOver;
+-      else if (tp->mac_version == RTL_GIGA_MAC_VER_11)
+-              /* special workaround needed */
+-              tp->irq_mask |= RxFIFOOver;
+ }
+ static int rtl_alloc_irq(struct rtl8169_private *tp)
+@@ -5321,7 +5310,6 @@ static int rtl_jumbo_max(struct rtl8169_
+       case RTL_GIGA_MAC_VER_02 ... RTL_GIGA_MAC_VER_06:
+               return JUMBO_7K;
+       /* RTL8168b */
+-      case RTL_GIGA_MAC_VER_11:
+       case RTL_GIGA_MAC_VER_17:
+               return JUMBO_4K;
+       /* RTL8168c */
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -276,15 +276,6 @@ static void rtl8169sce_hw_phy_config(str
+       rtl_writephy_batch(phydev, phy_reg_init);
+ }
+-static void rtl8168bb_hw_phy_config(struct rtl8169_private *tp,
+-                                  struct phy_device *phydev)
+-{
+-      phy_write(phydev, 0x1f, 0x0001);
+-      phy_set_bits(phydev, 0x16, BIT(0));
+-      phy_write(phydev, 0x10, 0xf41b);
+-      phy_write(phydev, 0x1f, 0x0000);
+-}
+-
+ static void rtl8168bef_hw_phy_config(struct rtl8169_private *tp,
+                                    struct phy_device *phydev)
+ {
+@@ -1136,7 +1127,6 @@ void r8169_hw_phy_config(struct rtl8169_
+               [RTL_GIGA_MAC_VER_08] = rtl8102e_hw_phy_config,
+               [RTL_GIGA_MAC_VER_09] = rtl8102e_hw_phy_config,
+               [RTL_GIGA_MAC_VER_10] = NULL,
+-              [RTL_GIGA_MAC_VER_11] = rtl8168bb_hw_phy_config,
+               [RTL_GIGA_MAC_VER_14] = rtl8401_hw_phy_config,
+               [RTL_GIGA_MAC_VER_17] = rtl8168bef_hw_phy_config,
+               [RTL_GIGA_MAC_VER_18] = rtl8168cp_1_hw_phy_config,
diff --git a/target/linux/generic/backport-6.12/780-23-v6.14-r8169-adjust-version-numbering-for-RTL8126.patch b/target/linux/generic/backport-6.12/780-23-v6.14-r8169-adjust-version-numbering-for-RTL8126.patch
new file mode 100644 (file)
index 0000000..7ea47bd
--- /dev/null
@@ -0,0 +1,257 @@
+From b299ea0069284186b0d3d54aebe87f0d195d457a Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Fri, 13 Dec 2024 20:01:41 +0100
+Subject: [PATCH] r8169: adjust version numbering for RTL8126
+
+Adjust version numbering for RTL8126, so that it doesn't overlap with
+new RTL8125 versions.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/6a354364-20e9-48ad-a198-468264288757@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169.h          |  4 +-
+ drivers/net/ethernet/realtek/r8169_main.c     | 62 +++++++++----------
+ .../net/ethernet/realtek/r8169_phy_config.c   |  4 +-
+ 3 files changed, 35 insertions(+), 35 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169.h
++++ b/drivers/net/ethernet/realtek/r8169.h
+@@ -69,8 +69,8 @@ enum mac_version {
+       RTL_GIGA_MAC_VER_61,
+       RTL_GIGA_MAC_VER_63,
+       RTL_GIGA_MAC_VER_64,
+-      RTL_GIGA_MAC_VER_65,
+-      RTL_GIGA_MAC_VER_66,
++      RTL_GIGA_MAC_VER_70,
++      RTL_GIGA_MAC_VER_71,
+       RTL_GIGA_MAC_NONE
+ };
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -139,8 +139,8 @@ static const struct {
+       /* reserve 62 for CFG_METHOD_4 in the vendor driver */
+       [RTL_GIGA_MAC_VER_63] = {"RTL8125B",            FIRMWARE_8125B_2},
+       [RTL_GIGA_MAC_VER_64] = {"RTL8125D",            FIRMWARE_8125D_1},
+-      [RTL_GIGA_MAC_VER_65] = {"RTL8126A",            FIRMWARE_8126A_2},
+-      [RTL_GIGA_MAC_VER_66] = {"RTL8126A",            FIRMWARE_8126A_3},
++      [RTL_GIGA_MAC_VER_70] = {"RTL8126A",            FIRMWARE_8126A_2},
++      [RTL_GIGA_MAC_VER_71] = {"RTL8126A",            FIRMWARE_8126A_3},
+ };
+ static const struct pci_device_id rtl8169_pci_tbl[] = {
+@@ -1227,7 +1227,7 @@ static void rtl_writephy(struct rtl8169_
+       case RTL_GIGA_MAC_VER_31:
+               r8168dp_2_mdio_write(tp, location, val);
+               break;
+-      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_66:
++      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_71:
+               r8168g_mdio_write(tp, location, val);
+               break;
+       default:
+@@ -1242,7 +1242,7 @@ static int rtl_readphy(struct rtl8169_pr
+       case RTL_GIGA_MAC_VER_28:
+       case RTL_GIGA_MAC_VER_31:
+               return r8168dp_2_mdio_read(tp, location);
+-      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_66:
++      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_71:
+               return r8168g_mdio_read(tp, location);
+       default:
+               return r8169_mdio_read(tp, location);
+@@ -1573,7 +1573,7 @@ static void __rtl8169_set_wol(struct rtl
+               break;
+       case RTL_GIGA_MAC_VER_34:
+       case RTL_GIGA_MAC_VER_37:
+-      case RTL_GIGA_MAC_VER_39 ... RTL_GIGA_MAC_VER_66:
++      case RTL_GIGA_MAC_VER_39 ... RTL_GIGA_MAC_VER_71:
+               r8169_mod_reg8_cond(tp, Config2, PME_SIGNAL, wolopts);
+               break;
+       default:
+@@ -2046,7 +2046,7 @@ static void rtl_set_eee_txidle_timer(str
+               tp->tx_lpi_timer = timer_val;
+               r8168_mac_ocp_write(tp, 0xe048, timer_val);
+               break;
+-      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_66:
++      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_71:
+               tp->tx_lpi_timer = timer_val;
+               RTL_W16(tp, EEE_TXIDLE_TIMER_8125, timer_val);
+               break;
+@@ -2254,8 +2254,8 @@ static enum mac_version rtl8169_get_mac_
+               enum mac_version ver;
+       } mac_info[] = {
+               /* 8126A family. */
+-              { 0x7cf, 0x64a, RTL_GIGA_MAC_VER_66 },
+-              { 0x7cf, 0x649, RTL_GIGA_MAC_VER_65 },
++              { 0x7cf, 0x64a, RTL_GIGA_MAC_VER_71 },
++              { 0x7cf, 0x649, RTL_GIGA_MAC_VER_70 },
+               /* 8125D family. */
+               { 0x7cf, 0x688, RTL_GIGA_MAC_VER_64 },
+@@ -2525,7 +2525,7 @@ static void rtl_init_rxcfg(struct rtl816
+       case RTL_GIGA_MAC_VER_61:
+               RTL_W32(tp, RxConfig, RX_FETCH_DFLT_8125 | RX_DMA_BURST);
+               break;
+-      case RTL_GIGA_MAC_VER_63 ... RTL_GIGA_MAC_VER_66:
++      case RTL_GIGA_MAC_VER_63 ... RTL_GIGA_MAC_VER_71:
+               RTL_W32(tp, RxConfig, RX_FETCH_DFLT_8125 | RX_DMA_BURST |
+                       RX_PAUSE_SLOT_ON);
+               break;
+@@ -2657,7 +2657,7 @@ static void rtl_wait_txrx_fifo_empty(str
+       case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_61:
+               rtl_loop_wait_high(tp, &rtl_rxtx_empty_cond, 100, 42);
+               break;
+-      case RTL_GIGA_MAC_VER_63 ... RTL_GIGA_MAC_VER_66:
++      case RTL_GIGA_MAC_VER_63 ... RTL_GIGA_MAC_VER_71:
+               RTL_W8(tp, ChipCmd, RTL_R8(tp, ChipCmd) | StopReq);
+               rtl_loop_wait_high(tp, &rtl_rxtx_empty_cond, 100, 42);
+               rtl_loop_wait_high(tp, &rtl_rxtx_empty_cond_2, 100, 42);
+@@ -2923,7 +2923,7 @@ static void rtl_enable_exit_l1(struct rt
+       case RTL_GIGA_MAC_VER_37 ... RTL_GIGA_MAC_VER_38:
+               rtl_eri_set_bits(tp, 0xd4, 0x0c00);
+               break;
+-      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_66:
++      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_71:
+               r8168_mac_ocp_modify(tp, 0xc0ac, 0, 0x1f80);
+               break;
+       default:
+@@ -2937,7 +2937,7 @@ static void rtl_disable_exit_l1(struct r
+       case RTL_GIGA_MAC_VER_34 ... RTL_GIGA_MAC_VER_38:
+               rtl_eri_clear_bits(tp, 0xd4, 0x1f00);
+               break;
+-      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_66:
++      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_71:
+               r8168_mac_ocp_modify(tp, 0xc0ac, 0x1f80, 0);
+               break;
+       default:
+@@ -2963,8 +2963,8 @@ static void rtl_hw_aspm_clkreq_enable(st
+               rtl_mod_config5(tp, 0, ASPM_en);
+               switch (tp->mac_version) {
+-              case RTL_GIGA_MAC_VER_65:
+-              case RTL_GIGA_MAC_VER_66:
++              case RTL_GIGA_MAC_VER_70:
++              case RTL_GIGA_MAC_VER_71:
+                       val8 = RTL_R8(tp, INT_CFG0_8125) | INT_CFG0_CLKREQEN;
+                       RTL_W8(tp, INT_CFG0_8125, val8);
+                       break;
+@@ -2975,7 +2975,7 @@ static void rtl_hw_aspm_clkreq_enable(st
+               switch (tp->mac_version) {
+               case RTL_GIGA_MAC_VER_46 ... RTL_GIGA_MAC_VER_48:
+-              case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_66:
++              case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_71:
+                       /* reset ephy tx/rx disable timer */
+                       r8168_mac_ocp_modify(tp, 0xe094, 0xff00, 0);
+                       /* chip can trigger L1.2 */
+@@ -2987,7 +2987,7 @@ static void rtl_hw_aspm_clkreq_enable(st
+       } else {
+               switch (tp->mac_version) {
+               case RTL_GIGA_MAC_VER_46 ... RTL_GIGA_MAC_VER_48:
+-              case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_66:
++              case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_71:
+                       r8168_mac_ocp_modify(tp, 0xe092, 0x00ff, 0);
+                       break;
+               default:
+@@ -2995,8 +2995,8 @@ static void rtl_hw_aspm_clkreq_enable(st
+               }
+               switch (tp->mac_version) {
+-              case RTL_GIGA_MAC_VER_65:
+-              case RTL_GIGA_MAC_VER_66:
++              case RTL_GIGA_MAC_VER_70:
++              case RTL_GIGA_MAC_VER_71:
+                       val8 = RTL_R8(tp, INT_CFG0_8125) & ~INT_CFG0_CLKREQEN;
+                       RTL_W8(tp, INT_CFG0_8125, val8);
+                       break;
+@@ -3716,12 +3716,12 @@ static void rtl_hw_start_8125_common(str
+       /* disable new tx descriptor format */
+       r8168_mac_ocp_modify(tp, 0xeb58, 0x0001, 0x0000);
+-      if (tp->mac_version == RTL_GIGA_MAC_VER_65 ||
+-          tp->mac_version == RTL_GIGA_MAC_VER_66)
++      if (tp->mac_version == RTL_GIGA_MAC_VER_70 ||
++          tp->mac_version == RTL_GIGA_MAC_VER_71)
+               RTL_W8(tp, 0xD8, RTL_R8(tp, 0xD8) & ~0x02);
+-      if (tp->mac_version == RTL_GIGA_MAC_VER_65 ||
+-          tp->mac_version == RTL_GIGA_MAC_VER_66)
++      if (tp->mac_version == RTL_GIGA_MAC_VER_70 ||
++          tp->mac_version == RTL_GIGA_MAC_VER_71)
+               r8168_mac_ocp_modify(tp, 0xe614, 0x0700, 0x0400);
+       else if (tp->mac_version == RTL_GIGA_MAC_VER_63)
+               r8168_mac_ocp_modify(tp, 0xe614, 0x0700, 0x0200);
+@@ -3739,8 +3739,8 @@ static void rtl_hw_start_8125_common(str
+       r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000);
+       r8168_mac_ocp_modify(tp, 0xe040, 0x1000, 0x0000);
+       r8168_mac_ocp_modify(tp, 0xea1c, 0x0003, 0x0001);
+-      if (tp->mac_version == RTL_GIGA_MAC_VER_65 ||
+-          tp->mac_version == RTL_GIGA_MAC_VER_66)
++      if (tp->mac_version == RTL_GIGA_MAC_VER_70 ||
++          tp->mac_version == RTL_GIGA_MAC_VER_71)
+               r8168_mac_ocp_modify(tp, 0xea1c, 0x0300, 0x0000);
+       else
+               r8168_mac_ocp_modify(tp, 0xea1c, 0x0004, 0x0000);
+@@ -3860,8 +3860,8 @@ static void rtl_hw_config(struct rtl8169
+               [RTL_GIGA_MAC_VER_61] = rtl_hw_start_8125a_2,
+               [RTL_GIGA_MAC_VER_63] = rtl_hw_start_8125b,
+               [RTL_GIGA_MAC_VER_64] = rtl_hw_start_8125d,
+-              [RTL_GIGA_MAC_VER_65] = rtl_hw_start_8126a,
+-              [RTL_GIGA_MAC_VER_66] = rtl_hw_start_8126a,
++              [RTL_GIGA_MAC_VER_70] = rtl_hw_start_8126a,
++              [RTL_GIGA_MAC_VER_71] = rtl_hw_start_8126a,
+       };
+       if (hw_configs[tp->mac_version])
+@@ -3882,8 +3882,8 @@ static void rtl_hw_start_8125(struct rtl
+                       RTL_W32(tp, i, 0);
+               break;
+       case RTL_GIGA_MAC_VER_63:
+-      case RTL_GIGA_MAC_VER_65:
+-      case RTL_GIGA_MAC_VER_66:
++      case RTL_GIGA_MAC_VER_70:
++      case RTL_GIGA_MAC_VER_71:
+               for (i = 0xa00; i < 0xa80; i += 4)
+                       RTL_W32(tp, i, 0);
+               RTL_W16(tp, INT_CFG1_8125, 0x0000);
+@@ -4115,7 +4115,7 @@ static void rtl8169_cleanup(struct rtl81
+               RTL_W8(tp, ChipCmd, RTL_R8(tp, ChipCmd) | StopReq);
+               rtl_loop_wait_high(tp, &rtl_txcfg_empty_cond, 100, 666);
+               break;
+-      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_66:
++      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_71:
+               rtl_enable_rxdvgate(tp);
+               fsleep(2000);
+               break;
+@@ -4272,7 +4272,7 @@ static unsigned int rtl_quirk_packet_pad
+       switch (tp->mac_version) {
+       case RTL_GIGA_MAC_VER_34:
+-      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_66:
++      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_71:
+               padto = max_t(unsigned int, padto, ETH_ZLEN);
+               break;
+       default:
+@@ -5291,7 +5291,7 @@ static void rtl_hw_initialize(struct rtl
+       case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_48:
+               rtl_hw_init_8168g(tp);
+               break;
+-      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_66:
++      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_71:
+               rtl_hw_init_8125(tp);
+               break;
+       default:
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -1162,8 +1162,8 @@ void r8169_hw_phy_config(struct rtl8169_
+               [RTL_GIGA_MAC_VER_61] = rtl8125a_2_hw_phy_config,
+               [RTL_GIGA_MAC_VER_63] = rtl8125b_hw_phy_config,
+               [RTL_GIGA_MAC_VER_64] = rtl8125d_hw_phy_config,
+-              [RTL_GIGA_MAC_VER_65] = rtl8126a_hw_phy_config,
+-              [RTL_GIGA_MAC_VER_66] = rtl8126a_hw_phy_config,
++              [RTL_GIGA_MAC_VER_70] = rtl8126a_hw_phy_config,
++              [RTL_GIGA_MAC_VER_71] = rtl8126a_hw_phy_config,
+       };
+       if (phy_configs[ver])
diff --git a/target/linux/generic/backport-6.12/780-24-v6.14-r8169-add-support-for-RTL8125D-rev.b.patch b/target/linux/generic/backport-6.12/780-24-v6.14-r8169-add-support-for-RTL8125D-rev.b.patch
new file mode 100644 (file)
index 0000000..bbc447f
--- /dev/null
@@ -0,0 +1,90 @@
+From b3593df26ab19f114d613693fa8a92ab202803d0 Mon Sep 17 00:00:00 2001
+From: ChunHao Lin <hau@realtek.com>
+Date: Fri, 13 Dec 2024 20:02:58 +0100
+Subject: [PATCH] r8169: add support for RTL8125D rev.b
+
+Add support for RTL8125D rev.b. Its XID is 0x689. It is basically
+based on the one with XID 0x688, but with different firmware file.
+
+Signed-off-by: ChunHao Lin <hau@realtek.com>
+[hkallweit1@gmail.com: rebased after adjusted version numbering]
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/75e5e9ec-d01f-43ac-b0f4-e7456baf18d1@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169.h            | 1 +
+ drivers/net/ethernet/realtek/r8169_main.c       | 6 ++++++
+ drivers/net/ethernet/realtek/r8169_phy_config.c | 1 +
+ 3 files changed, 8 insertions(+)
+
+--- a/drivers/net/ethernet/realtek/r8169.h
++++ b/drivers/net/ethernet/realtek/r8169.h
+@@ -69,6 +69,7 @@ enum mac_version {
+       RTL_GIGA_MAC_VER_61,
+       RTL_GIGA_MAC_VER_63,
+       RTL_GIGA_MAC_VER_64,
++      RTL_GIGA_MAC_VER_65,
+       RTL_GIGA_MAC_VER_70,
+       RTL_GIGA_MAC_VER_71,
+       RTL_GIGA_MAC_NONE
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -56,6 +56,7 @@
+ #define FIRMWARE_8125A_3      "rtl_nic/rtl8125a-3.fw"
+ #define FIRMWARE_8125B_2      "rtl_nic/rtl8125b-2.fw"
+ #define FIRMWARE_8125D_1      "rtl_nic/rtl8125d-1.fw"
++#define FIRMWARE_8125D_2      "rtl_nic/rtl8125d-2.fw"
+ #define FIRMWARE_8126A_2      "rtl_nic/rtl8126a-2.fw"
+ #define FIRMWARE_8126A_3      "rtl_nic/rtl8126a-3.fw"
+@@ -139,6 +140,7 @@ static const struct {
+       /* reserve 62 for CFG_METHOD_4 in the vendor driver */
+       [RTL_GIGA_MAC_VER_63] = {"RTL8125B",            FIRMWARE_8125B_2},
+       [RTL_GIGA_MAC_VER_64] = {"RTL8125D",            FIRMWARE_8125D_1},
++      [RTL_GIGA_MAC_VER_65] = {"RTL8125D",            FIRMWARE_8125D_2},
+       [RTL_GIGA_MAC_VER_70] = {"RTL8126A",            FIRMWARE_8126A_2},
+       [RTL_GIGA_MAC_VER_71] = {"RTL8126A",            FIRMWARE_8126A_3},
+ };
+@@ -705,6 +707,7 @@ MODULE_FIRMWARE(FIRMWARE_8107E_2);
+ MODULE_FIRMWARE(FIRMWARE_8125A_3);
+ MODULE_FIRMWARE(FIRMWARE_8125B_2);
+ MODULE_FIRMWARE(FIRMWARE_8125D_1);
++MODULE_FIRMWARE(FIRMWARE_8125D_2);
+ MODULE_FIRMWARE(FIRMWARE_8126A_2);
+ MODULE_FIRMWARE(FIRMWARE_8126A_3);
+@@ -2258,6 +2261,7 @@ static enum mac_version rtl8169_get_mac_
+               { 0x7cf, 0x649, RTL_GIGA_MAC_VER_70 },
+               /* 8125D family. */
++              { 0x7cf, 0x689, RTL_GIGA_MAC_VER_65 },
+               { 0x7cf, 0x688, RTL_GIGA_MAC_VER_64 },
+               /* 8125B family. */
+@@ -3860,6 +3864,7 @@ static void rtl_hw_config(struct rtl8169
+               [RTL_GIGA_MAC_VER_61] = rtl_hw_start_8125a_2,
+               [RTL_GIGA_MAC_VER_63] = rtl_hw_start_8125b,
+               [RTL_GIGA_MAC_VER_64] = rtl_hw_start_8125d,
++              [RTL_GIGA_MAC_VER_65] = rtl_hw_start_8125d,
+               [RTL_GIGA_MAC_VER_70] = rtl_hw_start_8126a,
+               [RTL_GIGA_MAC_VER_71] = rtl_hw_start_8126a,
+       };
+@@ -3878,6 +3883,7 @@ static void rtl_hw_start_8125(struct rtl
+       switch (tp->mac_version) {
+       case RTL_GIGA_MAC_VER_61:
+       case RTL_GIGA_MAC_VER_64:
++      case RTL_GIGA_MAC_VER_65:
+               for (i = 0xa00; i < 0xb00; i += 4)
+                       RTL_W32(tp, i, 0);
+               break;
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -1162,6 +1162,7 @@ void r8169_hw_phy_config(struct rtl8169_
+               [RTL_GIGA_MAC_VER_61] = rtl8125a_2_hw_phy_config,
+               [RTL_GIGA_MAC_VER_63] = rtl8125b_hw_phy_config,
+               [RTL_GIGA_MAC_VER_64] = rtl8125d_hw_phy_config,
++              [RTL_GIGA_MAC_VER_65] = rtl8125d_hw_phy_config,
+               [RTL_GIGA_MAC_VER_70] = rtl8126a_hw_phy_config,
+               [RTL_GIGA_MAC_VER_71] = rtl8126a_hw_phy_config,
+       };
diff --git a/target/linux/generic/backport-6.12/780-25-v6.14-r8169-add-support-for-RTL8125BP-rev.b.patch b/target/linux/generic/backport-6.12/780-25-v6.14-r8169-add-support-for-RTL8125BP-rev.b.patch
new file mode 100644 (file)
index 0000000..7b4f474
--- /dev/null
@@ -0,0 +1,184 @@
+From b11bff90f2ad52c5c55c822ecd20326619a73898 Mon Sep 17 00:00:00 2001
+From: ChunHao Lin <hau@realtek.com>
+Date: Tue, 7 Jan 2025 14:43:55 +0800
+Subject: [PATCH] r8169: add support for RTL8125BP rev.b
+
+Add support for RTL8125BP rev.b. Its XID is 0x689. This chip supports
+DASH and its dash type is "RTL_DASH_25_BP".
+
+Signed-off-by: ChunHao Lin <hau@realtek.com>
+Reviewed-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/20250107064355.104711-1-hau@realtek.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/realtek/r8169.h          |  1 +
+ drivers/net/ethernet/realtek/r8169_main.c     | 30 +++++++++++++++++++
+ .../net/ethernet/realtek/r8169_phy_config.c   | 23 ++++++++++++++
+ 3 files changed, 54 insertions(+)
+
+--- a/drivers/net/ethernet/realtek/r8169.h
++++ b/drivers/net/ethernet/realtek/r8169.h
+@@ -70,6 +70,7 @@ enum mac_version {
+       RTL_GIGA_MAC_VER_63,
+       RTL_GIGA_MAC_VER_64,
+       RTL_GIGA_MAC_VER_65,
++      RTL_GIGA_MAC_VER_66,
+       RTL_GIGA_MAC_VER_70,
+       RTL_GIGA_MAC_VER_71,
+       RTL_GIGA_MAC_NONE
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -57,6 +57,7 @@
+ #define FIRMWARE_8125B_2      "rtl_nic/rtl8125b-2.fw"
+ #define FIRMWARE_8125D_1      "rtl_nic/rtl8125d-1.fw"
+ #define FIRMWARE_8125D_2      "rtl_nic/rtl8125d-2.fw"
++#define FIRMWARE_8125BP_2     "rtl_nic/rtl8125bp-2.fw"
+ #define FIRMWARE_8126A_2      "rtl_nic/rtl8126a-2.fw"
+ #define FIRMWARE_8126A_3      "rtl_nic/rtl8126a-3.fw"
+@@ -141,6 +142,7 @@ static const struct {
+       [RTL_GIGA_MAC_VER_63] = {"RTL8125B",            FIRMWARE_8125B_2},
+       [RTL_GIGA_MAC_VER_64] = {"RTL8125D",            FIRMWARE_8125D_1},
+       [RTL_GIGA_MAC_VER_65] = {"RTL8125D",            FIRMWARE_8125D_2},
++      [RTL_GIGA_MAC_VER_66] = {"RTL8125BP",           FIRMWARE_8125BP_2},
+       [RTL_GIGA_MAC_VER_70] = {"RTL8126A",            FIRMWARE_8126A_2},
+       [RTL_GIGA_MAC_VER_71] = {"RTL8126A",            FIRMWARE_8126A_3},
+ };
+@@ -631,6 +633,7 @@ enum rtl_dash_type {
+       RTL_DASH_NONE,
+       RTL_DASH_DP,
+       RTL_DASH_EP,
++      RTL_DASH_25_BP,
+ };
+ struct rtl8169_private {
+@@ -708,6 +711,7 @@ MODULE_FIRMWARE(FIRMWARE_8125A_3);
+ MODULE_FIRMWARE(FIRMWARE_8125B_2);
+ MODULE_FIRMWARE(FIRMWARE_8125D_1);
+ MODULE_FIRMWARE(FIRMWARE_8125D_2);
++MODULE_FIRMWARE(FIRMWARE_8125BP_2);
+ MODULE_FIRMWARE(FIRMWARE_8126A_2);
+ MODULE_FIRMWARE(FIRMWARE_8126A_3);
+@@ -1360,10 +1364,19 @@ static void rtl8168ep_driver_start(struc
+               rtl_loop_wait_high(tp, &rtl_ep_ocp_read_cond, 10000, 30);
+ }
++static void rtl8125bp_driver_start(struct rtl8169_private *tp)
++{
++      r8168ep_ocp_write(tp, 0x01, 0x14, OOB_CMD_DRIVER_START);
++      r8168ep_ocp_write(tp, 0x01, 0x18, 0x00);
++      r8168ep_ocp_write(tp, 0x01, 0x10, 0x01);
++}
++
+ static void rtl8168_driver_start(struct rtl8169_private *tp)
+ {
+       if (tp->dash_type == RTL_DASH_DP)
+               rtl8168dp_driver_start(tp);
++      else if (tp->dash_type == RTL_DASH_25_BP)
++              rtl8125bp_driver_start(tp);
+       else
+               rtl8168ep_driver_start(tp);
+ }
+@@ -1384,10 +1397,19 @@ static void rtl8168ep_driver_stop(struct
+               rtl_loop_wait_low(tp, &rtl_ep_ocp_read_cond, 10000, 10);
+ }
++static void rtl8125bp_driver_stop(struct rtl8169_private *tp)
++{
++      r8168ep_ocp_write(tp, 0x01, 0x14, OOB_CMD_DRIVER_STOP);
++      r8168ep_ocp_write(tp, 0x01, 0x18, 0x00);
++      r8168ep_ocp_write(tp, 0x01, 0x10, 0x01);
++}
++
+ static void rtl8168_driver_stop(struct rtl8169_private *tp)
+ {
+       if (tp->dash_type == RTL_DASH_DP)
+               rtl8168dp_driver_stop(tp);
++      else if (tp->dash_type == RTL_DASH_25_BP)
++              rtl8125bp_driver_stop(tp);
+       else
+               rtl8168ep_driver_stop(tp);
+ }
+@@ -1410,6 +1432,7 @@ static bool rtl_dash_is_enabled(struct r
+       case RTL_DASH_DP:
+               return r8168dp_check_dash(tp);
+       case RTL_DASH_EP:
++      case RTL_DASH_25_BP:
+               return r8168ep_check_dash(tp);
+       default:
+               return false;
+@@ -1424,6 +1447,8 @@ static enum rtl_dash_type rtl_get_dash_t
+               return RTL_DASH_DP;
+       case RTL_GIGA_MAC_VER_51 ... RTL_GIGA_MAC_VER_53:
+               return RTL_DASH_EP;
++      case RTL_GIGA_MAC_VER_66:
++              return RTL_DASH_25_BP;
+       default:
+               return RTL_DASH_NONE;
+       }
+@@ -2260,6 +2285,9 @@ static enum mac_version rtl8169_get_mac_
+               { 0x7cf, 0x64a, RTL_GIGA_MAC_VER_71 },
+               { 0x7cf, 0x649, RTL_GIGA_MAC_VER_70 },
++              /* 8125BP family. */
++              { 0x7cf, 0x681, RTL_GIGA_MAC_VER_66 },
++
+               /* 8125D family. */
+               { 0x7cf, 0x689, RTL_GIGA_MAC_VER_65 },
+               { 0x7cf, 0x688, RTL_GIGA_MAC_VER_64 },
+@@ -3865,6 +3893,7 @@ static void rtl_hw_config(struct rtl8169
+               [RTL_GIGA_MAC_VER_63] = rtl_hw_start_8125b,
+               [RTL_GIGA_MAC_VER_64] = rtl_hw_start_8125d,
+               [RTL_GIGA_MAC_VER_65] = rtl_hw_start_8125d,
++              [RTL_GIGA_MAC_VER_66] = rtl_hw_start_8125d,
+               [RTL_GIGA_MAC_VER_70] = rtl_hw_start_8126a,
+               [RTL_GIGA_MAC_VER_71] = rtl_hw_start_8126a,
+       };
+@@ -3884,6 +3913,7 @@ static void rtl_hw_start_8125(struct rtl
+       case RTL_GIGA_MAC_VER_61:
+       case RTL_GIGA_MAC_VER_64:
+       case RTL_GIGA_MAC_VER_65:
++      case RTL_GIGA_MAC_VER_66:
+               for (i = 0xa00; i < 0xb00; i += 4)
+                       RTL_W32(tp, i, 0);
+               break;
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -1102,6 +1102,28 @@ static void rtl8125d_hw_phy_config(struc
+       rtl8125_config_eee_phy(phydev);
+ }
++static void rtl8125bp_hw_phy_config(struct rtl8169_private *tp,
++                                  struct phy_device *phydev)
++{
++      r8169_apply_firmware(tp);
++      rtl8168g_enable_gphy_10m(phydev);
++
++      r8168g_phy_param(phydev, 0x8010, 0x0800, 0x0000);
++
++      phy_write(phydev, 0x1f, 0x0b87);
++      phy_write(phydev, 0x16, 0x8088);
++      phy_modify(phydev, 0x17, 0xff00, 0x9000);
++      phy_write(phydev, 0x16, 0x808f);
++      phy_modify(phydev, 0x17, 0xff00, 0x9000);
++      phy_write(phydev, 0x1f, 0x0000);
++
++      r8168g_phy_param(phydev, 0x8174, 0x2000, 0x1800);
++
++      rtl8125_legacy_force_mode(phydev);
++      rtl8168g_disable_aldps(phydev);
++      rtl8125_config_eee_phy(phydev);
++}
++
+ static void rtl8126a_hw_phy_config(struct rtl8169_private *tp,
+                                  struct phy_device *phydev)
+ {
+@@ -1163,6 +1185,7 @@ void r8169_hw_phy_config(struct rtl8169_
+               [RTL_GIGA_MAC_VER_63] = rtl8125b_hw_phy_config,
+               [RTL_GIGA_MAC_VER_64] = rtl8125d_hw_phy_config,
+               [RTL_GIGA_MAC_VER_65] = rtl8125d_hw_phy_config,
++              [RTL_GIGA_MAC_VER_66] = rtl8125bp_hw_phy_config,
+               [RTL_GIGA_MAC_VER_70] = rtl8126a_hw_phy_config,
+               [RTL_GIGA_MAC_VER_71] = rtl8126a_hw_phy_config,
+       };
diff --git a/target/linux/generic/backport-6.12/780-26-v6.15-r8169-make-Kconfig-option-for-LED-support-user-visib.patch b/target/linux/generic/backport-6.12/780-26-v6.15-r8169-make-Kconfig-option-for-LED-support-user-visib.patch
new file mode 100644 (file)
index 0000000..62acd26
--- /dev/null
@@ -0,0 +1,28 @@
+From 135c3c86a7cef4ba3d368da15b16c275b74582d3 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Mon, 3 Feb 2025 21:35:24 +0100
+Subject: [PATCH] r8169: make Kconfig option for LED support user-visible
+
+Make config option R8169_LEDS user-visible, so that users can remove
+support if not needed.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/d29f0cdb-32bf-435f-b59d-dc96bca1e3ab@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/Kconfig | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/realtek/Kconfig
++++ b/drivers/net/ethernet/realtek/Kconfig
+@@ -114,7 +114,8 @@ config R8169
+         will be called r8169.  This is recommended.
+ config R8169_LEDS
+-      def_bool R8169 && LEDS_TRIGGER_NETDEV
++      bool "Support for controlling the NIC LEDs"
++      depends on R8169 && LEDS_TRIGGER_NETDEV
+       depends on !(R8169=y && LEDS_CLASS=m)
+       help
+         Optional support for controlling the NIC LED's with the netdev
diff --git a/target/linux/generic/backport-6.12/780-28-v6.15-r8169-add-support-for-Intel-Killer-E5000.patch b/target/linux/generic/backport-6.12/780-28-v6.15-r8169-add-support-for-Intel-Killer-E5000.patch
new file mode 100644 (file)
index 0000000..d741d19
--- /dev/null
@@ -0,0 +1,25 @@
+From d30460f42675fef5cd4b44ffbc49b545524555e3 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Wed, 12 Feb 2025 08:03:56 +0100
+Subject: [PATCH] r8169: add support for Intel Killer E5000
+
+This adds support for the Intel Killer E5000 which seems to be a
+rebranded RTL8126. Copied from r8126 vendor driver.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/9db73e9b-e2e8-45de-97a5-041c5f71d774@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -169,6 +169,7 @@ static const struct pci_device_id rtl816
+       { PCI_VDEVICE(REALTEK,  0x8125) },
+       { PCI_VDEVICE(REALTEK,  0x8126) },
+       { PCI_VDEVICE(REALTEK,  0x3000) },
++      { PCI_VDEVICE(REALTEK,  0x5000) },
+       {}
+ };
diff --git a/target/linux/generic/backport-6.12/780-29-v6.15-r8169-add-PHY-c45-ops-for-MDIO_MMD_VENDOR2-registers.patch b/target/linux/generic/backport-6.12/780-29-v6.15-r8169-add-PHY-c45-ops-for-MDIO_MMD_VENDOR2-registers.patch
new file mode 100644 (file)
index 0000000..1336ec6
--- /dev/null
@@ -0,0 +1,67 @@
+From 853e80369cfceb2331bf34f251ba11c6602cc67f Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Thu, 13 Feb 2025 20:15:42 +0100
+Subject: [PATCH] r8169: add PHY c45 ops for MDIO_MMD_VENDOR2 registers
+
+The integrated PHYs on chip versions from RTL8168g allow to address
+MDIO_MMD_VEND2 registers. All c22 standard registers are mapped to
+MDIO_MMD_VEND2 registers. So far the paging mechanism is used to
+address PHY registers. Add support for c45 ops to address MDIO_MMD_VEND2
+registers directly, w/o the paging.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/d6f97eaa-0f13-468f-89cb-75a41087bc4a@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 32 +++++++++++++++++++++++
+ 1 file changed, 32 insertions(+)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -5225,6 +5225,33 @@ static int r8169_mdio_write_reg(struct m
+       return 0;
+ }
++static int r8169_mdio_read_reg_c45(struct mii_bus *mii_bus, int addr,
++                                 int devnum, int regnum)
++{
++      struct rtl8169_private *tp = mii_bus->priv;
++
++      if (addr > 0)
++              return -ENODEV;
++
++      if (devnum == MDIO_MMD_VEND2 && regnum > MDIO_STAT2)
++              return r8168_phy_ocp_read(tp, regnum);
++
++      return 0;
++}
++
++static int r8169_mdio_write_reg_c45(struct mii_bus *mii_bus, int addr,
++                                  int devnum, int regnum, u16 val)
++{
++      struct rtl8169_private *tp = mii_bus->priv;
++
++      if (addr > 0 || devnum != MDIO_MMD_VEND2 || regnum <= MDIO_STAT2)
++              return -ENODEV;
++
++      r8168_phy_ocp_write(tp, regnum, val);
++
++      return 0;
++}
++
+ static int r8169_mdio_register(struct rtl8169_private *tp)
+ {
+       struct pci_dev *pdev = tp->pci_dev;
+@@ -5255,6 +5282,11 @@ static int r8169_mdio_register(struct rt
+       new_bus->read = r8169_mdio_read_reg;
+       new_bus->write = r8169_mdio_write_reg;
++      if (tp->mac_version >= RTL_GIGA_MAC_VER_40) {
++              new_bus->read_c45 = r8169_mdio_read_reg_c45;
++              new_bus->write_c45 = r8169_mdio_write_reg_c45;
++      }
++
+       ret = devm_mdiobus_register(&pdev->dev, new_bus);
+       if (ret)
+               return ret;
diff --git a/target/linux/generic/backport-6.12/780-30-v6.15-r8169-increase-max-jumbo-packet-size-on-RTL8125-RTL8.patch b/target/linux/generic/backport-6.12/780-30-v6.15-r8169-increase-max-jumbo-packet-size-on-RTL8125-RTL8.patch
new file mode 100644 (file)
index 0000000..852c9a3
--- /dev/null
@@ -0,0 +1,40 @@
+From 473367a5ffe1607a61be481e2feda684eb5faea9 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Fri, 7 Mar 2025 08:29:47 +0100
+Subject: [PATCH] r8169: increase max jumbo packet size on RTL8125/RTL8126
+
+Realtek confirmed that all RTL8125/RTL8126 chip versions support up to
+16K jumbo packets. Reflect this in the driver.
+
+Tested by Rui on RTL8125B with 12K jumbo packets.
+
+Suggested-by: Rui Salvaterra <rsalvaterra@gmail.com>
+Tested-by: Rui Salvaterra <rsalvaterra@gmail.com>
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/396762ad-cc65-4e60-b01e-8847db89e98b@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -89,6 +89,7 @@
+ #define JUMBO_6K      (6 * SZ_1K - VLAN_ETH_HLEN - ETH_FCS_LEN)
+ #define JUMBO_7K      (7 * SZ_1K - VLAN_ETH_HLEN - ETH_FCS_LEN)
+ #define JUMBO_9K      (9 * SZ_1K - VLAN_ETH_HLEN - ETH_FCS_LEN)
++#define JUMBO_16K     (SZ_16K - VLAN_ETH_HLEN - ETH_FCS_LEN)
+ static const struct {
+       const char *name;
+@@ -5384,6 +5385,9 @@ static int rtl_jumbo_max(struct rtl8169_
+       /* RTL8168c */
+       case RTL_GIGA_MAC_VER_18 ... RTL_GIGA_MAC_VER_24:
+               return JUMBO_6K;
++      /* RTL8125/8126 */
++      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_71:
++              return JUMBO_16K;
+       default:
+               return JUMBO_9K;
+       }
diff --git a/target/linux/generic/backport-6.12/780-31-v6.15-r8169-switch-away-from-deprecated-pcim_iomap_table.patch b/target/linux/generic/backport-6.12/780-31-v6.15-r8169-switch-away-from-deprecated-pcim_iomap_table.patch
new file mode 100644 (file)
index 0000000..0721247
--- /dev/null
@@ -0,0 +1,35 @@
+From 34e5ededf4b8ad4c9e58f0cab8596e26c8fa59a2 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Wed, 12 Mar 2025 20:21:42 +0100
+Subject: [PATCH] r8169: switch away from deprecated pcim_iomap_table
+
+Avoid using deprecated pcim_iomap_table by switching to
+pcim_iomap_region.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/a36b4cf3-c792-40fa-8164-5dc9d5f14dd0@gmail.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 9 ++++-----
+ 1 file changed, 4 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -5471,11 +5471,10 @@ static int rtl_init_one(struct pci_dev *
+       if (region < 0)
+               return dev_err_probe(&pdev->dev, -ENODEV, "no MMIO resource found\n");
+-      rc = pcim_iomap_regions(pdev, BIT(region), KBUILD_MODNAME);
+-      if (rc < 0)
+-              return dev_err_probe(&pdev->dev, rc, "cannot remap MMIO, aborting\n");
+-
+-      tp->mmio_addr = pcim_iomap_table(pdev)[region];
++      tp->mmio_addr = pcim_iomap_region(pdev, region, KBUILD_MODNAME);
++      if (IS_ERR(tp->mmio_addr))
++              return dev_err_probe(&pdev->dev, PTR_ERR(tp->mmio_addr),
++                                   "cannot remap MMIO, aborting\n");
+       txconfig = RTL_R32(tp, TxConfig);
+       if (txconfig == ~0U)
diff --git a/target/linux/generic/backport-6.12/780-32-v6.15-r8169-enable-RTL8168H-RTL8168EP-RTL8168FP-ASPM-suppo.patch b/target/linux/generic/backport-6.12/780-32-v6.15-r8169-enable-RTL8168H-RTL8168EP-RTL8168FP-ASPM-suppo.patch
new file mode 100644 (file)
index 0000000..331d296
--- /dev/null
@@ -0,0 +1,27 @@
+From 3d9b8ac5341269d31e59fd5d58d47266ac78bc32 Mon Sep 17 00:00:00 2001
+From: ChunHao Lin <hau@realtek.com>
+Date: Tue, 18 Mar 2025 16:37:20 +0800
+Subject: [PATCH] r8169: enable RTL8168H/RTL8168EP/RTL8168FP ASPM support
+
+This patch will enable RTL8168H/RTL8168EP/RTL8168FP ASPM support on
+the platforms that have tested with ASPM enabled.
+
+Signed-off-by: ChunHao Lin <hau@realtek.com>
+Reviewed-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/20250318083721.4127-2-hau@realtek.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -5422,7 +5422,7 @@ done:
+ /* register is set if system vendor successfully tested ASPM 1.2 */
+ static bool rtl_aspm_is_safe(struct rtl8169_private *tp)
+ {
+-      if (tp->mac_version >= RTL_GIGA_MAC_VER_61 &&
++      if (tp->mac_version >= RTL_GIGA_MAC_VER_46 &&
+           r8168_mac_ocp_read(tp, 0xc0b2) & 0xf)
+               return true;
diff --git a/target/linux/generic/backport-6.12/780-34-v6.16-r8169-add-helper-rtl_csi_mod-for-accessing-extended.patch b/target/linux/generic/backport-6.12/780-34-v6.16-r8169-add-helper-rtl_csi_mod-for-accessing-extended.patch
new file mode 100644 (file)
index 0000000..383f9eb
--- /dev/null
@@ -0,0 +1,74 @@
+From 8c40d99e5f43e0545a3f4fea9156313847e2eb79 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Wed, 9 Apr 2025 21:05:37 +0200
+Subject: [PATCH] r8169: add helper rtl_csi_mod for accessing extended config
+ space
+
+Add a helper for the Realtek-specific mechanism for accessing extended
+config space if native access isn't possible.
+This avoids code duplication.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/b368fd91-57d7-4cb5-9342-98b4d8fe9aea@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 26 ++++++++++++++---------
+ 1 file changed, 16 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -2849,10 +2849,23 @@ static u32 rtl_csi_read(struct rtl8169_p
+               RTL_R32(tp, CSIDR) : ~0;
+ }
++static void rtl_csi_mod(struct rtl8169_private *tp, int addr,
++                      u32 mask, u32 set)
++{
++      u32 val;
++
++      WARN(addr % 4, "Invalid CSI address %#x\n", addr);
++
++      netdev_notice_once(tp->dev,
++              "No native access to PCI extended config space, falling back to CSI\n");
++
++      val = rtl_csi_read(tp, addr);
++      rtl_csi_write(tp, addr, (val & ~mask) | set);
++}
++
+ static void rtl_disable_zrxdc_timeout(struct rtl8169_private *tp)
+ {
+       struct pci_dev *pdev = tp->pci_dev;
+-      u32 csi;
+       int rc;
+       u8 val;
+@@ -2869,16 +2882,12 @@ static void rtl_disable_zrxdc_timeout(st
+               }
+       }
+-      netdev_notice_once(tp->dev,
+-              "No native access to PCI extended config space, falling back to CSI\n");
+-      csi = rtl_csi_read(tp, RTL_GEN3_RELATED_OFF);
+-      rtl_csi_write(tp, RTL_GEN3_RELATED_OFF, csi & ~RTL_GEN3_ZRXDC_NONCOMPL);
++      rtl_csi_mod(tp, RTL_GEN3_RELATED_OFF, RTL_GEN3_ZRXDC_NONCOMPL, 0);
+ }
+ static void rtl_set_aspm_entry_latency(struct rtl8169_private *tp, u8 val)
+ {
+       struct pci_dev *pdev = tp->pci_dev;
+-      u32 csi;
+       /* According to Realtek the value at config space address 0x070f
+        * controls the L0s/L1 entrance latency. We try standard ECAM access
+@@ -2890,10 +2899,7 @@ static void rtl_set_aspm_entry_latency(s
+           pci_write_config_byte(pdev, 0x070f, val) == PCIBIOS_SUCCESSFUL)
+               return;
+-      netdev_notice_once(tp->dev,
+-              "No native access to PCI extended config space, falling back to CSI\n");
+-      csi = rtl_csi_read(tp, 0x070c) & 0x00ffffff;
+-      rtl_csi_write(tp, 0x070c, csi | val << 24);
++      rtl_csi_mod(tp, 0x070c, 0xff000000, val << 24);
+ }
+ static void rtl_set_def_aspm_entry_latency(struct rtl8169_private *tp)
diff --git a/target/linux/generic/backport-6.12/780-35-v6.16-r8169-add-helper-rtl8125_phy_param.patch b/target/linux/generic/backport-6.12/780-35-v6.16-r8169-add-helper-rtl8125_phy_param.patch
new file mode 100644 (file)
index 0000000..30ebb4b
--- /dev/null
@@ -0,0 +1,82 @@
+From 0c49baf099ba2147a6ff3bbdc3197c6ddbee5469 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Wed, 9 Apr 2025 21:14:47 +0200
+Subject: [PATCH] r8169: add helper rtl8125_phy_param
+
+The integrated PHY's of RTL8125/8126 have an own mechanism to access
+PHY parameters, similar to what r8168g_phy_param does on earlier PHY
+versions. Add helper rtl8125_phy_param to simplify the code.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/847b7356-12d6-441b-ade9-4b6e1539b84a@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ .../net/ethernet/realtek/r8169_phy_config.c   | 36 +++++++++----------
+ 1 file changed, 16 insertions(+), 20 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -50,6 +50,15 @@ static void r8168g_phy_param(struct phy_
+       phy_restore_page(phydev, oldpage, 0);
+ }
++static void rtl8125_phy_param(struct phy_device *phydev, u16 parm,
++                            u16 mask, u16 val)
++{
++      phy_lock_mdio_bus(phydev);
++      __phy_write_mmd(phydev, MDIO_MMD_VEND2, 0xb87c, parm);
++      __phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xb87e, mask, val);
++      phy_unlock_mdio_bus(phydev);
++}
++
+ struct phy_reg {
+       u16 reg;
+       u16 val;
+@@ -1004,12 +1013,8 @@ static void rtl8125a_2_hw_phy_config(str
+       phy_write_paged(phydev, 0xac5, 0x16, 0x01ff);
+       phy_modify_paged(phydev, 0xac8, 0x15, 0x00f0, 0x0030);
+-      phy_write(phydev, 0x1f, 0x0b87);
+-      phy_write(phydev, 0x16, 0x80a2);
+-      phy_write(phydev, 0x17, 0x0153);
+-      phy_write(phydev, 0x16, 0x809c);
+-      phy_write(phydev, 0x17, 0x0153);
+-      phy_write(phydev, 0x1f, 0x0000);
++      rtl8125_phy_param(phydev, 0x80a2, 0xffff, 0x0153);
++      rtl8125_phy_param(phydev, 0x809c, 0xffff, 0x0153);
+       phy_write(phydev, 0x1f, 0x0a43);
+       phy_write(phydev, 0x13, 0x81B3);
+@@ -1061,14 +1066,9 @@ static void rtl8125b_hw_phy_config(struc
+       phy_modify_paged(phydev, 0xac4, 0x13, 0x00f0, 0x0090);
+       phy_modify_paged(phydev, 0xad3, 0x10, 0x0003, 0x0001);
+-      phy_write(phydev, 0x1f, 0x0b87);
+-      phy_write(phydev, 0x16, 0x80f5);
+-      phy_write(phydev, 0x17, 0x760e);
+-      phy_write(phydev, 0x16, 0x8107);
+-      phy_write(phydev, 0x17, 0x360e);
+-      phy_write(phydev, 0x16, 0x8551);
+-      phy_modify(phydev, 0x17, 0xff00, 0x0800);
+-      phy_write(phydev, 0x1f, 0x0000);
++      rtl8125_phy_param(phydev, 0x80f5, 0xffff, 0x760e);
++      rtl8125_phy_param(phydev, 0x8107, 0xffff, 0x360e);
++      rtl8125_phy_param(phydev, 0x8551, 0xff00, 0x0800);
+       phy_modify_paged(phydev, 0xbf0, 0x10, 0xe000, 0xa000);
+       phy_modify_paged(phydev, 0xbf4, 0x13, 0x0f00, 0x0300);
+@@ -1110,12 +1110,8 @@ static void rtl8125bp_hw_phy_config(stru
+       r8168g_phy_param(phydev, 0x8010, 0x0800, 0x0000);
+-      phy_write(phydev, 0x1f, 0x0b87);
+-      phy_write(phydev, 0x16, 0x8088);
+-      phy_modify(phydev, 0x17, 0xff00, 0x9000);
+-      phy_write(phydev, 0x16, 0x808f);
+-      phy_modify(phydev, 0x17, 0xff00, 0x9000);
+-      phy_write(phydev, 0x1f, 0x0000);
++      rtl8125_phy_param(phydev, 0x8088, 0xff00, 0x9000);
++      rtl8125_phy_param(phydev, 0x808f, 0xff00, 0x9000);
+       r8168g_phy_param(phydev, 0x8174, 0x2000, 0x1800);
diff --git a/target/linux/generic/backport-6.12/780-36-v6.16-r8169-refactor-chip-version-detection.patch b/target/linux/generic/backport-6.12/780-36-v6.16-r8169-refactor-chip-version-detection.patch
new file mode 100644 (file)
index 0000000..ae9cfb8
--- /dev/null
@@ -0,0 +1,401 @@
+From 2b065c098c37a3ed28df7c3be59dca61b9da8402 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Tue, 15 Apr 2025 21:29:34 +0200
+Subject: [PATCH] r8169: refactor chip version detection
+
+Refactor chip version detection and merge both configuration tables.
+Apart from reducing the code by a third, this paves the way for
+merging chip version handling if only difference is the firmware.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Michal Swiatkowski <michal.swiatkowski@linux.intel.com>
+Link: https://patch.msgid.link/1fea533a-dd5a-4198-a9e2-895e11083947@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 325 +++++++++-------------
+ 1 file changed, 128 insertions(+), 197 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -91,61 +91,114 @@
+ #define JUMBO_9K      (9 * SZ_1K - VLAN_ETH_HLEN - ETH_FCS_LEN)
+ #define JUMBO_16K     (SZ_16K - VLAN_ETH_HLEN - ETH_FCS_LEN)
+-static const struct {
++static const struct rtl_chip_info {
++      u16 mask;
++      u16 val;
++      enum mac_version mac_version;
+       const char *name;
+       const char *fw_name;
+ } rtl_chip_infos[] = {
+-      /* PCI devices. */
+-      [RTL_GIGA_MAC_VER_02] = {"RTL8169s"                             },
+-      [RTL_GIGA_MAC_VER_03] = {"RTL8110s"                             },
+-      [RTL_GIGA_MAC_VER_04] = {"RTL8169sb/8110sb"                     },
+-      [RTL_GIGA_MAC_VER_05] = {"RTL8169sc/8110sc"                     },
+-      [RTL_GIGA_MAC_VER_06] = {"RTL8169sc/8110sc"                     },
+-      /* PCI-E devices. */
+-      [RTL_GIGA_MAC_VER_07] = {"RTL8102e"                             },
+-      [RTL_GIGA_MAC_VER_08] = {"RTL8102e"                             },
+-      [RTL_GIGA_MAC_VER_09] = {"RTL8102e/RTL8103e"                    },
+-      [RTL_GIGA_MAC_VER_10] = {"RTL8101e/RTL8100e"                    },
+-      [RTL_GIGA_MAC_VER_14] = {"RTL8401"                              },
+-      [RTL_GIGA_MAC_VER_17] = {"RTL8168b/8111b"                       },
+-      [RTL_GIGA_MAC_VER_18] = {"RTL8168cp/8111cp"                     },
+-      [RTL_GIGA_MAC_VER_19] = {"RTL8168c/8111c"                       },
+-      [RTL_GIGA_MAC_VER_20] = {"RTL8168c/8111c"                       },
+-      [RTL_GIGA_MAC_VER_21] = {"RTL8168c/8111c"                       },
+-      [RTL_GIGA_MAC_VER_22] = {"RTL8168c/8111c"                       },
+-      [RTL_GIGA_MAC_VER_23] = {"RTL8168cp/8111cp"                     },
+-      [RTL_GIGA_MAC_VER_24] = {"RTL8168cp/8111cp"                     },
+-      [RTL_GIGA_MAC_VER_25] = {"RTL8168d/8111d",      FIRMWARE_8168D_1},
+-      [RTL_GIGA_MAC_VER_26] = {"RTL8168d/8111d",      FIRMWARE_8168D_2},
+-      [RTL_GIGA_MAC_VER_28] = {"RTL8168dp/8111dp"                     },
+-      [RTL_GIGA_MAC_VER_29] = {"RTL8105e",            FIRMWARE_8105E_1},
+-      [RTL_GIGA_MAC_VER_30] = {"RTL8105e",            FIRMWARE_8105E_1},
+-      [RTL_GIGA_MAC_VER_31] = {"RTL8168dp/8111dp"                     },
+-      [RTL_GIGA_MAC_VER_32] = {"RTL8168e/8111e",      FIRMWARE_8168E_1},
+-      [RTL_GIGA_MAC_VER_33] = {"RTL8168e/8111e",      FIRMWARE_8168E_2},
+-      [RTL_GIGA_MAC_VER_34] = {"RTL8168evl/8111evl",  FIRMWARE_8168E_3},
+-      [RTL_GIGA_MAC_VER_35] = {"RTL8168f/8111f",      FIRMWARE_8168F_1},
+-      [RTL_GIGA_MAC_VER_36] = {"RTL8168f/8111f",      FIRMWARE_8168F_2},
+-      [RTL_GIGA_MAC_VER_37] = {"RTL8402",             FIRMWARE_8402_1 },
+-      [RTL_GIGA_MAC_VER_38] = {"RTL8411",             FIRMWARE_8411_1 },
+-      [RTL_GIGA_MAC_VER_39] = {"RTL8106e",            FIRMWARE_8106E_1},
+-      [RTL_GIGA_MAC_VER_40] = {"RTL8168g/8111g",      FIRMWARE_8168G_2},
+-      [RTL_GIGA_MAC_VER_42] = {"RTL8168gu/8111gu",    FIRMWARE_8168G_3},
+-      [RTL_GIGA_MAC_VER_43] = {"RTL8106eus",          FIRMWARE_8106E_2},
+-      [RTL_GIGA_MAC_VER_44] = {"RTL8411b",            FIRMWARE_8411_2 },
+-      [RTL_GIGA_MAC_VER_46] = {"RTL8168h/8111h",      FIRMWARE_8168H_2},
+-      [RTL_GIGA_MAC_VER_48] = {"RTL8107e",            FIRMWARE_8107E_2},
+-      [RTL_GIGA_MAC_VER_51] = {"RTL8168ep/8111ep"                     },
+-      [RTL_GIGA_MAC_VER_52] = {"RTL8168fp/RTL8117",  FIRMWARE_8168FP_3},
+-      [RTL_GIGA_MAC_VER_53] = {"RTL8168fp/RTL8117",                   },
+-      [RTL_GIGA_MAC_VER_61] = {"RTL8125A",            FIRMWARE_8125A_3},
+-      /* reserve 62 for CFG_METHOD_4 in the vendor driver */
+-      [RTL_GIGA_MAC_VER_63] = {"RTL8125B",            FIRMWARE_8125B_2},
+-      [RTL_GIGA_MAC_VER_64] = {"RTL8125D",            FIRMWARE_8125D_1},
+-      [RTL_GIGA_MAC_VER_65] = {"RTL8125D",            FIRMWARE_8125D_2},
+-      [RTL_GIGA_MAC_VER_66] = {"RTL8125BP",           FIRMWARE_8125BP_2},
+-      [RTL_GIGA_MAC_VER_70] = {"RTL8126A",            FIRMWARE_8126A_2},
+-      [RTL_GIGA_MAC_VER_71] = {"RTL8126A",            FIRMWARE_8126A_3},
++      /* 8126A family. */
++      { 0x7cf, 0x64a, RTL_GIGA_MAC_VER_71, "RTL8126A", FIRMWARE_8126A_3 },
++      { 0x7cf, 0x649, RTL_GIGA_MAC_VER_70, "RTL8126A", FIRMWARE_8126A_2 },
++
++      /* 8125BP family. */
++      { 0x7cf, 0x681, RTL_GIGA_MAC_VER_66, "RTL8125BP", FIRMWARE_8125BP_2 },
++
++      /* 8125D family. */
++      { 0x7cf, 0x689, RTL_GIGA_MAC_VER_65, "RTL8125D", FIRMWARE_8125D_2 },
++      { 0x7cf, 0x688, RTL_GIGA_MAC_VER_64, "RTL8125D", FIRMWARE_8125D_1 },
++
++      /* 8125B family. */
++      { 0x7cf, 0x641, RTL_GIGA_MAC_VER_63, "RTL8125B", FIRMWARE_8125B_2 },
++
++      /* 8125A family. */
++      { 0x7cf, 0x609, RTL_GIGA_MAC_VER_61, "RTL8125A", FIRMWARE_8125A_3 },
++
++      /* RTL8117 */
++      { 0x7cf, 0x54b, RTL_GIGA_MAC_VER_53, "RTL8168fp/RTL8117" },
++      { 0x7cf, 0x54a, RTL_GIGA_MAC_VER_52, "RTL8168fp/RTL8117",
++        FIRMWARE_8168FP_3 },
++
++      /* 8168EP family. */
++      { 0x7cf, 0x502, RTL_GIGA_MAC_VER_51, "RTL8168ep/8111ep" },
++
++      /* 8168H family. */
++      { 0x7cf, 0x541, RTL_GIGA_MAC_VER_46, "RTL8168h/8111h",
++        FIRMWARE_8168H_2 },
++      /* Realtek calls it RTL8168M, but it's handled like RTL8168H */
++      { 0x7cf, 0x6c0, RTL_GIGA_MAC_VER_46, "RTL8168M", FIRMWARE_8168H_2 },
++
++      /* 8168G family. */
++      { 0x7cf, 0x5c8, RTL_GIGA_MAC_VER_44, "RTL8411b", FIRMWARE_8411_2 },
++      { 0x7cf, 0x509, RTL_GIGA_MAC_VER_42, "RTL8168gu/8111gu",
++        FIRMWARE_8168G_3 },
++      { 0x7cf, 0x4c0, RTL_GIGA_MAC_VER_40, "RTL8168g/8111g",
++        FIRMWARE_8168G_2 },
++
++      /* 8168F family. */
++      { 0x7c8, 0x488, RTL_GIGA_MAC_VER_38, "RTL8411", FIRMWARE_8411_1 },
++      { 0x7cf, 0x481, RTL_GIGA_MAC_VER_36, "RTL8168f/8111f",
++        FIRMWARE_8168F_2 },
++      { 0x7cf, 0x480, RTL_GIGA_MAC_VER_35, "RTL8168f/8111f",
++        FIRMWARE_8168F_1 },
++
++      /* 8168E family. */
++      { 0x7c8, 0x2c8, RTL_GIGA_MAC_VER_34, "RTL8168evl/8111evl",
++        FIRMWARE_8168E_3 },
++      { 0x7cf, 0x2c1, RTL_GIGA_MAC_VER_32, "RTL8168e/8111e",
++        FIRMWARE_8168E_1 },
++      { 0x7c8, 0x2c0, RTL_GIGA_MAC_VER_33, "RTL8168e/8111e",
++        FIRMWARE_8168E_2 },
++
++      /* 8168D family. */
++      { 0x7cf, 0x281, RTL_GIGA_MAC_VER_25, "RTL8168d/8111d",
++        FIRMWARE_8168D_1 },
++      { 0x7c8, 0x280, RTL_GIGA_MAC_VER_26, "RTL8168d/8111d",
++        FIRMWARE_8168D_2 },
++
++      /* 8168DP family. */
++      { 0x7cf, 0x28a, RTL_GIGA_MAC_VER_28, "RTL8168dp/8111dp" },
++      { 0x7cf, 0x28b, RTL_GIGA_MAC_VER_31, "RTL8168dp/8111dp" },
++
++      /* 8168C family. */
++      { 0x7cf, 0x3c9, RTL_GIGA_MAC_VER_23, "RTL8168cp/8111cp" },
++      { 0x7cf, 0x3c8, RTL_GIGA_MAC_VER_18, "RTL8168cp/8111cp" },
++      { 0x7c8, 0x3c8, RTL_GIGA_MAC_VER_24, "RTL8168cp/8111cp" },
++      { 0x7cf, 0x3c0, RTL_GIGA_MAC_VER_19, "RTL8168c/8111c" },
++      { 0x7cf, 0x3c2, RTL_GIGA_MAC_VER_20, "RTL8168c/8111c" },
++      { 0x7cf, 0x3c3, RTL_GIGA_MAC_VER_21, "RTL8168c/8111c" },
++      { 0x7c8, 0x3c0, RTL_GIGA_MAC_VER_22, "RTL8168c/8111c" },
++
++      /* 8168B family. */
++      { 0x7c8, 0x380, RTL_GIGA_MAC_VER_17, "RTL8168b/8111b" },
++      /* This one is very old and rare, support has been removed.
++       * { 0x7c8, 0x300, RTL_GIGA_MAC_VER_11, "RTL8168b/8111b" },
++       */
++
++      /* 8101 family. */
++      { 0x7c8, 0x448, RTL_GIGA_MAC_VER_39, "RTL8106e", FIRMWARE_8106E_1 },
++      { 0x7c8, 0x440, RTL_GIGA_MAC_VER_37, "RTL8402", FIRMWARE_8402_1 },
++      { 0x7cf, 0x409, RTL_GIGA_MAC_VER_29, "RTL8105e", FIRMWARE_8105E_1 },
++      { 0x7c8, 0x408, RTL_GIGA_MAC_VER_30, "RTL8105e", FIRMWARE_8105E_1 },
++      { 0x7cf, 0x349, RTL_GIGA_MAC_VER_08, "RTL8102e" },
++      { 0x7cf, 0x249, RTL_GIGA_MAC_VER_08, "RTL8102e" },
++      { 0x7cf, 0x348, RTL_GIGA_MAC_VER_07, "RTL8102e" },
++      { 0x7cf, 0x248, RTL_GIGA_MAC_VER_07, "RTL8102e" },
++      { 0x7cf, 0x240, RTL_GIGA_MAC_VER_14, "RTL8401" },
++      { 0x7c8, 0x348, RTL_GIGA_MAC_VER_09, "RTL8102e/RTL8103e" },
++      { 0x7c8, 0x248, RTL_GIGA_MAC_VER_09, "RTL8102e/RTL8103e" },
++      { 0x7c8, 0x340, RTL_GIGA_MAC_VER_10, "RTL8101e/RTL8100e" },
++
++      /* 8110 family. */
++      { 0xfc8, 0x980, RTL_GIGA_MAC_VER_06, "RTL8169sc/8110sc" },
++      { 0xfc8, 0x180, RTL_GIGA_MAC_VER_05, "RTL8169sc/8110sc" },
++      { 0xfc8, 0x100, RTL_GIGA_MAC_VER_04, "RTL8169sb/8110sb" },
++      { 0xfc8, 0x040, RTL_GIGA_MAC_VER_03, "RTL8110s" },
++      { 0xfc8, 0x008, RTL_GIGA_MAC_VER_02, "RTL8169s" },
++
++      /* Catch-all */
++      { 0x000, 0x000, RTL_GIGA_MAC_NONE }
+ };
+ static const struct pci_device_id rtl8169_pci_tbl[] = {
+@@ -2265,151 +2318,30 @@ static const struct ethtool_ops rtl8169_
+       .get_eth_ctrl_stats     = rtl8169_get_eth_ctrl_stats,
+ };
+-static enum mac_version rtl8169_get_mac_version(u16 xid, bool gmii)
++static const struct rtl_chip_info *rtl8169_get_chip_version(u16 xid, bool gmii)
+ {
+-      /*
+-       * The driver currently handles the 8168Bf and the 8168Be identically
+-       * but they can be identified more specifically through the test below
+-       * if needed:
+-       *
+-       * (RTL_R32(tp, TxConfig) & 0x700000) == 0x500000 ? 8168Bf : 8168Be
+-       *
+-       * Same thing for the 8101Eb and the 8101Ec:
+-       *
+-       * (RTL_R32(tp, TxConfig) & 0x700000) == 0x200000 ? 8101Eb : 8101Ec
+-       */
+-      static const struct rtl_mac_info {
+-              u16 mask;
+-              u16 val;
+-              enum mac_version ver;
+-      } mac_info[] = {
+-              /* 8126A family. */
+-              { 0x7cf, 0x64a, RTL_GIGA_MAC_VER_71 },
+-              { 0x7cf, 0x649, RTL_GIGA_MAC_VER_70 },
+-
+-              /* 8125BP family. */
+-              { 0x7cf, 0x681, RTL_GIGA_MAC_VER_66 },
+-
+-              /* 8125D family. */
+-              { 0x7cf, 0x689, RTL_GIGA_MAC_VER_65 },
+-              { 0x7cf, 0x688, RTL_GIGA_MAC_VER_64 },
+-
+-              /* 8125B family. */
+-              { 0x7cf, 0x641, RTL_GIGA_MAC_VER_63 },
+-
+-              /* 8125A family. */
+-              { 0x7cf, 0x609, RTL_GIGA_MAC_VER_61 },
+-              /* It seems only XID 609 made it to the mass market.
+-               * { 0x7cf, 0x608,      RTL_GIGA_MAC_VER_60 },
+-               * { 0x7c8, 0x608,      RTL_GIGA_MAC_VER_61 },
+-               */
+-
+-              /* RTL8117 */
+-              { 0x7cf, 0x54b, RTL_GIGA_MAC_VER_53 },
+-              { 0x7cf, 0x54a, RTL_GIGA_MAC_VER_52 },
+-
+-              /* 8168EP family. */
+-              { 0x7cf, 0x502, RTL_GIGA_MAC_VER_51 },
+-              /* It seems this chip version never made it to
+-               * the wild. Let's disable detection.
+-               * { 0x7cf, 0x501,      RTL_GIGA_MAC_VER_50 },
+-               * { 0x7cf, 0x500,      RTL_GIGA_MAC_VER_49 },
+-               */
+-
+-              /* 8168H family. */
+-              { 0x7cf, 0x541, RTL_GIGA_MAC_VER_46 },
+-              /* It seems this chip version never made it to
+-               * the wild. Let's disable detection.
+-               * { 0x7cf, 0x540,      RTL_GIGA_MAC_VER_45 },
+-               */
+-              /* Realtek calls it RTL8168M, but it's handled like RTL8168H */
+-              { 0x7cf, 0x6c0, RTL_GIGA_MAC_VER_46 },
+-
+-              /* 8168G family. */
+-              { 0x7cf, 0x5c8, RTL_GIGA_MAC_VER_44 },
+-              { 0x7cf, 0x509, RTL_GIGA_MAC_VER_42 },
+-              /* It seems this chip version never made it to
+-               * the wild. Let's disable detection.
+-               * { 0x7cf, 0x4c1,      RTL_GIGA_MAC_VER_41 },
+-               */
+-              { 0x7cf, 0x4c0, RTL_GIGA_MAC_VER_40 },
+-
+-              /* 8168F family. */
+-              { 0x7c8, 0x488, RTL_GIGA_MAC_VER_38 },
+-              { 0x7cf, 0x481, RTL_GIGA_MAC_VER_36 },
+-              { 0x7cf, 0x480, RTL_GIGA_MAC_VER_35 },
+-
+-              /* 8168E family. */
+-              { 0x7c8, 0x2c8, RTL_GIGA_MAC_VER_34 },
+-              { 0x7cf, 0x2c1, RTL_GIGA_MAC_VER_32 },
+-              { 0x7c8, 0x2c0, RTL_GIGA_MAC_VER_33 },
+-
+-              /* 8168D family. */
+-              { 0x7cf, 0x281, RTL_GIGA_MAC_VER_25 },
+-              { 0x7c8, 0x280, RTL_GIGA_MAC_VER_26 },
+-
+-              /* 8168DP family. */
+-              /* It seems this early RTL8168dp version never made it to
+-               * the wild. Support has been removed.
+-               * { 0x7cf, 0x288,      RTL_GIGA_MAC_VER_27 },
+-               */
+-              { 0x7cf, 0x28a, RTL_GIGA_MAC_VER_28 },
+-              { 0x7cf, 0x28b, RTL_GIGA_MAC_VER_31 },
+-
+-              /* 8168C family. */
+-              { 0x7cf, 0x3c9, RTL_GIGA_MAC_VER_23 },
+-              { 0x7cf, 0x3c8, RTL_GIGA_MAC_VER_18 },
+-              { 0x7c8, 0x3c8, RTL_GIGA_MAC_VER_24 },
+-              { 0x7cf, 0x3c0, RTL_GIGA_MAC_VER_19 },
+-              { 0x7cf, 0x3c2, RTL_GIGA_MAC_VER_20 },
+-              { 0x7cf, 0x3c3, RTL_GIGA_MAC_VER_21 },
+-              { 0x7c8, 0x3c0, RTL_GIGA_MAC_VER_22 },
+-
+-              /* 8168B family. */
+-              { 0x7c8, 0x380, RTL_GIGA_MAC_VER_17 },
+-              /* This one is very old and rare, support has been removed.
+-               * { 0x7c8, 0x300,      RTL_GIGA_MAC_VER_11 },
+-               */
+-
+-              /* 8101 family. */
+-              { 0x7c8, 0x448, RTL_GIGA_MAC_VER_39 },
+-              { 0x7c8, 0x440, RTL_GIGA_MAC_VER_37 },
+-              { 0x7cf, 0x409, RTL_GIGA_MAC_VER_29 },
+-              { 0x7c8, 0x408, RTL_GIGA_MAC_VER_30 },
+-              { 0x7cf, 0x349, RTL_GIGA_MAC_VER_08 },
+-              { 0x7cf, 0x249, RTL_GIGA_MAC_VER_08 },
+-              { 0x7cf, 0x348, RTL_GIGA_MAC_VER_07 },
+-              { 0x7cf, 0x248, RTL_GIGA_MAC_VER_07 },
+-              { 0x7cf, 0x240, RTL_GIGA_MAC_VER_14 },
+-              { 0x7c8, 0x348, RTL_GIGA_MAC_VER_09 },
+-              { 0x7c8, 0x248, RTL_GIGA_MAC_VER_09 },
+-              { 0x7c8, 0x340, RTL_GIGA_MAC_VER_10 },
+-
+-              /* 8110 family. */
+-              { 0xfc8, 0x980, RTL_GIGA_MAC_VER_06 },
+-              { 0xfc8, 0x180, RTL_GIGA_MAC_VER_05 },
+-              { 0xfc8, 0x100, RTL_GIGA_MAC_VER_04 },
+-              { 0xfc8, 0x040, RTL_GIGA_MAC_VER_03 },
+-              { 0xfc8, 0x008, RTL_GIGA_MAC_VER_02 },
+-
+-              /* Catch-all */
+-              { 0x000, 0x000, RTL_GIGA_MAC_NONE   }
++      /* Chips combining a 1Gbps MAC with a 100Mbps PHY */
++      static const struct rtl_chip_info rtl8106eus_info = {
++              .mac_version = RTL_GIGA_MAC_VER_43,
++              .name = "RTL8106eus",
++              .fw_name = FIRMWARE_8106E_2,
+       };
+-      const struct rtl_mac_info *p = mac_info;
+-      enum mac_version ver;
++      static const struct rtl_chip_info rtl8107e_info = {
++              .mac_version = RTL_GIGA_MAC_VER_48,
++              .name = "RTL8107e",
++              .fw_name = FIRMWARE_8107E_2,
++      };
++      const struct rtl_chip_info *p = rtl_chip_infos;
+       while ((xid & p->mask) != p->val)
+               p++;
+-      ver = p->ver;
+-      if (ver != RTL_GIGA_MAC_NONE && !gmii) {
+-              if (ver == RTL_GIGA_MAC_VER_42)
+-                      ver = RTL_GIGA_MAC_VER_43;
+-              else if (ver == RTL_GIGA_MAC_VER_46)
+-                      ver = RTL_GIGA_MAC_VER_48;
+-      }
++      if (p->mac_version == RTL_GIGA_MAC_VER_42 && !gmii)
++              return &rtl8106eus_info;
++      if (p->mac_version == RTL_GIGA_MAC_VER_46 && !gmii)
++              return &rtl8107e_info;
+-      return ver;
++      return p;
+ }
+ static void rtl_release_firmware(struct rtl8169_private *tp)
+@@ -5437,9 +5369,9 @@ static bool rtl_aspm_is_safe(struct rtl8
+ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+ {
++      const struct rtl_chip_info *chip;
+       struct rtl8169_private *tp;
+       int jumbo_max, region, rc;
+-      enum mac_version chipset;
+       struct net_device *dev;
+       u32 txconfig;
+       u16 xid;
+@@ -5489,12 +5421,13 @@ static int rtl_init_one(struct pci_dev *
+       xid = (txconfig >> 20) & 0xfcf;
+       /* Identify chip attached to board */
+-      chipset = rtl8169_get_mac_version(xid, tp->supports_gmii);
+-      if (chipset == RTL_GIGA_MAC_NONE)
++      chip = rtl8169_get_chip_version(xid, tp->supports_gmii);
++      if (chip->mac_version == RTL_GIGA_MAC_NONE)
+               return dev_err_probe(&pdev->dev, -ENODEV,
+                                    "unknown chip XID %03x, contact r8169 maintainers (see MAINTAINERS file)\n",
+                                    xid);
+-      tp->mac_version = chipset;
++      tp->mac_version = chip->mac_version;
++      tp->fw_name = chip->fw_name;
+       /* Disable ASPM L1 as that cause random device stop working
+        * problems as well as full system hangs for some PCIe devices users.
+@@ -5599,8 +5532,6 @@ static int rtl_init_one(struct pci_dev *
+       rtl_set_irq_mask(tp);
+-      tp->fw_name = rtl_chip_infos[chipset].fw_name;
+-
+       tp->counters = dmam_alloc_coherent (&pdev->dev, sizeof(*tp->counters),
+                                           &tp->counters_phys_addr,
+                                           GFP_KERNEL);
+@@ -5625,7 +5556,7 @@ static int rtl_init_one(struct pci_dev *
+       }
+       netdev_info(dev, "%s, %pM, XID %03x, IRQ %d\n",
+-                  rtl_chip_infos[chipset].name, dev->dev_addr, xid, tp->irq);
++                  chip->name, dev->dev_addr, xid, tp->irq);
+       if (jumbo_max)
+               netdev_info(dev, "jumbo features [frames: %d bytes, tx checksumming: %s]\n",
diff --git a/target/linux/generic/backport-6.12/780-37-v6.16-r8169-add-RTL_GIGA_MAC_VER_LAST-to-facilitate-adding.patch b/target/linux/generic/backport-6.12/780-37-v6.16-r8169-add-RTL_GIGA_MAC_VER_LAST-to-facilitate-adding.patch
new file mode 100644 (file)
index 0000000..6dad3f3
--- /dev/null
@@ -0,0 +1,159 @@
+From fe733618b27a8c033f0d246c2efff56fca322656 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Tue, 15 Apr 2025 21:39:23 +0200
+Subject: [PATCH] r8169: add RTL_GIGA_MAC_VER_LAST to facilitate adding support
+ for new chip versions
+
+Add a new mac_version enum value RTL_GIGA_MAC_VER_LAST. Benefit is that
+when adding support for a new chip version we have to touch less code,
+except something changes fundamentally.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/06991f47-2aec-4aa2-8918-2c6e79332303@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169.h      |  3 ++-
+ drivers/net/ethernet/realtek/r8169_main.c | 28 +++++++++++------------
+ 2 files changed, 16 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169.h
++++ b/drivers/net/ethernet/realtek/r8169.h
+@@ -73,7 +73,8 @@ enum mac_version {
+       RTL_GIGA_MAC_VER_66,
+       RTL_GIGA_MAC_VER_70,
+       RTL_GIGA_MAC_VER_71,
+-      RTL_GIGA_MAC_NONE
++      RTL_GIGA_MAC_NONE,
++      RTL_GIGA_MAC_VER_LAST = RTL_GIGA_MAC_NONE - 1
+ };
+ struct rtl8169_private;
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -1289,7 +1289,7 @@ static void rtl_writephy(struct rtl8169_
+       case RTL_GIGA_MAC_VER_31:
+               r8168dp_2_mdio_write(tp, location, val);
+               break;
+-      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_71:
++      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_LAST:
+               r8168g_mdio_write(tp, location, val);
+               break;
+       default:
+@@ -1304,7 +1304,7 @@ static int rtl_readphy(struct rtl8169_pr
+       case RTL_GIGA_MAC_VER_28:
+       case RTL_GIGA_MAC_VER_31:
+               return r8168dp_2_mdio_read(tp, location);
+-      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_71:
++      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_LAST:
+               return r8168g_mdio_read(tp, location);
+       default:
+               return r8169_mdio_read(tp, location);
+@@ -1656,7 +1656,7 @@ static void __rtl8169_set_wol(struct rtl
+               break;
+       case RTL_GIGA_MAC_VER_34:
+       case RTL_GIGA_MAC_VER_37:
+-      case RTL_GIGA_MAC_VER_39 ... RTL_GIGA_MAC_VER_71:
++      case RTL_GIGA_MAC_VER_39 ... RTL_GIGA_MAC_VER_LAST:
+               r8169_mod_reg8_cond(tp, Config2, PME_SIGNAL, wolopts);
+               break;
+       default:
+@@ -2129,7 +2129,7 @@ static void rtl_set_eee_txidle_timer(str
+               tp->tx_lpi_timer = timer_val;
+               r8168_mac_ocp_write(tp, 0xe048, timer_val);
+               break;
+-      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_71:
++      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_LAST:
+               tp->tx_lpi_timer = timer_val;
+               RTL_W16(tp, EEE_TXIDLE_TIMER_8125, timer_val);
+               break;
+@@ -2491,7 +2491,7 @@ static void rtl_init_rxcfg(struct rtl816
+       case RTL_GIGA_MAC_VER_61:
+               RTL_W32(tp, RxConfig, RX_FETCH_DFLT_8125 | RX_DMA_BURST);
+               break;
+-      case RTL_GIGA_MAC_VER_63 ... RTL_GIGA_MAC_VER_71:
++      case RTL_GIGA_MAC_VER_63 ... RTL_GIGA_MAC_VER_LAST:
+               RTL_W32(tp, RxConfig, RX_FETCH_DFLT_8125 | RX_DMA_BURST |
+                       RX_PAUSE_SLOT_ON);
+               break;
+@@ -2623,7 +2623,7 @@ static void rtl_wait_txrx_fifo_empty(str
+       case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_61:
+               rtl_loop_wait_high(tp, &rtl_rxtx_empty_cond, 100, 42);
+               break;
+-      case RTL_GIGA_MAC_VER_63 ... RTL_GIGA_MAC_VER_71:
++      case RTL_GIGA_MAC_VER_63 ... RTL_GIGA_MAC_VER_LAST:
+               RTL_W8(tp, ChipCmd, RTL_R8(tp, ChipCmd) | StopReq);
+               rtl_loop_wait_high(tp, &rtl_rxtx_empty_cond, 100, 42);
+               rtl_loop_wait_high(tp, &rtl_rxtx_empty_cond_2, 100, 42);
+@@ -2895,7 +2895,7 @@ static void rtl_enable_exit_l1(struct rt
+       case RTL_GIGA_MAC_VER_37 ... RTL_GIGA_MAC_VER_38:
+               rtl_eri_set_bits(tp, 0xd4, 0x0c00);
+               break;
+-      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_71:
++      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_LAST:
+               r8168_mac_ocp_modify(tp, 0xc0ac, 0, 0x1f80);
+               break;
+       default:
+@@ -2909,7 +2909,7 @@ static void rtl_disable_exit_l1(struct r
+       case RTL_GIGA_MAC_VER_34 ... RTL_GIGA_MAC_VER_38:
+               rtl_eri_clear_bits(tp, 0xd4, 0x1f00);
+               break;
+-      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_71:
++      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_LAST:
+               r8168_mac_ocp_modify(tp, 0xc0ac, 0x1f80, 0);
+               break;
+       default:
+@@ -2947,7 +2947,7 @@ static void rtl_hw_aspm_clkreq_enable(st
+               switch (tp->mac_version) {
+               case RTL_GIGA_MAC_VER_46 ... RTL_GIGA_MAC_VER_48:
+-              case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_71:
++              case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_LAST:
+                       /* reset ephy tx/rx disable timer */
+                       r8168_mac_ocp_modify(tp, 0xe094, 0xff00, 0);
+                       /* chip can trigger L1.2 */
+@@ -2959,7 +2959,7 @@ static void rtl_hw_aspm_clkreq_enable(st
+       } else {
+               switch (tp->mac_version) {
+               case RTL_GIGA_MAC_VER_46 ... RTL_GIGA_MAC_VER_48:
+-              case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_71:
++              case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_LAST:
+                       r8168_mac_ocp_modify(tp, 0xe092, 0x00ff, 0);
+                       break;
+               default:
+@@ -4091,7 +4091,7 @@ static void rtl8169_cleanup(struct rtl81
+               RTL_W8(tp, ChipCmd, RTL_R8(tp, ChipCmd) | StopReq);
+               rtl_loop_wait_high(tp, &rtl_txcfg_empty_cond, 100, 666);
+               break;
+-      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_71:
++      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_LAST:
+               rtl_enable_rxdvgate(tp);
+               fsleep(2000);
+               break;
+@@ -4248,7 +4248,7 @@ static unsigned int rtl_quirk_packet_pad
+       switch (tp->mac_version) {
+       case RTL_GIGA_MAC_VER_34:
+-      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_71:
++      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_LAST:
+               padto = max_t(unsigned int, padto, ETH_ZLEN);
+               break;
+       default:
+@@ -5299,7 +5299,7 @@ static void rtl_hw_initialize(struct rtl
+       case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_48:
+               rtl_hw_init_8168g(tp);
+               break;
+-      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_71:
++      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_LAST:
+               rtl_hw_init_8125(tp);
+               break;
+       default:
+@@ -5324,7 +5324,7 @@ static int rtl_jumbo_max(struct rtl8169_
+       case RTL_GIGA_MAC_VER_18 ... RTL_GIGA_MAC_VER_24:
+               return JUMBO_6K;
+       /* RTL8125/8126 */
+-      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_71:
++      case RTL_GIGA_MAC_VER_61 ... RTL_GIGA_MAC_VER_LAST:
+               return JUMBO_16K;
+       default:
+               return JUMBO_9K;
diff --git a/target/linux/generic/backport-6.12/780-38-v6.16-r8169-use-pci_prepare_to_sleep-in-rtl_shutdown.patch b/target/linux/generic/backport-6.12/780-38-v6.16-r8169-use-pci_prepare_to_sleep-in-rtl_shutdown.patch
new file mode 100644 (file)
index 0000000..0f085f0
--- /dev/null
@@ -0,0 +1,37 @@
+From b7ed5d5a78fccee96cf8919ac2c7a064c2f4c45b Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Mon, 21 Apr 2025 11:25:18 +0200
+Subject: [PATCH] r8169: use pci_prepare_to_sleep in rtl_shutdown
+
+Use pci_prepare_to_sleep() like PCI core does in pci_pm_suspend_noirq.
+This aligns setting a low-power mode during shutdown with the handling
+of the transition to system suspend. Also the transition to runtime
+suspend uses pci_target_state() instead of setting D3hot unconditionally.
+
+Note: pci_prepare_to_sleep() uses device_may_wakeup() to check whether
+      device may generate wakeup events. So we don't lose anything by
+      not passing tp->saved_wolopts any longer.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
+Link: https://patch.msgid.link/f573fdbd-ba6d-41c1-b68f-311d3c88db2c@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -5037,10 +5037,8 @@ static void rtl_shutdown(struct pci_dev
+       /* Restore original MAC address */
+       rtl_rar_set(tp, tp->dev->perm_addr);
+-      if (system_state == SYSTEM_POWER_OFF && !tp->dash_enabled) {
+-              pci_wake_from_d3(pdev, tp->saved_wolopts);
+-              pci_set_power_state(pdev, PCI_D3hot);
+-      }
++      if (system_state == SYSTEM_POWER_OFF && !tp->dash_enabled)
++              pci_prepare_to_sleep(pdev);
+ }
+ static void rtl_remove_one(struct pci_dev *pdev)
diff --git a/target/linux/generic/backport-6.12/780-39-v6.16-r8169-merge-chip-versions-70-and-71-RTL8126A.patch b/target/linux/generic/backport-6.12/780-39-v6.16-r8169-merge-chip-versions-70-and-71-RTL8126A.patch
new file mode 100644 (file)
index 0000000..caab6c6
--- /dev/null
@@ -0,0 +1,106 @@
+From 4dec0702b8627c364a0e1f2c5b249e06709a1c24 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Fri, 18 Apr 2025 11:23:45 +0200
+Subject: [PATCH] r8169: merge chip versions 70 and 71 (RTL8126A)
+
+Handling of both chip versions is the same, only difference is
+the firmware. So we can merge handling of both chip versions.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/97d7ae79-d021-4b6b-b424-89e5e305b029@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169.h            |  1 -
+ drivers/net/ethernet/realtek/r8169_main.c       | 15 ++++-----------
+ drivers/net/ethernet/realtek/r8169_phy_config.c |  1 -
+ 3 files changed, 4 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169.h
++++ b/drivers/net/ethernet/realtek/r8169.h
+@@ -72,7 +72,6 @@ enum mac_version {
+       RTL_GIGA_MAC_VER_65,
+       RTL_GIGA_MAC_VER_66,
+       RTL_GIGA_MAC_VER_70,
+-      RTL_GIGA_MAC_VER_71,
+       RTL_GIGA_MAC_NONE,
+       RTL_GIGA_MAC_VER_LAST = RTL_GIGA_MAC_NONE - 1
+ };
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -99,7 +99,7 @@ static const struct rtl_chip_info {
+       const char *fw_name;
+ } rtl_chip_infos[] = {
+       /* 8126A family. */
+-      { 0x7cf, 0x64a, RTL_GIGA_MAC_VER_71, "RTL8126A", FIRMWARE_8126A_3 },
++      { 0x7cf, 0x64a, RTL_GIGA_MAC_VER_70, "RTL8126A", FIRMWARE_8126A_3 },
+       { 0x7cf, 0x649, RTL_GIGA_MAC_VER_70, "RTL8126A", FIRMWARE_8126A_2 },
+       /* 8125BP family. */
+@@ -2936,7 +2936,6 @@ static void rtl_hw_aspm_clkreq_enable(st
+               rtl_mod_config5(tp, 0, ASPM_en);
+               switch (tp->mac_version) {
+               case RTL_GIGA_MAC_VER_70:
+-              case RTL_GIGA_MAC_VER_71:
+                       val8 = RTL_R8(tp, INT_CFG0_8125) | INT_CFG0_CLKREQEN;
+                       RTL_W8(tp, INT_CFG0_8125, val8);
+                       break;
+@@ -2968,7 +2967,6 @@ static void rtl_hw_aspm_clkreq_enable(st
+               switch (tp->mac_version) {
+               case RTL_GIGA_MAC_VER_70:
+-              case RTL_GIGA_MAC_VER_71:
+                       val8 = RTL_R8(tp, INT_CFG0_8125) & ~INT_CFG0_CLKREQEN;
+                       RTL_W8(tp, INT_CFG0_8125, val8);
+                       break;
+@@ -3688,12 +3686,10 @@ static void rtl_hw_start_8125_common(str
+       /* disable new tx descriptor format */
+       r8168_mac_ocp_modify(tp, 0xeb58, 0x0001, 0x0000);
+-      if (tp->mac_version == RTL_GIGA_MAC_VER_70 ||
+-          tp->mac_version == RTL_GIGA_MAC_VER_71)
++      if (tp->mac_version == RTL_GIGA_MAC_VER_70)
+               RTL_W8(tp, 0xD8, RTL_R8(tp, 0xD8) & ~0x02);
+-      if (tp->mac_version == RTL_GIGA_MAC_VER_70 ||
+-          tp->mac_version == RTL_GIGA_MAC_VER_71)
++      if (tp->mac_version == RTL_GIGA_MAC_VER_70)
+               r8168_mac_ocp_modify(tp, 0xe614, 0x0700, 0x0400);
+       else if (tp->mac_version == RTL_GIGA_MAC_VER_63)
+               r8168_mac_ocp_modify(tp, 0xe614, 0x0700, 0x0200);
+@@ -3711,8 +3707,7 @@ static void rtl_hw_start_8125_common(str
+       r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000);
+       r8168_mac_ocp_modify(tp, 0xe040, 0x1000, 0x0000);
+       r8168_mac_ocp_modify(tp, 0xea1c, 0x0003, 0x0001);
+-      if (tp->mac_version == RTL_GIGA_MAC_VER_70 ||
+-          tp->mac_version == RTL_GIGA_MAC_VER_71)
++      if (tp->mac_version == RTL_GIGA_MAC_VER_70)
+               r8168_mac_ocp_modify(tp, 0xea1c, 0x0300, 0x0000);
+       else
+               r8168_mac_ocp_modify(tp, 0xea1c, 0x0004, 0x0000);
+@@ -3835,7 +3830,6 @@ static void rtl_hw_config(struct rtl8169
+               [RTL_GIGA_MAC_VER_65] = rtl_hw_start_8125d,
+               [RTL_GIGA_MAC_VER_66] = rtl_hw_start_8125d,
+               [RTL_GIGA_MAC_VER_70] = rtl_hw_start_8126a,
+-              [RTL_GIGA_MAC_VER_71] = rtl_hw_start_8126a,
+       };
+       if (hw_configs[tp->mac_version])
+@@ -3859,7 +3853,6 @@ static void rtl_hw_start_8125(struct rtl
+               break;
+       case RTL_GIGA_MAC_VER_63:
+       case RTL_GIGA_MAC_VER_70:
+-      case RTL_GIGA_MAC_VER_71:
+               for (i = 0xa00; i < 0xa80; i += 4)
+                       RTL_W32(tp, i, 0);
+               RTL_W16(tp, INT_CFG1_8125, 0x0000);
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -1183,7 +1183,6 @@ void r8169_hw_phy_config(struct rtl8169_
+               [RTL_GIGA_MAC_VER_65] = rtl8125d_hw_phy_config,
+               [RTL_GIGA_MAC_VER_66] = rtl8125bp_hw_phy_config,
+               [RTL_GIGA_MAC_VER_70] = rtl8126a_hw_phy_config,
+-              [RTL_GIGA_MAC_VER_71] = rtl8126a_hw_phy_config,
+       };
+       if (phy_configs[ver])
diff --git a/target/linux/generic/backport-6.12/780-40-v6.16-r8169-merge-chip-versions-64-and-65-RTL8125D.patch b/target/linux/generic/backport-6.12/780-40-v6.16-r8169-merge-chip-versions-64-and-65-RTL8125D.patch
new file mode 100644 (file)
index 0000000..5110c27
--- /dev/null
@@ -0,0 +1,65 @@
+From f372ef6ed5a6b0401c884561d4bba1843e54d46a Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Fri, 18 Apr 2025 11:24:30 +0200
+Subject: [PATCH] r8169: merge chip versions 64 and 65 (RTL8125D)
+
+Handling of both chip versions is the same, only difference is
+the firmware. So we can merge handling of both chip versions.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/0baad123-c679-4154-923f-fdc12783e900@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169.h            | 1 -
+ drivers/net/ethernet/realtek/r8169_main.c       | 4 +---
+ drivers/net/ethernet/realtek/r8169_phy_config.c | 1 -
+ 3 files changed, 1 insertion(+), 5 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169.h
++++ b/drivers/net/ethernet/realtek/r8169.h
+@@ -69,7 +69,6 @@ enum mac_version {
+       RTL_GIGA_MAC_VER_61,
+       RTL_GIGA_MAC_VER_63,
+       RTL_GIGA_MAC_VER_64,
+-      RTL_GIGA_MAC_VER_65,
+       RTL_GIGA_MAC_VER_66,
+       RTL_GIGA_MAC_VER_70,
+       RTL_GIGA_MAC_NONE,
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -106,7 +106,7 @@ static const struct rtl_chip_info {
+       { 0x7cf, 0x681, RTL_GIGA_MAC_VER_66, "RTL8125BP", FIRMWARE_8125BP_2 },
+       /* 8125D family. */
+-      { 0x7cf, 0x689, RTL_GIGA_MAC_VER_65, "RTL8125D", FIRMWARE_8125D_2 },
++      { 0x7cf, 0x689, RTL_GIGA_MAC_VER_64, "RTL8125D", FIRMWARE_8125D_2 },
+       { 0x7cf, 0x688, RTL_GIGA_MAC_VER_64, "RTL8125D", FIRMWARE_8125D_1 },
+       /* 8125B family. */
+@@ -3827,7 +3827,6 @@ static void rtl_hw_config(struct rtl8169
+               [RTL_GIGA_MAC_VER_61] = rtl_hw_start_8125a_2,
+               [RTL_GIGA_MAC_VER_63] = rtl_hw_start_8125b,
+               [RTL_GIGA_MAC_VER_64] = rtl_hw_start_8125d,
+-              [RTL_GIGA_MAC_VER_65] = rtl_hw_start_8125d,
+               [RTL_GIGA_MAC_VER_66] = rtl_hw_start_8125d,
+               [RTL_GIGA_MAC_VER_70] = rtl_hw_start_8126a,
+       };
+@@ -3846,7 +3845,6 @@ static void rtl_hw_start_8125(struct rtl
+       switch (tp->mac_version) {
+       case RTL_GIGA_MAC_VER_61:
+       case RTL_GIGA_MAC_VER_64:
+-      case RTL_GIGA_MAC_VER_65:
+       case RTL_GIGA_MAC_VER_66:
+               for (i = 0xa00; i < 0xb00; i += 4)
+                       RTL_W32(tp, i, 0);
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -1180,7 +1180,6 @@ void r8169_hw_phy_config(struct rtl8169_
+               [RTL_GIGA_MAC_VER_61] = rtl8125a_2_hw_phy_config,
+               [RTL_GIGA_MAC_VER_63] = rtl8125b_hw_phy_config,
+               [RTL_GIGA_MAC_VER_64] = rtl8125d_hw_phy_config,
+-              [RTL_GIGA_MAC_VER_65] = rtl8125d_hw_phy_config,
+               [RTL_GIGA_MAC_VER_66] = rtl8125bp_hw_phy_config,
+               [RTL_GIGA_MAC_VER_70] = rtl8126a_hw_phy_config,
+       };
diff --git a/target/linux/generic/backport-6.12/780-41-v6.16-r8169-merge-chip-versions-52-and-53-RTL8117.patch b/target/linux/generic/backport-6.12/780-41-v6.16-r8169-merge-chip-versions-52-and-53-RTL8117.patch
new file mode 100644 (file)
index 0000000..b28c28f
--- /dev/null
@@ -0,0 +1,113 @@
+From 4f51e7d370a04122fa78470b031d6487c52298b1 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Fri, 18 Apr 2025 11:25:17 +0200
+Subject: [PATCH] r8169: merge chip versions 52 and 53 (RTL8117)
+
+Handling of both chip versions is the same, only difference is
+the firmware. So we can merge handling of both chip versions.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/ae866b71-c904-434e-befb-848c831e33ff@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169.h            |  1 -
+ drivers/net/ethernet/realtek/r8169_main.c       | 17 +++++++----------
+ drivers/net/ethernet/realtek/r8169_phy_config.c |  1 -
+ 3 files changed, 7 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169.h
++++ b/drivers/net/ethernet/realtek/r8169.h
+@@ -64,7 +64,6 @@ enum mac_version {
+       /* support for RTL_GIGA_MAC_VER_50 has been removed */
+       RTL_GIGA_MAC_VER_51,
+       RTL_GIGA_MAC_VER_52,
+-      RTL_GIGA_MAC_VER_53,
+       /* support for RTL_GIGA_MAC_VER_60 has been removed */
+       RTL_GIGA_MAC_VER_61,
+       RTL_GIGA_MAC_VER_63,
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -116,7 +116,7 @@ static const struct rtl_chip_info {
+       { 0x7cf, 0x609, RTL_GIGA_MAC_VER_61, "RTL8125A", FIRMWARE_8125A_3 },
+       /* RTL8117 */
+-      { 0x7cf, 0x54b, RTL_GIGA_MAC_VER_53, "RTL8168fp/RTL8117" },
++      { 0x7cf, 0x54b, RTL_GIGA_MAC_VER_52, "RTL8168fp/RTL8117" },
+       { 0x7cf, 0x54a, RTL_GIGA_MAC_VER_52, "RTL8168fp/RTL8117",
+         FIRMWARE_8168FP_3 },
+@@ -830,7 +830,7 @@ static bool rtl_is_8168evl_up(struct rtl
+ {
+       return tp->mac_version >= RTL_GIGA_MAC_VER_34 &&
+              tp->mac_version != RTL_GIGA_MAC_VER_39 &&
+-             tp->mac_version <= RTL_GIGA_MAC_VER_53;
++             tp->mac_version <= RTL_GIGA_MAC_VER_52;
+ }
+ static bool rtl_supports_eee(struct rtl8169_private *tp)
+@@ -998,9 +998,7 @@ void r8169_get_led_name(struct rtl8169_p
+ static void r8168fp_adjust_ocp_cmd(struct rtl8169_private *tp, u32 *cmd, int type)
+ {
+       /* based on RTL8168FP_OOBMAC_BASE in vendor driver */
+-      if (type == ERIAR_OOB &&
+-          (tp->mac_version == RTL_GIGA_MAC_VER_52 ||
+-           tp->mac_version == RTL_GIGA_MAC_VER_53))
++      if (type == ERIAR_OOB && tp->mac_version == RTL_GIGA_MAC_VER_52)
+               *cmd |= 0xf70 << 18;
+ }
+@@ -1500,7 +1498,7 @@ static enum rtl_dash_type rtl_get_dash_t
+       case RTL_GIGA_MAC_VER_28:
+       case RTL_GIGA_MAC_VER_31:
+               return RTL_DASH_DP;
+-      case RTL_GIGA_MAC_VER_51 ... RTL_GIGA_MAC_VER_53:
++      case RTL_GIGA_MAC_VER_51 ... RTL_GIGA_MAC_VER_52:
+               return RTL_DASH_EP;
+       case RTL_GIGA_MAC_VER_66:
+               return RTL_DASH_25_BP;
+@@ -2485,7 +2483,7 @@ static void rtl_init_rxcfg(struct rtl816
+       case RTL_GIGA_MAC_VER_38:
+               RTL_W32(tp, RxConfig, RX128_INT_EN | RX_MULTI_EN | RX_DMA_BURST);
+               break;
+-      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_53:
++      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_52:
+               RTL_W32(tp, RxConfig, RX128_INT_EN | RX_MULTI_EN | RX_DMA_BURST | RX_EARLY_OFF);
+               break;
+       case RTL_GIGA_MAC_VER_61:
+@@ -2616,7 +2614,7 @@ DECLARE_RTL_COND(rtl_rxtx_empty_cond_2)
+ static void rtl_wait_txrx_fifo_empty(struct rtl8169_private *tp)
+ {
+       switch (tp->mac_version) {
+-      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_53:
++      case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_52:
+               rtl_loop_wait_high(tp, &rtl_txcfg_empty_cond, 100, 42);
+               rtl_loop_wait_high(tp, &rtl_rxtx_empty_cond, 100, 42);
+               break;
+@@ -3823,7 +3821,6 @@ static void rtl_hw_config(struct rtl8169
+               [RTL_GIGA_MAC_VER_48] = rtl_hw_start_8168h_1,
+               [RTL_GIGA_MAC_VER_51] = rtl_hw_start_8168ep_3,
+               [RTL_GIGA_MAC_VER_52] = rtl_hw_start_8117,
+-              [RTL_GIGA_MAC_VER_53] = rtl_hw_start_8117,
+               [RTL_GIGA_MAC_VER_61] = rtl_hw_start_8125a_2,
+               [RTL_GIGA_MAC_VER_63] = rtl_hw_start_8125b,
+               [RTL_GIGA_MAC_VER_64] = rtl_hw_start_8125d,
+@@ -5282,7 +5279,7 @@ static void rtl_hw_init_8125(struct rtl8
+ static void rtl_hw_initialize(struct rtl8169_private *tp)
+ {
+       switch (tp->mac_version) {
+-      case RTL_GIGA_MAC_VER_51 ... RTL_GIGA_MAC_VER_53:
++      case RTL_GIGA_MAC_VER_51 ... RTL_GIGA_MAC_VER_52:
+               rtl8168ep_stop_cmac(tp);
+               fallthrough;
+       case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_48:
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -1176,7 +1176,6 @@ void r8169_hw_phy_config(struct rtl8169_
+               [RTL_GIGA_MAC_VER_48] = rtl8168h_2_hw_phy_config,
+               [RTL_GIGA_MAC_VER_51] = rtl8168ep_2_hw_phy_config,
+               [RTL_GIGA_MAC_VER_52] = rtl8117_hw_phy_config,
+-              [RTL_GIGA_MAC_VER_53] = rtl8117_hw_phy_config,
+               [RTL_GIGA_MAC_VER_61] = rtl8125a_2_hw_phy_config,
+               [RTL_GIGA_MAC_VER_63] = rtl8125b_hw_phy_config,
+               [RTL_GIGA_MAC_VER_64] = rtl8125d_hw_phy_config,
diff --git a/target/linux/generic/backport-6.12/780-42-v6.16-r8169-add-support-for-RTL8127A.patch b/target/linux/generic/backport-6.12/780-42-v6.16-r8169-add-support-for-RTL8127A.patch
new file mode 100644 (file)
index 0000000..45587a3
--- /dev/null
@@ -0,0 +1,323 @@
+From f24f7b2f3af9e008ded20f804d7829ee2efd43f2 Mon Sep 17 00:00:00 2001
+From: ChunHao Lin <hau@realtek.com>
+Date: Thu, 15 May 2025 17:53:03 +0800
+Subject: [PATCH] r8169: add support for RTL8127A
+
+This adds support for 10Gbs chip RTL8127A.
+
+Signed-off-by: ChunHao Lin <hau@realtek.com>
+Reviewed-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/20250515095303.3138-1-hau@realtek.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169.h          |   1 +
+ drivers/net/ethernet/realtek/r8169_main.c     |  29 ++-
+ .../net/ethernet/realtek/r8169_phy_config.c   | 166 ++++++++++++++++++
+ 3 files changed, 193 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169.h
++++ b/drivers/net/ethernet/realtek/r8169.h
+@@ -70,6 +70,7 @@ enum mac_version {
+       RTL_GIGA_MAC_VER_64,
+       RTL_GIGA_MAC_VER_66,
+       RTL_GIGA_MAC_VER_70,
++      RTL_GIGA_MAC_VER_80,
+       RTL_GIGA_MAC_NONE,
+       RTL_GIGA_MAC_VER_LAST = RTL_GIGA_MAC_NONE - 1
+ };
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -60,6 +60,7 @@
+ #define FIRMWARE_8125BP_2     "rtl_nic/rtl8125bp-2.fw"
+ #define FIRMWARE_8126A_2      "rtl_nic/rtl8126a-2.fw"
+ #define FIRMWARE_8126A_3      "rtl_nic/rtl8126a-3.fw"
++#define FIRMWARE_8127A_1      "rtl_nic/rtl8127a-1.fw"
+ #define TX_DMA_BURST  7       /* Maximum PCI burst, '7' is unlimited */
+ #define InterFrameGap 0x03    /* 3 means InterFrameGap = the shortest one */
+@@ -98,6 +99,9 @@ static const struct rtl_chip_info {
+       const char *name;
+       const char *fw_name;
+ } rtl_chip_infos[] = {
++      /* 8127A family. */
++      { 0x7cf, 0x6c9, RTL_GIGA_MAC_VER_80, "RTL8127A", FIRMWARE_8127A_1 },
++
+       /* 8126A family. */
+       { 0x7cf, 0x64a, RTL_GIGA_MAC_VER_70, "RTL8126A", FIRMWARE_8126A_3 },
+       { 0x7cf, 0x649, RTL_GIGA_MAC_VER_70, "RTL8126A", FIRMWARE_8126A_2 },
+@@ -222,8 +226,10 @@ static const struct pci_device_id rtl816
+       { 0x0001, 0x8168, PCI_ANY_ID, 0x2410 },
+       { PCI_VDEVICE(REALTEK,  0x8125) },
+       { PCI_VDEVICE(REALTEK,  0x8126) },
++      { PCI_VDEVICE(REALTEK,  0x8127) },
+       { PCI_VDEVICE(REALTEK,  0x3000) },
+       { PCI_VDEVICE(REALTEK,  0x5000) },
++      { PCI_VDEVICE(REALTEK,  0x0e10) },
+       {}
+ };
+@@ -769,6 +775,7 @@ MODULE_FIRMWARE(FIRMWARE_8125D_2);
+ MODULE_FIRMWARE(FIRMWARE_8125BP_2);
+ MODULE_FIRMWARE(FIRMWARE_8126A_2);
+ MODULE_FIRMWARE(FIRMWARE_8126A_3);
++MODULE_FIRMWARE(FIRMWARE_8127A_1);
+ static inline struct device *tp_to_dev(struct rtl8169_private *tp)
+ {
+@@ -2934,6 +2941,7 @@ static void rtl_hw_aspm_clkreq_enable(st
+               rtl_mod_config5(tp, 0, ASPM_en);
+               switch (tp->mac_version) {
+               case RTL_GIGA_MAC_VER_70:
++              case RTL_GIGA_MAC_VER_80:
+                       val8 = RTL_R8(tp, INT_CFG0_8125) | INT_CFG0_CLKREQEN;
+                       RTL_W8(tp, INT_CFG0_8125, val8);
+                       break;
+@@ -2965,6 +2973,7 @@ static void rtl_hw_aspm_clkreq_enable(st
+               switch (tp->mac_version) {
+               case RTL_GIGA_MAC_VER_70:
++              case RTL_GIGA_MAC_VER_80:
+                       val8 = RTL_R8(tp, INT_CFG0_8125) & ~INT_CFG0_CLKREQEN;
+                       RTL_W8(tp, INT_CFG0_8125, val8);
+                       break;
+@@ -3684,10 +3693,13 @@ static void rtl_hw_start_8125_common(str
+       /* disable new tx descriptor format */
+       r8168_mac_ocp_modify(tp, 0xeb58, 0x0001, 0x0000);
+-      if (tp->mac_version == RTL_GIGA_MAC_VER_70)
++      if (tp->mac_version == RTL_GIGA_MAC_VER_70 ||
++          tp->mac_version == RTL_GIGA_MAC_VER_80)
+               RTL_W8(tp, 0xD8, RTL_R8(tp, 0xD8) & ~0x02);
+-      if (tp->mac_version == RTL_GIGA_MAC_VER_70)
++      if (tp->mac_version == RTL_GIGA_MAC_VER_80)
++              r8168_mac_ocp_modify(tp, 0xe614, 0x0f00, 0x0f00);
++      else if (tp->mac_version == RTL_GIGA_MAC_VER_70)
+               r8168_mac_ocp_modify(tp, 0xe614, 0x0700, 0x0400);
+       else if (tp->mac_version == RTL_GIGA_MAC_VER_63)
+               r8168_mac_ocp_modify(tp, 0xe614, 0x0700, 0x0200);
+@@ -3705,7 +3717,8 @@ static void rtl_hw_start_8125_common(str
+       r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000);
+       r8168_mac_ocp_modify(tp, 0xe040, 0x1000, 0x0000);
+       r8168_mac_ocp_modify(tp, 0xea1c, 0x0003, 0x0001);
+-      if (tp->mac_version == RTL_GIGA_MAC_VER_70)
++      if (tp->mac_version == RTL_GIGA_MAC_VER_70 ||
++          tp->mac_version == RTL_GIGA_MAC_VER_80)
+               r8168_mac_ocp_modify(tp, 0xea1c, 0x0300, 0x0000);
+       else
+               r8168_mac_ocp_modify(tp, 0xea1c, 0x0004, 0x0000);
+@@ -3783,6 +3796,12 @@ static void rtl_hw_start_8126a(struct rt
+       rtl_hw_start_8125_common(tp);
+ }
++static void rtl_hw_start_8127a(struct rtl8169_private *tp)
++{
++      rtl_set_def_aspm_entry_latency(tp);
++      rtl_hw_start_8125_common(tp);
++}
++
+ static void rtl_hw_config(struct rtl8169_private *tp)
+ {
+       static const rtl_generic_fct hw_configs[] = {
+@@ -3826,6 +3845,7 @@ static void rtl_hw_config(struct rtl8169
+               [RTL_GIGA_MAC_VER_64] = rtl_hw_start_8125d,
+               [RTL_GIGA_MAC_VER_66] = rtl_hw_start_8125d,
+               [RTL_GIGA_MAC_VER_70] = rtl_hw_start_8126a,
++              [RTL_GIGA_MAC_VER_80] = rtl_hw_start_8127a,
+       };
+       if (hw_configs[tp->mac_version])
+@@ -3843,8 +3863,11 @@ static void rtl_hw_start_8125(struct rtl
+       case RTL_GIGA_MAC_VER_61:
+       case RTL_GIGA_MAC_VER_64:
+       case RTL_GIGA_MAC_VER_66:
++      case RTL_GIGA_MAC_VER_80:
+               for (i = 0xa00; i < 0xb00; i += 4)
+                       RTL_W32(tp, i, 0);
++              if (tp->mac_version == RTL_GIGA_MAC_VER_80)
++                      RTL_W16(tp, INT_CFG1_8125, 0x0000);
+               break;
+       case RTL_GIGA_MAC_VER_63:
+       case RTL_GIGA_MAC_VER_70:
+--- a/drivers/net/ethernet/realtek/r8169_phy_config.c
++++ b/drivers/net/ethernet/realtek/r8169_phy_config.c
+@@ -1130,6 +1130,171 @@ static void rtl8126a_hw_phy_config(struc
+       rtl8125_common_config_eee_phy(phydev);
+ }
++static void rtl8127a_1_hw_phy_config(struct rtl8169_private *tp,
++                                   struct phy_device *phydev)
++{
++      r8169_apply_firmware(tp);
++      rtl8168g_enable_gphy_10m(phydev);
++
++      r8168g_phy_param(phydev, 0x8415, 0xff00, 0x9300);
++      r8168g_phy_param(phydev, 0x81a3, 0xff00, 0x0f00);
++      r8168g_phy_param(phydev, 0x81ae, 0xff00, 0x0f00);
++      r8168g_phy_param(phydev, 0x81b9, 0xff00, 0xb900);
++      rtl8125_phy_param(phydev, 0x83b0, 0x0e00, 0x0000);
++      rtl8125_phy_param(phydev, 0x83C5, 0x0e00, 0x0000);
++      rtl8125_phy_param(phydev, 0x83da, 0x0e00, 0x0000);
++      rtl8125_phy_param(phydev, 0x83ef, 0x0e00, 0x0000);
++      phy_modify_paged(phydev, 0x0bf3, 0x14, 0x01f0, 0x0160);
++      phy_modify_paged(phydev, 0x0bf3, 0x15, 0x001f, 0x0014);
++      phy_modify_paged(phydev, 0x0bf2, 0x14, 0x6000, 0x0000);
++      phy_modify_paged(phydev, 0x0bf2, 0x16, 0xc000, 0x0000);
++      phy_modify_paged(phydev, 0x0bf2, 0x14, 0x1fff, 0x0187);
++      phy_modify_paged(phydev, 0x0bf2, 0x15, 0x003f, 0x0003);
++
++      r8168g_phy_param(phydev, 0x8173, 0xffff, 0x8620);
++      r8168g_phy_param(phydev, 0x8175, 0xffff, 0x8671);
++      r8168g_phy_param(phydev, 0x817c, 0x0000, 0x2000);
++      r8168g_phy_param(phydev, 0x8187, 0x0000, 0x2000);
++      r8168g_phy_param(phydev, 0x8192, 0x0000, 0x2000);
++      r8168g_phy_param(phydev, 0x819d, 0x0000, 0x2000);
++      r8168g_phy_param(phydev, 0x81a8, 0x2000, 0x0000);
++      r8168g_phy_param(phydev, 0x81b3, 0x2000, 0x0000);
++      r8168g_phy_param(phydev, 0x81be, 0x0000, 0x2000);
++      r8168g_phy_param(phydev, 0x817d, 0xff00, 0xa600);
++      r8168g_phy_param(phydev, 0x8188, 0xff00, 0xa600);
++      r8168g_phy_param(phydev, 0x8193, 0xff00, 0xa600);
++      r8168g_phy_param(phydev, 0x819e, 0xff00, 0xa600);
++      r8168g_phy_param(phydev, 0x81a9, 0xff00, 0x1400);
++      r8168g_phy_param(phydev, 0x81b4, 0xff00, 0x1400);
++      r8168g_phy_param(phydev, 0x81bf, 0xff00, 0xa600);
++
++      phy_modify_paged(phydev, 0x0aea, 0x15, 0x0028, 0x0000);
++
++      rtl8125_phy_param(phydev, 0x84f0, 0xffff, 0x201c);
++      rtl8125_phy_param(phydev, 0x84f2, 0xffff, 0x3117);
++
++      phy_write_paged(phydev, 0x0aec, 0x13, 0x0000);
++      phy_write_paged(phydev, 0x0ae2, 0x10, 0xffff);
++      phy_write_paged(phydev, 0x0aec, 0x17, 0xffff);
++      phy_write_paged(phydev, 0x0aed, 0x11, 0xffff);
++      phy_write_paged(phydev, 0x0aec, 0x14, 0x0000);
++      phy_modify_paged(phydev, 0x0aed, 0x10, 0x0001, 0x0000);
++      phy_write_paged(phydev, 0x0adb, 0x14, 0x0150);
++      rtl8125_phy_param(phydev, 0x8197, 0xff00, 0x5000);
++      rtl8125_phy_param(phydev, 0x8231, 0xff00, 0x5000);
++      rtl8125_phy_param(phydev, 0x82cb, 0xff00, 0x5000);
++      rtl8125_phy_param(phydev, 0x82cd, 0xff00, 0x5700);
++      rtl8125_phy_param(phydev, 0x8233, 0xff00, 0x5700);
++      rtl8125_phy_param(phydev, 0x8199, 0xff00, 0x5700);
++
++      rtl8125_phy_param(phydev, 0x815a, 0xffff, 0x0150);
++      rtl8125_phy_param(phydev, 0x81f4, 0xffff, 0x0150);
++      rtl8125_phy_param(phydev, 0x828e, 0xffff, 0x0150);
++      rtl8125_phy_param(phydev, 0x81b1, 0xffff, 0x0000);
++      rtl8125_phy_param(phydev, 0x824b, 0xffff, 0x0000);
++      rtl8125_phy_param(phydev, 0x82e5, 0xffff, 0x0000);
++
++      rtl8125_phy_param(phydev, 0x84f7, 0xff00, 0x2800);
++      phy_modify_paged(phydev, 0x0aec, 0x11, 0x0000, 0x1000);
++      rtl8125_phy_param(phydev, 0x81b3, 0xff00, 0xad00);
++      rtl8125_phy_param(phydev, 0x824d, 0xff00, 0xad00);
++      rtl8125_phy_param(phydev, 0x82e7, 0xff00, 0xad00);
++      phy_modify_paged(phydev, 0x0ae4, 0x17, 0x000f, 0x0001);
++      rtl8125_phy_param(phydev, 0x82ce, 0xf000, 0x4000);
++
++      rtl8125_phy_param(phydev, 0x84ac, 0xffff, 0x0000);
++      rtl8125_phy_param(phydev, 0x84ae, 0xffff, 0x0000);
++      rtl8125_phy_param(phydev, 0x84b0, 0xffff, 0xf818);
++      rtl8125_phy_param(phydev, 0x84b2, 0xff00, 0x6000);
++
++      rtl8125_phy_param(phydev, 0x8ffc, 0xffff, 0x6008);
++      rtl8125_phy_param(phydev, 0x8ffe, 0xffff, 0xf450);
++
++      rtl8125_phy_param(phydev, 0x8015, 0x0000, 0x0200);
++      rtl8125_phy_param(phydev, 0x8016, 0x0800, 0x0000);
++      rtl8125_phy_param(phydev, 0x8fe6, 0xff00, 0x0800);
++      rtl8125_phy_param(phydev, 0x8fe4, 0xffff, 0x2114);
++
++      rtl8125_phy_param(phydev, 0x8647, 0xffff, 0xa7b1);
++      rtl8125_phy_param(phydev, 0x8649, 0xffff, 0xbbca);
++      rtl8125_phy_param(phydev, 0x864b, 0xff00, 0xdc00);
++
++      rtl8125_phy_param(phydev, 0x8154, 0xc000, 0x4000);
++      rtl8125_phy_param(phydev, 0x8158, 0xc000, 0x0000);
++
++      rtl8125_phy_param(phydev, 0x826c, 0xffff, 0xffff);
++      rtl8125_phy_param(phydev, 0x826e, 0xffff, 0xffff);
++
++      rtl8125_phy_param(phydev, 0x8872, 0xff00, 0x0e00);
++      r8168g_phy_param(phydev, 0x8012, 0x0000, 0x0800);
++      r8168g_phy_param(phydev, 0x8012, 0x0000, 0x4000);
++      phy_modify_paged(phydev, 0x0b57, 0x13, 0x0000, 0x0001);
++      r8168g_phy_param(phydev, 0x834a, 0xff00, 0x0700);
++      rtl8125_phy_param(phydev, 0x8217, 0x3f00, 0x2a00);
++      r8168g_phy_param(phydev, 0x81b1, 0xff00, 0x0b00);
++      rtl8125_phy_param(phydev, 0x8fed, 0xff00, 0x4e00);
++
++      rtl8125_phy_param(phydev, 0x88ac, 0xff00, 0x2300);
++      phy_modify_paged(phydev, 0x0bf0, 0x16, 0x0000, 0x3800);
++      rtl8125_phy_param(phydev, 0x88de, 0xff00, 0x0000);
++      rtl8125_phy_param(phydev, 0x80b4, 0xffff, 0x5195);
++
++      r8168g_phy_param(phydev, 0x8370, 0xffff, 0x8671);
++      r8168g_phy_param(phydev, 0x8372, 0xffff, 0x86c8);
++
++      r8168g_phy_param(phydev, 0x8401, 0xffff, 0x86c8);
++      r8168g_phy_param(phydev, 0x8403, 0xffff, 0x86da);
++      r8168g_phy_param(phydev, 0x8406, 0x1800, 0x1000);
++      r8168g_phy_param(phydev, 0x8408, 0x1800, 0x1000);
++      r8168g_phy_param(phydev, 0x840a, 0x1800, 0x1000);
++      r8168g_phy_param(phydev, 0x840c, 0x1800, 0x1000);
++      r8168g_phy_param(phydev, 0x840e, 0x1800, 0x1000);
++      r8168g_phy_param(phydev, 0x8410, 0x1800, 0x1000);
++      r8168g_phy_param(phydev, 0x8412, 0x1800, 0x1000);
++      r8168g_phy_param(phydev, 0x8414, 0x1800, 0x1000);
++      r8168g_phy_param(phydev, 0x8416, 0x1800, 0x1000);
++
++      r8168g_phy_param(phydev, 0x82bd, 0xffff, 0x1f40);
++
++      phy_modify_paged(phydev, 0x0bfb, 0x12, 0x07ff, 0x0328);
++      phy_write_paged(phydev, 0x0bfb, 0x13, 0x3e14);
++
++      r8168g_phy_param(phydev, 0x81c4, 0xffff, 0x003b);
++      r8168g_phy_param(phydev, 0x81c6, 0xffff, 0x0086);
++      r8168g_phy_param(phydev, 0x81c8, 0xffff, 0x00b7);
++      r8168g_phy_param(phydev, 0x81ca, 0xffff, 0x00db);
++      r8168g_phy_param(phydev, 0x81cc, 0xffff, 0x00fe);
++      r8168g_phy_param(phydev, 0x81ce, 0xffff, 0x00fe);
++      r8168g_phy_param(phydev, 0x81d0, 0xffff, 0x00fe);
++      r8168g_phy_param(phydev, 0x81d2, 0xffff, 0x00fe);
++      r8168g_phy_param(phydev, 0x81d4, 0xffff, 0x00c3);
++      r8168g_phy_param(phydev, 0x81d6, 0xffff, 0x0078);
++      r8168g_phy_param(phydev, 0x81d8, 0xffff, 0x0047);
++      r8168g_phy_param(phydev, 0x81da, 0xffff, 0x0023);
++
++      rtl8125_phy_param(phydev, 0x88d7, 0xffff, 0x01a0);
++      rtl8125_phy_param(phydev, 0x88d9, 0xffff, 0x01a0);
++      rtl8125_phy_param(phydev, 0x8ffa, 0xffff, 0x002a);
++
++      rtl8125_phy_param(phydev, 0x8fee, 0xffff, 0xffdf);
++      rtl8125_phy_param(phydev, 0x8ff0, 0xffff, 0xffff);
++      rtl8125_phy_param(phydev, 0x8ff2, 0xffff, 0x0a4a);
++      rtl8125_phy_param(phydev, 0x8ff4, 0xffff, 0xaa5a);
++      rtl8125_phy_param(phydev, 0x8ff6, 0xffff, 0x0a4a);
++
++      rtl8125_phy_param(phydev, 0x8ff8, 0xffff, 0xaa5a);
++      rtl8125_phy_param(phydev, 0x88d5, 0xff00, 0x0200);
++
++      r8168g_phy_param(phydev, 0x84bb, 0xff00, 0x0a00);
++      r8168g_phy_param(phydev, 0x84c0, 0xff00, 0x1600);
++
++      phy_modify_paged(phydev, 0x0a43, 0x10, 0x0000, 0x0003);
++
++      rtl8125_legacy_force_mode(phydev);
++      rtl8168g_disable_aldps(phydev);
++      rtl8125_common_config_eee_phy(phydev);
++}
++
+ void r8169_hw_phy_config(struct rtl8169_private *tp, struct phy_device *phydev,
+                        enum mac_version ver)
+ {
+@@ -1181,6 +1346,7 @@ void r8169_hw_phy_config(struct rtl8169_
+               [RTL_GIGA_MAC_VER_64] = rtl8125d_hw_phy_config,
+               [RTL_GIGA_MAC_VER_66] = rtl8125bp_hw_phy_config,
+               [RTL_GIGA_MAC_VER_70] = rtl8126a_hw_phy_config,
++              [RTL_GIGA_MAC_VER_80] = rtl8127a_1_hw_phy_config,
+       };
+       if (phy_configs[ver])
diff --git a/target/linux/generic/backport-6.12/781-01-v6.13-net-phy-realtek-read-duplex-and-gbit-master-from-PHY.patch b/target/linux/generic/backport-6.12/781-01-v6.13-net-phy-realtek-read-duplex-and-gbit-master-from-PHY.patch
new file mode 100644 (file)
index 0000000..31b8aab
--- /dev/null
@@ -0,0 +1,106 @@
+From 081c9c0265c91b8333165aa6230c20bcbc6f7cbf Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 10 Oct 2024 14:07:16 +0100
+Subject: [PATCH] net: phy: realtek: read duplex and gbit master from PHYSR
+ register
+
+The PHYSR MMD register is present and defined equally for all RTL82xx
+Ethernet PHYs.
+Read duplex and Gbit master bits from rtlgen_decode_speed() and rename
+it to rtlgen_decode_physr().
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/b9a76341da851a18c985bc4774fa295babec79bb.1728565530.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek.c | 41 +++++++++++++++++++++++++++++++--------
+ 1 file changed, 33 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/phy/realtek.c
++++ b/drivers/net/phy/realtek.c
+@@ -80,15 +80,18 @@
+ #define RTL822X_VND2_GANLPAR                          0xa414
+-#define RTL822X_VND2_PHYSR                            0xa434
+-
+ #define RTL8366RB_POWER_SAVE                  0x15
+ #define RTL8366RB_POWER_SAVE_ON                       BIT(12)
+ #define RTL9000A_GINMR                                0x14
+ #define RTL9000A_GINMR_LINK_STATUS            BIT(4)
+-#define RTLGEN_SPEED_MASK                     0x0630
++#define RTL_VND2_PHYSR                                0xa434
++#define RTL_VND2_PHYSR_DUPLEX                 BIT(3)
++#define RTL_VND2_PHYSR_SPEEDL                 GENMASK(5, 4)
++#define RTL_VND2_PHYSR_SPEEDH                 GENMASK(10, 9)
++#define RTL_VND2_PHYSR_MASTER                 BIT(11)
++#define RTL_VND2_PHYSR_SPEED_MASK             (RTL_VND2_PHYSR_SPEEDL | RTL_VND2_PHYSR_SPEEDH)
+ #define RTL_GENERIC_PHYID                     0x001cc800
+ #define RTL_8211FVD_PHYID                     0x001cc878
+@@ -661,9 +664,18 @@ static int rtl8366rb_config_init(struct
+ }
+ /* get actual speed to cover the downshift case */
+-static void rtlgen_decode_speed(struct phy_device *phydev, int val)
++static void rtlgen_decode_physr(struct phy_device *phydev, int val)
+ {
+-      switch (val & RTLGEN_SPEED_MASK) {
++      /* bit 3
++       * 0: Half Duplex
++       * 1: Full Duplex
++       */
++      if (val & RTL_VND2_PHYSR_DUPLEX)
++              phydev->duplex = DUPLEX_FULL;
++      else
++              phydev->duplex = DUPLEX_HALF;
++
++      switch (val & RTL_VND2_PHYSR_SPEED_MASK) {
+       case 0x0000:
+               phydev->speed = SPEED_10;
+               break;
+@@ -685,6 +697,19 @@ static void rtlgen_decode_speed(struct p
+       default:
+               break;
+       }
++
++      /* bit 11
++       * 0: Slave Mode
++       * 1: Master Mode
++       */
++      if (phydev->speed >= 1000) {
++              if (val & RTL_VND2_PHYSR_MASTER)
++                      phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
++              else
++                      phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
++      } else {
++              phydev->master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED;
++      }
+ }
+ static int rtlgen_read_status(struct phy_device *phydev)
+@@ -702,7 +727,7 @@ static int rtlgen_read_status(struct phy
+       if (val < 0)
+               return val;
+-      rtlgen_decode_speed(phydev, val);
++      rtlgen_decode_physr(phydev, val);
+       return 0;
+ }
+@@ -1008,11 +1033,11 @@ static int rtl822x_c45_read_status(struc
+               return 0;
+       /* Read actual speed from vendor register. */
+-      val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_PHYSR);
++      val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL_VND2_PHYSR);
+       if (val < 0)
+               return val;
+-      rtlgen_decode_speed(phydev, val);
++      rtlgen_decode_physr(phydev, val);
+       return 0;
+ }
diff --git a/target/linux/generic/backport-6.12/781-02-v6.13-net-phy-realtek-change-order-of-calls-in-C22-read_st.patch b/target/linux/generic/backport-6.12/781-02-v6.13-net-phy-realtek-change-order-of-calls-in-C22-read_st.patch
new file mode 100644 (file)
index 0000000..2e5d7bb
--- /dev/null
@@ -0,0 +1,53 @@
+From 68d5cd09e8919679ce13b85950debea4b2e98e04 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 10 Oct 2024 14:07:26 +0100
+Subject: [PATCH] net: phy: realtek: change order of calls in C22 read_status()
+
+Always call rtlgen_read_status() first, so genphy_read_status() which
+is called by it clears bits in case auto-negotiation has not completed.
+Also clear 10GBT link-partner advertisement bits in case auto-negotiation
+is disabled or has not completed.
+
+Suggested-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/b15929a41621d215c6b2b57393368086589569ec.1728565530.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek.c | 22 +++++++++++++++-------
+ 1 file changed, 15 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/phy/realtek.c
++++ b/drivers/net/phy/realtek.c
+@@ -950,17 +950,25 @@ static void rtl822xb_update_interface(st
+ static int rtl822x_read_status(struct phy_device *phydev)
+ {
+-      if (phydev->autoneg == AUTONEG_ENABLE) {
+-              int lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
++      int lpadv, ret;
+-              if (lpadv < 0)
+-                      return lpadv;
++      ret = rtlgen_read_status(phydev);
++      if (ret < 0)
++              return ret;
+-              mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising,
+-                                                lpadv);
++      if (phydev->autoneg == AUTONEG_DISABLE ||
++          !phydev->autoneg_complete) {
++              mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
++              return 0;
+       }
+-      return rtlgen_read_status(phydev);
++      lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
++      if (lpadv < 0)
++              return lpadv;
++
++      mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, lpadv);
++
++      return 0;
+ }
+ static int rtl822xb_read_status(struct phy_device *phydev)
diff --git a/target/linux/generic/backport-6.12/781-03-v6.13-net-phy-realtek-clear-1000Base-T-link-partner-advert.patch b/target/linux/generic/backport-6.12/781-03-v6.13-net-phy-realtek-clear-1000Base-T-link-partner-advert.patch
new file mode 100644 (file)
index 0000000..f7d8a5a
--- /dev/null
@@ -0,0 +1,30 @@
+From 5cb409b3960e75467cbb0a8e1e5596b4490570e3 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 10 Oct 2024 14:07:39 +0100
+Subject: [PATCH] net: phy: realtek: clear 1000Base-T link partner
+ advertisement
+
+Clear 1000Base-T link partner advertisement bits in Clause-45
+read_status() function in case auto-negotiation is disabled or has not
+been completed.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/9dc9b47b2d675708afef3ad366bfd78eb584d958.1728565530.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/net/phy/realtek.c
++++ b/drivers/net/phy/realtek.c
+@@ -1027,6 +1027,10 @@ static int rtl822x_c45_read_status(struc
+       if (ret < 0)
+               return ret;
++      if (phydev->autoneg == AUTONEG_DISABLE ||
++          !genphy_c45_aneg_done(phydev))
++              mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
++
+       /* Vendor register as C45 has no standardized support for 1000BaseT */
+       if (phydev->autoneg == AUTONEG_ENABLE) {
+               val = phy_read_mmd(phydev, MDIO_MMD_VEND2,
diff --git a/target/linux/generic/backport-6.12/781-06-v6.14-net-phy-realtek-add-support-for-reading-MDIO_MMD_VEN.patch b/target/linux/generic/backport-6.12/781-06-v6.14-net-phy-realtek-add-support-for-reading-MDIO_MMD_VEN.patch
new file mode 100644 (file)
index 0000000..2add672
--- /dev/null
@@ -0,0 +1,47 @@
+From 3d483a10327f38595f714f9f9e9dde43a622cb0f Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Sat, 11 Jan 2025 21:49:31 +0100
+Subject: [PATCH] net: phy: realtek: add support for reading MDIO_MMD_VEND2
+ regs on RTL8125/RTL8126
+
+RTL8125/RTL8126 don't support MMD access to the internal PHY, but
+provide a mechanism to access at least all MDIO_MMD_VEND2 registers.
+By exposing this mechanism standard MMD access functions can be used
+to access the MDIO_MMD_VEND2 registers.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/e821b302-5fe6-49ab-aabd-05da500581c0@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek.c | 12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/realtek.c
++++ b/drivers/net/phy/realtek.c
+@@ -736,7 +736,11 @@ static int rtlgen_read_mmd(struct phy_de
+ {
+       int ret;
+-      if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE) {
++      if (devnum == MDIO_MMD_VEND2) {
++              rtl821x_write_page(phydev, regnum >> 4);
++              ret = __phy_read(phydev, 0x10 + ((regnum & 0xf) >> 1));
++              rtl821x_write_page(phydev, 0);
++      } else if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE) {
+               rtl821x_write_page(phydev, 0xa5c);
+               ret = __phy_read(phydev, 0x12);
+               rtl821x_write_page(phydev, 0);
+@@ -760,7 +764,11 @@ static int rtlgen_write_mmd(struct phy_d
+ {
+       int ret;
+-      if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) {
++      if (devnum == MDIO_MMD_VEND2) {
++              rtl821x_write_page(phydev, regnum >> 4);
++              ret = __phy_write(phydev, 0x10 + ((regnum & 0xf) >> 1), val);
++              rtl821x_write_page(phydev, 0);
++      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) {
+               rtl821x_write_page(phydev, 0xa5d);
+               ret = __phy_write(phydev, 0x10, val);
+               rtl821x_write_page(phydev, 0);
diff --git a/target/linux/generic/backport-6.12/781-07-v6.14-net-phy-realtek-clear-1000Base-T-lpa-if-link-is-down.patch b/target/linux/generic/backport-6.12/781-07-v6.14-net-phy-realtek-clear-1000Base-T-lpa-if-link-is-down.patch
new file mode 100644 (file)
index 0000000..002b99e
--- /dev/null
@@ -0,0 +1,52 @@
+From 34d5a86ff7bbe225fba3ad91f9b4dc85fb408e18 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 15 Jan 2025 14:43:35 +0000
+Subject: [PATCH] net: phy: realtek: clear 1000Base-T lpa if link is down
+
+Only read 1000Base-T link partner advertisement if autonegotiation has
+completed and otherwise 1000Base-T link partner advertisement bits.
+
+This fixes bogus 1000Base-T link partner advertisement after link goes
+down (eg. by disconnecting the wire).
+Fixes: 5cb409b3960e ("net: phy: realtek: clear 1000Base-T link partner advertisement")
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Michal Swiatkowski <michal.swiatkowski@linux.intel.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/realtek.c | 19 ++++++++-----------
+ 1 file changed, 8 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/phy/realtek.c
++++ b/drivers/net/phy/realtek.c
+@@ -1031,23 +1031,20 @@ static int rtl822x_c45_read_status(struc
+ {
+       int ret, val;
+-      ret = genphy_c45_read_status(phydev);
+-      if (ret < 0)
+-              return ret;
+-
+-      if (phydev->autoneg == AUTONEG_DISABLE ||
+-          !genphy_c45_aneg_done(phydev))
+-              mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
+-
+       /* Vendor register as C45 has no standardized support for 1000BaseT */
+-      if (phydev->autoneg == AUTONEG_ENABLE) {
++      if (phydev->autoneg == AUTONEG_ENABLE && genphy_c45_aneg_done(phydev)) {
+               val = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+                                  RTL822X_VND2_GANLPAR);
+               if (val < 0)
+                       return val;
+-
+-              mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val);
++      } else {
++              val = 0;
+       }
++      mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val);
++
++      ret = genphy_c45_read_status(phydev);
++      if (ret < 0)
++              return ret;
+       if (!phydev->link)
+               return 0;
diff --git a/target/linux/generic/backport-6.12/781-08-v6.14-net-phy-realtek-clear-master_slave_state-if-link-is-.patch b/target/linux/generic/backport-6.12/781-08-v6.14-net-phy-realtek-clear-master_slave_state-if-link-is-.patch
new file mode 100644 (file)
index 0000000..f8cfc4d
--- /dev/null
@@ -0,0 +1,35 @@
+From ea8318cb33e593bbfc59d637eae45a69732c5387 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 15 Jan 2025 14:43:43 +0000
+Subject: [PATCH] net: phy: realtek: clear master_slave_state if link is down
+
+rtlgen_decode_physr() which sets master_slave_state isn't called in case
+the link is down and other than rtlgen_read_status(),
+rtl822x_c45_read_status() doesn't implicitely clear master_slave_state.
+
+Avoid stale master_slave_state by always setting it to
+MASTER_SLAVE_STATE_UNKNOWN in rtl822x_c45_read_status() in case the link
+is down.
+
+Fixes: 081c9c0265c9 ("net: phy: realtek: read duplex and gbit master from PHYSR register")
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Michal Swiatkowski <michal.swiatkowski@linux.intel.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/realtek.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/realtek.c
++++ b/drivers/net/phy/realtek.c
+@@ -1046,8 +1046,10 @@ static int rtl822x_c45_read_status(struc
+       if (ret < 0)
+               return ret;
+-      if (!phydev->link)
++      if (!phydev->link) {
++              phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+               return 0;
++      }
+       /* Read actual speed from vendor register. */
+       val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL_VND2_PHYSR);
diff --git a/target/linux/generic/backport-6.12/781-09-v6.14-net-phy-realtek-always-clear-NBase-T-lpa.patch b/target/linux/generic/backport-6.12/781-09-v6.14-net-phy-realtek-always-clear-NBase-T-lpa.patch
new file mode 100644 (file)
index 0000000..e628bed
--- /dev/null
@@ -0,0 +1,42 @@
+From d3eb58549842c60ed46f37da7f4da969e3d6ecd3 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 15 Jan 2025 14:45:00 +0000
+Subject: [PATCH] net: phy: realtek: always clear NBase-T lpa
+
+Clear NBase-T link partner advertisement before calling
+rtlgen_read_status() to avoid phy_resolve_aneg_linkmode() wrongly
+setting speed and duplex.
+
+This fixes bogus 2.5G/5G/10G link partner advertisement and thus
+speed and duplex being set by phy_resolve_aneg_linkmode() due to stale
+NBase-T lpa.
+
+Fixes: 68d5cd09e891 ("net: phy: realtek: change order of calls in C22 read_status()")
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Michal Swiatkowski <michal.swiatkowski@linux.intel.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/realtek.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/realtek.c
++++ b/drivers/net/phy/realtek.c
+@@ -960,15 +960,15 @@ static int rtl822x_read_status(struct ph
+ {
+       int lpadv, ret;
++      mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
++
+       ret = rtlgen_read_status(phydev);
+       if (ret < 0)
+               return ret;
+       if (phydev->autoneg == AUTONEG_DISABLE ||
+-          !phydev->autoneg_complete) {
+-              mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
++          !phydev->autoneg_complete)
+               return 0;
+-      }
+       lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
+       if (lpadv < 0)
diff --git a/target/linux/generic/backport-6.12/781-10-v6.14-net-phy-move-realtek-PHY-driver-to-its-own-subdirect.patch b/target/linux/generic/backport-6.12/781-10-v6.14-net-phy-move-realtek-PHY-driver-to-its-own-subdirect.patch
new file mode 100644 (file)
index 0000000..633468f
--- /dev/null
@@ -0,0 +1,3247 @@
+From 1416a9b2ba710d31954131c06d46f298e340aa2c Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Sat, 11 Jan 2025 21:50:19 +0100
+Subject: [PATCH] net: phy: move realtek PHY driver to its own subdirectory
+
+In preparation of adding a source file with hwmon support, move the
+Realtek PHY driver to its own subdirectory and rename realtek.c to
+realtek_main.c.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/c566551b-c915-4e34-9b33-129a6ddd6e4c@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/Kconfig                               | 5 +----
+ drivers/net/phy/Makefile                              | 2 +-
+ drivers/net/phy/realtek/Kconfig                       | 5 +++++
+ drivers/net/phy/realtek/Makefile                      | 3 +++
+ drivers/net/phy/{realtek.c => realtek/realtek_main.c} | 0
+ 5 files changed, 10 insertions(+), 5 deletions(-)
+ create mode 100644 drivers/net/phy/realtek/Kconfig
+ create mode 100644 drivers/net/phy/realtek/Makefile
+ rename drivers/net/phy/{realtek.c => realtek/realtek_main.c} (100%)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -343,10 +343,7 @@ config QSEMI_PHY
+       help
+         Currently supports the qs6612
+-config REALTEK_PHY
+-      tristate "Realtek PHYs"
+-      help
+-        Supports the Realtek 821x PHY.
++source "drivers/net/phy/realtek/Kconfig"
+ config RENESAS_PHY
+       tristate "Renesas PHYs"
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -94,7 +94,7 @@ obj-$(CONFIG_NXP_CBTX_PHY)   += nxp-cbtx.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_REALTEK_PHY)     += realtek/
+ obj-$(CONFIG_RENESAS_PHY)     += uPD60620.o
+ obj-$(CONFIG_ROCKCHIP_PHY)    += rockchip.o
+ obj-$(CONFIG_SMSC_PHY)                += smsc.o
+--- /dev/null
++++ b/drivers/net/phy/realtek/Kconfig
+@@ -0,0 +1,5 @@
++# SPDX-License-Identifier: GPL-2.0-only
++config REALTEK_PHY
++      tristate "Realtek PHYs"
++      help
++        Currently supports RTL821x/RTL822x and fast ethernet PHYs
+--- /dev/null
++++ b/drivers/net/phy/realtek/Makefile
+@@ -0,0 +1,3 @@
++# SPDX-License-Identifier: GPL-2.0
++realtek-y                     += realtek_main.o
++obj-$(CONFIG_REALTEK_PHY)     += realtek.o
+--- a/drivers/net/phy/realtek.c
++++ /dev/null
+@@ -1,1589 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0+
+-/* drivers/net/phy/realtek.c
+- *
+- * Driver for Realtek PHYs
+- *
+- * Author: Johnson Leung <r58129@freescale.com>
+- *
+- * Copyright (c) 2004 Freescale Semiconductor, Inc.
+- */
+-#include <linux/bitops.h>
+-#include <linux/of.h>
+-#include <linux/phy.h>
+-#include <linux/module.h>
+-#include <linux/delay.h>
+-#include <linux/clk.h>
+-
+-#define RTL821x_PHYSR                         0x11
+-#define RTL821x_PHYSR_DUPLEX                  BIT(13)
+-#define RTL821x_PHYSR_SPEED                   GENMASK(15, 14)
+-
+-#define RTL821x_INER                          0x12
+-#define RTL8211B_INER_INIT                    0x6400
+-#define RTL8211E_INER_LINK_STATUS             BIT(10)
+-#define RTL8211F_INER_LINK_STATUS             BIT(4)
+-
+-#define RTL821x_INSR                          0x13
+-
+-#define RTL821x_EXT_PAGE_SELECT                       0x1e
+-#define RTL821x_PAGE_SELECT                   0x1f
+-
+-#define RTL8211F_PHYCR1                               0x18
+-#define RTL8211F_PHYCR2                               0x19
+-#define RTL8211F_INSR                         0x1d
+-
+-#define RTL8211F_LEDCR                                0x10
+-#define RTL8211F_LEDCR_MODE                   BIT(15)
+-#define RTL8211F_LEDCR_ACT_TXRX                       BIT(4)
+-#define RTL8211F_LEDCR_LINK_1000              BIT(3)
+-#define RTL8211F_LEDCR_LINK_100                       BIT(1)
+-#define RTL8211F_LEDCR_LINK_10                        BIT(0)
+-#define RTL8211F_LEDCR_MASK                   GENMASK(4, 0)
+-#define RTL8211F_LEDCR_SHIFT                  5
+-
+-#define RTL8211F_TX_DELAY                     BIT(8)
+-#define RTL8211F_RX_DELAY                     BIT(3)
+-
+-#define RTL8211F_ALDPS_PLL_OFF                        BIT(1)
+-#define RTL8211F_ALDPS_ENABLE                 BIT(2)
+-#define RTL8211F_ALDPS_XTAL_OFF                       BIT(12)
+-
+-#define RTL8211E_CTRL_DELAY                   BIT(13)
+-#define RTL8211E_TX_DELAY                     BIT(12)
+-#define RTL8211E_RX_DELAY                     BIT(11)
+-
+-#define RTL8211F_CLKOUT_EN                    BIT(0)
+-
+-#define RTL8201F_ISR                          0x1e
+-#define RTL8201F_ISR_ANERR                    BIT(15)
+-#define RTL8201F_ISR_DUPLEX                   BIT(13)
+-#define RTL8201F_ISR_LINK                     BIT(11)
+-#define RTL8201F_ISR_MASK                     (RTL8201F_ISR_ANERR | \
+-                                               RTL8201F_ISR_DUPLEX | \
+-                                               RTL8201F_ISR_LINK)
+-#define RTL8201F_IER                          0x13
+-
+-#define RTL822X_VND1_SERDES_OPTION                    0x697a
+-#define RTL822X_VND1_SERDES_OPTION_MODE_MASK          GENMASK(5, 0)
+-#define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII               0
+-#define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX             2
+-
+-#define RTL822X_VND1_SERDES_CTRL3                     0x7580
+-#define RTL822X_VND1_SERDES_CTRL3_MODE_MASK           GENMASK(5, 0)
+-#define RTL822X_VND1_SERDES_CTRL3_MODE_SGMII                  0x02
+-#define RTL822X_VND1_SERDES_CTRL3_MODE_2500BASEX              0x16
+-
+-/* RTL822X_VND2_XXXXX registers are only accessible when phydev->is_c45
+- * is set, they cannot be accessed by C45-over-C22.
+- */
+-#define RTL822X_VND2_GBCR                             0xa412
+-
+-#define RTL822X_VND2_GANLPAR                          0xa414
+-
+-#define RTL8366RB_POWER_SAVE                  0x15
+-#define RTL8366RB_POWER_SAVE_ON                       BIT(12)
+-
+-#define RTL9000A_GINMR                                0x14
+-#define RTL9000A_GINMR_LINK_STATUS            BIT(4)
+-
+-#define RTL_VND2_PHYSR                                0xa434
+-#define RTL_VND2_PHYSR_DUPLEX                 BIT(3)
+-#define RTL_VND2_PHYSR_SPEEDL                 GENMASK(5, 4)
+-#define RTL_VND2_PHYSR_SPEEDH                 GENMASK(10, 9)
+-#define RTL_VND2_PHYSR_MASTER                 BIT(11)
+-#define RTL_VND2_PHYSR_SPEED_MASK             (RTL_VND2_PHYSR_SPEEDL | RTL_VND2_PHYSR_SPEEDH)
+-
+-#define RTL_GENERIC_PHYID                     0x001cc800
+-#define RTL_8211FVD_PHYID                     0x001cc878
+-#define RTL_8221B                             0x001cc840
+-#define RTL_8221B_VB_CG                               0x001cc849
+-#define RTL_8221B_VN_CG                               0x001cc84a
+-#define RTL_8251B                             0x001cc862
+-
+-#define RTL8211F_LED_COUNT                    3
+-
+-MODULE_DESCRIPTION("Realtek PHY driver");
+-MODULE_AUTHOR("Johnson Leung");
+-MODULE_LICENSE("GPL");
+-
+-struct rtl821x_priv {
+-      u16 phycr1;
+-      u16 phycr2;
+-      bool has_phycr2;
+-      struct clk *clk;
+-};
+-
+-static int rtl821x_read_page(struct phy_device *phydev)
+-{
+-      return __phy_read(phydev, RTL821x_PAGE_SELECT);
+-}
+-
+-static int rtl821x_write_page(struct phy_device *phydev, int page)
+-{
+-      return __phy_write(phydev, RTL821x_PAGE_SELECT, page);
+-}
+-
+-static int rtl821x_probe(struct phy_device *phydev)
+-{
+-      struct device *dev = &phydev->mdio.dev;
+-      struct rtl821x_priv *priv;
+-      u32 phy_id = phydev->drv->phy_id;
+-      int ret;
+-
+-      priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+-      if (!priv)
+-              return -ENOMEM;
+-
+-      priv->clk = devm_clk_get_optional_enabled(dev, NULL);
+-      if (IS_ERR(priv->clk))
+-              return dev_err_probe(dev, PTR_ERR(priv->clk),
+-                                   "failed to get phy clock\n");
+-
+-      ret = phy_read_paged(phydev, 0xa43, RTL8211F_PHYCR1);
+-      if (ret < 0)
+-              return ret;
+-
+-      priv->phycr1 = ret & (RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF);
+-      if (of_property_read_bool(dev->of_node, "realtek,aldps-enable"))
+-              priv->phycr1 |= RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF;
+-
+-      priv->has_phycr2 = !(phy_id == RTL_8211FVD_PHYID);
+-      if (priv->has_phycr2) {
+-              ret = phy_read_paged(phydev, 0xa43, RTL8211F_PHYCR2);
+-              if (ret < 0)
+-                      return ret;
+-
+-              priv->phycr2 = ret & RTL8211F_CLKOUT_EN;
+-              if (of_property_read_bool(dev->of_node, "realtek,clkout-disable"))
+-                      priv->phycr2 &= ~RTL8211F_CLKOUT_EN;
+-      }
+-
+-      phydev->priv = priv;
+-
+-      return 0;
+-}
+-
+-static int rtl8201_ack_interrupt(struct phy_device *phydev)
+-{
+-      int err;
+-
+-      err = phy_read(phydev, RTL8201F_ISR);
+-
+-      return (err < 0) ? err : 0;
+-}
+-
+-static int rtl821x_ack_interrupt(struct phy_device *phydev)
+-{
+-      int err;
+-
+-      err = phy_read(phydev, RTL821x_INSR);
+-
+-      return (err < 0) ? err : 0;
+-}
+-
+-static int rtl8211f_ack_interrupt(struct phy_device *phydev)
+-{
+-      int err;
+-
+-      err = phy_read_paged(phydev, 0xa43, RTL8211F_INSR);
+-
+-      return (err < 0) ? err : 0;
+-}
+-
+-static int rtl8201_config_intr(struct phy_device *phydev)
+-{
+-      u16 val;
+-      int err;
+-
+-      if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+-              err = rtl8201_ack_interrupt(phydev);
+-              if (err)
+-                      return err;
+-
+-              val = BIT(13) | BIT(12) | BIT(11);
+-              err = phy_write_paged(phydev, 0x7, RTL8201F_IER, val);
+-      } else {
+-              val = 0;
+-              err = phy_write_paged(phydev, 0x7, RTL8201F_IER, val);
+-              if (err)
+-                      return err;
+-
+-              err = rtl8201_ack_interrupt(phydev);
+-      }
+-
+-      return err;
+-}
+-
+-static int rtl8211b_config_intr(struct phy_device *phydev)
+-{
+-      int err;
+-
+-      if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+-              err = rtl821x_ack_interrupt(phydev);
+-              if (err)
+-                      return err;
+-
+-              err = phy_write(phydev, RTL821x_INER,
+-                              RTL8211B_INER_INIT);
+-      } else {
+-              err = phy_write(phydev, RTL821x_INER, 0);
+-              if (err)
+-                      return err;
+-
+-              err = rtl821x_ack_interrupt(phydev);
+-      }
+-
+-      return err;
+-}
+-
+-static int rtl8211e_config_intr(struct phy_device *phydev)
+-{
+-      int err;
+-
+-      if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+-              err = rtl821x_ack_interrupt(phydev);
+-              if (err)
+-                      return err;
+-
+-              err = phy_write(phydev, RTL821x_INER,
+-                              RTL8211E_INER_LINK_STATUS);
+-      } else {
+-              err = phy_write(phydev, RTL821x_INER, 0);
+-              if (err)
+-                      return err;
+-
+-              err = rtl821x_ack_interrupt(phydev);
+-      }
+-
+-      return err;
+-}
+-
+-static int rtl8211f_config_intr(struct phy_device *phydev)
+-{
+-      u16 val;
+-      int err;
+-
+-      if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+-              err = rtl8211f_ack_interrupt(phydev);
+-              if (err)
+-                      return err;
+-
+-              val = RTL8211F_INER_LINK_STATUS;
+-              err = phy_write_paged(phydev, 0xa42, RTL821x_INER, val);
+-      } else {
+-              val = 0;
+-              err = phy_write_paged(phydev, 0xa42, RTL821x_INER, val);
+-              if (err)
+-                      return err;
+-
+-              err = rtl8211f_ack_interrupt(phydev);
+-      }
+-
+-      return err;
+-}
+-
+-static irqreturn_t rtl8201_handle_interrupt(struct phy_device *phydev)
+-{
+-      int irq_status;
+-
+-      irq_status = phy_read(phydev, RTL8201F_ISR);
+-      if (irq_status < 0) {
+-              phy_error(phydev);
+-              return IRQ_NONE;
+-      }
+-
+-      if (!(irq_status & RTL8201F_ISR_MASK))
+-              return IRQ_NONE;
+-
+-      phy_trigger_machine(phydev);
+-
+-      return IRQ_HANDLED;
+-}
+-
+-static irqreturn_t rtl821x_handle_interrupt(struct phy_device *phydev)
+-{
+-      int irq_status, irq_enabled;
+-
+-      irq_status = phy_read(phydev, RTL821x_INSR);
+-      if (irq_status < 0) {
+-              phy_error(phydev);
+-              return IRQ_NONE;
+-      }
+-
+-      irq_enabled = phy_read(phydev, RTL821x_INER);
+-      if (irq_enabled < 0) {
+-              phy_error(phydev);
+-              return IRQ_NONE;
+-      }
+-
+-      if (!(irq_status & irq_enabled))
+-              return IRQ_NONE;
+-
+-      phy_trigger_machine(phydev);
+-
+-      return IRQ_HANDLED;
+-}
+-
+-static irqreturn_t rtl8211f_handle_interrupt(struct phy_device *phydev)
+-{
+-      int irq_status;
+-
+-      irq_status = phy_read_paged(phydev, 0xa43, RTL8211F_INSR);
+-      if (irq_status < 0) {
+-              phy_error(phydev);
+-              return IRQ_NONE;
+-      }
+-
+-      if (!(irq_status & RTL8211F_INER_LINK_STATUS))
+-              return IRQ_NONE;
+-
+-      phy_trigger_machine(phydev);
+-
+-      return IRQ_HANDLED;
+-}
+-
+-static int rtl8211_config_aneg(struct phy_device *phydev)
+-{
+-      int ret;
+-
+-      ret = genphy_config_aneg(phydev);
+-      if (ret < 0)
+-              return ret;
+-
+-      /* Quirk was copied from vendor driver. Unfortunately it includes no
+-       * description of the magic numbers.
+-       */
+-      if (phydev->speed == SPEED_100 && phydev->autoneg == AUTONEG_DISABLE) {
+-              phy_write(phydev, 0x17, 0x2138);
+-              phy_write(phydev, 0x0e, 0x0260);
+-      } else {
+-              phy_write(phydev, 0x17, 0x2108);
+-              phy_write(phydev, 0x0e, 0x0000);
+-      }
+-
+-      return 0;
+-}
+-
+-static int rtl8211c_config_init(struct phy_device *phydev)
+-{
+-      /* RTL8211C has an issue when operating in Gigabit slave mode */
+-      return phy_set_bits(phydev, MII_CTRL1000,
+-                          CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER);
+-}
+-
+-static int rtl8211f_config_init(struct phy_device *phydev)
+-{
+-      struct rtl821x_priv *priv = phydev->priv;
+-      struct device *dev = &phydev->mdio.dev;
+-      u16 val_txdly, val_rxdly;
+-      int ret;
+-
+-      ret = phy_modify_paged_changed(phydev, 0xa43, RTL8211F_PHYCR1,
+-                                     RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF,
+-                                     priv->phycr1);
+-      if (ret < 0) {
+-              dev_err(dev, "aldps mode  configuration failed: %pe\n",
+-                      ERR_PTR(ret));
+-              return ret;
+-      }
+-
+-      switch (phydev->interface) {
+-      case PHY_INTERFACE_MODE_RGMII:
+-              val_txdly = 0;
+-              val_rxdly = 0;
+-              break;
+-
+-      case PHY_INTERFACE_MODE_RGMII_RXID:
+-              val_txdly = 0;
+-              val_rxdly = RTL8211F_RX_DELAY;
+-              break;
+-
+-      case PHY_INTERFACE_MODE_RGMII_TXID:
+-              val_txdly = RTL8211F_TX_DELAY;
+-              val_rxdly = 0;
+-              break;
+-
+-      case PHY_INTERFACE_MODE_RGMII_ID:
+-              val_txdly = RTL8211F_TX_DELAY;
+-              val_rxdly = RTL8211F_RX_DELAY;
+-              break;
+-
+-      default: /* the rest of the modes imply leaving delay as is. */
+-              return 0;
+-      }
+-
+-      ret = phy_modify_paged_changed(phydev, 0xd08, 0x11, RTL8211F_TX_DELAY,
+-                                     val_txdly);
+-      if (ret < 0) {
+-              dev_err(dev, "Failed to update the TX delay register\n");
+-              return ret;
+-      } else if (ret) {
+-              dev_dbg(dev,
+-                      "%s 2ns TX delay (and changing the value from pin-strapping RXD1 or the bootloader)\n",
+-                      val_txdly ? "Enabling" : "Disabling");
+-      } else {
+-              dev_dbg(dev,
+-                      "2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n",
+-                      val_txdly ? "enabled" : "disabled");
+-      }
+-
+-      ret = phy_modify_paged_changed(phydev, 0xd08, 0x15, RTL8211F_RX_DELAY,
+-                                     val_rxdly);
+-      if (ret < 0) {
+-              dev_err(dev, "Failed to update the RX delay register\n");
+-              return ret;
+-      } else if (ret) {
+-              dev_dbg(dev,
+-                      "%s 2ns RX delay (and changing the value from pin-strapping RXD0 or the bootloader)\n",
+-                      val_rxdly ? "Enabling" : "Disabling");
+-      } else {
+-              dev_dbg(dev,
+-                      "2ns RX delay was already %s (by pin-strapping RXD0 or bootloader configuration)\n",
+-                      val_rxdly ? "enabled" : "disabled");
+-      }
+-
+-      if (priv->has_phycr2) {
+-              ret = phy_modify_paged(phydev, 0xa43, RTL8211F_PHYCR2,
+-                                     RTL8211F_CLKOUT_EN, priv->phycr2);
+-              if (ret < 0) {
+-                      dev_err(dev, "clkout configuration failed: %pe\n",
+-                              ERR_PTR(ret));
+-                      return ret;
+-              }
+-
+-              return genphy_soft_reset(phydev);
+-      }
+-
+-      return 0;
+-}
+-
+-static int rtl821x_suspend(struct phy_device *phydev)
+-{
+-      struct rtl821x_priv *priv = phydev->priv;
+-      int ret = 0;
+-
+-      if (!phydev->wol_enabled) {
+-              ret = genphy_suspend(phydev);
+-
+-              if (ret)
+-                      return ret;
+-
+-              clk_disable_unprepare(priv->clk);
+-      }
+-
+-      return ret;
+-}
+-
+-static int rtl821x_resume(struct phy_device *phydev)
+-{
+-      struct rtl821x_priv *priv = phydev->priv;
+-      int ret;
+-
+-      if (!phydev->wol_enabled)
+-              clk_prepare_enable(priv->clk);
+-
+-      ret = genphy_resume(phydev);
+-      if (ret < 0)
+-              return ret;
+-
+-      msleep(20);
+-
+-      return 0;
+-}
+-
+-static int rtl8211f_led_hw_is_supported(struct phy_device *phydev, u8 index,
+-                                      unsigned long rules)
+-{
+-      const unsigned long mask = BIT(TRIGGER_NETDEV_LINK_10) |
+-                                 BIT(TRIGGER_NETDEV_LINK_100) |
+-                                 BIT(TRIGGER_NETDEV_LINK_1000) |
+-                                 BIT(TRIGGER_NETDEV_RX) |
+-                                 BIT(TRIGGER_NETDEV_TX);
+-
+-      /* The RTL8211F PHY supports these LED settings on up to three LEDs:
+-       * - Link: Configurable subset of 10/100/1000 link rates
+-       * - Active: Blink on activity, RX or TX is not differentiated
+-       * The Active option has two modes, A and B:
+-       * - A: Link and Active indication at configurable, but matching,
+-       *      subset of 10/100/1000 link rates
+-       * - B: Link indication at configurable subset of 10/100/1000 link
+-       *      rates and Active indication always at all three 10+100+1000
+-       *      link rates.
+-       * This code currently uses mode B only.
+-       */
+-
+-      if (index >= RTL8211F_LED_COUNT)
+-              return -EINVAL;
+-
+-      /* Filter out any other unsupported triggers. */
+-      if (rules & ~mask)
+-              return -EOPNOTSUPP;
+-
+-      /* RX and TX are not differentiated, either both are set or not set. */
+-      if (!(rules & BIT(TRIGGER_NETDEV_RX)) ^ !(rules & BIT(TRIGGER_NETDEV_TX)))
+-              return -EOPNOTSUPP;
+-
+-      return 0;
+-}
+-
+-static int rtl8211f_led_hw_control_get(struct phy_device *phydev, u8 index,
+-                                     unsigned long *rules)
+-{
+-      int val;
+-
+-      if (index >= RTL8211F_LED_COUNT)
+-              return -EINVAL;
+-
+-      val = phy_read_paged(phydev, 0xd04, RTL8211F_LEDCR);
+-      if (val < 0)
+-              return val;
+-
+-      val >>= RTL8211F_LEDCR_SHIFT * index;
+-      val &= RTL8211F_LEDCR_MASK;
+-
+-      if (val & RTL8211F_LEDCR_LINK_10)
+-              set_bit(TRIGGER_NETDEV_LINK_10, rules);
+-
+-      if (val & RTL8211F_LEDCR_LINK_100)
+-              set_bit(TRIGGER_NETDEV_LINK_100, rules);
+-
+-      if (val & RTL8211F_LEDCR_LINK_1000)
+-              set_bit(TRIGGER_NETDEV_LINK_1000, rules);
+-
+-      if (val & RTL8211F_LEDCR_ACT_TXRX) {
+-              set_bit(TRIGGER_NETDEV_RX, rules);
+-              set_bit(TRIGGER_NETDEV_TX, rules);
+-      }
+-
+-      return 0;
+-}
+-
+-static int rtl8211f_led_hw_control_set(struct phy_device *phydev, u8 index,
+-                                     unsigned long rules)
+-{
+-      const u16 mask = RTL8211F_LEDCR_MASK << (RTL8211F_LEDCR_SHIFT * index);
+-      u16 reg = 0;
+-
+-      if (index >= RTL8211F_LED_COUNT)
+-              return -EINVAL;
+-
+-      if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
+-              reg |= RTL8211F_LEDCR_LINK_10;
+-
+-      if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
+-              reg |= RTL8211F_LEDCR_LINK_100;
+-
+-      if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
+-              reg |= RTL8211F_LEDCR_LINK_1000;
+-
+-      if (test_bit(TRIGGER_NETDEV_RX, &rules) ||
+-          test_bit(TRIGGER_NETDEV_TX, &rules)) {
+-              reg |= RTL8211F_LEDCR_ACT_TXRX;
+-      }
+-
+-      reg <<= RTL8211F_LEDCR_SHIFT * index;
+-      reg |= RTL8211F_LEDCR_MODE;      /* Mode B */
+-
+-      return phy_modify_paged(phydev, 0xd04, RTL8211F_LEDCR, mask, reg);
+-}
+-
+-static int rtl8211e_config_init(struct phy_device *phydev)
+-{
+-      int ret = 0, oldpage;
+-      u16 val;
+-
+-      /* enable TX/RX delay for rgmii-* modes, and disable them for rgmii. */
+-      switch (phydev->interface) {
+-      case PHY_INTERFACE_MODE_RGMII:
+-              val = RTL8211E_CTRL_DELAY | 0;
+-              break;
+-      case PHY_INTERFACE_MODE_RGMII_ID:
+-              val = RTL8211E_CTRL_DELAY | RTL8211E_TX_DELAY | RTL8211E_RX_DELAY;
+-              break;
+-      case PHY_INTERFACE_MODE_RGMII_RXID:
+-              val = RTL8211E_CTRL_DELAY | RTL8211E_RX_DELAY;
+-              break;
+-      case PHY_INTERFACE_MODE_RGMII_TXID:
+-              val = RTL8211E_CTRL_DELAY | RTL8211E_TX_DELAY;
+-              break;
+-      default: /* the rest of the modes imply leaving delays as is. */
+-              return 0;
+-      }
+-
+-      /* According to a sample driver there is a 0x1c config register on the
+-       * 0xa4 extension page (0x7) layout. It can be used to disable/enable
+-       * the RX/TX delays otherwise controlled by RXDLY/TXDLY pins.
+-       * The configuration register definition:
+-       * 14 = reserved
+-       * 13 = Force Tx RX Delay controlled by bit12 bit11,
+-       * 12 = RX Delay, 11 = TX Delay
+-       * 10:0 = Test && debug settings reserved by realtek
+-       */
+-      oldpage = phy_select_page(phydev, 0x7);
+-      if (oldpage < 0)
+-              goto err_restore_page;
+-
+-      ret = __phy_write(phydev, RTL821x_EXT_PAGE_SELECT, 0xa4);
+-      if (ret)
+-              goto err_restore_page;
+-
+-      ret = __phy_modify(phydev, 0x1c, RTL8211E_CTRL_DELAY
+-                         | RTL8211E_TX_DELAY | RTL8211E_RX_DELAY,
+-                         val);
+-
+-err_restore_page:
+-      return phy_restore_page(phydev, oldpage, ret);
+-}
+-
+-static int rtl8211b_suspend(struct phy_device *phydev)
+-{
+-      phy_write(phydev, MII_MMD_DATA, BIT(9));
+-
+-      return genphy_suspend(phydev);
+-}
+-
+-static int rtl8211b_resume(struct phy_device *phydev)
+-{
+-      phy_write(phydev, MII_MMD_DATA, 0);
+-
+-      return genphy_resume(phydev);
+-}
+-
+-static int rtl8366rb_config_init(struct phy_device *phydev)
+-{
+-      int ret;
+-
+-      ret = phy_set_bits(phydev, RTL8366RB_POWER_SAVE,
+-                         RTL8366RB_POWER_SAVE_ON);
+-      if (ret) {
+-              dev_err(&phydev->mdio.dev,
+-                      "error enabling power management\n");
+-      }
+-
+-      return ret;
+-}
+-
+-/* get actual speed to cover the downshift case */
+-static void rtlgen_decode_physr(struct phy_device *phydev, int val)
+-{
+-      /* bit 3
+-       * 0: Half Duplex
+-       * 1: Full Duplex
+-       */
+-      if (val & RTL_VND2_PHYSR_DUPLEX)
+-              phydev->duplex = DUPLEX_FULL;
+-      else
+-              phydev->duplex = DUPLEX_HALF;
+-
+-      switch (val & RTL_VND2_PHYSR_SPEED_MASK) {
+-      case 0x0000:
+-              phydev->speed = SPEED_10;
+-              break;
+-      case 0x0010:
+-              phydev->speed = SPEED_100;
+-              break;
+-      case 0x0020:
+-              phydev->speed = SPEED_1000;
+-              break;
+-      case 0x0200:
+-              phydev->speed = SPEED_10000;
+-              break;
+-      case 0x0210:
+-              phydev->speed = SPEED_2500;
+-              break;
+-      case 0x0220:
+-              phydev->speed = SPEED_5000;
+-              break;
+-      default:
+-              break;
+-      }
+-
+-      /* bit 11
+-       * 0: Slave Mode
+-       * 1: Master Mode
+-       */
+-      if (phydev->speed >= 1000) {
+-              if (val & RTL_VND2_PHYSR_MASTER)
+-                      phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
+-              else
+-                      phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
+-      } else {
+-              phydev->master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED;
+-      }
+-}
+-
+-static int rtlgen_read_status(struct phy_device *phydev)
+-{
+-      int ret, val;
+-
+-      ret = genphy_read_status(phydev);
+-      if (ret < 0)
+-              return ret;
+-
+-      if (!phydev->link)
+-              return 0;
+-
+-      val = phy_read_paged(phydev, 0xa43, 0x12);
+-      if (val < 0)
+-              return val;
+-
+-      rtlgen_decode_physr(phydev, val);
+-
+-      return 0;
+-}
+-
+-static int rtlgen_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
+-{
+-      int ret;
+-
+-      if (devnum == MDIO_MMD_VEND2) {
+-              rtl821x_write_page(phydev, regnum >> 4);
+-              ret = __phy_read(phydev, 0x10 + ((regnum & 0xf) >> 1));
+-              rtl821x_write_page(phydev, 0);
+-      } else if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE) {
+-              rtl821x_write_page(phydev, 0xa5c);
+-              ret = __phy_read(phydev, 0x12);
+-              rtl821x_write_page(phydev, 0);
+-      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) {
+-              rtl821x_write_page(phydev, 0xa5d);
+-              ret = __phy_read(phydev, 0x10);
+-              rtl821x_write_page(phydev, 0);
+-      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE) {
+-              rtl821x_write_page(phydev, 0xa5d);
+-              ret = __phy_read(phydev, 0x11);
+-              rtl821x_write_page(phydev, 0);
+-      } else {
+-              ret = -EOPNOTSUPP;
+-      }
+-
+-      return ret;
+-}
+-
+-static int rtlgen_write_mmd(struct phy_device *phydev, int devnum, u16 regnum,
+-                          u16 val)
+-{
+-      int ret;
+-
+-      if (devnum == MDIO_MMD_VEND2) {
+-              rtl821x_write_page(phydev, regnum >> 4);
+-              ret = __phy_write(phydev, 0x10 + ((regnum & 0xf) >> 1), val);
+-              rtl821x_write_page(phydev, 0);
+-      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) {
+-              rtl821x_write_page(phydev, 0xa5d);
+-              ret = __phy_write(phydev, 0x10, val);
+-              rtl821x_write_page(phydev, 0);
+-      } else {
+-              ret = -EOPNOTSUPP;
+-      }
+-
+-      return ret;
+-}
+-
+-static int rtl822x_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
+-{
+-      int ret = rtlgen_read_mmd(phydev, devnum, regnum);
+-
+-      if (ret != -EOPNOTSUPP)
+-              return ret;
+-
+-      if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE2) {
+-              rtl821x_write_page(phydev, 0xa6e);
+-              ret = __phy_read(phydev, 0x16);
+-              rtl821x_write_page(phydev, 0);
+-      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2) {
+-              rtl821x_write_page(phydev, 0xa6d);
+-              ret = __phy_read(phydev, 0x12);
+-              rtl821x_write_page(phydev, 0);
+-      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE2) {
+-              rtl821x_write_page(phydev, 0xa6d);
+-              ret = __phy_read(phydev, 0x10);
+-              rtl821x_write_page(phydev, 0);
+-      }
+-
+-      return ret;
+-}
+-
+-static int rtl822x_write_mmd(struct phy_device *phydev, int devnum, u16 regnum,
+-                           u16 val)
+-{
+-      int ret = rtlgen_write_mmd(phydev, devnum, regnum, val);
+-
+-      if (ret != -EOPNOTSUPP)
+-              return ret;
+-
+-      if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2) {
+-              rtl821x_write_page(phydev, 0xa6d);
+-              ret = __phy_write(phydev, 0x12, val);
+-              rtl821x_write_page(phydev, 0);
+-      }
+-
+-      return ret;
+-}
+-
+-static int rtl822xb_config_init(struct phy_device *phydev)
+-{
+-      bool has_2500, has_sgmii;
+-      u16 mode;
+-      int ret;
+-
+-      has_2500 = test_bit(PHY_INTERFACE_MODE_2500BASEX,
+-                          phydev->host_interfaces) ||
+-                 phydev->interface == PHY_INTERFACE_MODE_2500BASEX;
+-
+-      has_sgmii = test_bit(PHY_INTERFACE_MODE_SGMII,
+-                           phydev->host_interfaces) ||
+-                  phydev->interface == PHY_INTERFACE_MODE_SGMII;
+-
+-      /* fill in possible interfaces */
+-      __assign_bit(PHY_INTERFACE_MODE_2500BASEX, phydev->possible_interfaces,
+-                   has_2500);
+-      __assign_bit(PHY_INTERFACE_MODE_SGMII, phydev->possible_interfaces,
+-                   has_sgmii);
+-
+-      if (!has_2500 && !has_sgmii)
+-              return 0;
+-
+-      /* determine SerDes option mode */
+-      if (has_2500 && !has_sgmii) {
+-              mode = RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX;
+-              phydev->rate_matching = RATE_MATCH_PAUSE;
+-      } else {
+-              mode = RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII;
+-              phydev->rate_matching = RATE_MATCH_NONE;
+-      }
+-
+-      /* the following sequence with magic numbers sets up the SerDes
+-       * option mode
+-       */
+-      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x75f3, 0);
+-      if (ret < 0)
+-              return ret;
+-
+-      ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND1,
+-                                   RTL822X_VND1_SERDES_OPTION,
+-                                   RTL822X_VND1_SERDES_OPTION_MODE_MASK,
+-                                   mode);
+-      if (ret < 0)
+-              return ret;
+-
+-      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6a04, 0x0503);
+-      if (ret < 0)
+-              return ret;
+-
+-      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6f10, 0xd455);
+-      if (ret < 0)
+-              return ret;
+-
+-      return phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6f11, 0x8020);
+-}
+-
+-static int rtl822xb_get_rate_matching(struct phy_device *phydev,
+-                                    phy_interface_t iface)
+-{
+-      int val;
+-
+-      /* Only rate matching at 2500base-x */
+-      if (iface != PHY_INTERFACE_MODE_2500BASEX)
+-              return RATE_MATCH_NONE;
+-
+-      val = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_OPTION);
+-      if (val < 0)
+-              return val;
+-
+-      if ((val & RTL822X_VND1_SERDES_OPTION_MODE_MASK) ==
+-          RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX)
+-              return RATE_MATCH_PAUSE;
+-
+-      /* RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII */
+-      return RATE_MATCH_NONE;
+-}
+-
+-static int rtl822x_get_features(struct phy_device *phydev)
+-{
+-      int val;
+-
+-      val = phy_read_paged(phydev, 0xa61, 0x13);
+-      if (val < 0)
+-              return val;
+-
+-      linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+-                       phydev->supported, val & MDIO_PMA_SPEED_2_5G);
+-      linkmode_mod_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+-                       phydev->supported, val & MDIO_PMA_SPEED_5G);
+-      linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+-                       phydev->supported, val & MDIO_SPEED_10G);
+-
+-      return genphy_read_abilities(phydev);
+-}
+-
+-static int rtl822x_config_aneg(struct phy_device *phydev)
+-{
+-      int ret = 0;
+-
+-      if (phydev->autoneg == AUTONEG_ENABLE) {
+-              u16 adv = linkmode_adv_to_mii_10gbt_adv_t(phydev->advertising);
+-
+-              ret = phy_modify_paged_changed(phydev, 0xa5d, 0x12,
+-                                             MDIO_AN_10GBT_CTRL_ADV2_5G |
+-                                             MDIO_AN_10GBT_CTRL_ADV5G,
+-                                             adv);
+-              if (ret < 0)
+-                      return ret;
+-      }
+-
+-      return __genphy_config_aneg(phydev, ret);
+-}
+-
+-static void rtl822xb_update_interface(struct phy_device *phydev)
+-{
+-      int val;
+-
+-      if (!phydev->link)
+-              return;
+-
+-      /* Change interface according to serdes mode */
+-      val = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_CTRL3);
+-      if (val < 0)
+-              return;
+-
+-      switch (val & RTL822X_VND1_SERDES_CTRL3_MODE_MASK) {
+-      case RTL822X_VND1_SERDES_CTRL3_MODE_2500BASEX:
+-              phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+-              break;
+-      case RTL822X_VND1_SERDES_CTRL3_MODE_SGMII:
+-              phydev->interface = PHY_INTERFACE_MODE_SGMII;
+-              break;
+-      }
+-}
+-
+-static int rtl822x_read_status(struct phy_device *phydev)
+-{
+-      int lpadv, ret;
+-
+-      mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
+-
+-      ret = rtlgen_read_status(phydev);
+-      if (ret < 0)
+-              return ret;
+-
+-      if (phydev->autoneg == AUTONEG_DISABLE ||
+-          !phydev->autoneg_complete)
+-              return 0;
+-
+-      lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
+-      if (lpadv < 0)
+-              return lpadv;
+-
+-      mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, lpadv);
+-
+-      return 0;
+-}
+-
+-static int rtl822xb_read_status(struct phy_device *phydev)
+-{
+-      int ret;
+-
+-      ret = rtl822x_read_status(phydev);
+-      if (ret < 0)
+-              return ret;
+-
+-      rtl822xb_update_interface(phydev);
+-
+-      return 0;
+-}
+-
+-static int rtl822x_c45_get_features(struct phy_device *phydev)
+-{
+-      linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT,
+-                       phydev->supported);
+-
+-      return genphy_c45_pma_read_abilities(phydev);
+-}
+-
+-static int rtl822x_c45_config_aneg(struct phy_device *phydev)
+-{
+-      bool changed = false;
+-      int ret, val;
+-
+-      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;
+-
+-      val = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
+-
+-      /* Vendor register as C45 has no standardized support for 1000BaseT */
+-      ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, RTL822X_VND2_GBCR,
+-                                   ADVERTISE_1000FULL, val);
+-      if (ret < 0)
+-              return ret;
+-      if (ret > 0)
+-              changed = true;
+-
+-      return genphy_c45_check_and_restart_aneg(phydev, changed);
+-}
+-
+-static int rtl822x_c45_read_status(struct phy_device *phydev)
+-{
+-      int ret, val;
+-
+-      /* Vendor register as C45 has no standardized support for 1000BaseT */
+-      if (phydev->autoneg == AUTONEG_ENABLE && genphy_c45_aneg_done(phydev)) {
+-              val = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+-                                 RTL822X_VND2_GANLPAR);
+-              if (val < 0)
+-                      return val;
+-      } else {
+-              val = 0;
+-      }
+-      mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val);
+-
+-      ret = genphy_c45_read_status(phydev);
+-      if (ret < 0)
+-              return ret;
+-
+-      if (!phydev->link) {
+-              phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+-              return 0;
+-      }
+-
+-      /* Read actual speed from vendor register. */
+-      val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL_VND2_PHYSR);
+-      if (val < 0)
+-              return val;
+-
+-      rtlgen_decode_physr(phydev, val);
+-
+-      return 0;
+-}
+-
+-static int rtl822xb_c45_read_status(struct phy_device *phydev)
+-{
+-      int ret;
+-
+-      ret = rtl822x_c45_read_status(phydev);
+-      if (ret < 0)
+-              return ret;
+-
+-      rtl822xb_update_interface(phydev);
+-
+-      return 0;
+-}
+-
+-static bool rtlgen_supports_2_5gbps(struct phy_device *phydev)
+-{
+-      int val;
+-
+-      phy_write(phydev, RTL821x_PAGE_SELECT, 0xa61);
+-      val = phy_read(phydev, 0x13);
+-      phy_write(phydev, RTL821x_PAGE_SELECT, 0);
+-
+-      return val >= 0 && val & MDIO_PMA_SPEED_2_5G;
+-}
+-
+-/* On internal PHY's MMD reads over C22 always return 0.
+- * Check a MMD register which is known to be non-zero.
+- */
+-static bool rtlgen_supports_mmd(struct phy_device *phydev)
+-{
+-      int val;
+-
+-      phy_lock_mdio_bus(phydev);
+-      __phy_write(phydev, MII_MMD_CTRL, MDIO_MMD_PCS);
+-      __phy_write(phydev, MII_MMD_DATA, MDIO_PCS_EEE_ABLE);
+-      __phy_write(phydev, MII_MMD_CTRL, MDIO_MMD_PCS | MII_MMD_CTRL_NOINCR);
+-      val = __phy_read(phydev, MII_MMD_DATA);
+-      phy_unlock_mdio_bus(phydev);
+-
+-      return val > 0;
+-}
+-
+-static int rtlgen_match_phy_device(struct phy_device *phydev)
+-{
+-      return phydev->phy_id == RTL_GENERIC_PHYID &&
+-             !rtlgen_supports_2_5gbps(phydev);
+-}
+-
+-static int rtl8226_match_phy_device(struct phy_device *phydev)
+-{
+-      return phydev->phy_id == RTL_GENERIC_PHYID &&
+-             rtlgen_supports_2_5gbps(phydev) &&
+-             rtlgen_supports_mmd(phydev);
+-}
+-
+-static int rtlgen_is_c45_match(struct phy_device *phydev, unsigned int id,
+-                             bool is_c45)
+-{
+-      if (phydev->is_c45)
+-              return is_c45 && (id == phydev->c45_ids.device_ids[1]);
+-      else
+-              return !is_c45 && (id == phydev->phy_id);
+-}
+-
+-static int rtl8221b_match_phy_device(struct phy_device *phydev)
+-{
+-      return phydev->phy_id == RTL_8221B && rtlgen_supports_mmd(phydev);
+-}
+-
+-static int rtl8221b_vb_cg_c22_match_phy_device(struct phy_device *phydev)
+-{
+-      return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, false);
+-}
+-
+-static int rtl8221b_vb_cg_c45_match_phy_device(struct phy_device *phydev)
+-{
+-      return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, true);
+-}
+-
+-static int rtl8221b_vn_cg_c22_match_phy_device(struct phy_device *phydev)
+-{
+-      return rtlgen_is_c45_match(phydev, RTL_8221B_VN_CG, false);
+-}
+-
+-static int rtl8221b_vn_cg_c45_match_phy_device(struct phy_device *phydev)
+-{
+-      return rtlgen_is_c45_match(phydev, RTL_8221B_VN_CG, true);
+-}
+-
+-static int rtl_internal_nbaset_match_phy_device(struct phy_device *phydev)
+-{
+-      if (phydev->is_c45)
+-              return false;
+-
+-      switch (phydev->phy_id) {
+-      case RTL_GENERIC_PHYID:
+-      case RTL_8221B:
+-      case RTL_8251B:
+-      case 0x001cc841:
+-              break;
+-      default:
+-              return false;
+-      }
+-
+-      return rtlgen_supports_2_5gbps(phydev) && !rtlgen_supports_mmd(phydev);
+-}
+-
+-static int rtl8251b_c45_match_phy_device(struct phy_device *phydev)
+-{
+-      return rtlgen_is_c45_match(phydev, RTL_8251B, true);
+-}
+-
+-static int rtlgen_resume(struct phy_device *phydev)
+-{
+-      int ret = genphy_resume(phydev);
+-
+-      /* Internal PHY's from RTL8168h up may not be instantly ready */
+-      msleep(20);
+-
+-      return ret;
+-}
+-
+-static int rtlgen_c45_resume(struct phy_device *phydev)
+-{
+-      int ret = genphy_c45_pma_resume(phydev);
+-
+-      msleep(20);
+-
+-      return ret;
+-}
+-
+-static int rtl9000a_config_init(struct phy_device *phydev)
+-{
+-      phydev->autoneg = AUTONEG_DISABLE;
+-      phydev->speed = SPEED_100;
+-      phydev->duplex = DUPLEX_FULL;
+-
+-      return 0;
+-}
+-
+-static int rtl9000a_config_aneg(struct phy_device *phydev)
+-{
+-      int ret;
+-      u16 ctl = 0;
+-
+-      switch (phydev->master_slave_set) {
+-      case MASTER_SLAVE_CFG_MASTER_FORCE:
+-              ctl |= CTL1000_AS_MASTER;
+-              break;
+-      case MASTER_SLAVE_CFG_SLAVE_FORCE:
+-              break;
+-      case MASTER_SLAVE_CFG_UNKNOWN:
+-      case MASTER_SLAVE_CFG_UNSUPPORTED:
+-              return 0;
+-      default:
+-              phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+-              return -EOPNOTSUPP;
+-      }
+-
+-      ret = phy_modify_changed(phydev, MII_CTRL1000, CTL1000_AS_MASTER, ctl);
+-      if (ret == 1)
+-              ret = genphy_soft_reset(phydev);
+-
+-      return ret;
+-}
+-
+-static int rtl9000a_read_status(struct phy_device *phydev)
+-{
+-      int ret;
+-
+-      phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN;
+-      phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+-
+-      ret = genphy_update_link(phydev);
+-      if (ret)
+-              return ret;
+-
+-      ret = phy_read(phydev, MII_CTRL1000);
+-      if (ret < 0)
+-              return ret;
+-      if (ret & CTL1000_AS_MASTER)
+-              phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE;
+-      else
+-              phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE;
+-
+-      ret = phy_read(phydev, MII_STAT1000);
+-      if (ret < 0)
+-              return ret;
+-      if (ret & LPA_1000MSRES)
+-              phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
+-      else
+-              phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
+-
+-      return 0;
+-}
+-
+-static int rtl9000a_ack_interrupt(struct phy_device *phydev)
+-{
+-      int err;
+-
+-      err = phy_read(phydev, RTL8211F_INSR);
+-
+-      return (err < 0) ? err : 0;
+-}
+-
+-static int rtl9000a_config_intr(struct phy_device *phydev)
+-{
+-      u16 val;
+-      int err;
+-
+-      if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+-              err = rtl9000a_ack_interrupt(phydev);
+-              if (err)
+-                      return err;
+-
+-              val = (u16)~RTL9000A_GINMR_LINK_STATUS;
+-              err = phy_write_paged(phydev, 0xa42, RTL9000A_GINMR, val);
+-      } else {
+-              val = ~0;
+-              err = phy_write_paged(phydev, 0xa42, RTL9000A_GINMR, val);
+-              if (err)
+-                      return err;
+-
+-              err = rtl9000a_ack_interrupt(phydev);
+-      }
+-
+-      return phy_write_paged(phydev, 0xa42, RTL9000A_GINMR, val);
+-}
+-
+-static irqreturn_t rtl9000a_handle_interrupt(struct phy_device *phydev)
+-{
+-      int irq_status;
+-
+-      irq_status = phy_read(phydev, RTL8211F_INSR);
+-      if (irq_status < 0) {
+-              phy_error(phydev);
+-              return IRQ_NONE;
+-      }
+-
+-      if (!(irq_status & RTL8211F_INER_LINK_STATUS))
+-              return IRQ_NONE;
+-
+-      phy_trigger_machine(phydev);
+-
+-      return IRQ_HANDLED;
+-}
+-
+-static struct phy_driver realtek_drvs[] = {
+-      {
+-              PHY_ID_MATCH_EXACT(0x00008201),
+-              .name           = "RTL8201CP Ethernet",
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001cc816),
+-              .name           = "RTL8201F Fast Ethernet",
+-              .config_intr    = &rtl8201_config_intr,
+-              .handle_interrupt = rtl8201_handle_interrupt,
+-              .suspend        = genphy_suspend,
+-              .resume         = genphy_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              PHY_ID_MATCH_MODEL(0x001cc880),
+-              .name           = "RTL8208 Fast Ethernet",
+-              .read_mmd       = genphy_read_mmd_unsupported,
+-              .write_mmd      = genphy_write_mmd_unsupported,
+-              .suspend        = genphy_suspend,
+-              .resume         = genphy_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001cc910),
+-              .name           = "RTL8211 Gigabit Ethernet",
+-              .config_aneg    = rtl8211_config_aneg,
+-              .read_mmd       = &genphy_read_mmd_unsupported,
+-              .write_mmd      = &genphy_write_mmd_unsupported,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001cc912),
+-              .name           = "RTL8211B Gigabit Ethernet",
+-              .config_intr    = &rtl8211b_config_intr,
+-              .handle_interrupt = rtl821x_handle_interrupt,
+-              .read_mmd       = &genphy_read_mmd_unsupported,
+-              .write_mmd      = &genphy_write_mmd_unsupported,
+-              .suspend        = rtl8211b_suspend,
+-              .resume         = rtl8211b_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001cc913),
+-              .name           = "RTL8211C Gigabit Ethernet",
+-              .config_init    = rtl8211c_config_init,
+-              .read_mmd       = &genphy_read_mmd_unsupported,
+-              .write_mmd      = &genphy_write_mmd_unsupported,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001cc914),
+-              .name           = "RTL8211DN Gigabit Ethernet",
+-              .config_intr    = rtl8211e_config_intr,
+-              .handle_interrupt = rtl821x_handle_interrupt,
+-              .suspend        = genphy_suspend,
+-              .resume         = genphy_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001cc915),
+-              .name           = "RTL8211E Gigabit Ethernet",
+-              .config_init    = &rtl8211e_config_init,
+-              .config_intr    = &rtl8211e_config_intr,
+-              .handle_interrupt = rtl821x_handle_interrupt,
+-              .suspend        = genphy_suspend,
+-              .resume         = genphy_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001cc916),
+-              .name           = "RTL8211F Gigabit Ethernet",
+-              .probe          = rtl821x_probe,
+-              .config_init    = &rtl8211f_config_init,
+-              .read_status    = rtlgen_read_status,
+-              .config_intr    = &rtl8211f_config_intr,
+-              .handle_interrupt = rtl8211f_handle_interrupt,
+-              .suspend        = rtl821x_suspend,
+-              .resume         = rtl821x_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-              .flags          = PHY_ALWAYS_CALL_SUSPEND,
+-              .led_hw_is_supported = rtl8211f_led_hw_is_supported,
+-              .led_hw_control_get = rtl8211f_led_hw_control_get,
+-              .led_hw_control_set = rtl8211f_led_hw_control_set,
+-      }, {
+-              PHY_ID_MATCH_EXACT(RTL_8211FVD_PHYID),
+-              .name           = "RTL8211F-VD Gigabit Ethernet",
+-              .probe          = rtl821x_probe,
+-              .config_init    = &rtl8211f_config_init,
+-              .read_status    = rtlgen_read_status,
+-              .config_intr    = &rtl8211f_config_intr,
+-              .handle_interrupt = rtl8211f_handle_interrupt,
+-              .suspend        = rtl821x_suspend,
+-              .resume         = rtl821x_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-              .flags          = PHY_ALWAYS_CALL_SUSPEND,
+-      }, {
+-              .name           = "Generic FE-GE Realtek PHY",
+-              .match_phy_device = rtlgen_match_phy_device,
+-              .read_status    = rtlgen_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-              .read_mmd       = rtlgen_read_mmd,
+-              .write_mmd      = rtlgen_write_mmd,
+-      }, {
+-              .name           = "RTL8226 2.5Gbps PHY",
+-              .match_phy_device = rtl8226_match_phy_device,
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .read_status    = rtl822x_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              .match_phy_device = rtl8221b_match_phy_device,
+-              .name           = "RTL8226B_RTL8221B 2.5Gbps PHY",
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .config_init    = rtl822xb_config_init,
+-              .get_rate_matching = rtl822xb_get_rate_matching,
+-              .read_status    = rtl822xb_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001cc838),
+-              .name           = "RTL8226-CG 2.5Gbps PHY",
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .read_status    = rtl822x_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001cc848),
+-              .name           = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY",
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .config_init    = rtl822xb_config_init,
+-              .get_rate_matching = rtl822xb_get_rate_matching,
+-              .read_status    = rtl822xb_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              .match_phy_device = rtl8221b_vb_cg_c22_match_phy_device,
+-              .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C22)",
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .config_init    = rtl822xb_config_init,
+-              .get_rate_matching = rtl822xb_get_rate_matching,
+-              .read_status    = rtl822xb_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              .match_phy_device = rtl8221b_vb_cg_c45_match_phy_device,
+-              .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C45)",
+-              .config_init    = rtl822xb_config_init,
+-              .get_rate_matching = rtl822xb_get_rate_matching,
+-              .get_features   = rtl822x_c45_get_features,
+-              .config_aneg    = rtl822x_c45_config_aneg,
+-              .read_status    = rtl822xb_c45_read_status,
+-              .suspend        = genphy_c45_pma_suspend,
+-              .resume         = rtlgen_c45_resume,
+-      }, {
+-              .match_phy_device = rtl8221b_vn_cg_c22_match_phy_device,
+-              .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C22)",
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .config_init    = rtl822xb_config_init,
+-              .get_rate_matching = rtl822xb_get_rate_matching,
+-              .read_status    = rtl822xb_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              .match_phy_device = rtl8221b_vn_cg_c45_match_phy_device,
+-              .name           = "RTL8221B-VN-CG 2.5Gbps PHY (C45)",
+-              .config_init    = rtl822xb_config_init,
+-              .get_rate_matching = rtl822xb_get_rate_matching,
+-              .get_features   = rtl822x_c45_get_features,
+-              .config_aneg    = rtl822x_c45_config_aneg,
+-              .read_status    = rtl822xb_c45_read_status,
+-              .suspend        = genphy_c45_pma_suspend,
+-              .resume         = rtlgen_c45_resume,
+-      }, {
+-              .match_phy_device = rtl8251b_c45_match_phy_device,
+-              .name           = "RTL8251B 5Gbps PHY",
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .read_status    = rtl822x_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              .match_phy_device = rtl_internal_nbaset_match_phy_device,
+-              .name           = "Realtek Internal NBASE-T PHY",
+-              .flags          = PHY_IS_INTERNAL,
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .read_status    = rtl822x_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-              .read_mmd       = rtl822x_read_mmd,
+-              .write_mmd      = rtl822x_write_mmd,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001ccad0),
+-              .name           = "RTL8224 2.5Gbps PHY",
+-              .get_features   = rtl822x_c45_get_features,
+-              .config_aneg    = rtl822x_c45_config_aneg,
+-              .read_status    = rtl822x_c45_read_status,
+-              .suspend        = genphy_c45_pma_suspend,
+-              .resume         = rtlgen_c45_resume,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001cc961),
+-              .name           = "RTL8366RB Gigabit Ethernet",
+-              .config_init    = &rtl8366rb_config_init,
+-              /* These interrupts are handled by the irq controller
+-               * embedded inside the RTL8366RB, they get unmasked when the
+-               * irq is requested and ACKed by reading the status register,
+-               * which is done by the irqchip code.
+-               */
+-              .config_intr    = genphy_no_config_intr,
+-              .handle_interrupt = genphy_handle_interrupt_no_ack,
+-              .suspend        = genphy_suspend,
+-              .resume         = genphy_resume,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001ccb00),
+-              .name           = "RTL9000AA_RTL9000AN Ethernet",
+-              .features       = PHY_BASIC_T1_FEATURES,
+-              .config_init    = rtl9000a_config_init,
+-              .config_aneg    = rtl9000a_config_aneg,
+-              .read_status    = rtl9000a_read_status,
+-              .config_intr    = rtl9000a_config_intr,
+-              .handle_interrupt = rtl9000a_handle_interrupt,
+-              .suspend        = genphy_suspend,
+-              .resume         = genphy_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001cc942),
+-              .name           = "RTL8365MB-VC Gigabit Ethernet",
+-              /* Interrupt handling analogous to RTL8366RB */
+-              .config_intr    = genphy_no_config_intr,
+-              .handle_interrupt = genphy_handle_interrupt_no_ack,
+-              .suspend        = genphy_suspend,
+-              .resume         = genphy_resume,
+-      }, {
+-              PHY_ID_MATCH_EXACT(0x001cc960),
+-              .name           = "RTL8366S Gigabit Ethernet",
+-              .suspend        = genphy_suspend,
+-              .resume         = genphy_resume,
+-              .read_mmd       = genphy_read_mmd_unsupported,
+-              .write_mmd      = genphy_write_mmd_unsupported,
+-      },
+-};
+-
+-module_phy_driver(realtek_drvs);
+-
+-static const struct mdio_device_id __maybe_unused realtek_tbl[] = {
+-      { PHY_ID_MATCH_VENDOR(0x001cc800) },
+-      { }
+-};
+-
+-MODULE_DEVICE_TABLE(mdio, realtek_tbl);
+--- /dev/null
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -0,0 +1,1589 @@
++// SPDX-License-Identifier: GPL-2.0+
++/* drivers/net/phy/realtek.c
++ *
++ * Driver for Realtek PHYs
++ *
++ * Author: Johnson Leung <r58129@freescale.com>
++ *
++ * Copyright (c) 2004 Freescale Semiconductor, Inc.
++ */
++#include <linux/bitops.h>
++#include <linux/of.h>
++#include <linux/phy.h>
++#include <linux/module.h>
++#include <linux/delay.h>
++#include <linux/clk.h>
++
++#define RTL821x_PHYSR                         0x11
++#define RTL821x_PHYSR_DUPLEX                  BIT(13)
++#define RTL821x_PHYSR_SPEED                   GENMASK(15, 14)
++
++#define RTL821x_INER                          0x12
++#define RTL8211B_INER_INIT                    0x6400
++#define RTL8211E_INER_LINK_STATUS             BIT(10)
++#define RTL8211F_INER_LINK_STATUS             BIT(4)
++
++#define RTL821x_INSR                          0x13
++
++#define RTL821x_EXT_PAGE_SELECT                       0x1e
++#define RTL821x_PAGE_SELECT                   0x1f
++
++#define RTL8211F_PHYCR1                               0x18
++#define RTL8211F_PHYCR2                               0x19
++#define RTL8211F_INSR                         0x1d
++
++#define RTL8211F_LEDCR                                0x10
++#define RTL8211F_LEDCR_MODE                   BIT(15)
++#define RTL8211F_LEDCR_ACT_TXRX                       BIT(4)
++#define RTL8211F_LEDCR_LINK_1000              BIT(3)
++#define RTL8211F_LEDCR_LINK_100                       BIT(1)
++#define RTL8211F_LEDCR_LINK_10                        BIT(0)
++#define RTL8211F_LEDCR_MASK                   GENMASK(4, 0)
++#define RTL8211F_LEDCR_SHIFT                  5
++
++#define RTL8211F_TX_DELAY                     BIT(8)
++#define RTL8211F_RX_DELAY                     BIT(3)
++
++#define RTL8211F_ALDPS_PLL_OFF                        BIT(1)
++#define RTL8211F_ALDPS_ENABLE                 BIT(2)
++#define RTL8211F_ALDPS_XTAL_OFF                       BIT(12)
++
++#define RTL8211E_CTRL_DELAY                   BIT(13)
++#define RTL8211E_TX_DELAY                     BIT(12)
++#define RTL8211E_RX_DELAY                     BIT(11)
++
++#define RTL8211F_CLKOUT_EN                    BIT(0)
++
++#define RTL8201F_ISR                          0x1e
++#define RTL8201F_ISR_ANERR                    BIT(15)
++#define RTL8201F_ISR_DUPLEX                   BIT(13)
++#define RTL8201F_ISR_LINK                     BIT(11)
++#define RTL8201F_ISR_MASK                     (RTL8201F_ISR_ANERR | \
++                                               RTL8201F_ISR_DUPLEX | \
++                                               RTL8201F_ISR_LINK)
++#define RTL8201F_IER                          0x13
++
++#define RTL822X_VND1_SERDES_OPTION                    0x697a
++#define RTL822X_VND1_SERDES_OPTION_MODE_MASK          GENMASK(5, 0)
++#define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII               0
++#define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX             2
++
++#define RTL822X_VND1_SERDES_CTRL3                     0x7580
++#define RTL822X_VND1_SERDES_CTRL3_MODE_MASK           GENMASK(5, 0)
++#define RTL822X_VND1_SERDES_CTRL3_MODE_SGMII                  0x02
++#define RTL822X_VND1_SERDES_CTRL3_MODE_2500BASEX              0x16
++
++/* RTL822X_VND2_XXXXX registers are only accessible when phydev->is_c45
++ * is set, they cannot be accessed by C45-over-C22.
++ */
++#define RTL822X_VND2_GBCR                             0xa412
++
++#define RTL822X_VND2_GANLPAR                          0xa414
++
++#define RTL8366RB_POWER_SAVE                  0x15
++#define RTL8366RB_POWER_SAVE_ON                       BIT(12)
++
++#define RTL9000A_GINMR                                0x14
++#define RTL9000A_GINMR_LINK_STATUS            BIT(4)
++
++#define RTL_VND2_PHYSR                                0xa434
++#define RTL_VND2_PHYSR_DUPLEX                 BIT(3)
++#define RTL_VND2_PHYSR_SPEEDL                 GENMASK(5, 4)
++#define RTL_VND2_PHYSR_SPEEDH                 GENMASK(10, 9)
++#define RTL_VND2_PHYSR_MASTER                 BIT(11)
++#define RTL_VND2_PHYSR_SPEED_MASK             (RTL_VND2_PHYSR_SPEEDL | RTL_VND2_PHYSR_SPEEDH)
++
++#define RTL_GENERIC_PHYID                     0x001cc800
++#define RTL_8211FVD_PHYID                     0x001cc878
++#define RTL_8221B                             0x001cc840
++#define RTL_8221B_VB_CG                               0x001cc849
++#define RTL_8221B_VN_CG                               0x001cc84a
++#define RTL_8251B                             0x001cc862
++
++#define RTL8211F_LED_COUNT                    3
++
++MODULE_DESCRIPTION("Realtek PHY driver");
++MODULE_AUTHOR("Johnson Leung");
++MODULE_LICENSE("GPL");
++
++struct rtl821x_priv {
++      u16 phycr1;
++      u16 phycr2;
++      bool has_phycr2;
++      struct clk *clk;
++};
++
++static int rtl821x_read_page(struct phy_device *phydev)
++{
++      return __phy_read(phydev, RTL821x_PAGE_SELECT);
++}
++
++static int rtl821x_write_page(struct phy_device *phydev, int page)
++{
++      return __phy_write(phydev, RTL821x_PAGE_SELECT, page);
++}
++
++static int rtl821x_probe(struct phy_device *phydev)
++{
++      struct device *dev = &phydev->mdio.dev;
++      struct rtl821x_priv *priv;
++      u32 phy_id = phydev->drv->phy_id;
++      int ret;
++
++      priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++      if (!priv)
++              return -ENOMEM;
++
++      priv->clk = devm_clk_get_optional_enabled(dev, NULL);
++      if (IS_ERR(priv->clk))
++              return dev_err_probe(dev, PTR_ERR(priv->clk),
++                                   "failed to get phy clock\n");
++
++      ret = phy_read_paged(phydev, 0xa43, RTL8211F_PHYCR1);
++      if (ret < 0)
++              return ret;
++
++      priv->phycr1 = ret & (RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF);
++      if (of_property_read_bool(dev->of_node, "realtek,aldps-enable"))
++              priv->phycr1 |= RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF;
++
++      priv->has_phycr2 = !(phy_id == RTL_8211FVD_PHYID);
++      if (priv->has_phycr2) {
++              ret = phy_read_paged(phydev, 0xa43, RTL8211F_PHYCR2);
++              if (ret < 0)
++                      return ret;
++
++              priv->phycr2 = ret & RTL8211F_CLKOUT_EN;
++              if (of_property_read_bool(dev->of_node, "realtek,clkout-disable"))
++                      priv->phycr2 &= ~RTL8211F_CLKOUT_EN;
++      }
++
++      phydev->priv = priv;
++
++      return 0;
++}
++
++static int rtl8201_ack_interrupt(struct phy_device *phydev)
++{
++      int err;
++
++      err = phy_read(phydev, RTL8201F_ISR);
++
++      return (err < 0) ? err : 0;
++}
++
++static int rtl821x_ack_interrupt(struct phy_device *phydev)
++{
++      int err;
++
++      err = phy_read(phydev, RTL821x_INSR);
++
++      return (err < 0) ? err : 0;
++}
++
++static int rtl8211f_ack_interrupt(struct phy_device *phydev)
++{
++      int err;
++
++      err = phy_read_paged(phydev, 0xa43, RTL8211F_INSR);
++
++      return (err < 0) ? err : 0;
++}
++
++static int rtl8201_config_intr(struct phy_device *phydev)
++{
++      u16 val;
++      int err;
++
++      if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
++              err = rtl8201_ack_interrupt(phydev);
++              if (err)
++                      return err;
++
++              val = BIT(13) | BIT(12) | BIT(11);
++              err = phy_write_paged(phydev, 0x7, RTL8201F_IER, val);
++      } else {
++              val = 0;
++              err = phy_write_paged(phydev, 0x7, RTL8201F_IER, val);
++              if (err)
++                      return err;
++
++              err = rtl8201_ack_interrupt(phydev);
++      }
++
++      return err;
++}
++
++static int rtl8211b_config_intr(struct phy_device *phydev)
++{
++      int err;
++
++      if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
++              err = rtl821x_ack_interrupt(phydev);
++              if (err)
++                      return err;
++
++              err = phy_write(phydev, RTL821x_INER,
++                              RTL8211B_INER_INIT);
++      } else {
++              err = phy_write(phydev, RTL821x_INER, 0);
++              if (err)
++                      return err;
++
++              err = rtl821x_ack_interrupt(phydev);
++      }
++
++      return err;
++}
++
++static int rtl8211e_config_intr(struct phy_device *phydev)
++{
++      int err;
++
++      if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
++              err = rtl821x_ack_interrupt(phydev);
++              if (err)
++                      return err;
++
++              err = phy_write(phydev, RTL821x_INER,
++                              RTL8211E_INER_LINK_STATUS);
++      } else {
++              err = phy_write(phydev, RTL821x_INER, 0);
++              if (err)
++                      return err;
++
++              err = rtl821x_ack_interrupt(phydev);
++      }
++
++      return err;
++}
++
++static int rtl8211f_config_intr(struct phy_device *phydev)
++{
++      u16 val;
++      int err;
++
++      if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
++              err = rtl8211f_ack_interrupt(phydev);
++              if (err)
++                      return err;
++
++              val = RTL8211F_INER_LINK_STATUS;
++              err = phy_write_paged(phydev, 0xa42, RTL821x_INER, val);
++      } else {
++              val = 0;
++              err = phy_write_paged(phydev, 0xa42, RTL821x_INER, val);
++              if (err)
++                      return err;
++
++              err = rtl8211f_ack_interrupt(phydev);
++      }
++
++      return err;
++}
++
++static irqreturn_t rtl8201_handle_interrupt(struct phy_device *phydev)
++{
++      int irq_status;
++
++      irq_status = phy_read(phydev, RTL8201F_ISR);
++      if (irq_status < 0) {
++              phy_error(phydev);
++              return IRQ_NONE;
++      }
++
++      if (!(irq_status & RTL8201F_ISR_MASK))
++              return IRQ_NONE;
++
++      phy_trigger_machine(phydev);
++
++      return IRQ_HANDLED;
++}
++
++static irqreturn_t rtl821x_handle_interrupt(struct phy_device *phydev)
++{
++      int irq_status, irq_enabled;
++
++      irq_status = phy_read(phydev, RTL821x_INSR);
++      if (irq_status < 0) {
++              phy_error(phydev);
++              return IRQ_NONE;
++      }
++
++      irq_enabled = phy_read(phydev, RTL821x_INER);
++      if (irq_enabled < 0) {
++              phy_error(phydev);
++              return IRQ_NONE;
++      }
++
++      if (!(irq_status & irq_enabled))
++              return IRQ_NONE;
++
++      phy_trigger_machine(phydev);
++
++      return IRQ_HANDLED;
++}
++
++static irqreturn_t rtl8211f_handle_interrupt(struct phy_device *phydev)
++{
++      int irq_status;
++
++      irq_status = phy_read_paged(phydev, 0xa43, RTL8211F_INSR);
++      if (irq_status < 0) {
++              phy_error(phydev);
++              return IRQ_NONE;
++      }
++
++      if (!(irq_status & RTL8211F_INER_LINK_STATUS))
++              return IRQ_NONE;
++
++      phy_trigger_machine(phydev);
++
++      return IRQ_HANDLED;
++}
++
++static int rtl8211_config_aneg(struct phy_device *phydev)
++{
++      int ret;
++
++      ret = genphy_config_aneg(phydev);
++      if (ret < 0)
++              return ret;
++
++      /* Quirk was copied from vendor driver. Unfortunately it includes no
++       * description of the magic numbers.
++       */
++      if (phydev->speed == SPEED_100 && phydev->autoneg == AUTONEG_DISABLE) {
++              phy_write(phydev, 0x17, 0x2138);
++              phy_write(phydev, 0x0e, 0x0260);
++      } else {
++              phy_write(phydev, 0x17, 0x2108);
++              phy_write(phydev, 0x0e, 0x0000);
++      }
++
++      return 0;
++}
++
++static int rtl8211c_config_init(struct phy_device *phydev)
++{
++      /* RTL8211C has an issue when operating in Gigabit slave mode */
++      return phy_set_bits(phydev, MII_CTRL1000,
++                          CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER);
++}
++
++static int rtl8211f_config_init(struct phy_device *phydev)
++{
++      struct rtl821x_priv *priv = phydev->priv;
++      struct device *dev = &phydev->mdio.dev;
++      u16 val_txdly, val_rxdly;
++      int ret;
++
++      ret = phy_modify_paged_changed(phydev, 0xa43, RTL8211F_PHYCR1,
++                                     RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF,
++                                     priv->phycr1);
++      if (ret < 0) {
++              dev_err(dev, "aldps mode  configuration failed: %pe\n",
++                      ERR_PTR(ret));
++              return ret;
++      }
++
++      switch (phydev->interface) {
++      case PHY_INTERFACE_MODE_RGMII:
++              val_txdly = 0;
++              val_rxdly = 0;
++              break;
++
++      case PHY_INTERFACE_MODE_RGMII_RXID:
++              val_txdly = 0;
++              val_rxdly = RTL8211F_RX_DELAY;
++              break;
++
++      case PHY_INTERFACE_MODE_RGMII_TXID:
++              val_txdly = RTL8211F_TX_DELAY;
++              val_rxdly = 0;
++              break;
++
++      case PHY_INTERFACE_MODE_RGMII_ID:
++              val_txdly = RTL8211F_TX_DELAY;
++              val_rxdly = RTL8211F_RX_DELAY;
++              break;
++
++      default: /* the rest of the modes imply leaving delay as is. */
++              return 0;
++      }
++
++      ret = phy_modify_paged_changed(phydev, 0xd08, 0x11, RTL8211F_TX_DELAY,
++                                     val_txdly);
++      if (ret < 0) {
++              dev_err(dev, "Failed to update the TX delay register\n");
++              return ret;
++      } else if (ret) {
++              dev_dbg(dev,
++                      "%s 2ns TX delay (and changing the value from pin-strapping RXD1 or the bootloader)\n",
++                      val_txdly ? "Enabling" : "Disabling");
++      } else {
++              dev_dbg(dev,
++                      "2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n",
++                      val_txdly ? "enabled" : "disabled");
++      }
++
++      ret = phy_modify_paged_changed(phydev, 0xd08, 0x15, RTL8211F_RX_DELAY,
++                                     val_rxdly);
++      if (ret < 0) {
++              dev_err(dev, "Failed to update the RX delay register\n");
++              return ret;
++      } else if (ret) {
++              dev_dbg(dev,
++                      "%s 2ns RX delay (and changing the value from pin-strapping RXD0 or the bootloader)\n",
++                      val_rxdly ? "Enabling" : "Disabling");
++      } else {
++              dev_dbg(dev,
++                      "2ns RX delay was already %s (by pin-strapping RXD0 or bootloader configuration)\n",
++                      val_rxdly ? "enabled" : "disabled");
++      }
++
++      if (priv->has_phycr2) {
++              ret = phy_modify_paged(phydev, 0xa43, RTL8211F_PHYCR2,
++                                     RTL8211F_CLKOUT_EN, priv->phycr2);
++              if (ret < 0) {
++                      dev_err(dev, "clkout configuration failed: %pe\n",
++                              ERR_PTR(ret));
++                      return ret;
++              }
++
++              return genphy_soft_reset(phydev);
++      }
++
++      return 0;
++}
++
++static int rtl821x_suspend(struct phy_device *phydev)
++{
++      struct rtl821x_priv *priv = phydev->priv;
++      int ret = 0;
++
++      if (!phydev->wol_enabled) {
++              ret = genphy_suspend(phydev);
++
++              if (ret)
++                      return ret;
++
++              clk_disable_unprepare(priv->clk);
++      }
++
++      return ret;
++}
++
++static int rtl821x_resume(struct phy_device *phydev)
++{
++      struct rtl821x_priv *priv = phydev->priv;
++      int ret;
++
++      if (!phydev->wol_enabled)
++              clk_prepare_enable(priv->clk);
++
++      ret = genphy_resume(phydev);
++      if (ret < 0)
++              return ret;
++
++      msleep(20);
++
++      return 0;
++}
++
++static int rtl8211f_led_hw_is_supported(struct phy_device *phydev, u8 index,
++                                      unsigned long rules)
++{
++      const unsigned long mask = BIT(TRIGGER_NETDEV_LINK_10) |
++                                 BIT(TRIGGER_NETDEV_LINK_100) |
++                                 BIT(TRIGGER_NETDEV_LINK_1000) |
++                                 BIT(TRIGGER_NETDEV_RX) |
++                                 BIT(TRIGGER_NETDEV_TX);
++
++      /* The RTL8211F PHY supports these LED settings on up to three LEDs:
++       * - Link: Configurable subset of 10/100/1000 link rates
++       * - Active: Blink on activity, RX or TX is not differentiated
++       * The Active option has two modes, A and B:
++       * - A: Link and Active indication at configurable, but matching,
++       *      subset of 10/100/1000 link rates
++       * - B: Link indication at configurable subset of 10/100/1000 link
++       *      rates and Active indication always at all three 10+100+1000
++       *      link rates.
++       * This code currently uses mode B only.
++       */
++
++      if (index >= RTL8211F_LED_COUNT)
++              return -EINVAL;
++
++      /* Filter out any other unsupported triggers. */
++      if (rules & ~mask)
++              return -EOPNOTSUPP;
++
++      /* RX and TX are not differentiated, either both are set or not set. */
++      if (!(rules & BIT(TRIGGER_NETDEV_RX)) ^ !(rules & BIT(TRIGGER_NETDEV_TX)))
++              return -EOPNOTSUPP;
++
++      return 0;
++}
++
++static int rtl8211f_led_hw_control_get(struct phy_device *phydev, u8 index,
++                                     unsigned long *rules)
++{
++      int val;
++
++      if (index >= RTL8211F_LED_COUNT)
++              return -EINVAL;
++
++      val = phy_read_paged(phydev, 0xd04, RTL8211F_LEDCR);
++      if (val < 0)
++              return val;
++
++      val >>= RTL8211F_LEDCR_SHIFT * index;
++      val &= RTL8211F_LEDCR_MASK;
++
++      if (val & RTL8211F_LEDCR_LINK_10)
++              set_bit(TRIGGER_NETDEV_LINK_10, rules);
++
++      if (val & RTL8211F_LEDCR_LINK_100)
++              set_bit(TRIGGER_NETDEV_LINK_100, rules);
++
++      if (val & RTL8211F_LEDCR_LINK_1000)
++              set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++
++      if (val & RTL8211F_LEDCR_ACT_TXRX) {
++              set_bit(TRIGGER_NETDEV_RX, rules);
++              set_bit(TRIGGER_NETDEV_TX, rules);
++      }
++
++      return 0;
++}
++
++static int rtl8211f_led_hw_control_set(struct phy_device *phydev, u8 index,
++                                     unsigned long rules)
++{
++      const u16 mask = RTL8211F_LEDCR_MASK << (RTL8211F_LEDCR_SHIFT * index);
++      u16 reg = 0;
++
++      if (index >= RTL8211F_LED_COUNT)
++              return -EINVAL;
++
++      if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
++              reg |= RTL8211F_LEDCR_LINK_10;
++
++      if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++              reg |= RTL8211F_LEDCR_LINK_100;
++
++      if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++              reg |= RTL8211F_LEDCR_LINK_1000;
++
++      if (test_bit(TRIGGER_NETDEV_RX, &rules) ||
++          test_bit(TRIGGER_NETDEV_TX, &rules)) {
++              reg |= RTL8211F_LEDCR_ACT_TXRX;
++      }
++
++      reg <<= RTL8211F_LEDCR_SHIFT * index;
++      reg |= RTL8211F_LEDCR_MODE;      /* Mode B */
++
++      return phy_modify_paged(phydev, 0xd04, RTL8211F_LEDCR, mask, reg);
++}
++
++static int rtl8211e_config_init(struct phy_device *phydev)
++{
++      int ret = 0, oldpage;
++      u16 val;
++
++      /* enable TX/RX delay for rgmii-* modes, and disable them for rgmii. */
++      switch (phydev->interface) {
++      case PHY_INTERFACE_MODE_RGMII:
++              val = RTL8211E_CTRL_DELAY | 0;
++              break;
++      case PHY_INTERFACE_MODE_RGMII_ID:
++              val = RTL8211E_CTRL_DELAY | RTL8211E_TX_DELAY | RTL8211E_RX_DELAY;
++              break;
++      case PHY_INTERFACE_MODE_RGMII_RXID:
++              val = RTL8211E_CTRL_DELAY | RTL8211E_RX_DELAY;
++              break;
++      case PHY_INTERFACE_MODE_RGMII_TXID:
++              val = RTL8211E_CTRL_DELAY | RTL8211E_TX_DELAY;
++              break;
++      default: /* the rest of the modes imply leaving delays as is. */
++              return 0;
++      }
++
++      /* According to a sample driver there is a 0x1c config register on the
++       * 0xa4 extension page (0x7) layout. It can be used to disable/enable
++       * the RX/TX delays otherwise controlled by RXDLY/TXDLY pins.
++       * The configuration register definition:
++       * 14 = reserved
++       * 13 = Force Tx RX Delay controlled by bit12 bit11,
++       * 12 = RX Delay, 11 = TX Delay
++       * 10:0 = Test && debug settings reserved by realtek
++       */
++      oldpage = phy_select_page(phydev, 0x7);
++      if (oldpage < 0)
++              goto err_restore_page;
++
++      ret = __phy_write(phydev, RTL821x_EXT_PAGE_SELECT, 0xa4);
++      if (ret)
++              goto err_restore_page;
++
++      ret = __phy_modify(phydev, 0x1c, RTL8211E_CTRL_DELAY
++                         | RTL8211E_TX_DELAY | RTL8211E_RX_DELAY,
++                         val);
++
++err_restore_page:
++      return phy_restore_page(phydev, oldpage, ret);
++}
++
++static int rtl8211b_suspend(struct phy_device *phydev)
++{
++      phy_write(phydev, MII_MMD_DATA, BIT(9));
++
++      return genphy_suspend(phydev);
++}
++
++static int rtl8211b_resume(struct phy_device *phydev)
++{
++      phy_write(phydev, MII_MMD_DATA, 0);
++
++      return genphy_resume(phydev);
++}
++
++static int rtl8366rb_config_init(struct phy_device *phydev)
++{
++      int ret;
++
++      ret = phy_set_bits(phydev, RTL8366RB_POWER_SAVE,
++                         RTL8366RB_POWER_SAVE_ON);
++      if (ret) {
++              dev_err(&phydev->mdio.dev,
++                      "error enabling power management\n");
++      }
++
++      return ret;
++}
++
++/* get actual speed to cover the downshift case */
++static void rtlgen_decode_physr(struct phy_device *phydev, int val)
++{
++      /* bit 3
++       * 0: Half Duplex
++       * 1: Full Duplex
++       */
++      if (val & RTL_VND2_PHYSR_DUPLEX)
++              phydev->duplex = DUPLEX_FULL;
++      else
++              phydev->duplex = DUPLEX_HALF;
++
++      switch (val & RTL_VND2_PHYSR_SPEED_MASK) {
++      case 0x0000:
++              phydev->speed = SPEED_10;
++              break;
++      case 0x0010:
++              phydev->speed = SPEED_100;
++              break;
++      case 0x0020:
++              phydev->speed = SPEED_1000;
++              break;
++      case 0x0200:
++              phydev->speed = SPEED_10000;
++              break;
++      case 0x0210:
++              phydev->speed = SPEED_2500;
++              break;
++      case 0x0220:
++              phydev->speed = SPEED_5000;
++              break;
++      default:
++              break;
++      }
++
++      /* bit 11
++       * 0: Slave Mode
++       * 1: Master Mode
++       */
++      if (phydev->speed >= 1000) {
++              if (val & RTL_VND2_PHYSR_MASTER)
++                      phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
++              else
++                      phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
++      } else {
++              phydev->master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED;
++      }
++}
++
++static int rtlgen_read_status(struct phy_device *phydev)
++{
++      int ret, val;
++
++      ret = genphy_read_status(phydev);
++      if (ret < 0)
++              return ret;
++
++      if (!phydev->link)
++              return 0;
++
++      val = phy_read_paged(phydev, 0xa43, 0x12);
++      if (val < 0)
++              return val;
++
++      rtlgen_decode_physr(phydev, val);
++
++      return 0;
++}
++
++static int rtlgen_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
++{
++      int ret;
++
++      if (devnum == MDIO_MMD_VEND2) {
++              rtl821x_write_page(phydev, regnum >> 4);
++              ret = __phy_read(phydev, 0x10 + ((regnum & 0xf) >> 1));
++              rtl821x_write_page(phydev, 0);
++      } else if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE) {
++              rtl821x_write_page(phydev, 0xa5c);
++              ret = __phy_read(phydev, 0x12);
++              rtl821x_write_page(phydev, 0);
++      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) {
++              rtl821x_write_page(phydev, 0xa5d);
++              ret = __phy_read(phydev, 0x10);
++              rtl821x_write_page(phydev, 0);
++      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE) {
++              rtl821x_write_page(phydev, 0xa5d);
++              ret = __phy_read(phydev, 0x11);
++              rtl821x_write_page(phydev, 0);
++      } else {
++              ret = -EOPNOTSUPP;
++      }
++
++      return ret;
++}
++
++static int rtlgen_write_mmd(struct phy_device *phydev, int devnum, u16 regnum,
++                          u16 val)
++{
++      int ret;
++
++      if (devnum == MDIO_MMD_VEND2) {
++              rtl821x_write_page(phydev, regnum >> 4);
++              ret = __phy_write(phydev, 0x10 + ((regnum & 0xf) >> 1), val);
++              rtl821x_write_page(phydev, 0);
++      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) {
++              rtl821x_write_page(phydev, 0xa5d);
++              ret = __phy_write(phydev, 0x10, val);
++              rtl821x_write_page(phydev, 0);
++      } else {
++              ret = -EOPNOTSUPP;
++      }
++
++      return ret;
++}
++
++static int rtl822x_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
++{
++      int ret = rtlgen_read_mmd(phydev, devnum, regnum);
++
++      if (ret != -EOPNOTSUPP)
++              return ret;
++
++      if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE2) {
++              rtl821x_write_page(phydev, 0xa6e);
++              ret = __phy_read(phydev, 0x16);
++              rtl821x_write_page(phydev, 0);
++      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2) {
++              rtl821x_write_page(phydev, 0xa6d);
++              ret = __phy_read(phydev, 0x12);
++              rtl821x_write_page(phydev, 0);
++      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE2) {
++              rtl821x_write_page(phydev, 0xa6d);
++              ret = __phy_read(phydev, 0x10);
++              rtl821x_write_page(phydev, 0);
++      }
++
++      return ret;
++}
++
++static int rtl822x_write_mmd(struct phy_device *phydev, int devnum, u16 regnum,
++                           u16 val)
++{
++      int ret = rtlgen_write_mmd(phydev, devnum, regnum, val);
++
++      if (ret != -EOPNOTSUPP)
++              return ret;
++
++      if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2) {
++              rtl821x_write_page(phydev, 0xa6d);
++              ret = __phy_write(phydev, 0x12, val);
++              rtl821x_write_page(phydev, 0);
++      }
++
++      return ret;
++}
++
++static int rtl822xb_config_init(struct phy_device *phydev)
++{
++      bool has_2500, has_sgmii;
++      u16 mode;
++      int ret;
++
++      has_2500 = test_bit(PHY_INTERFACE_MODE_2500BASEX,
++                          phydev->host_interfaces) ||
++                 phydev->interface == PHY_INTERFACE_MODE_2500BASEX;
++
++      has_sgmii = test_bit(PHY_INTERFACE_MODE_SGMII,
++                           phydev->host_interfaces) ||
++                  phydev->interface == PHY_INTERFACE_MODE_SGMII;
++
++      /* fill in possible interfaces */
++      __assign_bit(PHY_INTERFACE_MODE_2500BASEX, phydev->possible_interfaces,
++                   has_2500);
++      __assign_bit(PHY_INTERFACE_MODE_SGMII, phydev->possible_interfaces,
++                   has_sgmii);
++
++      if (!has_2500 && !has_sgmii)
++              return 0;
++
++      /* determine SerDes option mode */
++      if (has_2500 && !has_sgmii) {
++              mode = RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX;
++              phydev->rate_matching = RATE_MATCH_PAUSE;
++      } else {
++              mode = RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII;
++              phydev->rate_matching = RATE_MATCH_NONE;
++      }
++
++      /* the following sequence with magic numbers sets up the SerDes
++       * option mode
++       */
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x75f3, 0);
++      if (ret < 0)
++              return ret;
++
++      ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND1,
++                                   RTL822X_VND1_SERDES_OPTION,
++                                   RTL822X_VND1_SERDES_OPTION_MODE_MASK,
++                                   mode);
++      if (ret < 0)
++              return ret;
++
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6a04, 0x0503);
++      if (ret < 0)
++              return ret;
++
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6f10, 0xd455);
++      if (ret < 0)
++              return ret;
++
++      return phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6f11, 0x8020);
++}
++
++static int rtl822xb_get_rate_matching(struct phy_device *phydev,
++                                    phy_interface_t iface)
++{
++      int val;
++
++      /* Only rate matching at 2500base-x */
++      if (iface != PHY_INTERFACE_MODE_2500BASEX)
++              return RATE_MATCH_NONE;
++
++      val = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_OPTION);
++      if (val < 0)
++              return val;
++
++      if ((val & RTL822X_VND1_SERDES_OPTION_MODE_MASK) ==
++          RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX)
++              return RATE_MATCH_PAUSE;
++
++      /* RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII */
++      return RATE_MATCH_NONE;
++}
++
++static int rtl822x_get_features(struct phy_device *phydev)
++{
++      int val;
++
++      val = phy_read_paged(phydev, 0xa61, 0x13);
++      if (val < 0)
++              return val;
++
++      linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
++                       phydev->supported, val & MDIO_PMA_SPEED_2_5G);
++      linkmode_mod_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
++                       phydev->supported, val & MDIO_PMA_SPEED_5G);
++      linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
++                       phydev->supported, val & MDIO_SPEED_10G);
++
++      return genphy_read_abilities(phydev);
++}
++
++static int rtl822x_config_aneg(struct phy_device *phydev)
++{
++      int ret = 0;
++
++      if (phydev->autoneg == AUTONEG_ENABLE) {
++              u16 adv = linkmode_adv_to_mii_10gbt_adv_t(phydev->advertising);
++
++              ret = phy_modify_paged_changed(phydev, 0xa5d, 0x12,
++                                             MDIO_AN_10GBT_CTRL_ADV2_5G |
++                                             MDIO_AN_10GBT_CTRL_ADV5G,
++                                             adv);
++              if (ret < 0)
++                      return ret;
++      }
++
++      return __genphy_config_aneg(phydev, ret);
++}
++
++static void rtl822xb_update_interface(struct phy_device *phydev)
++{
++      int val;
++
++      if (!phydev->link)
++              return;
++
++      /* Change interface according to serdes mode */
++      val = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_CTRL3);
++      if (val < 0)
++              return;
++
++      switch (val & RTL822X_VND1_SERDES_CTRL3_MODE_MASK) {
++      case RTL822X_VND1_SERDES_CTRL3_MODE_2500BASEX:
++              phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
++              break;
++      case RTL822X_VND1_SERDES_CTRL3_MODE_SGMII:
++              phydev->interface = PHY_INTERFACE_MODE_SGMII;
++              break;
++      }
++}
++
++static int rtl822x_read_status(struct phy_device *phydev)
++{
++      int lpadv, ret;
++
++      mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
++
++      ret = rtlgen_read_status(phydev);
++      if (ret < 0)
++              return ret;
++
++      if (phydev->autoneg == AUTONEG_DISABLE ||
++          !phydev->autoneg_complete)
++              return 0;
++
++      lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
++      if (lpadv < 0)
++              return lpadv;
++
++      mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, lpadv);
++
++      return 0;
++}
++
++static int rtl822xb_read_status(struct phy_device *phydev)
++{
++      int ret;
++
++      ret = rtl822x_read_status(phydev);
++      if (ret < 0)
++              return ret;
++
++      rtl822xb_update_interface(phydev);
++
++      return 0;
++}
++
++static int rtl822x_c45_get_features(struct phy_device *phydev)
++{
++      linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT,
++                       phydev->supported);
++
++      return genphy_c45_pma_read_abilities(phydev);
++}
++
++static int rtl822x_c45_config_aneg(struct phy_device *phydev)
++{
++      bool changed = false;
++      int ret, val;
++
++      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;
++
++      val = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
++
++      /* Vendor register as C45 has no standardized support for 1000BaseT */
++      ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, RTL822X_VND2_GBCR,
++                                   ADVERTISE_1000FULL, val);
++      if (ret < 0)
++              return ret;
++      if (ret > 0)
++              changed = true;
++
++      return genphy_c45_check_and_restart_aneg(phydev, changed);
++}
++
++static int rtl822x_c45_read_status(struct phy_device *phydev)
++{
++      int ret, val;
++
++      /* Vendor register as C45 has no standardized support for 1000BaseT */
++      if (phydev->autoneg == AUTONEG_ENABLE && genphy_c45_aneg_done(phydev)) {
++              val = phy_read_mmd(phydev, MDIO_MMD_VEND2,
++                                 RTL822X_VND2_GANLPAR);
++              if (val < 0)
++                      return val;
++      } else {
++              val = 0;
++      }
++      mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val);
++
++      ret = genphy_c45_read_status(phydev);
++      if (ret < 0)
++              return ret;
++
++      if (!phydev->link) {
++              phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
++              return 0;
++      }
++
++      /* Read actual speed from vendor register. */
++      val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL_VND2_PHYSR);
++      if (val < 0)
++              return val;
++
++      rtlgen_decode_physr(phydev, val);
++
++      return 0;
++}
++
++static int rtl822xb_c45_read_status(struct phy_device *phydev)
++{
++      int ret;
++
++      ret = rtl822x_c45_read_status(phydev);
++      if (ret < 0)
++              return ret;
++
++      rtl822xb_update_interface(phydev);
++
++      return 0;
++}
++
++static bool rtlgen_supports_2_5gbps(struct phy_device *phydev)
++{
++      int val;
++
++      phy_write(phydev, RTL821x_PAGE_SELECT, 0xa61);
++      val = phy_read(phydev, 0x13);
++      phy_write(phydev, RTL821x_PAGE_SELECT, 0);
++
++      return val >= 0 && val & MDIO_PMA_SPEED_2_5G;
++}
++
++/* On internal PHY's MMD reads over C22 always return 0.
++ * Check a MMD register which is known to be non-zero.
++ */
++static bool rtlgen_supports_mmd(struct phy_device *phydev)
++{
++      int val;
++
++      phy_lock_mdio_bus(phydev);
++      __phy_write(phydev, MII_MMD_CTRL, MDIO_MMD_PCS);
++      __phy_write(phydev, MII_MMD_DATA, MDIO_PCS_EEE_ABLE);
++      __phy_write(phydev, MII_MMD_CTRL, MDIO_MMD_PCS | MII_MMD_CTRL_NOINCR);
++      val = __phy_read(phydev, MII_MMD_DATA);
++      phy_unlock_mdio_bus(phydev);
++
++      return val > 0;
++}
++
++static int rtlgen_match_phy_device(struct phy_device *phydev)
++{
++      return phydev->phy_id == RTL_GENERIC_PHYID &&
++             !rtlgen_supports_2_5gbps(phydev);
++}
++
++static int rtl8226_match_phy_device(struct phy_device *phydev)
++{
++      return phydev->phy_id == RTL_GENERIC_PHYID &&
++             rtlgen_supports_2_5gbps(phydev) &&
++             rtlgen_supports_mmd(phydev);
++}
++
++static int rtlgen_is_c45_match(struct phy_device *phydev, unsigned int id,
++                             bool is_c45)
++{
++      if (phydev->is_c45)
++              return is_c45 && (id == phydev->c45_ids.device_ids[1]);
++      else
++              return !is_c45 && (id == phydev->phy_id);
++}
++
++static int rtl8221b_match_phy_device(struct phy_device *phydev)
++{
++      return phydev->phy_id == RTL_8221B && rtlgen_supports_mmd(phydev);
++}
++
++static int rtl8221b_vb_cg_c22_match_phy_device(struct phy_device *phydev)
++{
++      return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, false);
++}
++
++static int rtl8221b_vb_cg_c45_match_phy_device(struct phy_device *phydev)
++{
++      return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, true);
++}
++
++static int rtl8221b_vn_cg_c22_match_phy_device(struct phy_device *phydev)
++{
++      return rtlgen_is_c45_match(phydev, RTL_8221B_VN_CG, false);
++}
++
++static int rtl8221b_vn_cg_c45_match_phy_device(struct phy_device *phydev)
++{
++      return rtlgen_is_c45_match(phydev, RTL_8221B_VN_CG, true);
++}
++
++static int rtl_internal_nbaset_match_phy_device(struct phy_device *phydev)
++{
++      if (phydev->is_c45)
++              return false;
++
++      switch (phydev->phy_id) {
++      case RTL_GENERIC_PHYID:
++      case RTL_8221B:
++      case RTL_8251B:
++      case 0x001cc841:
++              break;
++      default:
++              return false;
++      }
++
++      return rtlgen_supports_2_5gbps(phydev) && !rtlgen_supports_mmd(phydev);
++}
++
++static int rtl8251b_c45_match_phy_device(struct phy_device *phydev)
++{
++      return rtlgen_is_c45_match(phydev, RTL_8251B, true);
++}
++
++static int rtlgen_resume(struct phy_device *phydev)
++{
++      int ret = genphy_resume(phydev);
++
++      /* Internal PHY's from RTL8168h up may not be instantly ready */
++      msleep(20);
++
++      return ret;
++}
++
++static int rtlgen_c45_resume(struct phy_device *phydev)
++{
++      int ret = genphy_c45_pma_resume(phydev);
++
++      msleep(20);
++
++      return ret;
++}
++
++static int rtl9000a_config_init(struct phy_device *phydev)
++{
++      phydev->autoneg = AUTONEG_DISABLE;
++      phydev->speed = SPEED_100;
++      phydev->duplex = DUPLEX_FULL;
++
++      return 0;
++}
++
++static int rtl9000a_config_aneg(struct phy_device *phydev)
++{
++      int ret;
++      u16 ctl = 0;
++
++      switch (phydev->master_slave_set) {
++      case MASTER_SLAVE_CFG_MASTER_FORCE:
++              ctl |= CTL1000_AS_MASTER;
++              break;
++      case MASTER_SLAVE_CFG_SLAVE_FORCE:
++              break;
++      case MASTER_SLAVE_CFG_UNKNOWN:
++      case MASTER_SLAVE_CFG_UNSUPPORTED:
++              return 0;
++      default:
++              phydev_warn(phydev, "Unsupported Master/Slave mode\n");
++              return -EOPNOTSUPP;
++      }
++
++      ret = phy_modify_changed(phydev, MII_CTRL1000, CTL1000_AS_MASTER, ctl);
++      if (ret == 1)
++              ret = genphy_soft_reset(phydev);
++
++      return ret;
++}
++
++static int rtl9000a_read_status(struct phy_device *phydev)
++{
++      int ret;
++
++      phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN;
++      phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
++
++      ret = genphy_update_link(phydev);
++      if (ret)
++              return ret;
++
++      ret = phy_read(phydev, MII_CTRL1000);
++      if (ret < 0)
++              return ret;
++      if (ret & CTL1000_AS_MASTER)
++              phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE;
++      else
++              phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE;
++
++      ret = phy_read(phydev, MII_STAT1000);
++      if (ret < 0)
++              return ret;
++      if (ret & LPA_1000MSRES)
++              phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
++      else
++              phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
++
++      return 0;
++}
++
++static int rtl9000a_ack_interrupt(struct phy_device *phydev)
++{
++      int err;
++
++      err = phy_read(phydev, RTL8211F_INSR);
++
++      return (err < 0) ? err : 0;
++}
++
++static int rtl9000a_config_intr(struct phy_device *phydev)
++{
++      u16 val;
++      int err;
++
++      if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
++              err = rtl9000a_ack_interrupt(phydev);
++              if (err)
++                      return err;
++
++              val = (u16)~RTL9000A_GINMR_LINK_STATUS;
++              err = phy_write_paged(phydev, 0xa42, RTL9000A_GINMR, val);
++      } else {
++              val = ~0;
++              err = phy_write_paged(phydev, 0xa42, RTL9000A_GINMR, val);
++              if (err)
++                      return err;
++
++              err = rtl9000a_ack_interrupt(phydev);
++      }
++
++      return phy_write_paged(phydev, 0xa42, RTL9000A_GINMR, val);
++}
++
++static irqreturn_t rtl9000a_handle_interrupt(struct phy_device *phydev)
++{
++      int irq_status;
++
++      irq_status = phy_read(phydev, RTL8211F_INSR);
++      if (irq_status < 0) {
++              phy_error(phydev);
++              return IRQ_NONE;
++      }
++
++      if (!(irq_status & RTL8211F_INER_LINK_STATUS))
++              return IRQ_NONE;
++
++      phy_trigger_machine(phydev);
++
++      return IRQ_HANDLED;
++}
++
++static struct phy_driver realtek_drvs[] = {
++      {
++              PHY_ID_MATCH_EXACT(0x00008201),
++              .name           = "RTL8201CP Ethernet",
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001cc816),
++              .name           = "RTL8201F Fast Ethernet",
++              .config_intr    = &rtl8201_config_intr,
++              .handle_interrupt = rtl8201_handle_interrupt,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              PHY_ID_MATCH_MODEL(0x001cc880),
++              .name           = "RTL8208 Fast Ethernet",
++              .read_mmd       = genphy_read_mmd_unsupported,
++              .write_mmd      = genphy_write_mmd_unsupported,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001cc910),
++              .name           = "RTL8211 Gigabit Ethernet",
++              .config_aneg    = rtl8211_config_aneg,
++              .read_mmd       = &genphy_read_mmd_unsupported,
++              .write_mmd      = &genphy_write_mmd_unsupported,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001cc912),
++              .name           = "RTL8211B Gigabit Ethernet",
++              .config_intr    = &rtl8211b_config_intr,
++              .handle_interrupt = rtl821x_handle_interrupt,
++              .read_mmd       = &genphy_read_mmd_unsupported,
++              .write_mmd      = &genphy_write_mmd_unsupported,
++              .suspend        = rtl8211b_suspend,
++              .resume         = rtl8211b_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001cc913),
++              .name           = "RTL8211C Gigabit Ethernet",
++              .config_init    = rtl8211c_config_init,
++              .read_mmd       = &genphy_read_mmd_unsupported,
++              .write_mmd      = &genphy_write_mmd_unsupported,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001cc914),
++              .name           = "RTL8211DN Gigabit Ethernet",
++              .config_intr    = rtl8211e_config_intr,
++              .handle_interrupt = rtl821x_handle_interrupt,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001cc915),
++              .name           = "RTL8211E Gigabit Ethernet",
++              .config_init    = &rtl8211e_config_init,
++              .config_intr    = &rtl8211e_config_intr,
++              .handle_interrupt = rtl821x_handle_interrupt,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001cc916),
++              .name           = "RTL8211F Gigabit Ethernet",
++              .probe          = rtl821x_probe,
++              .config_init    = &rtl8211f_config_init,
++              .read_status    = rtlgen_read_status,
++              .config_intr    = &rtl8211f_config_intr,
++              .handle_interrupt = rtl8211f_handle_interrupt,
++              .suspend        = rtl821x_suspend,
++              .resume         = rtl821x_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++              .flags          = PHY_ALWAYS_CALL_SUSPEND,
++              .led_hw_is_supported = rtl8211f_led_hw_is_supported,
++              .led_hw_control_get = rtl8211f_led_hw_control_get,
++              .led_hw_control_set = rtl8211f_led_hw_control_set,
++      }, {
++              PHY_ID_MATCH_EXACT(RTL_8211FVD_PHYID),
++              .name           = "RTL8211F-VD Gigabit Ethernet",
++              .probe          = rtl821x_probe,
++              .config_init    = &rtl8211f_config_init,
++              .read_status    = rtlgen_read_status,
++              .config_intr    = &rtl8211f_config_intr,
++              .handle_interrupt = rtl8211f_handle_interrupt,
++              .suspend        = rtl821x_suspend,
++              .resume         = rtl821x_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++              .flags          = PHY_ALWAYS_CALL_SUSPEND,
++      }, {
++              .name           = "Generic FE-GE Realtek PHY",
++              .match_phy_device = rtlgen_match_phy_device,
++              .read_status    = rtlgen_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++              .read_mmd       = rtlgen_read_mmd,
++              .write_mmd      = rtlgen_write_mmd,
++      }, {
++              .name           = "RTL8226 2.5Gbps PHY",
++              .match_phy_device = rtl8226_match_phy_device,
++              .get_features   = rtl822x_get_features,
++              .config_aneg    = rtl822x_config_aneg,
++              .read_status    = rtl822x_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              .match_phy_device = rtl8221b_match_phy_device,
++              .name           = "RTL8226B_RTL8221B 2.5Gbps PHY",
++              .get_features   = rtl822x_get_features,
++              .config_aneg    = rtl822x_config_aneg,
++              .config_init    = rtl822xb_config_init,
++              .get_rate_matching = rtl822xb_get_rate_matching,
++              .read_status    = rtl822xb_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001cc838),
++              .name           = "RTL8226-CG 2.5Gbps PHY",
++              .get_features   = rtl822x_get_features,
++              .config_aneg    = rtl822x_config_aneg,
++              .read_status    = rtl822x_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001cc848),
++              .name           = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY",
++              .get_features   = rtl822x_get_features,
++              .config_aneg    = rtl822x_config_aneg,
++              .config_init    = rtl822xb_config_init,
++              .get_rate_matching = rtl822xb_get_rate_matching,
++              .read_status    = rtl822xb_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              .match_phy_device = rtl8221b_vb_cg_c22_match_phy_device,
++              .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C22)",
++              .get_features   = rtl822x_get_features,
++              .config_aneg    = rtl822x_config_aneg,
++              .config_init    = rtl822xb_config_init,
++              .get_rate_matching = rtl822xb_get_rate_matching,
++              .read_status    = rtl822xb_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              .match_phy_device = rtl8221b_vb_cg_c45_match_phy_device,
++              .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C45)",
++              .config_init    = rtl822xb_config_init,
++              .get_rate_matching = rtl822xb_get_rate_matching,
++              .get_features   = rtl822x_c45_get_features,
++              .config_aneg    = rtl822x_c45_config_aneg,
++              .read_status    = rtl822xb_c45_read_status,
++              .suspend        = genphy_c45_pma_suspend,
++              .resume         = rtlgen_c45_resume,
++      }, {
++              .match_phy_device = rtl8221b_vn_cg_c22_match_phy_device,
++              .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C22)",
++              .get_features   = rtl822x_get_features,
++              .config_aneg    = rtl822x_config_aneg,
++              .config_init    = rtl822xb_config_init,
++              .get_rate_matching = rtl822xb_get_rate_matching,
++              .read_status    = rtl822xb_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              .match_phy_device = rtl8221b_vn_cg_c45_match_phy_device,
++              .name           = "RTL8221B-VN-CG 2.5Gbps PHY (C45)",
++              .config_init    = rtl822xb_config_init,
++              .get_rate_matching = rtl822xb_get_rate_matching,
++              .get_features   = rtl822x_c45_get_features,
++              .config_aneg    = rtl822x_c45_config_aneg,
++              .read_status    = rtl822xb_c45_read_status,
++              .suspend        = genphy_c45_pma_suspend,
++              .resume         = rtlgen_c45_resume,
++      }, {
++              .match_phy_device = rtl8251b_c45_match_phy_device,
++              .name           = "RTL8251B 5Gbps PHY",
++              .get_features   = rtl822x_get_features,
++              .config_aneg    = rtl822x_config_aneg,
++              .read_status    = rtl822x_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              .match_phy_device = rtl_internal_nbaset_match_phy_device,
++              .name           = "Realtek Internal NBASE-T PHY",
++              .flags          = PHY_IS_INTERNAL,
++              .get_features   = rtl822x_get_features,
++              .config_aneg    = rtl822x_config_aneg,
++              .read_status    = rtl822x_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++              .read_mmd       = rtl822x_read_mmd,
++              .write_mmd      = rtl822x_write_mmd,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001ccad0),
++              .name           = "RTL8224 2.5Gbps PHY",
++              .get_features   = rtl822x_c45_get_features,
++              .config_aneg    = rtl822x_c45_config_aneg,
++              .read_status    = rtl822x_c45_read_status,
++              .suspend        = genphy_c45_pma_suspend,
++              .resume         = rtlgen_c45_resume,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001cc961),
++              .name           = "RTL8366RB Gigabit Ethernet",
++              .config_init    = &rtl8366rb_config_init,
++              /* These interrupts are handled by the irq controller
++               * embedded inside the RTL8366RB, they get unmasked when the
++               * irq is requested and ACKed by reading the status register,
++               * which is done by the irqchip code.
++               */
++              .config_intr    = genphy_no_config_intr,
++              .handle_interrupt = genphy_handle_interrupt_no_ack,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001ccb00),
++              .name           = "RTL9000AA_RTL9000AN Ethernet",
++              .features       = PHY_BASIC_T1_FEATURES,
++              .config_init    = rtl9000a_config_init,
++              .config_aneg    = rtl9000a_config_aneg,
++              .read_status    = rtl9000a_read_status,
++              .config_intr    = rtl9000a_config_intr,
++              .handle_interrupt = rtl9000a_handle_interrupt,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001cc942),
++              .name           = "RTL8365MB-VC Gigabit Ethernet",
++              /* Interrupt handling analogous to RTL8366RB */
++              .config_intr    = genphy_no_config_intr,
++              .handle_interrupt = genphy_handle_interrupt_no_ack,
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++      }, {
++              PHY_ID_MATCH_EXACT(0x001cc960),
++              .name           = "RTL8366S Gigabit Ethernet",
++              .suspend        = genphy_suspend,
++              .resume         = genphy_resume,
++              .read_mmd       = genphy_read_mmd_unsupported,
++              .write_mmd      = genphy_write_mmd_unsupported,
++      },
++};
++
++module_phy_driver(realtek_drvs);
++
++static const struct mdio_device_id __maybe_unused realtek_tbl[] = {
++      { PHY_ID_MATCH_VENDOR(0x001cc800) },
++      { }
++};
++
++MODULE_DEVICE_TABLE(mdio, realtek_tbl);
diff --git a/target/linux/generic/backport-6.12/781-11-v6.14-net-phy-realtek-add-hwmon-support-for-temp-sensor-on.patch b/target/linux/generic/backport-6.12/781-11-v6.14-net-phy-realtek-add-hwmon-support-for-temp-sensor-on.patch
new file mode 100644 (file)
index 0000000..2dec701
--- /dev/null
@@ -0,0 +1,173 @@
+From 33700ca45b7d2e1655d4cad95e25671e8a94e2f0 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Sat, 11 Jan 2025 21:51:24 +0100
+Subject: [PATCH] net: phy: realtek: add hwmon support for temp sensor on
+ RTL822x
+
+This adds hwmon support for the temperature sensor on RTL822x.
+It's available on the standalone versions of the PHY's, and on
+the integrated PHY's in RTL8125B/RTL8125D/RTL8126.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/ad6bfe9f-6375-4a00-84b4-bfb38a21bd71@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/Kconfig         |  6 ++
+ drivers/net/phy/realtek/Makefile        |  1 +
+ drivers/net/phy/realtek/realtek.h       | 10 ++++
+ drivers/net/phy/realtek/realtek_hwmon.c | 79 +++++++++++++++++++++++++
+ drivers/net/phy/realtek/realtek_main.c  | 12 ++++
+ 5 files changed, 108 insertions(+)
+ create mode 100644 drivers/net/phy/realtek/realtek.h
+ create mode 100644 drivers/net/phy/realtek/realtek_hwmon.c
+
+--- a/drivers/net/phy/realtek/Kconfig
++++ b/drivers/net/phy/realtek/Kconfig
+@@ -3,3 +3,9 @@ config REALTEK_PHY
+       tristate "Realtek PHYs"
+       help
+         Currently supports RTL821x/RTL822x and fast ethernet PHYs
++
++config REALTEK_PHY_HWMON
++      def_bool REALTEK_PHY && HWMON
++      depends on !(REALTEK_PHY=y && HWMON=m)
++      help
++        Optional hwmon support for the temperature sensor
+--- a/drivers/net/phy/realtek/Makefile
++++ b/drivers/net/phy/realtek/Makefile
+@@ -1,3 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0
+ realtek-y                     += realtek_main.o
++realtek-$(CONFIG_REALTEK_PHY_HWMON) += realtek_hwmon.o
+ obj-$(CONFIG_REALTEK_PHY)     += realtek.o
+--- /dev/null
++++ b/drivers/net/phy/realtek/realtek.h
+@@ -0,0 +1,10 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++
++#ifndef REALTEK_H
++#define REALTEK_H
++
++#include <linux/phy.h>
++
++int rtl822x_hwmon_init(struct phy_device *phydev);
++
++#endif /* REALTEK_H */
+--- /dev/null
++++ b/drivers/net/phy/realtek/realtek_hwmon.c
+@@ -0,0 +1,79 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * HWMON support for Realtek PHY's
++ *
++ * Author: Heiner Kallweit <hkallweit1@gmail.com>
++ */
++
++#include <linux/hwmon.h>
++#include <linux/phy.h>
++
++#include "realtek.h"
++
++#define RTL822X_VND2_TSALRM                           0xa662
++#define RTL822X_VND2_TSRR                             0xbd84
++#define RTL822X_VND2_TSSR                             0xb54c
++
++static int rtl822x_hwmon_get_temp(int raw)
++{
++      if (raw >= 512)
++              raw -= 1024;
++
++      return 1000 * raw / 2;
++}
++
++static int rtl822x_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
++                            u32 attr, int channel, long *val)
++{
++      struct phy_device *phydev = dev_get_drvdata(dev);
++      int raw;
++
++      switch (attr) {
++      case hwmon_temp_input:
++              raw = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_TSRR) & 0x3ff;
++              *val = rtl822x_hwmon_get_temp(raw);
++              break;
++      case hwmon_temp_max:
++              /* Chip reduces speed to 1G if threshold is exceeded */
++              raw = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_TSSR) >> 6;
++              *val = rtl822x_hwmon_get_temp(raw);
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      return 0;
++}
++
++static const struct hwmon_ops rtl822x_hwmon_ops = {
++      .visible = 0444,
++      .read = rtl822x_hwmon_read,
++};
++
++static const struct hwmon_channel_info * const rtl822x_hwmon_info[] = {
++      HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX),
++      NULL
++};
++
++static const struct hwmon_chip_info rtl822x_hwmon_chip_info = {
++      .ops = &rtl822x_hwmon_ops,
++      .info = rtl822x_hwmon_info,
++};
++
++int rtl822x_hwmon_init(struct phy_device *phydev)
++{
++      struct device *hwdev, *dev = &phydev->mdio.dev;
++      const char *name;
++
++      /* Ensure over-temp alarm is reset. */
++      phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_TSALRM, 3);
++
++      name = devm_hwmon_sanitize_name(dev, dev_name(dev));
++      if (IS_ERR(name))
++              return PTR_ERR(name);
++
++      hwdev = devm_hwmon_device_register_with_info(dev, name, phydev,
++                                                   &rtl822x_hwmon_chip_info,
++                                                   NULL);
++      return PTR_ERR_OR_ZERO(hwdev);
++}
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -14,6 +14,8 @@
+ #include <linux/delay.h>
+ #include <linux/clk.h>
++#include "realtek.h"
++
+ #define RTL821x_PHYSR                         0x11
+ #define RTL821x_PHYSR_DUPLEX                  BIT(13)
+ #define RTL821x_PHYSR_SPEED                   GENMASK(15, 14)
+@@ -820,6 +822,15 @@ static int rtl822x_write_mmd(struct phy_
+       return ret;
+ }
++static int rtl822x_probe(struct phy_device *phydev)
++{
++      if (IS_ENABLED(CONFIG_REALTEK_PHY_HWMON) &&
++          phydev->phy_id != RTL_GENERIC_PHYID)
++              return rtl822x_hwmon_init(phydev);
++
++      return 0;
++}
++
+ static int rtl822xb_config_init(struct phy_device *phydev)
+ {
+       bool has_2500, has_sgmii;
+@@ -1518,6 +1529,7 @@ static struct phy_driver realtek_drvs[]
+               .match_phy_device = rtl_internal_nbaset_match_phy_device,
+               .name           = "Realtek Internal NBASE-T PHY",
+               .flags          = PHY_IS_INTERNAL,
++              .probe          = rtl822x_probe,
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
+               .read_status    = rtl822x_read_status,
diff --git a/target/linux/generic/backport-6.12/781-12-v6.14-net-phy-realtek-HWMON-support-for-standalone-version.patch b/target/linux/generic/backport-6.12/781-12-v6.14-net-phy-realtek-HWMON-support-for-standalone-version.patch
new file mode 100644 (file)
index 0000000..8b8c97c
--- /dev/null
@@ -0,0 +1,64 @@
+From 64ff63aeefb03139ae27454bd4208244579ae88e Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Fri, 17 Jan 2025 23:24:21 +0100
+Subject: [PATCH] net: phy: realtek: HWMON support for standalone versions of
+ RTL8221B and RTL8251
+
+HWMON support has been added for the RTL8221/8251 PHYs integrated together
+with the MAC inside the RTL8125/8126 chips. This patch extends temperature
+reading support for standalone variants of the mentioned PHYs.
+
+I don't know whether the earlier revisions of the RTL8226 also have a
+built-in temperature sensor, so they have been skipped for now.
+
+Tested on RTL8221B-VB-CG.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/realtek/realtek_main.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -1474,6 +1474,7 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               .match_phy_device = rtl8221b_vb_cg_c22_match_phy_device,
+               .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C22)",
++              .probe          = rtl822x_probe,
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
+               .config_init    = rtl822xb_config_init,
+@@ -1486,6 +1487,7 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               .match_phy_device = rtl8221b_vb_cg_c45_match_phy_device,
+               .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C45)",
++              .probe          = rtl822x_probe,
+               .config_init    = rtl822xb_config_init,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+               .get_features   = rtl822x_c45_get_features,
+@@ -1496,6 +1498,7 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               .match_phy_device = rtl8221b_vn_cg_c22_match_phy_device,
+               .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C22)",
++              .probe          = rtl822x_probe,
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
+               .config_init    = rtl822xb_config_init,
+@@ -1508,6 +1511,7 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               .match_phy_device = rtl8221b_vn_cg_c45_match_phy_device,
+               .name           = "RTL8221B-VN-CG 2.5Gbps PHY (C45)",
++              .probe          = rtl822x_probe,
+               .config_init    = rtl822xb_config_init,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+               .get_features   = rtl822x_c45_get_features,
+@@ -1518,6 +1522,7 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               .match_phy_device = rtl8251b_c45_match_phy_device,
+               .name           = "RTL8251B 5Gbps PHY",
++              .probe          = rtl822x_probe,
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
+               .read_status    = rtl822x_read_status,
diff --git a/target/linux/generic/backport-6.12/781-13-v6.15-net-phy-realtek-make-HWMON-support-a-user-visible-Kc.patch b/target/linux/generic/backport-6.12/781-13-v6.15-net-phy-realtek-make-HWMON-support-a-user-visible-Kc.patch
new file mode 100644 (file)
index 0000000..821d7ee
--- /dev/null
@@ -0,0 +1,35 @@
+From 51773846fab24a353bed4ebb660997ced4bc32d7 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Mon, 3 Feb 2025 21:33:39 +0100
+Subject: [PATCH] net: phy: realtek: make HWMON support a user-visible Kconfig
+ symbol
+
+Make config symbol REALTEK_PHY_HWMON user-visible, so that users can
+remove support if not needed.
+
+Suggested-by: Geert Uytterhoeven <geert@linux-m68k.org>
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/3466ee92-166a-4b0f-9ae7-42b9e046f333@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/Kconfig | 8 ++++++--
+ 1 file changed, 6 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/realtek/Kconfig
++++ b/drivers/net/phy/realtek/Kconfig
+@@ -4,8 +4,12 @@ config REALTEK_PHY
+       help
+         Currently supports RTL821x/RTL822x and fast ethernet PHYs
++if REALTEK_PHY
++
+ config REALTEK_PHY_HWMON
+-      def_bool REALTEK_PHY && HWMON
+-      depends on !(REALTEK_PHY=y && HWMON=m)
++      bool "HWMON support for Realtek PHYs"
++      depends on HWMON && !(REALTEK_PHY=y && HWMON=m)
+       help
+         Optional hwmon support for the temperature sensor
++
++endif # REALTEK_PHY
diff --git a/target/linux/generic/backport-6.12/781-14-v6.15-net-phy-realtek-use-string-choices-helpers.patch b/target/linux/generic/backport-6.12/781-14-v6.15-net-phy-realtek-use-string-choices-helpers.patch
new file mode 100644 (file)
index 0000000..d3e09bc
--- /dev/null
@@ -0,0 +1,54 @@
+From 0bea93fdbaf8675b7e8124bdcaf51497dcc8bcfa Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Mon, 3 Feb 2025 21:41:36 +0100
+Subject: [PATCH] net: phy: realtek: use string choices helpers
+
+Use string choices helpers to simplify the code.
+
+Reported-by: kernel test robot <lkp@intel.com>
+Closes: https://lore.kernel.org/oe-kbuild-all/202501190707.qQS8PGHW-lkp@intel.com/
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/realtek/realtek_main.c | 9 +++++----
+ 1 file changed, 5 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -13,6 +13,7 @@
+ #include <linux/module.h>
+ #include <linux/delay.h>
+ #include <linux/clk.h>
++#include <linux/string_choices.h>
+ #include "realtek.h"
+@@ -422,11 +423,11 @@ static int rtl8211f_config_init(struct p
+       } else if (ret) {
+               dev_dbg(dev,
+                       "%s 2ns TX delay (and changing the value from pin-strapping RXD1 or the bootloader)\n",
+-                      val_txdly ? "Enabling" : "Disabling");
++                      str_enable_disable(val_txdly));
+       } else {
+               dev_dbg(dev,
+                       "2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n",
+-                      val_txdly ? "enabled" : "disabled");
++                      str_enabled_disabled(val_txdly));
+       }
+       ret = phy_modify_paged_changed(phydev, 0xd08, 0x15, RTL8211F_RX_DELAY,
+@@ -437,11 +438,11 @@ static int rtl8211f_config_init(struct p
+       } else if (ret) {
+               dev_dbg(dev,
+                       "%s 2ns RX delay (and changing the value from pin-strapping RXD0 or the bootloader)\n",
+-                      val_rxdly ? "Enabling" : "Disabling");
++                      str_enable_disable(val_rxdly));
+       } else {
+               dev_dbg(dev,
+                       "2ns RX delay was already %s (by pin-strapping RXD0 or bootloader configuration)\n",
+-                      val_rxdly ? "enabled" : "disabled");
++                      str_enabled_disabled(val_rxdly));
+       }
+       if (priv->has_phycr2) {
diff --git a/target/linux/generic/backport-6.12/781-15-v6.15-net-phy-realtek-improve-mmd-register-access-for-inte.patch b/target/linux/generic/backport-6.12/781-15-v6.15-net-phy-realtek-improve-mmd-register-access-for-inte.patch
new file mode 100644 (file)
index 0000000..3a3a20d
--- /dev/null
@@ -0,0 +1,134 @@
+From da681ed73fb980286fc29de707b35d76bb33e123 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Thu, 13 Feb 2025 20:18:17 +0100
+Subject: [PATCH] net: phy: realtek: improve mmd register access for internal
+ PHY's
+
+r8169 provides the MDIO bus for the internal PHY's. It has been extended
+with c45 access functions for addressing MDIO_MMD_VEND2 registers.
+So we can switch from paged access to directly addressing the
+MDIO_MMD_VEND2 registers.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/a5f2333c-dda9-48ad-9801-77049766e632@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 79 +++++++++++---------------
+ 1 file changed, 33 insertions(+), 46 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -735,29 +735,31 @@ static int rtlgen_read_status(struct phy
+       return 0;
+ }
++static int rtlgen_read_vend2(struct phy_device *phydev, int regnum)
++{
++      return __mdiobus_c45_read(phydev->mdio.bus, 0, MDIO_MMD_VEND2, regnum);
++}
++
++static int rtlgen_write_vend2(struct phy_device *phydev, int regnum, u16 val)
++{
++      return __mdiobus_c45_write(phydev->mdio.bus, 0, MDIO_MMD_VEND2, regnum,
++                                 val);
++}
++
+ static int rtlgen_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
+ {
+       int ret;
+-      if (devnum == MDIO_MMD_VEND2) {
+-              rtl821x_write_page(phydev, regnum >> 4);
+-              ret = __phy_read(phydev, 0x10 + ((regnum & 0xf) >> 1));
+-              rtl821x_write_page(phydev, 0);
+-      } else if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE) {
+-              rtl821x_write_page(phydev, 0xa5c);
+-              ret = __phy_read(phydev, 0x12);
+-              rtl821x_write_page(phydev, 0);
+-      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) {
+-              rtl821x_write_page(phydev, 0xa5d);
+-              ret = __phy_read(phydev, 0x10);
+-              rtl821x_write_page(phydev, 0);
+-      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE) {
+-              rtl821x_write_page(phydev, 0xa5d);
+-              ret = __phy_read(phydev, 0x11);
+-              rtl821x_write_page(phydev, 0);
+-      } else {
++      if (devnum == MDIO_MMD_VEND2)
++              ret = rtlgen_read_vend2(phydev, regnum);
++      else if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE)
++              ret = rtlgen_read_vend2(phydev, 0xa5c4);
++      else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV)
++              ret = rtlgen_read_vend2(phydev, 0xa5d0);
++      else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE)
++              ret = rtlgen_read_vend2(phydev, 0xa5d2);
++      else
+               ret = -EOPNOTSUPP;
+-      }
+       return ret;
+ }
+@@ -767,17 +769,12 @@ static int rtlgen_write_mmd(struct phy_d
+ {
+       int ret;
+-      if (devnum == MDIO_MMD_VEND2) {
+-              rtl821x_write_page(phydev, regnum >> 4);
+-              ret = __phy_write(phydev, 0x10 + ((regnum & 0xf) >> 1), val);
+-              rtl821x_write_page(phydev, 0);
+-      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) {
+-              rtl821x_write_page(phydev, 0xa5d);
+-              ret = __phy_write(phydev, 0x10, val);
+-              rtl821x_write_page(phydev, 0);
+-      } else {
++      if (devnum == MDIO_MMD_VEND2)
++              ret = rtlgen_write_vend2(phydev, regnum, val);
++      else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV)
++              ret = rtlgen_write_vend2(phydev, regnum, 0xa5d0);
++      else
+               ret = -EOPNOTSUPP;
+-      }
+       return ret;
+ }
+@@ -789,19 +786,12 @@ static int rtl822x_read_mmd(struct phy_d
+       if (ret != -EOPNOTSUPP)
+               return ret;
+-      if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE2) {
+-              rtl821x_write_page(phydev, 0xa6e);
+-              ret = __phy_read(phydev, 0x16);
+-              rtl821x_write_page(phydev, 0);
+-      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2) {
+-              rtl821x_write_page(phydev, 0xa6d);
+-              ret = __phy_read(phydev, 0x12);
+-              rtl821x_write_page(phydev, 0);
+-      } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE2) {
+-              rtl821x_write_page(phydev, 0xa6d);
+-              ret = __phy_read(phydev, 0x10);
+-              rtl821x_write_page(phydev, 0);
+-      }
++      if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE2)
++              ret = rtlgen_read_vend2(phydev, 0xa6ec);
++      else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2)
++              ret = rtlgen_read_vend2(phydev, 0xa6d4);
++      else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE2)
++              ret = rtlgen_read_vend2(phydev, 0xa6d0);
+       return ret;
+ }
+@@ -814,11 +804,8 @@ static int rtl822x_write_mmd(struct phy_
+       if (ret != -EOPNOTSUPP)
+               return ret;
+-      if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2) {
+-              rtl821x_write_page(phydev, 0xa6d);
+-              ret = __phy_write(phydev, 0x12, val);
+-              rtl821x_write_page(phydev, 0);
+-      }
++      if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2)
++              ret = rtlgen_write_vend2(phydev, 0xa6d4, val);
+       return ret;
+ }
diff --git a/target/linux/generic/backport-6.12/781-16-v6.15-net-phy-realtek-switch-from-paged-to-MMD-ops-in-rtl8.patch b/target/linux/generic/backport-6.12/781-16-v6.15-net-phy-realtek-switch-from-paged-to-MMD-ops-in-rtl8.patch
new file mode 100644 (file)
index 0000000..5e3c3ce
--- /dev/null
@@ -0,0 +1,52 @@
+From 02d3b306ac2f0b174753d1c5b9e4e5fb8ec5057e Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Thu, 13 Feb 2025 20:19:14 +0100
+Subject: [PATCH] net: phy: realtek: switch from paged to MMD ops in rtl822x
+ functions
+
+The MDIO bus provided by r8169 for the internal PHY's now supports
+c45 ops for the MDIO_MMD_VEND2 device. So we can switch to standard
+MMD ops here.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/81416f95-0fac-4225-87b4-828e3738b8ed@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 11 +++++------
+ 1 file changed, 5 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -901,7 +901,7 @@ static int rtl822x_get_features(struct p
+ {
+       int val;
+-      val = phy_read_paged(phydev, 0xa61, 0x13);
++      val = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xa616);
+       if (val < 0)
+               return val;
+@@ -922,10 +922,9 @@ static int rtl822x_config_aneg(struct ph
+       if (phydev->autoneg == AUTONEG_ENABLE) {
+               u16 adv = linkmode_adv_to_mii_10gbt_adv_t(phydev->advertising);
+-              ret = phy_modify_paged_changed(phydev, 0xa5d, 0x12,
+-                                             MDIO_AN_10GBT_CTRL_ADV2_5G |
+-                                             MDIO_AN_10GBT_CTRL_ADV5G,
+-                                             adv);
++              ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, 0xa5d4,
++                                           MDIO_AN_10GBT_CTRL_ADV2_5G |
++                                           MDIO_AN_10GBT_CTRL_ADV5G, adv);
+               if (ret < 0)
+                       return ret;
+       }
+@@ -969,7 +968,7 @@ static int rtl822x_read_status(struct ph
+           !phydev->autoneg_complete)
+               return 0;
+-      lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
++      lpadv = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xa5d6);
+       if (lpadv < 0)
+               return lpadv;
diff --git a/target/linux/generic/backport-6.12/781-17-v6.15-net-phy-realtek-add-helper-RTL822X_VND2_C22_REG.patch b/target/linux/generic/backport-6.12/781-17-v6.15-net-phy-realtek-add-helper-RTL822X_VND2_C22_REG.patch
new file mode 100644 (file)
index 0000000..5134d08
--- /dev/null
@@ -0,0 +1,48 @@
+From 8af2136e77989a64fae0284bf76fd584e32edd3a Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Fri, 14 Feb 2025 21:31:14 +0100
+Subject: [PATCH] net: phy: realtek: add helper RTL822X_VND2_C22_REG
+
+C22 register space is mapped to 0xa400 in MMD VEND2 register space.
+Add a helper to access mapped C22 registers.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/6344277b-c5c7-449b-ac89-d5425306ca76@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 9 ++++-----
+ 1 file changed, 4 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -79,9 +79,7 @@
+ /* RTL822X_VND2_XXXXX registers are only accessible when phydev->is_c45
+  * is set, they cannot be accessed by C45-over-C22.
+  */
+-#define RTL822X_VND2_GBCR                             0xa412
+-
+-#define RTL822X_VND2_GANLPAR                          0xa414
++#define RTL822X_VND2_C22_REG(reg)             (0xa400 + 2 * (reg))
+ #define RTL8366RB_POWER_SAVE                  0x15
+ #define RTL8366RB_POWER_SAVE_ON                       BIT(12)
+@@ -1015,7 +1013,8 @@ static int rtl822x_c45_config_aneg(struc
+       val = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
+       /* Vendor register as C45 has no standardized support for 1000BaseT */
+-      ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, RTL822X_VND2_GBCR,
++      ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2,
++                                   RTL822X_VND2_C22_REG(MII_CTRL1000),
+                                    ADVERTISE_1000FULL, val);
+       if (ret < 0)
+               return ret;
+@@ -1032,7 +1031,7 @@ static int rtl822x_c45_read_status(struc
+       /* Vendor register as C45 has no standardized support for 1000BaseT */
+       if (phydev->autoneg == AUTONEG_ENABLE && genphy_c45_aneg_done(phydev)) {
+               val = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+-                                 RTL822X_VND2_GANLPAR);
++                                 RTL822X_VND2_C22_REG(MII_STAT1000));
+               if (val < 0)
+                       return val;
+       } else {
diff --git a/target/linux/generic/backport-6.12/781-18-v6.15-net-phy-realtek-add-defines-for-shadowed-c45-standar.patch b/target/linux/generic/backport-6.12/781-18-v6.15-net-phy-realtek-add-defines-for-shadowed-c45-standar.patch
new file mode 100644 (file)
index 0000000..ff7d5b1
--- /dev/null
@@ -0,0 +1,113 @@
+From fabcfd6d10999024a721ae1b965b57eb8a305ace Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Sat, 15 Feb 2025 14:29:15 +0100
+Subject: [PATCH] net: phy: realtek: add defines for shadowed c45 standard
+ registers
+
+Realtek shadows standard c45 registers in VEND2 device register space.
+Add defines for these VEND2 registers, based on the names of the
+standard c45 registers.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/c90bdf76-f8b8-4d06-9656-7a52d5658ee6@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 33 +++++++++++++++++---------
+ 1 file changed, 22 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -94,6 +94,16 @@
+ #define RTL_VND2_PHYSR_MASTER                 BIT(11)
+ #define RTL_VND2_PHYSR_SPEED_MASK             (RTL_VND2_PHYSR_SPEEDL | RTL_VND2_PHYSR_SPEEDH)
++#define       RTL_MDIO_PCS_EEE_ABLE                   0xa5c4
++#define       RTL_MDIO_AN_EEE_ADV                     0xa5d0
++#define       RTL_MDIO_AN_EEE_LPABLE                  0xa5d2
++#define       RTL_MDIO_AN_10GBT_CTRL                  0xa5d4
++#define       RTL_MDIO_AN_10GBT_STAT                  0xa5d6
++#define       RTL_MDIO_PMA_SPEED                      0xa616
++#define       RTL_MDIO_AN_EEE_LPABLE2                 0xa6d0
++#define       RTL_MDIO_AN_EEE_ADV2                    0xa6d4
++#define       RTL_MDIO_PCS_EEE_ABLE2                  0xa6ec
++
+ #define RTL_GENERIC_PHYID                     0x001cc800
+ #define RTL_8211FVD_PHYID                     0x001cc878
+ #define RTL_8221B                             0x001cc840
+@@ -751,11 +761,11 @@ static int rtlgen_read_mmd(struct phy_de
+       if (devnum == MDIO_MMD_VEND2)
+               ret = rtlgen_read_vend2(phydev, regnum);
+       else if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE)
+-              ret = rtlgen_read_vend2(phydev, 0xa5c4);
++              ret = rtlgen_read_vend2(phydev, RTL_MDIO_PCS_EEE_ABLE);
+       else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV)
+-              ret = rtlgen_read_vend2(phydev, 0xa5d0);
++              ret = rtlgen_read_vend2(phydev, RTL_MDIO_AN_EEE_ADV);
+       else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE)
+-              ret = rtlgen_read_vend2(phydev, 0xa5d2);
++              ret = rtlgen_read_vend2(phydev, RTL_MDIO_AN_EEE_LPABLE);
+       else
+               ret = -EOPNOTSUPP;
+@@ -770,7 +780,7 @@ static int rtlgen_write_mmd(struct phy_d
+       if (devnum == MDIO_MMD_VEND2)
+               ret = rtlgen_write_vend2(phydev, regnum, val);
+       else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV)
+-              ret = rtlgen_write_vend2(phydev, regnum, 0xa5d0);
++              ret = rtlgen_write_vend2(phydev, regnum, RTL_MDIO_AN_EEE_ADV);
+       else
+               ret = -EOPNOTSUPP;
+@@ -785,11 +795,11 @@ static int rtl822x_read_mmd(struct phy_d
+               return ret;
+       if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE2)
+-              ret = rtlgen_read_vend2(phydev, 0xa6ec);
++              ret = rtlgen_read_vend2(phydev, RTL_MDIO_PCS_EEE_ABLE2);
+       else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2)
+-              ret = rtlgen_read_vend2(phydev, 0xa6d4);
++              ret = rtlgen_read_vend2(phydev, RTL_MDIO_AN_EEE_ADV2);
+       else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE2)
+-              ret = rtlgen_read_vend2(phydev, 0xa6d0);
++              ret = rtlgen_read_vend2(phydev, RTL_MDIO_AN_EEE_LPABLE2);
+       return ret;
+ }
+@@ -803,7 +813,7 @@ static int rtl822x_write_mmd(struct phy_
+               return ret;
+       if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2)
+-              ret = rtlgen_write_vend2(phydev, 0xa6d4, val);
++              ret = rtlgen_write_vend2(phydev, RTL_MDIO_AN_EEE_ADV2, val);
+       return ret;
+ }
+@@ -899,7 +909,7 @@ static int rtl822x_get_features(struct p
+ {
+       int val;
+-      val = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xa616);
++      val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL_MDIO_PMA_SPEED);
+       if (val < 0)
+               return val;
+@@ -920,7 +930,8 @@ static int rtl822x_config_aneg(struct ph
+       if (phydev->autoneg == AUTONEG_ENABLE) {
+               u16 adv = linkmode_adv_to_mii_10gbt_adv_t(phydev->advertising);
+-              ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, 0xa5d4,
++              ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2,
++                                           RTL_MDIO_AN_10GBT_CTRL,
+                                            MDIO_AN_10GBT_CTRL_ADV2_5G |
+                                            MDIO_AN_10GBT_CTRL_ADV5G, adv);
+               if (ret < 0)
+@@ -966,7 +977,7 @@ static int rtl822x_read_status(struct ph
+           !phydev->autoneg_complete)
+               return 0;
+-      lpadv = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xa5d6);
++      lpadv = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL_MDIO_AN_10GBT_STAT);
+       if (lpadv < 0)
+               return lpadv;
diff --git a/target/linux/generic/backport-6.12/781-19-v6.15-net-phy-realtek-disable-PHY-mode-EEE.patch b/target/linux/generic/backport-6.12/781-19-v6.15-net-phy-realtek-disable-PHY-mode-EEE.patch
new file mode 100644 (file)
index 0000000..4d755d4
--- /dev/null
@@ -0,0 +1,54 @@
+From bfc17c1658353f22843c7c13e27c2d31950f1887 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Sun, 16 Mar 2025 12:39:54 +0000
+Subject: [PATCH] net: phy: realtek: disable PHY-mode EEE
+
+Realtek RTL8211F has a "PHY-mode" EEE support which interferes with an
+IEEE 802.3 compliant implementation. This mode defaults to enabled, and
+results in the MAC receive path not seeing the link transition to LPI
+state.
+
+Fix this by disabling PHY-mode EEE.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/E1ttnHW-00785s-Uq@rmk-PC.armlinux.org.uk
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek/realtek_main.c | 11 +++++++++--
+ 1 file changed, 9 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -33,6 +33,9 @@
+ #define RTL8211F_PHYCR1                               0x18
+ #define RTL8211F_PHYCR2                               0x19
++#define RTL8211F_CLKOUT_EN                    BIT(0)
++#define RTL8211F_PHYCR2_PHY_EEE_ENABLE                BIT(5)
++
+ #define RTL8211F_INSR                         0x1d
+ #define RTL8211F_LEDCR                                0x10
+@@ -55,8 +58,6 @@
+ #define RTL8211E_TX_DELAY                     BIT(12)
+ #define RTL8211E_RX_DELAY                     BIT(11)
+-#define RTL8211F_CLKOUT_EN                    BIT(0)
+-
+ #define RTL8201F_ISR                          0x1e
+ #define RTL8201F_ISR_ANERR                    BIT(15)
+ #define RTL8201F_ISR_DUPLEX                   BIT(13)
+@@ -453,6 +454,12 @@ static int rtl8211f_config_init(struct p
+                       str_enabled_disabled(val_rxdly));
+       }
++      /* Disable PHY-mode EEE so LPI is passed to the MAC */
++      ret = phy_modify_paged(phydev, 0xa43, RTL8211F_PHYCR2,
++                             RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0);
++      if (ret)
++              return ret;
++
+       if (priv->has_phycr2) {
+               ret = phy_modify_paged(phydev, 0xa43, RTL8211F_PHYCR2,
+                                      RTL8211F_CLKOUT_EN, priv->phycr2);
diff --git a/target/linux/generic/backport-6.12/781-20-v6.16-net-phy-realtek-Add-support-for-WOL-magic-packet-on.patch b/target/linux/generic/backport-6.12/781-20-v6.16-net-phy-realtek-Add-support-for-WOL-magic-packet-on.patch
new file mode 100644 (file)
index 0000000..4ba06cc
--- /dev/null
@@ -0,0 +1,126 @@
+From 7840e4d6f48a75413470935ebdc4bab4fc0c035e Mon Sep 17 00:00:00 2001
+From: Daniel Braunwarth <daniel.braunwarth@kuka.com>
+Date: Tue, 29 Apr 2025 13:33:37 +0200
+Subject: [PATCH] net: phy: realtek: Add support for WOL magic packet on
+ RTL8211F
+
+The RTL8211F supports multiple WOL modes. This patch adds support for
+magic packets.
+
+The PHY notifies the system via the INTB/PMEB pin when a WOL event
+occurs.
+
+Signed-off-by: Daniel Braunwarth <daniel.braunwarth@kuka.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250429-realtek_wol-v2-1-8f84def1ef2c@kuka.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 69 ++++++++++++++++++++++++++
+ 1 file changed, 69 insertions(+)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -10,6 +10,7 @@
+ #include <linux/bitops.h>
+ #include <linux/of.h>
+ #include <linux/phy.h>
++#include <linux/netdevice.h>
+ #include <linux/module.h>
+ #include <linux/delay.h>
+ #include <linux/clk.h>
+@@ -38,6 +39,24 @@
+ #define RTL8211F_INSR                         0x1d
++/* RTL8211F WOL interrupt configuration */
++#define RTL8211F_INTBCR_PAGE                  0xd40
++#define RTL8211F_INTBCR                               0x16
++#define RTL8211F_INTBCR_INTB_PMEB             BIT(5)
++
++/* RTL8211F WOL settings */
++#define RTL8211F_WOL_SETTINGS_PAGE            0xd8a
++#define RTL8211F_WOL_SETTINGS_EVENTS          16
++#define RTL8211F_WOL_EVENT_MAGIC              BIT(12)
++#define RTL8211F_WOL_SETTINGS_STATUS          17
++#define RTL8211F_WOL_STATUS_RESET             (BIT(15) | 0x1fff)
++
++/* RTL8211F Unique phyiscal and multicast address (WOL) */
++#define RTL8211F_PHYSICAL_ADDR_PAGE           0xd8c
++#define RTL8211F_PHYSICAL_ADDR_WORD0          16
++#define RTL8211F_PHYSICAL_ADDR_WORD1          17
++#define RTL8211F_PHYSICAL_ADDR_WORD2          18
++
+ #define RTL8211F_LEDCR                                0x10
+ #define RTL8211F_LEDCR_MODE                   BIT(15)
+ #define RTL8211F_LEDCR_ACT_TXRX                       BIT(4)
+@@ -123,6 +142,7 @@ struct rtl821x_priv {
+       u16 phycr2;
+       bool has_phycr2;
+       struct clk *clk;
++      u32 saved_wolopts;
+ };
+ static int rtl821x_read_page(struct phy_device *phydev)
+@@ -354,6 +374,53 @@ static irqreturn_t rtl8211f_handle_inter
+       return IRQ_HANDLED;
+ }
++static void rtl8211f_get_wol(struct phy_device *dev, struct ethtool_wolinfo *wol)
++{
++      wol->supported = WAKE_MAGIC;
++      if (phy_read_paged(dev, RTL8211F_WOL_SETTINGS_PAGE, RTL8211F_WOL_SETTINGS_EVENTS)
++          & RTL8211F_WOL_EVENT_MAGIC)
++              wol->wolopts = WAKE_MAGIC;
++}
++
++static int rtl8211f_set_wol(struct phy_device *dev, struct ethtool_wolinfo *wol)
++{
++      const u8 *mac_addr = dev->attached_dev->dev_addr;
++      int oldpage;
++
++      oldpage = phy_save_page(dev);
++      if (oldpage < 0)
++              goto err;
++
++      if (wol->wolopts & WAKE_MAGIC) {
++              /* Store the device address for the magic packet */
++              rtl821x_write_page(dev, RTL8211F_PHYSICAL_ADDR_PAGE);
++              __phy_write(dev, RTL8211F_PHYSICAL_ADDR_WORD0, mac_addr[1] << 8 | (mac_addr[0]));
++              __phy_write(dev, RTL8211F_PHYSICAL_ADDR_WORD1, mac_addr[3] << 8 | (mac_addr[2]));
++              __phy_write(dev, RTL8211F_PHYSICAL_ADDR_WORD2, mac_addr[5] << 8 | (mac_addr[4]));
++
++              /* Enable magic packet matching and reset WOL status */
++              rtl821x_write_page(dev, RTL8211F_WOL_SETTINGS_PAGE);
++              __phy_write(dev, RTL8211F_WOL_SETTINGS_EVENTS, RTL8211F_WOL_EVENT_MAGIC);
++              __phy_write(dev, RTL8211F_WOL_SETTINGS_STATUS, RTL8211F_WOL_STATUS_RESET);
++
++              /* Enable the WOL interrupt */
++              rtl821x_write_page(dev, RTL8211F_INTBCR_PAGE);
++              __phy_set_bits(dev, RTL8211F_INTBCR, RTL8211F_INTBCR_INTB_PMEB);
++      } else {
++              /* Disable the WOL interrupt */
++              rtl821x_write_page(dev, RTL8211F_INTBCR_PAGE);
++              __phy_clear_bits(dev, RTL8211F_INTBCR, RTL8211F_INTBCR_INTB_PMEB);
++
++              /* Disable magic packet matching and reset WOL status */
++              rtl821x_write_page(dev, RTL8211F_WOL_SETTINGS_PAGE);
++              __phy_write(dev, RTL8211F_WOL_SETTINGS_EVENTS, 0);
++              __phy_write(dev, RTL8211F_WOL_SETTINGS_STATUS, RTL8211F_WOL_STATUS_RESET);
++      }
++
++err:
++      return phy_restore_page(dev, oldpage, 0);
++}
++
+ static int rtl8211_config_aneg(struct phy_device *phydev)
+ {
+       int ret;
+@@ -1400,6 +1467,8 @@ static struct phy_driver realtek_drvs[]
+               .read_status    = rtlgen_read_status,
+               .config_intr    = &rtl8211f_config_intr,
+               .handle_interrupt = rtl8211f_handle_interrupt,
++              .set_wol        = rtl8211f_set_wol,
++              .get_wol        = rtl8211f_get_wol,
+               .suspend        = rtl821x_suspend,
+               .resume         = rtl821x_resume,
+               .read_page      = rtl821x_read_page,
diff --git a/target/linux/generic/backport-6.12/781-21-v6.16-net-phy-realtek-remove-unsed-RTL821x_PHYSR-macros.patch b/target/linux/generic/backport-6.12/781-21-v6.16-net-phy-realtek-remove-unsed-RTL821x_PHYSR-macros.patch
new file mode 100644 (file)
index 0000000..e054937
--- /dev/null
@@ -0,0 +1,28 @@
+From f3b265358b911fe9e495619bdfa7797749474f95 Mon Sep 17 00:00:00 2001
+From: Michael Klein <michael@fossekall.de>
+Date: Sun, 4 May 2025 19:29:11 +0200
+Subject: [PATCH] net: phy: realtek: remove unsed RTL821x_PHYSR* macros
+
+These macros have there since the first revision but were never used, so
+let's just remove them.
+
+Signed-off-by: Michael Klein <michael@fossekall.de>
+Link: https://patch.msgid.link/20250504172916.243185-2-michael@fossekall.de
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek/realtek_main.c | 4 ----
+ 1 file changed, 4 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -18,10 +18,6 @@
+ #include "realtek.h"
+-#define RTL821x_PHYSR                         0x11
+-#define RTL821x_PHYSR_DUPLEX                  BIT(13)
+-#define RTL821x_PHYSR_SPEED                   GENMASK(15, 14)
+-
+ #define RTL821x_INER                          0x12
+ #define RTL8211B_INER_INIT                    0x6400
+ #define RTL8211E_INER_LINK_STATUS             BIT(10)
diff --git a/target/linux/generic/backport-6.12/781-22-v6.16-net-phy-realtek-Clean-up-RTL821x-ExtPage-access.patch b/target/linux/generic/backport-6.12/781-22-v6.16-net-phy-realtek-Clean-up-RTL821x-ExtPage-access.patch
new file mode 100644 (file)
index 0000000..62bbf32
--- /dev/null
@@ -0,0 +1,95 @@
+From 7c6fa3ffd2650347b1d37f028e232e53d617c1af Mon Sep 17 00:00:00 2001
+From: Michael Klein <michael@fossekall.de>
+Date: Sun, 4 May 2025 19:29:12 +0200
+Subject: [PATCH] net: phy: realtek: Clean up RTL821x ExtPage access
+
+Factor out RTL8211E extension page access code to
+rtl821x_modify_ext_page() and clean up rtl8211e_config_init()
+
+Signed-off-by: Michael Klein <michael@fossekall.de>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250504172916.243185-3-michael@fossekall.de
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek/realtek_main.c | 38 ++++++++++++++++----------
+ 1 file changed, 23 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -26,7 +26,9 @@
+ #define RTL821x_INSR                          0x13
+ #define RTL821x_EXT_PAGE_SELECT                       0x1e
++
+ #define RTL821x_PAGE_SELECT                   0x1f
++#define RTL821x_SET_EXT_PAGE                  0x07
+ #define RTL8211F_PHYCR1                               0x18
+ #define RTL8211F_PHYCR2                               0x19
+@@ -69,9 +71,12 @@
+ #define RTL8211F_ALDPS_ENABLE                 BIT(2)
+ #define RTL8211F_ALDPS_XTAL_OFF                       BIT(12)
++#define RTL8211E_RGMII_EXT_PAGE                       0xa4
++#define RTL8211E_RGMII_DELAY                  0x1c
+ #define RTL8211E_CTRL_DELAY                   BIT(13)
+ #define RTL8211E_TX_DELAY                     BIT(12)
+ #define RTL8211E_RX_DELAY                     BIT(11)
++#define RTL8211E_DELAY_MASK                   GENMASK(13, 11)
+ #define RTL8201F_ISR                          0x1e
+ #define RTL8201F_ISR_ANERR                    BIT(15)
+@@ -151,6 +156,21 @@ static int rtl821x_write_page(struct phy
+       return __phy_write(phydev, RTL821x_PAGE_SELECT, page);
+ }
++static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page,
++                                 u32 regnum, u16 mask, u16 set)
++{
++      int oldpage, ret = 0;
++
++      oldpage = phy_select_page(phydev, RTL821x_SET_EXT_PAGE);
++      if (oldpage >= 0) {
++              ret = __phy_write(phydev, RTL821x_EXT_PAGE_SELECT, ext_page);
++              if (ret == 0)
++                      ret = __phy_modify(phydev, regnum, mask, set);
++      }
++
++      return phy_restore_page(phydev, oldpage, ret);
++}
++
+ static int rtl821x_probe(struct phy_device *phydev)
+ {
+       struct device *dev = &phydev->mdio.dev;
+@@ -670,7 +690,6 @@ static int rtl8211f_led_hw_control_set(s
+ static int rtl8211e_config_init(struct phy_device *phydev)
+ {
+-      int ret = 0, oldpage;
+       u16 val;
+       /* enable TX/RX delay for rgmii-* modes, and disable them for rgmii. */
+@@ -700,20 +719,9 @@ static int rtl8211e_config_init(struct p
+        * 12 = RX Delay, 11 = TX Delay
+        * 10:0 = Test && debug settings reserved by realtek
+        */
+-      oldpage = phy_select_page(phydev, 0x7);
+-      if (oldpage < 0)
+-              goto err_restore_page;
+-
+-      ret = __phy_write(phydev, RTL821x_EXT_PAGE_SELECT, 0xa4);
+-      if (ret)
+-              goto err_restore_page;
+-
+-      ret = __phy_modify(phydev, 0x1c, RTL8211E_CTRL_DELAY
+-                         | RTL8211E_TX_DELAY | RTL8211E_RX_DELAY,
+-                         val);
+-
+-err_restore_page:
+-      return phy_restore_page(phydev, oldpage, ret);
++      return rtl821x_modify_ext_page(phydev, RTL8211E_RGMII_EXT_PAGE,
++                                     RTL8211E_RGMII_DELAY,
++                                     RTL8211E_DELAY_MASK, val);
+ }
+ static int rtl8211b_suspend(struct phy_device *phydev)
diff --git a/target/linux/generic/backport-6.12/781-23-v6.16-net-phy-realtek-add-RTL8211F-register-defines.patch b/target/linux/generic/backport-6.12/781-23-v6.16-net-phy-realtek-add-RTL8211F-register-defines.patch
new file mode 100644 (file)
index 0000000..9116992
--- /dev/null
@@ -0,0 +1,139 @@
+From 12d40df259e38851a0d973535e6023b33e2ea4f9 Mon Sep 17 00:00:00 2001
+From: Michael Klein <michael@fossekall.de>
+Date: Sun, 4 May 2025 19:29:13 +0200
+Subject: [PATCH] net: phy: realtek: add RTL8211F register defines
+
+Add some more defines for RTL8211F page and register numbers.
+
+Signed-off-by: Michael Klein <michael@fossekall.de>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250504172916.243185-4-michael@fossekall.de
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek/realtek_main.c | 34 ++++++++++++++++++--------
+ 1 file changed, 24 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -30,11 +30,14 @@
+ #define RTL821x_PAGE_SELECT                   0x1f
+ #define RTL821x_SET_EXT_PAGE                  0x07
++/* RTL8211F PHY configuration */
++#define RTL8211F_PHYCR_PAGE                   0xa43
+ #define RTL8211F_PHYCR1                               0x18
+ #define RTL8211F_PHYCR2                               0x19
+ #define RTL8211F_CLKOUT_EN                    BIT(0)
+ #define RTL8211F_PHYCR2_PHY_EEE_ENABLE                BIT(5)
++#define RTL8211F_INSR_PAGE                    0xa43
+ #define RTL8211F_INSR                         0x1d
+ /* RTL8211F WOL interrupt configuration */
+@@ -55,6 +58,8 @@
+ #define RTL8211F_PHYSICAL_ADDR_WORD1          17
+ #define RTL8211F_PHYSICAL_ADDR_WORD2          18
++/* RTL8211F LED configuration */
++#define RTL8211F_LEDCR_PAGE                   0xd04
+ #define RTL8211F_LEDCR                                0x10
+ #define RTL8211F_LEDCR_MODE                   BIT(15)
+ #define RTL8211F_LEDCR_ACT_TXRX                       BIT(4)
+@@ -64,7 +69,13 @@
+ #define RTL8211F_LEDCR_MASK                   GENMASK(4, 0)
+ #define RTL8211F_LEDCR_SHIFT                  5
++/* RTL8211F RGMII configuration */
++#define RTL8211F_RGMII_PAGE                   0xd08
++
++#define RTL8211F_TXCR                         0x11
+ #define RTL8211F_TX_DELAY                     BIT(8)
++
++#define RTL8211F_RXCR                         0x15
+ #define RTL8211F_RX_DELAY                     BIT(3)
+ #define RTL8211F_ALDPS_PLL_OFF                        BIT(1)
+@@ -187,7 +198,7 @@ static int rtl821x_probe(struct phy_devi
+               return dev_err_probe(dev, PTR_ERR(priv->clk),
+                                    "failed to get phy clock\n");
+-      ret = phy_read_paged(phydev, 0xa43, RTL8211F_PHYCR1);
++      ret = phy_read_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1);
+       if (ret < 0)
+               return ret;
+@@ -197,7 +208,7 @@ static int rtl821x_probe(struct phy_devi
+       priv->has_phycr2 = !(phy_id == RTL_8211FVD_PHYID);
+       if (priv->has_phycr2) {
+-              ret = phy_read_paged(phydev, 0xa43, RTL8211F_PHYCR2);
++              ret = phy_read_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2);
+               if (ret < 0)
+                       return ret;
+@@ -233,7 +244,7 @@ static int rtl8211f_ack_interrupt(struct
+ {
+       int err;
+-      err = phy_read_paged(phydev, 0xa43, RTL8211F_INSR);
++      err = phy_read_paged(phydev, RTL8211F_INSR_PAGE, RTL8211F_INSR);
+       return (err < 0) ? err : 0;
+ }
+@@ -376,7 +387,7 @@ static irqreturn_t rtl8211f_handle_inter
+ {
+       int irq_status;
+-      irq_status = phy_read_paged(phydev, 0xa43, RTL8211F_INSR);
++      irq_status = phy_read_paged(phydev, RTL8211F_INSR_PAGE, RTL8211F_INSR);
+       if (irq_status < 0) {
+               phy_error(phydev);
+               return IRQ_NONE;
+@@ -473,7 +484,7 @@ static int rtl8211f_config_init(struct p
+       u16 val_txdly, val_rxdly;
+       int ret;
+-      ret = phy_modify_paged_changed(phydev, 0xa43, RTL8211F_PHYCR1,
++      ret = phy_modify_paged_changed(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1,
+                                      RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF,
+                                      priv->phycr1);
+       if (ret < 0) {
+@@ -507,7 +518,8 @@ static int rtl8211f_config_init(struct p
+               return 0;
+       }
+-      ret = phy_modify_paged_changed(phydev, 0xd08, 0x11, RTL8211F_TX_DELAY,
++      ret = phy_modify_paged_changed(phydev, RTL8211F_RGMII_PAGE,
++                                     RTL8211F_TXCR, RTL8211F_TX_DELAY,
+                                      val_txdly);
+       if (ret < 0) {
+               dev_err(dev, "Failed to update the TX delay register\n");
+@@ -522,7 +534,8 @@ static int rtl8211f_config_init(struct p
+                       str_enabled_disabled(val_txdly));
+       }
+-      ret = phy_modify_paged_changed(phydev, 0xd08, 0x15, RTL8211F_RX_DELAY,
++      ret = phy_modify_paged_changed(phydev, RTL8211F_RGMII_PAGE,
++                                     RTL8211F_RXCR, RTL8211F_RX_DELAY,
+                                      val_rxdly);
+       if (ret < 0) {
+               dev_err(dev, "Failed to update the RX delay register\n");
+@@ -538,14 +551,15 @@ static int rtl8211f_config_init(struct p
+       }
+       /* Disable PHY-mode EEE so LPI is passed to the MAC */
+-      ret = phy_modify_paged(phydev, 0xa43, RTL8211F_PHYCR2,
++      ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2,
+                              RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0);
+       if (ret)
+               return ret;
+       if (priv->has_phycr2) {
+-              ret = phy_modify_paged(phydev, 0xa43, RTL8211F_PHYCR2,
+-                                     RTL8211F_CLKOUT_EN, priv->phycr2);
++              ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE,
++                                     RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN,
++                                     priv->phycr2);
+               if (ret < 0) {
+                       dev_err(dev, "clkout configuration failed: %pe\n",
+                               ERR_PTR(ret));
diff --git a/target/linux/generic/backport-6.12/781-24-v6.16-net-phy-realtek-Group-RTL82-macro-definitions.patch b/target/linux/generic/backport-6.12/781-24-v6.16-net-phy-realtek-Group-RTL82-macro-definitions.patch
new file mode 100644 (file)
index 0000000..0319a2b
--- /dev/null
@@ -0,0 +1,123 @@
+From 8c4d0172657c1f2d86b9c19172150abcd0e35c39 Mon Sep 17 00:00:00 2001
+From: Michael Klein <michael@fossekall.de>
+Date: Sun, 4 May 2025 19:29:14 +0200
+Subject: [PATCH] net: phy: realtek: Group RTL82* macro definitions
+
+Group macro definitions by PHY in lexicographic order. Within each PHY
+block, definitions are order by page number and then register number.
+
+Signed-off-by: Michael Klein <michael@fossekall.de>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250504172916.243185-5-michael@fossekall.de
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek/realtek_main.c | 72 +++++++++++++-------------
+ 1 file changed, 37 insertions(+), 35 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -18,6 +18,16 @@
+ #include "realtek.h"
++#define RTL8201F_IER                          0x13
++
++#define RTL8201F_ISR                          0x1e
++#define RTL8201F_ISR_ANERR                    BIT(15)
++#define RTL8201F_ISR_DUPLEX                   BIT(13)
++#define RTL8201F_ISR_LINK                     BIT(11)
++#define RTL8201F_ISR_MASK                     (RTL8201F_ISR_ANERR | \
++                                               RTL8201F_ISR_DUPLEX | \
++                                               RTL8201F_ISR_LINK)
++
+ #define RTL821x_INER                          0x12
+ #define RTL8211B_INER_INIT                    0x6400
+ #define RTL8211E_INER_LINK_STATUS             BIT(10)
+@@ -30,9 +40,21 @@
+ #define RTL821x_PAGE_SELECT                   0x1f
+ #define RTL821x_SET_EXT_PAGE                  0x07
++/* RTL8211E extension page 164/0xa4 */
++#define RTL8211E_RGMII_EXT_PAGE                       0xa4
++#define RTL8211E_RGMII_DELAY                  0x1c
++#define RTL8211E_CTRL_DELAY                   BIT(13)
++#define RTL8211E_TX_DELAY                     BIT(12)
++#define RTL8211E_RX_DELAY                     BIT(11)
++#define RTL8211E_DELAY_MASK                   GENMASK(13, 11)
++
+ /* RTL8211F PHY configuration */
+ #define RTL8211F_PHYCR_PAGE                   0xa43
+ #define RTL8211F_PHYCR1                               0x18
++#define RTL8211F_ALDPS_PLL_OFF                        BIT(1)
++#define RTL8211F_ALDPS_ENABLE                 BIT(2)
++#define RTL8211F_ALDPS_XTAL_OFF                       BIT(12)
++
+ #define RTL8211F_PHYCR2                               0x19
+ #define RTL8211F_CLKOUT_EN                    BIT(0)
+ #define RTL8211F_PHYCR2_PHY_EEE_ENABLE                BIT(5)
+@@ -40,24 +62,6 @@
+ #define RTL8211F_INSR_PAGE                    0xa43
+ #define RTL8211F_INSR                         0x1d
+-/* RTL8211F WOL interrupt configuration */
+-#define RTL8211F_INTBCR_PAGE                  0xd40
+-#define RTL8211F_INTBCR                               0x16
+-#define RTL8211F_INTBCR_INTB_PMEB             BIT(5)
+-
+-/* RTL8211F WOL settings */
+-#define RTL8211F_WOL_SETTINGS_PAGE            0xd8a
+-#define RTL8211F_WOL_SETTINGS_EVENTS          16
+-#define RTL8211F_WOL_EVENT_MAGIC              BIT(12)
+-#define RTL8211F_WOL_SETTINGS_STATUS          17
+-#define RTL8211F_WOL_STATUS_RESET             (BIT(15) | 0x1fff)
+-
+-/* RTL8211F Unique phyiscal and multicast address (WOL) */
+-#define RTL8211F_PHYSICAL_ADDR_PAGE           0xd8c
+-#define RTL8211F_PHYSICAL_ADDR_WORD0          16
+-#define RTL8211F_PHYSICAL_ADDR_WORD1          17
+-#define RTL8211F_PHYSICAL_ADDR_WORD2          18
+-
+ /* RTL8211F LED configuration */
+ #define RTL8211F_LEDCR_PAGE                   0xd04
+ #define RTL8211F_LEDCR                                0x10
+@@ -78,25 +82,23 @@
+ #define RTL8211F_RXCR                         0x15
+ #define RTL8211F_RX_DELAY                     BIT(3)
+-#define RTL8211F_ALDPS_PLL_OFF                        BIT(1)
+-#define RTL8211F_ALDPS_ENABLE                 BIT(2)
+-#define RTL8211F_ALDPS_XTAL_OFF                       BIT(12)
++/* RTL8211F WOL interrupt configuration */
++#define RTL8211F_INTBCR_PAGE                  0xd40
++#define RTL8211F_INTBCR                               0x16
++#define RTL8211F_INTBCR_INTB_PMEB             BIT(5)
+-#define RTL8211E_RGMII_EXT_PAGE                       0xa4
+-#define RTL8211E_RGMII_DELAY                  0x1c
+-#define RTL8211E_CTRL_DELAY                   BIT(13)
+-#define RTL8211E_TX_DELAY                     BIT(12)
+-#define RTL8211E_RX_DELAY                     BIT(11)
+-#define RTL8211E_DELAY_MASK                   GENMASK(13, 11)
++/* RTL8211F WOL settings */
++#define RTL8211F_WOL_SETTINGS_PAGE            0xd8a
++#define RTL8211F_WOL_SETTINGS_EVENTS          16
++#define RTL8211F_WOL_EVENT_MAGIC              BIT(12)
++#define RTL8211F_WOL_SETTINGS_STATUS          17
++#define RTL8211F_WOL_STATUS_RESET             (BIT(15) | 0x1fff)
+-#define RTL8201F_ISR                          0x1e
+-#define RTL8201F_ISR_ANERR                    BIT(15)
+-#define RTL8201F_ISR_DUPLEX                   BIT(13)
+-#define RTL8201F_ISR_LINK                     BIT(11)
+-#define RTL8201F_ISR_MASK                     (RTL8201F_ISR_ANERR | \
+-                                               RTL8201F_ISR_DUPLEX | \
+-                                               RTL8201F_ISR_LINK)
+-#define RTL8201F_IER                          0x13
++/* RTL8211F Unique phyiscal and multicast address (WOL) */
++#define RTL8211F_PHYSICAL_ADDR_PAGE           0xd8c
++#define RTL8211F_PHYSICAL_ADDR_WORD0          16
++#define RTL8211F_PHYSICAL_ADDR_WORD1          17
++#define RTL8211F_PHYSICAL_ADDR_WORD2          18
+ #define RTL822X_VND1_SERDES_OPTION                    0x697a
+ #define RTL822X_VND1_SERDES_OPTION_MODE_MASK          GENMASK(5, 0)
diff --git a/target/linux/generic/backport-6.12/781-25-v6.16-net-phy-realtek-use-__set_bit-in-rtl8211f_led_hw_con.patch b/target/linux/generic/backport-6.12/781-25-v6.16-net-phy-realtek-use-__set_bit-in-rtl8211f_led_hw_con.patch
new file mode 100644 (file)
index 0000000..a9e3f90
--- /dev/null
@@ -0,0 +1,42 @@
+From be1cc96ddf82bb0c0a159751f73239d6d3e9594a Mon Sep 17 00:00:00 2001
+From: Michael Klein <michael@fossekall.de>
+Date: Sun, 4 May 2025 19:29:15 +0200
+Subject: [PATCH] net: phy: realtek: use __set_bit() in
+ rtl8211f_led_hw_control_get()
+
+rtl8211f_led_hw_control_get() does not need atomic bit operations,
+replace set_bit() by __set_bit().
+
+Signed-off-by: Michael Klein <michael@fossekall.de>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250504172916.243185-6-michael@fossekall.de
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek/realtek_main.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -659,17 +659,17 @@ static int rtl8211f_led_hw_control_get(s
+       val &= RTL8211F_LEDCR_MASK;
+       if (val & RTL8211F_LEDCR_LINK_10)
+-              set_bit(TRIGGER_NETDEV_LINK_10, rules);
++              __set_bit(TRIGGER_NETDEV_LINK_10, rules);
+       if (val & RTL8211F_LEDCR_LINK_100)
+-              set_bit(TRIGGER_NETDEV_LINK_100, rules);
++              __set_bit(TRIGGER_NETDEV_LINK_100, rules);
+       if (val & RTL8211F_LEDCR_LINK_1000)
+-              set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++              __set_bit(TRIGGER_NETDEV_LINK_1000, rules);
+       if (val & RTL8211F_LEDCR_ACT_TXRX) {
+-              set_bit(TRIGGER_NETDEV_RX, rules);
+-              set_bit(TRIGGER_NETDEV_TX, rules);
++              __set_bit(TRIGGER_NETDEV_RX, rules);
++              __set_bit(TRIGGER_NETDEV_TX, rules);
+       }
+       return 0;
diff --git a/target/linux/generic/backport-6.12/781-26-v6.16-net-phy-realtek-Add-support-for-PHY-LEDs-on-RTL8211E.patch b/target/linux/generic/backport-6.12/781-26-v6.16-net-phy-realtek-Add-support-for-PHY-LEDs-on-RTL8211E.patch
new file mode 100644 (file)
index 0000000..a4b6450
--- /dev/null
@@ -0,0 +1,215 @@
+From 708686132ba02659267c0cebcc414348ece389a5 Mon Sep 17 00:00:00 2001
+From: Michael Klein <michael@fossekall.de>
+Date: Sun, 4 May 2025 19:29:16 +0200
+Subject: [PATCH] net: phy: realtek: Add support for PHY LEDs on RTL8211E
+
+Like the RTL8211F, the RTL8211E PHY supports up to three LEDs.
+Add netdev trigger support for them, too.
+
+Signed-off-by: Michael Klein <michael@fossekall.de>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250504172916.243185-7-michael@fossekall.de
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek/realtek_main.c | 125 +++++++++++++++++++++++--
+ 1 file changed, 119 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -40,6 +40,20 @@
+ #define RTL821x_PAGE_SELECT                   0x1f
+ #define RTL821x_SET_EXT_PAGE                  0x07
++/* RTL8211E extension page 44/0x2c */
++#define RTL8211E_LEDCR_EXT_PAGE                       0x2c
++#define RTL8211E_LEDCR1                               0x1a
++#define RTL8211E_LEDCR1_ACT_TXRX              BIT(4)
++#define RTL8211E_LEDCR1_MASK                  BIT(4)
++#define RTL8211E_LEDCR1_SHIFT                 1
++
++#define RTL8211E_LEDCR2                               0x1c
++#define RTL8211E_LEDCR2_LINK_1000             BIT(2)
++#define RTL8211E_LEDCR2_LINK_100              BIT(1)
++#define RTL8211E_LEDCR2_LINK_10                       BIT(0)
++#define RTL8211E_LEDCR2_MASK                  GENMASK(2, 0)
++#define RTL8211E_LEDCR2_SHIFT                 4
++
+ /* RTL8211E extension page 164/0xa4 */
+ #define RTL8211E_RGMII_EXT_PAGE                       0xa4
+ #define RTL8211E_RGMII_DELAY                  0x1c
+@@ -145,7 +159,8 @@
+ #define RTL_8221B_VN_CG                               0x001cc84a
+ #define RTL_8251B                             0x001cc862
+-#define RTL8211F_LED_COUNT                    3
++/* RTL8211E and RTL8211F support up to three LEDs */
++#define RTL8211x_LED_COUNT                    3
+ MODULE_DESCRIPTION("Realtek PHY driver");
+ MODULE_AUTHOR("Johnson Leung");
+@@ -169,6 +184,21 @@ static int rtl821x_write_page(struct phy
+       return __phy_write(phydev, RTL821x_PAGE_SELECT, page);
+ }
++static int rtl821x_read_ext_page(struct phy_device *phydev, u16 ext_page,
++                               u32 regnum)
++{
++      int oldpage, ret = 0;
++
++      oldpage = phy_select_page(phydev, RTL821x_SET_EXT_PAGE);
++      if (oldpage >= 0) {
++              ret = __phy_write(phydev, RTL821x_EXT_PAGE_SELECT, ext_page);
++              if (ret == 0)
++                      ret = __phy_read(phydev, regnum);
++      }
++
++      return phy_restore_page(phydev, oldpage, ret);
++}
++
+ static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page,
+                                  u32 regnum, u16 mask, u16 set)
+ {
+@@ -608,7 +638,7 @@ static int rtl821x_resume(struct phy_dev
+       return 0;
+ }
+-static int rtl8211f_led_hw_is_supported(struct phy_device *phydev, u8 index,
++static int rtl8211x_led_hw_is_supported(struct phy_device *phydev, u8 index,
+                                       unsigned long rules)
+ {
+       const unsigned long mask = BIT(TRIGGER_NETDEV_LINK_10) |
+@@ -627,9 +657,11 @@ static int rtl8211f_led_hw_is_supported(
+        *      rates and Active indication always at all three 10+100+1000
+        *      link rates.
+        * This code currently uses mode B only.
++       *
++       * RTL8211E PHY LED has one mode, which works like RTL8211F mode B.
+        */
+-      if (index >= RTL8211F_LED_COUNT)
++      if (index >= RTL8211x_LED_COUNT)
+               return -EINVAL;
+       /* Filter out any other unsupported triggers. */
+@@ -648,7 +680,7 @@ static int rtl8211f_led_hw_control_get(s
+ {
+       int val;
+-      if (index >= RTL8211F_LED_COUNT)
++      if (index >= RTL8211x_LED_COUNT)
+               return -EINVAL;
+       val = phy_read_paged(phydev, 0xd04, RTL8211F_LEDCR);
+@@ -681,7 +713,7 @@ static int rtl8211f_led_hw_control_set(s
+       const u16 mask = RTL8211F_LEDCR_MASK << (RTL8211F_LEDCR_SHIFT * index);
+       u16 reg = 0;
+-      if (index >= RTL8211F_LED_COUNT)
++      if (index >= RTL8211x_LED_COUNT)
+               return -EINVAL;
+       if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
+@@ -704,6 +736,84 @@ static int rtl8211f_led_hw_control_set(s
+       return phy_modify_paged(phydev, 0xd04, RTL8211F_LEDCR, mask, reg);
+ }
++static int rtl8211e_led_hw_control_get(struct phy_device *phydev, u8 index,
++                                     unsigned long *rules)
++{
++      int ret;
++      u16 cr1, cr2;
++
++      if (index >= RTL8211x_LED_COUNT)
++              return -EINVAL;
++
++      ret = rtl821x_read_ext_page(phydev, RTL8211E_LEDCR_EXT_PAGE,
++                                  RTL8211E_LEDCR1);
++      if (ret < 0)
++              return ret;
++
++      cr1 = ret >> RTL8211E_LEDCR1_SHIFT * index;
++      if (cr1 & RTL8211E_LEDCR1_ACT_TXRX) {
++              __set_bit(TRIGGER_NETDEV_RX, rules);
++              __set_bit(TRIGGER_NETDEV_TX, rules);
++      }
++
++      ret = rtl821x_read_ext_page(phydev, RTL8211E_LEDCR_EXT_PAGE,
++                                  RTL8211E_LEDCR2);
++      if (ret < 0)
++              return ret;
++
++      cr2 = ret >> RTL8211E_LEDCR2_SHIFT * index;
++      if (cr2 & RTL8211E_LEDCR2_LINK_10)
++              __set_bit(TRIGGER_NETDEV_LINK_10, rules);
++
++      if (cr2 & RTL8211E_LEDCR2_LINK_100)
++              __set_bit(TRIGGER_NETDEV_LINK_100, rules);
++
++      if (cr2 & RTL8211E_LEDCR2_LINK_1000)
++              __set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++
++      return ret;
++}
++
++static int rtl8211e_led_hw_control_set(struct phy_device *phydev, u8 index,
++                                     unsigned long rules)
++{
++      const u16 cr1mask =
++              RTL8211E_LEDCR1_MASK << (RTL8211E_LEDCR1_SHIFT * index);
++      const u16 cr2mask =
++              RTL8211E_LEDCR2_MASK << (RTL8211E_LEDCR2_SHIFT * index);
++      u16 cr1 = 0, cr2 = 0;
++      int ret;
++
++      if (index >= RTL8211x_LED_COUNT)
++              return -EINVAL;
++
++      if (test_bit(TRIGGER_NETDEV_RX, &rules) ||
++          test_bit(TRIGGER_NETDEV_TX, &rules)) {
++              cr1 |= RTL8211E_LEDCR1_ACT_TXRX;
++      }
++
++      cr1 <<= RTL8211E_LEDCR1_SHIFT * index;
++      ret = rtl821x_modify_ext_page(phydev, RTL8211E_LEDCR_EXT_PAGE,
++                                    RTL8211E_LEDCR1, cr1mask, cr1);
++      if (ret < 0)
++              return ret;
++
++      if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
++              cr2 |= RTL8211E_LEDCR2_LINK_10;
++
++      if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++              cr2 |= RTL8211E_LEDCR2_LINK_100;
++
++      if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++              cr2 |= RTL8211E_LEDCR2_LINK_1000;
++
++      cr2 <<= RTL8211E_LEDCR2_SHIFT * index;
++      ret = rtl821x_modify_ext_page(phydev, RTL8211E_LEDCR_EXT_PAGE,
++                                    RTL8211E_LEDCR2, cr2mask, cr2);
++
++      return ret;
++}
++
+ static int rtl8211e_config_init(struct phy_device *phydev)
+ {
+       u16 val;
+@@ -1479,6 +1589,9 @@ static struct phy_driver realtek_drvs[]
+               .resume         = genphy_resume,
+               .read_page      = rtl821x_read_page,
+               .write_page     = rtl821x_write_page,
++              .led_hw_is_supported = rtl8211x_led_hw_is_supported,
++              .led_hw_control_get = rtl8211e_led_hw_control_get,
++              .led_hw_control_set = rtl8211e_led_hw_control_set,
+       }, {
+               PHY_ID_MATCH_EXACT(0x001cc916),
+               .name           = "RTL8211F Gigabit Ethernet",
+@@ -1494,7 +1607,7 @@ static struct phy_driver realtek_drvs[]
+               .read_page      = rtl821x_read_page,
+               .write_page     = rtl821x_write_page,
+               .flags          = PHY_ALWAYS_CALL_SUSPEND,
+-              .led_hw_is_supported = rtl8211f_led_hw_is_supported,
++              .led_hw_is_supported = rtl8211x_led_hw_is_supported,
+               .led_hw_control_get = rtl8211f_led_hw_control_get,
+               .led_hw_control_set = rtl8211f_led_hw_control_set,
+       }, {
diff --git a/target/linux/generic/backport-6.12/781-27-v6.16-net-phy-realtek-add-RTL8127-internal-PHY.patch b/target/linux/generic/backport-6.12/781-27-v6.16-net-phy-realtek-add-RTL8127-internal-PHY.patch
new file mode 100644 (file)
index 0000000..04c631d
--- /dev/null
@@ -0,0 +1,35 @@
+From 83d9623161283f5f6883ee3fdb88ef2177b8a5f1 Mon Sep 17 00:00:00 2001
+From: ChunHao Lin <hau@realtek.com>
+Date: Fri, 16 May 2025 13:56:22 +0800
+Subject: [PATCH] net: phy: realtek: add RTL8127-internal PHY
+
+RTL8127-internal PHY is RTL8261C which is a integrated 10Gbps PHY with ID
+0x001cc890. It follows the code path of RTL8125/RTL8126 internal NBase-T
+PHY.
+
+Signed-off-by: ChunHao Lin <hau@realtek.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250516055622.3772-1-hau@realtek.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -158,6 +158,7 @@
+ #define RTL_8221B_VB_CG                               0x001cc849
+ #define RTL_8221B_VN_CG                               0x001cc84a
+ #define RTL_8251B                             0x001cc862
++#define RTL_8261C                             0x001cc890
+ /* RTL8211E and RTL8211F support up to three LEDs */
+ #define RTL8211x_LED_COUNT                    3
+@@ -1370,6 +1371,7 @@ static int rtl_internal_nbaset_match_phy
+       case RTL_GENERIC_PHYID:
+       case RTL_8221B:
+       case RTL_8251B:
++      case RTL_8261C:
+       case 0x001cc841:
+               break;
+       default:
diff --git a/target/linux/generic/backport-6.12/781-28-v6.18-net-phy-realtek-convert-RTL8226-CG-to-c45-only.patch b/target/linux/generic/backport-6.12/781-28-v6.18-net-phy-realtek-convert-RTL8226-CG-to-c45-only.patch
new file mode 100644 (file)
index 0000000..43f2c7b
--- /dev/null
@@ -0,0 +1,169 @@
+From 34167f1a024d2c5abae0b0325a6d0b8257160f86 Mon Sep 17 00:00:00 2001
+From: Markus Stockhausen <markus.stockhausen@gmx.de>
+Date: Wed, 13 Aug 2025 01:44:07 -0400
+Subject: net: phy: realtek: convert RTL8226-CG to c45 only
+
+Short: Convert the RTL8226-CG to c45 so it can be used in its
+Realtek based ecosystems.
+
+Long: The RTL8226-CG can be mainly found on devices of the
+Realtek Otto switch platform. Devices like the Zyxel XGS1210-12
+are based on it. These implement a hardware based phy polling
+in the background to update SoC status registers.
+
+The hardware provides 4 smi busses where phys are attached to.
+For each bus one can decide if it is polled in c45 or c22 mode.
+See https://svanheule.net/realtek/longan/register/smi_glb_ctrl
+With this setting the register access will be limited by the
+hardware. This is very complex (including caching and special
+c45-over-c22 handling). But basically it boils down to "enable
+protocol x and SoC will disable register access via protocol y".
+
+Mainline already gained support for the rtl9300 mdio driver
+in commit 24e31e474769 ("net: mdio: Add RTL9300 MDIO driver").
+
+It covers the basic features, but a lot effort is still needed
+to understand hardware properly. So it runs a simple setup by
+selecting the proper bus mode during startup.
+
+       /* Put the interfaces into C45 mode if required */
+       glb_ctrl_mask = GENMASK(19, 16);
+       for (i = 0; i < MAX_SMI_BUSSES; i++)
+               if (priv->smi_bus_is_c45[i])
+                       glb_ctrl_val |= GLB_CTRL_INTF_SEL(i);
+       ...
+       err = regmap_update_bits(regmap, SMI_GLB_CTRL,
+                                glb_ctrl_mask, glb_ctrl_val);
+
+To avoid complex coding later on, it limits access by only
+providing either c22 or c45:
+
+       bus->name = "Realtek Switch MDIO Bus";
+       if (priv->smi_bus_is_c45[mdio_bus]) {
+               bus->read_c45 = rtl9300_mdio_read_c45;
+               bus->write_c45 =  rtl9300_mdio_write_c45;
+       } else {
+               bus->read = rtl9300_mdio_read_c22;
+               bus->write = rtl9300_mdio_write_c22;
+       }
+
+Because of these limitations the existing RTL8226 phy driver
+is not working at all on Realtek switches. Convert the driver
+to c45-only.
+
+Luckily the RTL8226 seems to support proper MDIO_PMA_EXTABLE
+flags. So standard function genphy_c45_pma_read_abilities() can
+call genphy_c45_pma_read_ext_abilities() and 10/100/1000 is
+populated right. Thus conversion is straight forward.
+
+Outputs before - REMARK: For this a "hacked" bus was used that
+toggles the mode for each c22/c45 access. But that is slow and
+produces unstable data in the SoC status registers).
+
+Settings for lan9:
+        Supported ports: [ TP MII ]
+        Supported link modes:   10baseT/Half 10baseT/Full
+                                100baseT/Half 100baseT/Full
+                                1000baseT/Full
+                                2500baseT/Full
+        Supported pause frame use: Symmetric Receive-only
+        Supports auto-negotiation: Yes
+        Supported FEC modes: Not reported
+        Advertised link modes:  10baseT/Half 10baseT/Full
+                                100baseT/Half 100baseT/Full
+                                1000baseT/Full
+                                2500baseT/Full
+        Advertised pause frame use: Symmetric Receive-only
+        Advertised auto-negotiation: Yes
+        Advertised FEC modes: Not reported
+        Speed: Unknown!
+        Duplex: Unknown! (255)
+        Port: Twisted Pair
+        PHYAD: 24
+        Transceiver: external
+        Auto-negotiation: on
+        MDI-X: Unknown
+        Supports Wake-on: d
+        Wake-on: d
+        Link detected: no
+
+Outputs with this commit:
+
+Settings for lan9:
+        Supported ports: [ TP ]
+        Supported link modes:   10baseT/Half 10baseT/Full
+                                100baseT/Half 100baseT/Full
+                                1000baseT/Full
+                                2500baseT/Full
+        Supported pause frame use: Symmetric Receive-only
+        Supports auto-negotiation: Yes
+        Supported FEC modes: Not reported
+        Advertised link modes:  10baseT/Half 10baseT/Full
+                                100baseT/Half 100baseT/Full
+                                1000baseT/Full
+                                2500baseT/Full
+        Advertised pause frame use: Symmetric Receive-only
+        Advertised auto-negotiation: Yes
+        Advertised FEC modes: Not reported
+        Speed: Unknown!
+        Duplex: Unknown! (255)
+        Port: Twisted Pair
+        PHYAD: 24
+        Transceiver: external
+        Auto-negotiation: on
+        MDI-X: Unknown
+        Supports Wake-on: d
+        Wake-on: d
+        Link detected: no
+
+Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
+Link: https://patch.msgid.link/20250813054407.1108285-1-markus.stockhausen@gmx.de
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 28 +++++++++++++++++++++-------
+ 1 file changed, 21 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -1274,6 +1274,21 @@ static int rtl822x_c45_read_status(struc
+       return 0;
+ }
++static int rtl822x_c45_soft_reset(struct phy_device *phydev)
++{
++      int ret, val;
++
++      ret = phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1,
++                           MDIO_CTRL1_RESET, MDIO_CTRL1_RESET);
++      if (ret < 0)
++              return ret;
++
++      return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD,
++                                       MDIO_CTRL1, val,
++                                       !(val & MDIO_CTRL1_RESET),
++                                       5000, 100000, true);
++}
++
+ static int rtl822xb_c45_read_status(struct phy_device *phydev)
+ {
+       int ret;
+@@ -1660,13 +1675,12 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               PHY_ID_MATCH_EXACT(0x001cc838),
+               .name           = "RTL8226-CG 2.5Gbps PHY",
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .read_status    = rtl822x_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
++              .soft_reset     = rtl822x_c45_soft_reset,
++              .get_features   = rtl822x_c45_get_features,
++              .config_aneg    = rtl822x_c45_config_aneg,
++              .read_status    = rtl822x_c45_read_status,
++              .suspend        = genphy_c45_pma_suspend,
++              .resume         = rtlgen_c45_resume,
+       }, {
+               PHY_ID_MATCH_EXACT(0x001cc848),
+               .name           = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY",
diff --git a/target/linux/generic/backport-6.12/781-29-v6.18-net-phy-realtek-enable-serdes-option-mode-for-RTL8226-CG.patch b/target/linux/generic/backport-6.12/781-29-v6.18-net-phy-realtek-enable-serdes-option-mode-for-RTL8226-CG.patch
new file mode 100644 (file)
index 0000000..9ffb260
--- /dev/null
@@ -0,0 +1,92 @@
+From 3a752e67800106a5c42d802d67e06c60aa71d07b Mon Sep 17 00:00:00 2001
+From: Markus Stockhausen <markus.stockhausen@gmx.de>
+Date: Fri, 15 Aug 2025 04:20:09 -0400
+Subject: net: phy: realtek: enable serdes option mode for RTL8226-CG
+
+The RTL8226-CG can make use of the serdes option mode feature to
+dynamically switch between SGMII and 2500base-X. From what is
+known the setup sequence is much simpler with no magic values.
+
+Convert the exiting config_init() into a helper that configures
+the PHY depending on generation 1 or 2. Call the helper from two
+separated new config_init() functions.
+
+Finally convert the phy_driver specs of the RTL8226-CG to make
+use of the new configuration and switch over to the extended
+read_status() function to dynamically change the interface
+according to the serdes mode.
+
+Remark! The logic could be simpler if the serdes mode could be
+set before all other generation 2 magic values. Due to missing
+RTL8221B test hardware the mmd command order was kept.
+
+Tested on Zyxel XGS1210-12.
+
+Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
+Link: https://patch.msgid.link/20250815082009.3678865-1-markus.stockhausen@gmx.de
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 26 ++++++++++++++++++++------
+ 1 file changed, 20 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -1032,7 +1032,7 @@ static int rtl822x_probe(struct phy_devi
+       return 0;
+ }
+-static int rtl822xb_config_init(struct phy_device *phydev)
++static int rtl822x_set_serdes_option_mode(struct phy_device *phydev, bool gen1)
+ {
+       bool has_2500, has_sgmii;
+       u16 mode;
+@@ -1067,15 +1067,18 @@ static int rtl822xb_config_init(struct p
+       /* the following sequence with magic numbers sets up the SerDes
+        * option mode
+        */
+-      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x75f3, 0);
+-      if (ret < 0)
+-              return ret;
++
++      if (!gen1) {
++              ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x75f3, 0);
++              if (ret < 0)
++                      return ret;
++      }
+       ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND1,
+                                    RTL822X_VND1_SERDES_OPTION,
+                                    RTL822X_VND1_SERDES_OPTION_MODE_MASK,
+                                    mode);
+-      if (ret < 0)
++      if (gen1 || ret < 0)
+               return ret;
+       ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6a04, 0x0503);
+@@ -1089,6 +1092,16 @@ static int rtl822xb_config_init(struct p
+       return phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6f11, 0x8020);
+ }
++static int rtl822x_config_init(struct phy_device *phydev)
++{
++      return rtl822x_set_serdes_option_mode(phydev, true);
++}
++
++static int rtl822xb_config_init(struct phy_device *phydev)
++{
++      return rtl822x_set_serdes_option_mode(phydev, false);
++}
++
+ static int rtl822xb_get_rate_matching(struct phy_device *phydev,
+                                     phy_interface_t iface)
+ {
+@@ -1678,7 +1691,8 @@ static struct phy_driver realtek_drvs[]
+               .soft_reset     = rtl822x_c45_soft_reset,
+               .get_features   = rtl822x_c45_get_features,
+               .config_aneg    = rtl822x_c45_config_aneg,
+-              .read_status    = rtl822x_c45_read_status,
++              .config_init    = rtl822x_config_init,
++              .read_status    = rtl822xb_c45_read_status,
+               .suspend        = genphy_c45_pma_suspend,
+               .resume         = rtlgen_c45_resume,
+       }, {
diff --git a/target/linux/generic/backport-6.12/782-01-v6.16-net-phy-pass-PHY-driver-to-.match_phy_device-OP.patch b/target/linux/generic/backport-6.12/782-01-v6.16-net-phy-pass-PHY-driver-to-.match_phy_device-OP.patch
new file mode 100644 (file)
index 0000000..c22231e
--- /dev/null
@@ -0,0 +1,303 @@
+From 31afd6bc55cc0093c3e5b0a368319e423d4de8ea Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sat, 17 May 2025 22:13:45 +0200
+Subject: [PATCH] net: phy: pass PHY driver to .match_phy_device OP
+
+Pass PHY driver pointer to .match_phy_device OP in addition to phydev.
+Having access to the PHY driver struct might be useful to check the
+PHY ID of the driver is being matched for in case the PHY ID scanned in
+the phydev is not consistent.
+
+A scenario for this is a PHY that change PHY ID after a firmware is
+loaded, in such case, the PHY ID stored in PHY device struct is not
+valid anymore and PHY will manually scan the ID in the match_phy_device
+function.
+
+Having the PHY driver info is also useful for those PHY driver that
+implement multiple simple .match_phy_device OP to match specific MMD PHY
+ID. With this extra info if the parsing logic is the same, the matching
+function can be generalized by using the phy_id in the PHY driver
+instead of hardcoding.
+
+Rust wrapper callback is updated to align to the new match_phy_device
+arguments.
+
+Suggested-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Benno Lossin <lossin@kernel.org> # for Rust
+Reviewed-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
+Link: https://patch.msgid.link/20250517201353.5137-2-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/bcm87xx.c              |  6 ++++--
+ drivers/net/phy/icplus.c               |  6 ++++--
+ drivers/net/phy/marvell10g.c           | 12 ++++++++----
+ drivers/net/phy/micrel.c               |  6 ++++--
+ drivers/net/phy/nxp-c45-tja11xx.c      | 12 ++++++++----
+ drivers/net/phy/nxp-tja11xx.c          |  6 ++++--
+ drivers/net/phy/phy_device.c           |  2 +-
+ drivers/net/phy/realtek/realtek_main.c | 27 +++++++++++++++++---------
+ drivers/net/phy/teranetics.c           |  3 ++-
+ include/linux/phy.h                    |  3 ++-
+ rust/kernel/net/phy.rs                 |  1 +
+ 11 files changed, 56 insertions(+), 28 deletions(-)
+
+--- a/drivers/net/phy/bcm87xx.c
++++ b/drivers/net/phy/bcm87xx.c
+@@ -185,12 +185,14 @@ static irqreturn_t bcm87xx_handle_interr
+       return IRQ_HANDLED;
+ }
+-static int bcm8706_match_phy_device(struct phy_device *phydev)
++static int bcm8706_match_phy_device(struct phy_device *phydev,
++                                  const struct phy_driver *phydrv)
+ {
+       return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8706;
+ }
+-static int bcm8727_match_phy_device(struct phy_device *phydev)
++static int bcm8727_match_phy_device(struct phy_device *phydev,
++                                  const struct phy_driver *phydrv)
+ {
+       return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8727;
+ }
+--- a/drivers/net/phy/icplus.c
++++ b/drivers/net/phy/icplus.c
+@@ -520,12 +520,14 @@ static int ip101a_g_match_phy_device(str
+       return ip101a == !ret;
+ }
+-static int ip101a_match_phy_device(struct phy_device *phydev)
++static int ip101a_match_phy_device(struct phy_device *phydev,
++                                 const struct phy_driver *phydrv)
+ {
+       return ip101a_g_match_phy_device(phydev, true);
+ }
+-static int ip101g_match_phy_device(struct phy_device *phydev)
++static int ip101g_match_phy_device(struct phy_device *phydev,
++                                 const struct phy_driver *phydrv)
+ {
+       return ip101a_g_match_phy_device(phydev, false);
+ }
+--- a/drivers/net/phy/marvell10g.c
++++ b/drivers/net/phy/marvell10g.c
+@@ -1284,7 +1284,8 @@ static int mv3310_get_number_of_ports(st
+       return ret + 1;
+ }
+-static int mv3310_match_phy_device(struct phy_device *phydev)
++static int mv3310_match_phy_device(struct phy_device *phydev,
++                                 const struct phy_driver *phydrv)
+ {
+       if ((phydev->c45_ids.device_ids[MDIO_MMD_PMAPMD] &
+            MARVELL_PHY_ID_MASK) != MARVELL_PHY_ID_88X3310)
+@@ -1293,7 +1294,8 @@ static int mv3310_match_phy_device(struc
+       return mv3310_get_number_of_ports(phydev) == 1;
+ }
+-static int mv3340_match_phy_device(struct phy_device *phydev)
++static int mv3340_match_phy_device(struct phy_device *phydev,
++                                 const struct phy_driver *phydrv)
+ {
+       if ((phydev->c45_ids.device_ids[MDIO_MMD_PMAPMD] &
+            MARVELL_PHY_ID_MASK) != MARVELL_PHY_ID_88X3310)
+@@ -1317,12 +1319,14 @@ static int mv211x_match_phy_device(struc
+       return !!(val & MDIO_PCS_SPEED_5G) == has_5g;
+ }
+-static int mv2110_match_phy_device(struct phy_device *phydev)
++static int mv2110_match_phy_device(struct phy_device *phydev,
++                                 const struct phy_driver *phydrv)
+ {
+       return mv211x_match_phy_device(phydev, true);
+ }
+-static int mv2111_match_phy_device(struct phy_device *phydev)
++static int mv2111_match_phy_device(struct phy_device *phydev,
++                                 const struct phy_driver *phydrv)
+ {
+       return mv211x_match_phy_device(phydev, false);
+ }
+--- a/drivers/net/phy/micrel.c
++++ b/drivers/net/phy/micrel.c
+@@ -768,7 +768,8 @@ static int ksz8051_ksz8795_match_phy_dev
+               return !ret;
+ }
+-static int ksz8051_match_phy_device(struct phy_device *phydev)
++static int ksz8051_match_phy_device(struct phy_device *phydev,
++                                  const struct phy_driver *phydrv)
+ {
+       return ksz8051_ksz8795_match_phy_device(phydev, true);
+ }
+@@ -888,7 +889,8 @@ static int ksz8061_config_init(struct ph
+       return kszphy_config_init(phydev);
+ }
+-static int ksz8795_match_phy_device(struct phy_device *phydev)
++static int ksz8795_match_phy_device(struct phy_device *phydev,
++                                  const struct phy_driver *phydrv)
+ {
+       return ksz8051_ksz8795_match_phy_device(phydev, false);
+ }
+--- a/drivers/net/phy/nxp-c45-tja11xx.c
++++ b/drivers/net/phy/nxp-c45-tja11xx.c
+@@ -1944,13 +1944,15 @@ static int nxp_c45_macsec_ability(struct
+       return macsec_ability;
+ }
+-static int tja1103_match_phy_device(struct phy_device *phydev)
++static int tja1103_match_phy_device(struct phy_device *phydev,
++                                  const struct phy_driver *phydrv)
+ {
+       return phy_id_compare(phydev->phy_id, PHY_ID_TJA_1103, PHY_ID_MASK) &&
+              !nxp_c45_macsec_ability(phydev);
+ }
+-static int tja1104_match_phy_device(struct phy_device *phydev)
++static int tja1104_match_phy_device(struct phy_device *phydev,
++                                  const struct phy_driver *phydrv)
+ {
+       return phy_id_compare(phydev->phy_id, PHY_ID_TJA_1103, PHY_ID_MASK) &&
+              nxp_c45_macsec_ability(phydev);
+--- a/drivers/net/phy/nxp-tja11xx.c
++++ b/drivers/net/phy/nxp-tja11xx.c
+@@ -646,12 +646,14 @@ static int tja1102_match_phy_device(stru
+       return !ret;
+ }
+-static int tja1102_p0_match_phy_device(struct phy_device *phydev)
++static int tja1102_p0_match_phy_device(struct phy_device *phydev,
++                                     const struct phy_driver *phydrv)
+ {
+       return tja1102_match_phy_device(phydev, true);
+ }
+-static int tja1102_p1_match_phy_device(struct phy_device *phydev)
++static int tja1102_p1_match_phy_device(struct phy_device *phydev,
++                                     const struct phy_driver *phydrv)
+ {
+       return tja1102_match_phy_device(phydev, false);
+ }
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -600,7 +600,7 @@ static int phy_bus_match(struct device *
+               return 0;
+       if (phydrv->match_phy_device)
+-              return phydrv->match_phy_device(phydev);
++              return phydrv->match_phy_device(phydev, phydrv);
+       if (phydev->is_c45) {
+               for (i = 1; i < num_ids; i++) {
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -1343,13 +1343,15 @@ static bool rtlgen_supports_mmd(struct p
+       return val > 0;
+ }
+-static int rtlgen_match_phy_device(struct phy_device *phydev)
++static int rtlgen_match_phy_device(struct phy_device *phydev,
++                                 const struct phy_driver *phydrv)
+ {
+       return phydev->phy_id == RTL_GENERIC_PHYID &&
+              !rtlgen_supports_2_5gbps(phydev);
+ }
+-static int rtl8226_match_phy_device(struct phy_device *phydev)
++static int rtl8226_match_phy_device(struct phy_device *phydev,
++                                  const struct phy_driver *phydrv)
+ {
+       return phydev->phy_id == RTL_GENERIC_PHYID &&
+              rtlgen_supports_2_5gbps(phydev) &&
+@@ -1365,32 +1367,38 @@ static int rtlgen_is_c45_match(struct ph
+               return !is_c45 && (id == phydev->phy_id);
+ }
+-static int rtl8221b_match_phy_device(struct phy_device *phydev)
++static int rtl8221b_match_phy_device(struct phy_device *phydev,
++                                   const struct phy_driver *phydrv)
+ {
+       return phydev->phy_id == RTL_8221B && rtlgen_supports_mmd(phydev);
+ }
+-static int rtl8221b_vb_cg_c22_match_phy_device(struct phy_device *phydev)
++static int rtl8221b_vb_cg_c22_match_phy_device(struct phy_device *phydev,
++                                             const struct phy_driver *phydrv)
+ {
+       return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, false);
+ }
+-static int rtl8221b_vb_cg_c45_match_phy_device(struct phy_device *phydev)
++static int rtl8221b_vb_cg_c45_match_phy_device(struct phy_device *phydev,
++                                             const struct phy_driver *phydrv)
+ {
+       return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, true);
+ }
+-static int rtl8221b_vn_cg_c22_match_phy_device(struct phy_device *phydev)
++static int rtl8221b_vn_cg_c22_match_phy_device(struct phy_device *phydev,
++                                             const struct phy_driver *phydrv)
+ {
+       return rtlgen_is_c45_match(phydev, RTL_8221B_VN_CG, false);
+ }
+-static int rtl8221b_vn_cg_c45_match_phy_device(struct phy_device *phydev)
++static int rtl8221b_vn_cg_c45_match_phy_device(struct phy_device *phydev,
++                                             const struct phy_driver *phydrv)
+ {
+       return rtlgen_is_c45_match(phydev, RTL_8221B_VN_CG, true);
+ }
+-static int rtl_internal_nbaset_match_phy_device(struct phy_device *phydev)
++static int rtl_internal_nbaset_match_phy_device(struct phy_device *phydev,
++                                              const struct phy_driver *phydrv)
+ {
+       if (phydev->is_c45)
+               return false;
+@@ -1409,7 +1417,8 @@ static int rtl_internal_nbaset_match_phy
+       return rtlgen_supports_2_5gbps(phydev) && !rtlgen_supports_mmd(phydev);
+ }
+-static int rtl8251b_c45_match_phy_device(struct phy_device *phydev)
++static int rtl8251b_c45_match_phy_device(struct phy_device *phydev,
++                                       const struct phy_driver *phydrv)
+ {
+       return rtlgen_is_c45_match(phydev, RTL_8251B, true);
+ }
+--- a/drivers/net/phy/teranetics.c
++++ b/drivers/net/phy/teranetics.c
+@@ -67,7 +67,8 @@ static int teranetics_read_status(struct
+       return 0;
+ }
+-static int teranetics_match_phy_device(struct phy_device *phydev)
++static int teranetics_match_phy_device(struct phy_device *phydev,
++                                     const struct phy_driver *phydrv)
+ {
+       return phydev->c45_ids.device_ids[3] == PHY_ID_TN2020;
+ }
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1045,7 +1045,8 @@ struct phy_driver {
+        * driver for the given phydev.  If NULL, matching is based on
+        * phy_id and phy_id_mask.
+        */
+-      int (*match_phy_device)(struct phy_device *phydev);
++      int (*match_phy_device)(struct phy_device *phydev,
++                              const struct phy_driver *phydrv);
+       /**
+        * @set_wol: Some devices (e.g. qnap TS-119P II) require PHY
+--- a/rust/kernel/net/phy.rs
++++ b/rust/kernel/net/phy.rs
+@@ -421,6 +421,7 @@ impl<T: Driver> Adapter<T> {
+     /// `phydev` must be passed by the corresponding callback in `phy_driver`.
+     unsafe extern "C" fn match_phy_device_callback(
+         phydev: *mut bindings::phy_device,
++        _phydrv: *const bindings::phy_driver,
+     ) -> crate::ffi::c_int {
+         // SAFETY: This callback is called only in contexts
+         // where we hold `phy_device->lock`, so the accessors on
diff --git a/target/linux/generic/backport-6.12/782-04-v6.16-net-phy-introduce-genphy_match_phy_device.patch b/target/linux/generic/backport-6.12/782-04-v6.16-net-phy-introduce-genphy_match_phy_device.patch
new file mode 100644 (file)
index 0000000..fed9d3c
--- /dev/null
@@ -0,0 +1,109 @@
+From d6c45707ac84c2d9f274ece1cea4dddb97996bde Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sat, 17 May 2025 22:13:48 +0200
+Subject: [PATCH 4/5] net: phy: introduce genphy_match_phy_device()
+
+Introduce new API, genphy_match_phy_device(), to provide a way to check
+to match a PHY driver for a PHY device based on the info stored in the
+PHY device struct.
+
+The function generalize the logic used in phy_bus_match() to check the
+PHY ID whether if C45 or C22 ID should be used for matching.
+
+This is useful for custom .match_phy_device function that wants to use
+the generic logic under some condition. (example a PHY is already setup
+and provide the correct PHY ID)
+
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Link: https://patch.msgid.link/20250517201353.5137-5-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phy_device.c | 52 +++++++++++++++++++++++++-----------
+ include/linux/phy.h          |  3 +++
+ 2 files changed, 40 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -589,20 +589,26 @@ static int phy_scan_fixups(struct phy_de
+       return 0;
+ }
+-static int phy_bus_match(struct device *dev, const struct device_driver *drv)
++/**
++ * genphy_match_phy_device - match a PHY device with a PHY driver
++ * @phydev: target phy_device struct
++ * @phydrv: target phy_driver struct
++ *
++ * Description: Checks whether the given PHY device matches the specified
++ * PHY driver. For Clause 45 PHYs, iterates over the available device
++ * identifiers and compares them against the driver's expected PHY ID,
++ * applying the provided mask. For Clause 22 PHYs, a direct ID comparison
++ * is performed.
++ *
++ * Return: 1 if the PHY device matches the driver, 0 otherwise.
++ */
++int genphy_match_phy_device(struct phy_device *phydev,
++                          const struct phy_driver *phydrv)
+ {
+-      struct phy_device *phydev = to_phy_device(dev);
+-      const struct phy_driver *phydrv = to_phy_driver(drv);
+-      const int num_ids = ARRAY_SIZE(phydev->c45_ids.device_ids);
+-      int i;
+-
+-      if (!(phydrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY))
+-              return 0;
+-
+-      if (phydrv->match_phy_device)
+-              return phydrv->match_phy_device(phydev, phydrv);
+-
+       if (phydev->is_c45) {
++              const int num_ids = ARRAY_SIZE(phydev->c45_ids.device_ids);
++              int i;
++
+               for (i = 1; i < num_ids; i++) {
+                       if (phydev->c45_ids.device_ids[i] == 0xffffffff)
+                               continue;
+@@ -611,11 +617,27 @@ static int phy_bus_match(struct device *
+                                          phydrv->phy_id, phydrv->phy_id_mask))
+                               return 1;
+               }
++
+               return 0;
+-      } else {
+-              return phy_id_compare(phydev->phy_id, phydrv->phy_id,
+-                                    phydrv->phy_id_mask);
+       }
++
++      return phy_id_compare(phydev->phy_id, phydrv->phy_id,
++                            phydrv->phy_id_mask);
++}
++EXPORT_SYMBOL_GPL(genphy_match_phy_device);
++
++static int phy_bus_match(struct device *dev, const struct device_driver *drv)
++{
++      struct phy_device *phydev = to_phy_device(dev);
++      const struct phy_driver *phydrv = to_phy_driver(drv);
++
++      if (!(phydrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY))
++              return 0;
++
++      if (phydrv->match_phy_device)
++              return phydrv->match_phy_device(phydev, phydrv);
++
++      return genphy_match_phy_device(phydev, phydrv);
+ }
+ static ssize_t
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1963,6 +1963,9 @@ char *phy_attached_info_irq(struct phy_d
+       __malloc;
+ void phy_attached_info(struct phy_device *phydev);
++int genphy_match_phy_device(struct phy_device *phydev,
++                          const struct phy_driver *phydrv);
++
+ /* Clause 22 PHY */
+ int genphy_read_abilities(struct phy_device *phydev);
+ int genphy_setup_forced(struct phy_device *phydev);
diff --git a/target/linux/generic/backport-6.12/782-05-v6.16-net-phy-Add-support-for-Aeonsemi-AS21xxx-PHYs.patch b/target/linux/generic/backport-6.12/782-05-v6.16-net-phy-Add-support-for-Aeonsemi-AS21xxx-PHYs.patch
new file mode 100644 (file)
index 0000000..f1bcb35
--- /dev/null
@@ -0,0 +1,1180 @@
+From 830877d89edcd834e4b4d0fcc021ff619d89505e Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sat, 17 May 2025 22:13:49 +0200
+Subject: [PATCH 5/5] net: phy: Add support for Aeonsemi AS21xxx PHYs
+
+Add support for Aeonsemi AS21xxx 10G C45 PHYs. These PHYs integrate
+an IPC to setup some configuration and require special handling to
+sync with the parity bit. The parity bit is a way the IPC use to
+follow correct order of command sent.
+
+Supported PHYs AS21011JB1, AS21011PB1, AS21010JB1, AS21010PB1,
+AS21511JB1, AS21511PB1, AS21510JB1, AS21510PB1, AS21210JB1,
+AS21210PB1 that all register with the PHY ID 0x7500 0x7510
+before the firmware is loaded.
+
+They all support up to 5 LEDs with various HW mode supported.
+
+While implementing it was found some strange coincidence with using the
+same logic for implementing C22 in MMD regs in Broadcom PHYs.
+
+For reference here the AS21xxx PHY name logic:
+
+AS21x1xxB1
+    ^ ^^
+    | |J: Supports SyncE/PTP
+    | |P: No SyncE/PTP support
+    | 1: Supports 2nd Serdes
+    | 2: Not 2nd Serdes support
+    0: 10G, 5G, 2.5G
+    5: 5G, 2.5G
+    2: 2.5G
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Link: https://patch.msgid.link/20250517201353.5137-6-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ MAINTAINERS               |    6 +
+ drivers/net/phy/Kconfig   |   12 +
+ drivers/net/phy/Makefile  |    1 +
+ drivers/net/phy/as21xxx.c | 1087 +++++++++++++++++++++++++++++++++++++
+ 4 files changed, 1106 insertions(+)
+ create mode 100644 drivers/net/phy/as21xxx.c
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -637,6 +637,12 @@ F:        drivers/iio/accel/adxl380.h
+ F:    drivers/iio/accel/adxl380_i2c.c
+ F:    drivers/iio/accel/adxl380_spi.c
++AEONSEMI PHY DRIVER
++M:    Christian Marangi <ansuelsmth@gmail.com>
++L:    netdev@vger.kernel.org
++S:    Maintained
++F:    drivers/net/phy/as21xxx.c
++
+ AF8133J THREE-AXIS MAGNETOMETER DRIVER
+ M:    Ondřej Jirman <megi@xff.cz>
+ S:    Maintained
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -79,6 +79,18 @@ config SFP
+ comment "MII PHY device drivers"
++config AS21XXX_PHY
++      tristate "Aeonsemi AS21xxx PHYs"
++      help
++        Currently supports the Aeonsemi AS21xxx PHY.
++
++        These are C45 PHYs 10G that require all a generic firmware.
++
++        Supported PHYs AS21011JB1, AS21011PB1, AS21010JB1, AS21010PB1,
++        AS21511JB1, AS21511PB1, AS21510JB1, AS21510PB1, AS21210JB1,
++        AS21210PB1 that all register with the PHY ID 0x7500 0x7500
++        before the firmware is loaded.
++
+ config AIR_EN8811H_PHY
+       tristate "Airoha EN8811H 2.5 Gigabit PHY"
+       help
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -39,6 +39,7 @@ obj-$(CONFIG_AIR_EN8811H_PHY)   += air_e
+ obj-$(CONFIG_AMD_PHY)         += amd.o
+ obj-$(CONFIG_AMCC_QT2025_PHY) += qt2025.o
+ obj-$(CONFIG_AQUANTIA_PHY)    += aquantia/
++obj-$(CONFIG_AS21XXX_PHY)     += as21xxx.o
+ ifdef CONFIG_AX88796B_RUST_PHY
+   obj-$(CONFIG_AX88796B_PHY)  += ax88796b_rust.o
+ else
+--- /dev/null
++++ b/drivers/net/phy/as21xxx.c
+@@ -0,0 +1,1087 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Aeonsemi AS21XXxX PHY Driver
++ *
++ * Author: Christian Marangi <ansuelsmth@gmail.com>
++ */
++
++#include <linux/bitfield.h>
++#include <linux/firmware.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/phy.h>
++
++#define VEND1_GLB_REG_CPU_RESET_ADDR_LO_BASEADDR 0x3
++#define VEND1_GLB_REG_CPU_RESET_ADDR_HI_BASEADDR 0x4
++
++#define VEND1_GLB_REG_CPU_CTRL                0xe
++#define   VEND1_GLB_CPU_CTRL_MASK     GENMASK(4, 0)
++#define   VEND1_GLB_CPU_CTRL_LED_POLARITY_MASK GENMASK(12, 8)
++#define   VEND1_GLB_CPU_CTRL_LED_POLARITY(_n) FIELD_PREP(VEND1_GLB_CPU_CTRL_LED_POLARITY_MASK, \
++                                                       BIT(_n))
++
++#define VEND1_FW_START_ADDR           0x100
++
++#define VEND1_GLB_REG_MDIO_INDIRECT_ADDRCMD 0x101
++#define VEND1_GLB_REG_MDIO_INDIRECT_LOAD 0x102
++
++#define VEND1_GLB_REG_MDIO_INDIRECT_STATUS 0x103
++
++#define VEND1_PTP_CLK                 0x142
++#define   VEND1_PTP_CLK_EN            BIT(6)
++
++/* 5 LED at step of 0x20
++ * FE: Fast-Ethernet (10/100)
++ * GE: Gigabit-Ethernet (1000)
++ * NG: New-Generation (2500/5000/10000)
++ */
++#define VEND1_LED_REG(_n)             (0x1800 + ((_n) * 0x10))
++#define   VEND1_LED_REG_A_EVENT               GENMASK(15, 11)
++#define VEND1_LED_CONF                        0x1881
++#define   VEND1_LED_CONFG_BLINK               GENMASK(7, 0)
++
++#define VEND1_SPEED_STATUS            0x4002
++#define   VEND1_SPEED_MASK            GENMASK(7, 0)
++#define   VEND1_SPEED_10000           FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x3)
++#define   VEND1_SPEED_5000            FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x5)
++#define   VEND1_SPEED_2500            FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x9)
++#define   VEND1_SPEED_1000            FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x10)
++#define   VEND1_SPEED_100             FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x20)
++#define   VEND1_SPEED_10              FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x0)
++
++#define VEND1_IPC_CMD                 0x5801
++#define   AEON_IPC_CMD_PARITY         BIT(15)
++#define   AEON_IPC_CMD_SIZE           GENMASK(10, 6)
++#define   AEON_IPC_CMD_OPCODE         GENMASK(5, 0)
++
++#define IPC_CMD_NOOP                  0x0  /* Do nothing */
++#define IPC_CMD_INFO                  0x1  /* Get Firmware Version */
++#define IPC_CMD_SYS_CPU                       0x2  /* SYS_CPU */
++#define IPC_CMD_BULK_DATA             0xa  /* Pass bulk data in ipc registers. */
++#define IPC_CMD_BULK_WRITE            0xc  /* Write bulk data to memory */
++#define IPC_CMD_CFG_PARAM             0x1a /* Write config parameters to memory */
++#define IPC_CMD_NG_TESTMODE           0x1b /* Set NG test mode and tone */
++#define IPC_CMD_TEMP_MON              0x15 /* Temperature monitoring function */
++#define IPC_CMD_SET_LED                       0x23 /* Set led */
++
++#define VEND1_IPC_STS                 0x5802
++#define   AEON_IPC_STS_PARITY         BIT(15)
++#define   AEON_IPC_STS_SIZE           GENMASK(14, 10)
++#define   AEON_IPC_STS_OPCODE         GENMASK(9, 4)
++#define   AEON_IPC_STS_STATUS         GENMASK(3, 0)
++#define   AEON_IPC_STS_STATUS_RCVD    FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x1)
++#define   AEON_IPC_STS_STATUS_PROCESS FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x2)
++#define   AEON_IPC_STS_STATUS_SUCCESS FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x4)
++#define   AEON_IPC_STS_STATUS_ERROR   FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x8)
++#define   AEON_IPC_STS_STATUS_BUSY    FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0xe)
++#define   AEON_IPC_STS_STATUS_READY   FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0xf)
++
++#define VEND1_IPC_DATA0                       0x5808
++#define VEND1_IPC_DATA1                       0x5809
++#define VEND1_IPC_DATA2                       0x580a
++#define VEND1_IPC_DATA3                       0x580b
++#define VEND1_IPC_DATA4                       0x580c
++#define VEND1_IPC_DATA5                       0x580d
++#define VEND1_IPC_DATA6                       0x580e
++#define VEND1_IPC_DATA7                       0x580f
++#define VEND1_IPC_DATA(_n)            (VEND1_IPC_DATA0 + (_n))
++
++/* Sub command of CMD_INFO */
++#define IPC_INFO_VERSION              0x1
++
++/* Sub command of CMD_SYS_CPU */
++#define IPC_SYS_CPU_REBOOT            0x3
++#define IPC_SYS_CPU_IMAGE_OFST                0x4
++#define IPC_SYS_CPU_IMAGE_CHECK               0x5
++#define IPC_SYS_CPU_PHY_ENABLE                0x6
++
++/* Sub command of CMD_CFG_PARAM */
++#define IPC_CFG_PARAM_DIRECT          0x4
++
++/* CFG DIRECT sub command */
++#define IPC_CFG_PARAM_DIRECT_NG_PHYCTRL       0x1
++#define IPC_CFG_PARAM_DIRECT_CU_AN    0x2
++#define IPC_CFG_PARAM_DIRECT_SDS_PCS  0x3
++#define IPC_CFG_PARAM_DIRECT_AUTO_EEE 0x4
++#define IPC_CFG_PARAM_DIRECT_SDS_PMA  0x5
++#define IPC_CFG_PARAM_DIRECT_DPC_RA   0x6
++#define IPC_CFG_PARAM_DIRECT_DPC_PKT_CHK 0x7
++#define IPC_CFG_PARAM_DIRECT_DPC_SDS_WAIT_ETH 0x8
++#define IPC_CFG_PARAM_DIRECT_WDT      0x9
++#define IPC_CFG_PARAM_DIRECT_SDS_RESTART_AN 0x10
++#define IPC_CFG_PARAM_DIRECT_TEMP_MON 0x11
++#define IPC_CFG_PARAM_DIRECT_WOL      0x12
++
++/* Sub command of CMD_TEMP_MON */
++#define IPC_CMD_TEMP_MON_GET          0x4
++
++#define AS21XXX_MDIO_AN_C22           0xffe0
++
++#define PHY_ID_AS21XXX                        0x75009410
++/* AS21xxx ID Legend
++ * AS21x1xxB1
++ *     ^ ^^
++ *     | |J: Supports SyncE/PTP
++ *     | |P: No SyncE/PTP support
++ *     | 1: Supports 2nd Serdes
++ *     | 2: Not 2nd Serdes support
++ *     0: 10G, 5G, 2.5G
++ *     5: 5G, 2.5G
++ *     2: 2.5G
++ */
++#define PHY_ID_AS21011JB1             0x75009402
++#define PHY_ID_AS21011PB1             0x75009412
++#define PHY_ID_AS21010JB1             0x75009422
++#define PHY_ID_AS21010PB1             0x75009432
++#define PHY_ID_AS21511JB1             0x75009442
++#define PHY_ID_AS21511PB1             0x75009452
++#define PHY_ID_AS21510JB1             0x75009462
++#define PHY_ID_AS21510PB1             0x75009472
++#define PHY_ID_AS21210JB1             0x75009482
++#define PHY_ID_AS21210PB1             0x75009492
++#define PHY_VENDOR_AEONSEMI           0x75009400
++
++#define AEON_MAX_LEDS                 5
++#define AEON_IPC_DELAY                        10000
++#define AEON_IPC_TIMEOUT              (AEON_IPC_DELAY * 100)
++#define AEON_IPC_DATA_NUM_REGISTERS   8
++#define AEON_IPC_DATA_MAX             (AEON_IPC_DATA_NUM_REGISTERS * sizeof(u16))
++
++#define AEON_BOOT_ADDR                        0x1000
++#define AEON_CPU_BOOT_ADDR            0x2000
++#define AEON_CPU_CTRL_FW_LOAD         (BIT(4) | BIT(2) | BIT(1) | BIT(0))
++#define AEON_CPU_CTRL_FW_START                BIT(0)
++
++enum as21xxx_led_event {
++      VEND1_LED_REG_A_EVENT_ON_10 = 0x0,
++      VEND1_LED_REG_A_EVENT_ON_100,
++      VEND1_LED_REG_A_EVENT_ON_1000,
++      VEND1_LED_REG_A_EVENT_ON_2500,
++      VEND1_LED_REG_A_EVENT_ON_5000,
++      VEND1_LED_REG_A_EVENT_ON_10000,
++      VEND1_LED_REG_A_EVENT_ON_FE_GE,
++      VEND1_LED_REG_A_EVENT_ON_NG,
++      VEND1_LED_REG_A_EVENT_ON_FULL_DUPLEX,
++      VEND1_LED_REG_A_EVENT_ON_COLLISION,
++      VEND1_LED_REG_A_EVENT_BLINK_TX,
++      VEND1_LED_REG_A_EVENT_BLINK_RX,
++      VEND1_LED_REG_A_EVENT_BLINK_ACT,
++      VEND1_LED_REG_A_EVENT_ON_LINK,
++      VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_ACT,
++      VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_RX,
++      VEND1_LED_REG_A_EVENT_ON_FE_GE_BLINK_ACT,
++      VEND1_LED_REG_A_EVENT_ON_NG_BLINK_ACT,
++      VEND1_LED_REG_A_EVENT_ON_NG_BLINK_FE_GE,
++      VEND1_LED_REG_A_EVENT_ON_FD_BLINK_COLLISION,
++      VEND1_LED_REG_A_EVENT_ON,
++      VEND1_LED_REG_A_EVENT_OFF,
++};
++
++struct as21xxx_led_pattern_info {
++      unsigned int pattern;
++      u16 val;
++};
++
++struct as21xxx_priv {
++      bool parity_status;
++      /* Protect concurrent IPC access */
++      struct mutex ipc_lock;
++};
++
++static struct as21xxx_led_pattern_info as21xxx_led_supported_pattern[] = {
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK_10),
++              .val = VEND1_LED_REG_A_EVENT_ON_10
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK_100),
++              .val = VEND1_LED_REG_A_EVENT_ON_100
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK_1000),
++              .val = VEND1_LED_REG_A_EVENT_ON_1000
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK_2500),
++              .val = VEND1_LED_REG_A_EVENT_ON_2500
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK_5000),
++              .val = VEND1_LED_REG_A_EVENT_ON_5000
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK_10000),
++              .val = VEND1_LED_REG_A_EVENT_ON_10000
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK),
++              .val = VEND1_LED_REG_A_EVENT_ON_LINK
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK_10) |
++                         BIT(TRIGGER_NETDEV_LINK_100) |
++                         BIT(TRIGGER_NETDEV_LINK_1000),
++              .val = VEND1_LED_REG_A_EVENT_ON_FE_GE
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK_2500) |
++                         BIT(TRIGGER_NETDEV_LINK_5000) |
++                         BIT(TRIGGER_NETDEV_LINK_10000),
++              .val = VEND1_LED_REG_A_EVENT_ON_NG
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_FULL_DUPLEX),
++              .val = VEND1_LED_REG_A_EVENT_ON_FULL_DUPLEX
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_TX),
++              .val = VEND1_LED_REG_A_EVENT_BLINK_TX
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_RX),
++              .val = VEND1_LED_REG_A_EVENT_BLINK_RX
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_TX) |
++                         BIT(TRIGGER_NETDEV_RX),
++              .val = VEND1_LED_REG_A_EVENT_BLINK_ACT
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK_10) |
++                         BIT(TRIGGER_NETDEV_LINK_100) |
++                         BIT(TRIGGER_NETDEV_LINK_1000) |
++                         BIT(TRIGGER_NETDEV_LINK_2500) |
++                         BIT(TRIGGER_NETDEV_LINK_5000) |
++                         BIT(TRIGGER_NETDEV_LINK_10000),
++              .val = VEND1_LED_REG_A_EVENT_ON_LINK
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK_10) |
++                         BIT(TRIGGER_NETDEV_LINK_100) |
++                         BIT(TRIGGER_NETDEV_LINK_1000) |
++                         BIT(TRIGGER_NETDEV_LINK_2500) |
++                         BIT(TRIGGER_NETDEV_LINK_5000) |
++                         BIT(TRIGGER_NETDEV_LINK_10000) |
++                         BIT(TRIGGER_NETDEV_TX) |
++                         BIT(TRIGGER_NETDEV_RX),
++              .val = VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_ACT
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK_10) |
++                         BIT(TRIGGER_NETDEV_LINK_100) |
++                         BIT(TRIGGER_NETDEV_LINK_1000) |
++                         BIT(TRIGGER_NETDEV_LINK_2500) |
++                         BIT(TRIGGER_NETDEV_LINK_5000) |
++                         BIT(TRIGGER_NETDEV_LINK_10000) |
++                         BIT(TRIGGER_NETDEV_RX),
++              .val = VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_RX
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK_10) |
++                         BIT(TRIGGER_NETDEV_LINK_100) |
++                         BIT(TRIGGER_NETDEV_LINK_1000) |
++                         BIT(TRIGGER_NETDEV_TX) |
++                         BIT(TRIGGER_NETDEV_RX),
++              .val = VEND1_LED_REG_A_EVENT_ON_FE_GE_BLINK_ACT
++      },
++      {
++              .pattern = BIT(TRIGGER_NETDEV_LINK_2500) |
++                         BIT(TRIGGER_NETDEV_LINK_5000) |
++                         BIT(TRIGGER_NETDEV_LINK_10000) |
++                         BIT(TRIGGER_NETDEV_TX) |
++                         BIT(TRIGGER_NETDEV_RX),
++              .val = VEND1_LED_REG_A_EVENT_ON_NG_BLINK_ACT
++      }
++};
++
++static int aeon_firmware_boot(struct phy_device *phydev, const u8 *data,
++                            size_t size)
++{
++      int i, ret;
++      u16 val;
++
++      ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_CPU_CTRL,
++                           VEND1_GLB_CPU_CTRL_MASK, AEON_CPU_CTRL_FW_LOAD);
++      if (ret)
++              return ret;
++
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_FW_START_ADDR,
++                          AEON_BOOT_ADDR);
++      if (ret)
++              return ret;
++
++      ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1,
++                           VEND1_GLB_REG_MDIO_INDIRECT_ADDRCMD,
++                           0x3ffc, 0xc000);
++      if (ret)
++              return ret;
++
++      val = phy_read_mmd(phydev, MDIO_MMD_VEND1,
++                         VEND1_GLB_REG_MDIO_INDIRECT_STATUS);
++      if (val > 1) {
++              phydev_err(phydev, "wrong origin mdio_indirect_status: %x\n", val);
++              return -EINVAL;
++      }
++
++      /* Firmware is always aligned to u16 */
++      for (i = 0; i < size; i += 2) {
++              val = data[i + 1] << 8 | data[i];
++
++              ret = phy_write_mmd(phydev, MDIO_MMD_VEND1,
++                                  VEND1_GLB_REG_MDIO_INDIRECT_LOAD, val);
++              if (ret)
++                      return ret;
++      }
++
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1,
++                          VEND1_GLB_REG_CPU_RESET_ADDR_LO_BASEADDR,
++                          lower_16_bits(AEON_CPU_BOOT_ADDR));
++      if (ret)
++              return ret;
++
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1,
++                          VEND1_GLB_REG_CPU_RESET_ADDR_HI_BASEADDR,
++                          upper_16_bits(AEON_CPU_BOOT_ADDR));
++      if (ret)
++              return ret;
++
++      return phy_modify_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_CPU_CTRL,
++                            VEND1_GLB_CPU_CTRL_MASK, AEON_CPU_CTRL_FW_START);
++}
++
++static int aeon_firmware_load(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 = aeon_firmware_boot(phydev, fw->data, fw->size);
++
++      release_firmware(fw);
++
++      return ret;
++}
++
++static bool aeon_ipc_ready(u16 val, bool parity_status)
++{
++      u16 status;
++
++      if (FIELD_GET(AEON_IPC_STS_PARITY, val) != parity_status)
++              return false;
++
++      status = val & AEON_IPC_STS_STATUS;
++
++      return status != AEON_IPC_STS_STATUS_RCVD &&
++             status != AEON_IPC_STS_STATUS_PROCESS &&
++             status != AEON_IPC_STS_STATUS_BUSY;
++}
++
++static int aeon_ipc_wait_cmd(struct phy_device *phydev, bool parity_status)
++{
++      u16 val;
++
++      /* Exit condition logic:
++       * - Wait for parity bit equal
++       * - Wait for status success, error OR ready
++       */
++      return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, VEND1_IPC_STS, val,
++                                       aeon_ipc_ready(val, parity_status),
++                                       AEON_IPC_DELAY, AEON_IPC_TIMEOUT, false);
++}
++
++static int aeon_ipc_send_cmd(struct phy_device *phydev,
++                           struct as21xxx_priv *priv,
++                           u16 cmd, u16 *ret_sts)
++{
++      bool curr_parity;
++      int ret;
++
++      /* The IPC sync by using a single parity bit.
++       * Each CMD have alternately this bit set or clear
++       * to understand correct flow and packet order.
++       */
++      curr_parity = priv->parity_status;
++      if (priv->parity_status)
++              cmd |= AEON_IPC_CMD_PARITY;
++
++      /* Always update parity for next packet */
++      priv->parity_status = !priv->parity_status;
++
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_CMD, cmd);
++      if (ret)
++              return ret;
++
++      /* Wait for packet to be processed */
++      usleep_range(AEON_IPC_DELAY, AEON_IPC_DELAY + 5000);
++
++      /* With no ret_sts, ignore waiting for packet completion
++       * (ipc parity bit sync)
++       */
++      if (!ret_sts)
++              return 0;
++
++      ret = aeon_ipc_wait_cmd(phydev, curr_parity);
++      if (ret)
++              return ret;
++
++      ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_STS);
++      if (ret < 0)
++              return ret;
++
++      *ret_sts = ret;
++      if ((*ret_sts & AEON_IPC_STS_STATUS) != AEON_IPC_STS_STATUS_SUCCESS)
++              return -EINVAL;
++
++      return 0;
++}
++
++/* If data is NULL, return 0 or negative error.
++ * If data not NULL, return number of Bytes received from IPC or
++ * a negative error.
++ */
++static int aeon_ipc_send_msg(struct phy_device *phydev,
++                           u16 opcode, u16 *data, unsigned int data_len,
++                           u16 *ret_data)
++{
++      struct as21xxx_priv *priv = phydev->priv;
++      unsigned int ret_size;
++      u16 cmd, ret_sts;
++      int ret;
++      int i;
++
++      /* IPC have a max of 8 register to transfer data,
++       * make sure we never exceed this.
++       */
++      if (data_len > AEON_IPC_DATA_MAX)
++              return -EINVAL;
++
++      for (i = 0; i < data_len / sizeof(u16); i++)
++              phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i),
++                            data[i]);
++
++      cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, data_len) |
++            FIELD_PREP(AEON_IPC_CMD_OPCODE, opcode);
++
++      mutex_lock(&priv->ipc_lock);
++
++      ret = aeon_ipc_send_cmd(phydev, priv, cmd, &ret_sts);
++      if (ret) {
++              phydev_err(phydev, "failed to send ipc msg for %x: %d\n",
++                         opcode, ret);
++              goto out;
++      }
++
++      if (!data)
++              goto out;
++
++      if ((ret_sts & AEON_IPC_STS_STATUS) == AEON_IPC_STS_STATUS_ERROR) {
++              ret = -EINVAL;
++              goto out;
++      }
++
++      /* Prevent IPC from stack smashing the kernel.
++       * We can't trust IPC to return a good value and we always
++       * preallocate space for 16 Bytes.
++       */
++      ret_size = FIELD_GET(AEON_IPC_STS_SIZE, ret_sts);
++      if (ret_size > AEON_IPC_DATA_MAX) {
++              ret = -EINVAL;
++              goto out;
++      }
++
++      /* Read data from IPC data register for ret_size value from IPC */
++      for (i = 0; i < DIV_ROUND_UP(ret_size, sizeof(u16)); i++) {
++              ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i));
++              if (ret < 0)
++                      goto out;
++
++              ret_data[i] = ret;
++      }
++
++      ret = ret_size;
++
++out:
++      mutex_unlock(&priv->ipc_lock);
++
++      return ret;
++}
++
++static int aeon_ipc_noop(struct phy_device *phydev,
++                       struct as21xxx_priv *priv, u16 *ret_sts)
++{
++      u16 cmd;
++
++      cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, 0) |
++            FIELD_PREP(AEON_IPC_CMD_OPCODE, IPC_CMD_NOOP);
++
++      return aeon_ipc_send_cmd(phydev, priv, cmd, ret_sts);
++}
++
++/* Logic to sync parity bit with IPC.
++ * We send 2 NOP cmd with same partity and we wait for IPC
++ * to handle the packet only for the second one. This way
++ * we make sure we are sync for every next cmd.
++ */
++static int aeon_ipc_sync_parity(struct phy_device *phydev,
++                              struct as21xxx_priv *priv)
++{
++      u16 ret_sts;
++      int ret;
++
++      mutex_lock(&priv->ipc_lock);
++
++      /* Send NOP with no parity */
++      aeon_ipc_noop(phydev, priv, NULL);
++
++      /* Reset packet parity */
++      priv->parity_status = false;
++
++      /* Send second NOP with no parity */
++      ret = aeon_ipc_noop(phydev, priv, &ret_sts);
++
++      mutex_unlock(&priv->ipc_lock);
++
++      /* We expect to return -EINVAL */
++      if (ret != -EINVAL)
++              return ret;
++
++      if ((ret_sts & AEON_IPC_STS_STATUS) != AEON_IPC_STS_STATUS_READY) {
++              phydev_err(phydev, "Invalid IPC status on sync parity: %x\n",
++                         ret_sts);
++              return -EINVAL;
++      }
++
++      return 0;
++}
++
++static int aeon_ipc_get_fw_version(struct phy_device *phydev)
++{
++      u16 ret_data[AEON_IPC_DATA_NUM_REGISTERS], data[1];
++      char fw_version[AEON_IPC_DATA_MAX + 1];
++      int ret;
++
++      data[0] = IPC_INFO_VERSION;
++
++      ret = aeon_ipc_send_msg(phydev, IPC_CMD_INFO, data,
++                              sizeof(data), ret_data);
++      if (ret < 0)
++              return ret;
++
++      /* Make sure FW version is NULL terminated */
++      memcpy(fw_version, ret_data, ret);
++      fw_version[ret] = '\0';
++
++      phydev_info(phydev, "Firmware Version: %s\n", fw_version);
++
++      return 0;
++}
++
++static int aeon_dpc_ra_enable(struct phy_device *phydev)
++{
++      u16 data[2];
++
++      data[0] = IPC_CFG_PARAM_DIRECT;
++      data[1] = IPC_CFG_PARAM_DIRECT_DPC_RA;
++
++      return aeon_ipc_send_msg(phydev, IPC_CMD_CFG_PARAM, data,
++                               sizeof(data), NULL);
++}
++
++static int as21xxx_probe(struct phy_device *phydev)
++{
++      struct as21xxx_priv *priv;
++      int ret;
++
++      priv = devm_kzalloc(&phydev->mdio.dev,
++                          sizeof(*priv), GFP_KERNEL);
++      if (!priv)
++              return -ENOMEM;
++      phydev->priv = priv;
++
++      ret = devm_mutex_init(&phydev->mdio.dev,
++                            &priv->ipc_lock);
++      if (ret)
++              return ret;
++
++      ret = aeon_ipc_sync_parity(phydev, priv);
++      if (ret)
++              return ret;
++
++      ret = aeon_ipc_get_fw_version(phydev);
++      if (ret)
++              return ret;
++
++      /* Enable PTP clk if not already Enabled */
++      ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_PTP_CLK,
++                             VEND1_PTP_CLK_EN);
++      if (ret)
++              return ret;
++
++      return aeon_dpc_ra_enable(phydev);
++}
++
++static int as21xxx_read_link(struct phy_device *phydev, int *bmcr)
++{
++      int status;
++
++      /* Normal C22 BMCR report inconsistent data, use
++       * the mapped C22 in C45 to have more consistent link info.
++       */
++      *bmcr = phy_read_mmd(phydev, MDIO_MMD_AN,
++                           AS21XXX_MDIO_AN_C22 + MII_BMCR);
++      if (*bmcr < 0)
++              return *bmcr;
++
++      /* Autoneg is being started, therefore disregard current
++       * link status and report link as down.
++       */
++      if (*bmcr & BMCR_ANRESTART) {
++              phydev->link = 0;
++              return 0;
++      }
++
++      status = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
++      if (status < 0)
++              return status;
++
++      phydev->link = !!(status & MDIO_STAT1_LSTATUS);
++
++      return 0;
++}
++
++static int as21xxx_read_c22_lpa(struct phy_device *phydev)
++{
++      int lpagb;
++
++      /* MII_STAT1000 are only filled in the mapped C22
++       * in C45, use that to fill lpagb values and check.
++       */
++      lpagb = phy_read_mmd(phydev, MDIO_MMD_AN,
++                           AS21XXX_MDIO_AN_C22 + MII_STAT1000);
++      if (lpagb < 0)
++              return lpagb;
++
++      if (lpagb & LPA_1000MSFAIL) {
++              int adv = phy_read_mmd(phydev, MDIO_MMD_AN,
++                                     AS21XXX_MDIO_AN_C22 + 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);
++
++      return 0;
++}
++
++static int as21xxx_read_status(struct phy_device *phydev)
++{
++      int bmcr, old_link = phydev->link;
++      int ret;
++
++      ret = as21xxx_read_link(phydev, &bmcr);
++      if (ret)
++              return ret;
++
++      /* 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;
++
++      if (phydev->autoneg == AUTONEG_ENABLE) {
++              ret = genphy_c45_read_lpa(phydev);
++              if (ret)
++                      return ret;
++
++              ret = as21xxx_read_c22_lpa(phydev);
++              if (ret)
++                      return ret;
++
++              phy_resolve_aneg_linkmode(phydev);
++      } else {
++              int speed;
++
++              linkmode_zero(phydev->lp_advertising);
++
++              speed = phy_read_mmd(phydev, MDIO_MMD_VEND1,
++                                   VEND1_SPEED_STATUS);
++              if (speed < 0)
++                      return speed;
++
++              switch (speed & VEND1_SPEED_STATUS) {
++              case VEND1_SPEED_10000:
++                      phydev->speed = SPEED_10000;
++                      phydev->duplex = DUPLEX_FULL;
++                      break;
++              case VEND1_SPEED_5000:
++                      phydev->speed = SPEED_5000;
++                      phydev->duplex = DUPLEX_FULL;
++                      break;
++              case VEND1_SPEED_2500:
++                      phydev->speed = SPEED_2500;
++                      phydev->duplex = DUPLEX_FULL;
++                      break;
++              case VEND1_SPEED_1000:
++                      phydev->speed = SPEED_1000;
++                      if (bmcr & BMCR_FULLDPLX)
++                              phydev->duplex = DUPLEX_FULL;
++                      else
++                              phydev->duplex = DUPLEX_HALF;
++                      break;
++              case VEND1_SPEED_100:
++                      phydev->speed = SPEED_100;
++                      phydev->duplex = DUPLEX_FULL;
++                      break;
++              case VEND1_SPEED_10:
++                      phydev->speed = SPEED_10;
++                      phydev->duplex = DUPLEX_FULL;
++                      break;
++              default:
++                      return -EINVAL;
++              }
++      }
++
++      return 0;
++}
++
++static int as21xxx_led_brightness_set(struct phy_device *phydev,
++                                    u8 index, enum led_brightness value)
++{
++      u16 val = VEND1_LED_REG_A_EVENT_OFF;
++
++      if (index > AEON_MAX_LEDS)
++              return -EINVAL;
++
++      if (value)
++              val = VEND1_LED_REG_A_EVENT_ON;
++
++      return phy_modify_mmd(phydev, MDIO_MMD_VEND1,
++                            VEND1_LED_REG(index),
++                            VEND1_LED_REG_A_EVENT,
++                            FIELD_PREP(VEND1_LED_REG_A_EVENT, val));
++}
++
++static int as21xxx_led_hw_is_supported(struct phy_device *phydev, u8 index,
++                                     unsigned long rules)
++{
++      int i;
++
++      if (index > AEON_MAX_LEDS)
++              return -EINVAL;
++
++      for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
++              if (rules == as21xxx_led_supported_pattern[i].pattern)
++                      return 0;
++
++      return -EOPNOTSUPP;
++}
++
++static int as21xxx_led_hw_control_get(struct phy_device *phydev, u8 index,
++                                    unsigned long *rules)
++{
++      int i, val;
++
++      if (index > AEON_MAX_LEDS)
++              return -EINVAL;
++
++      val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_LED_REG(index));
++      if (val < 0)
++              return val;
++
++      val = FIELD_GET(VEND1_LED_REG_A_EVENT, val);
++      for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
++              if (val == as21xxx_led_supported_pattern[i].val) {
++                      *rules = as21xxx_led_supported_pattern[i].pattern;
++                      return 0;
++              }
++
++      /* Should be impossible */
++      return -EINVAL;
++}
++
++static int as21xxx_led_hw_control_set(struct phy_device *phydev, u8 index,
++                                    unsigned long rules)
++{
++      u16 val = 0;
++      int i;
++
++      if (index > AEON_MAX_LEDS)
++              return -EINVAL;
++
++      for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
++              if (rules == as21xxx_led_supported_pattern[i].pattern) {
++                      val = as21xxx_led_supported_pattern[i].val;
++                      break;
++              }
++
++      return phy_modify_mmd(phydev, MDIO_MMD_VEND1,
++                            VEND1_LED_REG(index),
++                            VEND1_LED_REG_A_EVENT,
++                            FIELD_PREP(VEND1_LED_REG_A_EVENT, val));
++}
++
++static int as21xxx_led_polarity_set(struct phy_device *phydev, int index,
++                                  unsigned long modes)
++{
++      bool led_active_low = false;
++      u16 mask, val = 0;
++      u32 mode;
++
++      if (index > AEON_MAX_LEDS)
++              return -EINVAL;
++
++      for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
++              switch (mode) {
++              case PHY_LED_ACTIVE_LOW:
++                      led_active_low = true;
++                      break;
++              case PHY_LED_ACTIVE_HIGH: /* default mode */
++                      led_active_low = false;
++                      break;
++              default:
++                      return -EINVAL;
++              }
++      }
++
++      mask = VEND1_GLB_CPU_CTRL_LED_POLARITY(index);
++      if (led_active_low)
++              val = VEND1_GLB_CPU_CTRL_LED_POLARITY(index);
++
++      return phy_modify_mmd(phydev, MDIO_MMD_VEND1,
++                            VEND1_GLB_REG_CPU_CTRL,
++                            mask, val);
++}
++
++static int as21xxx_match_phy_device(struct phy_device *phydev,
++                                  const struct phy_driver *phydrv)
++{
++      struct as21xxx_priv *priv;
++      u16 ret_sts;
++      u32 phy_id;
++      int ret;
++
++      /* Skip PHY that are not AS21xxx or already have firmware loaded */
++      if (phydev->c45_ids.device_ids[MDIO_MMD_PCS] != PHY_ID_AS21XXX)
++              return genphy_match_phy_device(phydev, phydrv);
++
++      /* Read PHY ID to handle firmware just loaded */
++      ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_PHYSID1);
++      if (ret < 0)
++              return ret;
++      phy_id = ret << 16;
++
++      ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_PHYSID2);
++      if (ret < 0)
++              return ret;
++      phy_id |= ret;
++
++      /* With PHY ID not the generic AS21xxx one assume
++       * the firmware just loaded
++       */
++      if (phy_id != PHY_ID_AS21XXX)
++              return phy_id == phydrv->phy_id;
++
++      /* Allocate temp priv and load the firmware */
++      priv = kzalloc(sizeof(*priv), GFP_KERNEL);
++      if (!priv)
++              return -ENOMEM;
++
++      mutex_init(&priv->ipc_lock);
++
++      ret = aeon_firmware_load(phydev);
++      if (ret)
++              goto out;
++
++      /* Sync parity... */
++      ret = aeon_ipc_sync_parity(phydev, priv);
++      if (ret)
++              goto out;
++
++      /* ...and send a third NOOP cmd to wait for firmware finish loading */
++      ret = aeon_ipc_noop(phydev, priv, &ret_sts);
++      if (ret)
++              goto out;
++
++out:
++      mutex_destroy(&priv->ipc_lock);
++      kfree(priv);
++
++      /* Return can either be 0 or a negative error code.
++       * Returning 0 here means THIS is NOT a suitable PHY.
++       *
++       * For the specific case of the generic Aeonsemi PHY ID that
++       * needs the firmware the be loaded first to have a correct PHY ID,
++       * this is OK as a matching PHY ID will be found right after.
++       * This relies on the driver probe order where the first PHY driver
++       * probed is the generic one.
++       */
++      return ret;
++}
++
++static struct phy_driver as21xxx_drivers[] = {
++      {
++              /* PHY expose in C45 as 0x7500 0x9410
++               * before firmware is loaded.
++               * This driver entry must be attempted first to load
++               * the firmware and thus update the ID registers.
++               */
++              PHY_ID_MATCH_EXACT(PHY_ID_AS21XXX),
++              .name           = "Aeonsemi AS21xxx",
++              .match_phy_device = as21xxx_match_phy_device,
++      },
++      {
++              PHY_ID_MATCH_EXACT(PHY_ID_AS21011JB1),
++              .name           = "Aeonsemi AS21011JB1",
++              .probe          = as21xxx_probe,
++              .match_phy_device = as21xxx_match_phy_device,
++              .read_status    = as21xxx_read_status,
++              .led_brightness_set = as21xxx_led_brightness_set,
++              .led_hw_is_supported = as21xxx_led_hw_is_supported,
++              .led_hw_control_set = as21xxx_led_hw_control_set,
++              .led_hw_control_get = as21xxx_led_hw_control_get,
++              .led_polarity_set = as21xxx_led_polarity_set,
++      },
++      {
++              PHY_ID_MATCH_EXACT(PHY_ID_AS21011PB1),
++              .name           = "Aeonsemi AS21011PB1",
++              .probe          = as21xxx_probe,
++              .match_phy_device = as21xxx_match_phy_device,
++              .read_status    = as21xxx_read_status,
++              .led_brightness_set = as21xxx_led_brightness_set,
++              .led_hw_is_supported = as21xxx_led_hw_is_supported,
++              .led_hw_control_set = as21xxx_led_hw_control_set,
++              .led_hw_control_get = as21xxx_led_hw_control_get,
++              .led_polarity_set = as21xxx_led_polarity_set,
++      },
++      {
++              PHY_ID_MATCH_EXACT(PHY_ID_AS21010PB1),
++              .name           = "Aeonsemi AS21010PB1",
++              .probe          = as21xxx_probe,
++              .match_phy_device = as21xxx_match_phy_device,
++              .read_status    = as21xxx_read_status,
++              .led_brightness_set = as21xxx_led_brightness_set,
++              .led_hw_is_supported = as21xxx_led_hw_is_supported,
++              .led_hw_control_set = as21xxx_led_hw_control_set,
++              .led_hw_control_get = as21xxx_led_hw_control_get,
++              .led_polarity_set = as21xxx_led_polarity_set,
++      },
++      {
++              PHY_ID_MATCH_EXACT(PHY_ID_AS21010JB1),
++              .name           = "Aeonsemi AS21010JB1",
++              .probe          = as21xxx_probe,
++              .match_phy_device = as21xxx_match_phy_device,
++              .read_status    = as21xxx_read_status,
++              .led_brightness_set = as21xxx_led_brightness_set,
++              .led_hw_is_supported = as21xxx_led_hw_is_supported,
++              .led_hw_control_set = as21xxx_led_hw_control_set,
++              .led_hw_control_get = as21xxx_led_hw_control_get,
++              .led_polarity_set = as21xxx_led_polarity_set,
++      },
++      {
++              PHY_ID_MATCH_EXACT(PHY_ID_AS21210PB1),
++              .name           = "Aeonsemi AS21210PB1",
++              .probe          = as21xxx_probe,
++              .match_phy_device = as21xxx_match_phy_device,
++              .read_status    = as21xxx_read_status,
++              .led_brightness_set = as21xxx_led_brightness_set,
++              .led_hw_is_supported = as21xxx_led_hw_is_supported,
++              .led_hw_control_set = as21xxx_led_hw_control_set,
++              .led_hw_control_get = as21xxx_led_hw_control_get,
++              .led_polarity_set = as21xxx_led_polarity_set,
++      },
++      {
++              PHY_ID_MATCH_EXACT(PHY_ID_AS21510JB1),
++              .name           = "Aeonsemi AS21510JB1",
++              .probe          = as21xxx_probe,
++              .match_phy_device = as21xxx_match_phy_device,
++              .read_status    = as21xxx_read_status,
++              .led_brightness_set = as21xxx_led_brightness_set,
++              .led_hw_is_supported = as21xxx_led_hw_is_supported,
++              .led_hw_control_set = as21xxx_led_hw_control_set,
++              .led_hw_control_get = as21xxx_led_hw_control_get,
++              .led_polarity_set = as21xxx_led_polarity_set,
++      },
++      {
++              PHY_ID_MATCH_EXACT(PHY_ID_AS21510PB1),
++              .name           = "Aeonsemi AS21510PB1",
++              .probe          = as21xxx_probe,
++              .match_phy_device = as21xxx_match_phy_device,
++              .read_status    = as21xxx_read_status,
++              .led_brightness_set = as21xxx_led_brightness_set,
++              .led_hw_is_supported = as21xxx_led_hw_is_supported,
++              .led_hw_control_set = as21xxx_led_hw_control_set,
++              .led_hw_control_get = as21xxx_led_hw_control_get,
++              .led_polarity_set = as21xxx_led_polarity_set,
++      },
++      {
++              PHY_ID_MATCH_EXACT(PHY_ID_AS21511JB1),
++              .name           = "Aeonsemi AS21511JB1",
++              .probe          = as21xxx_probe,
++              .match_phy_device = as21xxx_match_phy_device,
++              .read_status    = as21xxx_read_status,
++              .led_brightness_set = as21xxx_led_brightness_set,
++              .led_hw_is_supported = as21xxx_led_hw_is_supported,
++              .led_hw_control_set = as21xxx_led_hw_control_set,
++              .led_hw_control_get = as21xxx_led_hw_control_get,
++              .led_polarity_set = as21xxx_led_polarity_set,
++      },
++      {
++              PHY_ID_MATCH_EXACT(PHY_ID_AS21210JB1),
++              .name           = "Aeonsemi AS21210JB1",
++              .probe          = as21xxx_probe,
++              .match_phy_device = as21xxx_match_phy_device,
++              .read_status    = as21xxx_read_status,
++              .led_brightness_set = as21xxx_led_brightness_set,
++              .led_hw_is_supported = as21xxx_led_hw_is_supported,
++              .led_hw_control_set = as21xxx_led_hw_control_set,
++              .led_hw_control_get = as21xxx_led_hw_control_get,
++              .led_polarity_set = as21xxx_led_polarity_set,
++      },
++      {
++              PHY_ID_MATCH_EXACT(PHY_ID_AS21511PB1),
++              .name           = "Aeonsemi AS21511PB1",
++              .probe          = as21xxx_probe,
++              .match_phy_device = as21xxx_match_phy_device,
++              .read_status    = as21xxx_read_status,
++              .led_brightness_set = as21xxx_led_brightness_set,
++              .led_hw_is_supported = as21xxx_led_hw_is_supported,
++              .led_hw_control_set = as21xxx_led_hw_control_set,
++              .led_hw_control_get = as21xxx_led_hw_control_get,
++              .led_polarity_set = as21xxx_led_polarity_set,
++      },
++};
++module_phy_driver(as21xxx_drivers);
++
++static struct mdio_device_id __maybe_unused as21xxx_tbl[] = {
++      { PHY_ID_MATCH_VENDOR(PHY_VENDOR_AEONSEMI) },
++      { }
++};
++MODULE_DEVICE_TABLE(mdio, as21xxx_tbl);
++
++MODULE_DESCRIPTION("Aeonsemi AS21xxx PHY driver");
++MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
++MODULE_LICENSE("GPL");
diff --git a/target/linux/generic/backport-6.12/783-01-v6.18-net-dsa-Move-KS8995-to-the-DSA-subsystem.patch b/target/linux/generic/backport-6.12/783-01-v6.18-net-dsa-Move-KS8995-to-the-DSA-subsystem.patch
new file mode 100644 (file)
index 0000000..b91c0cd
--- /dev/null
@@ -0,0 +1,1092 @@
+From 9c370f7f92b0232fe925ca2caefb5e3126a0357d Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linus.walleij@linaro.org>
+Date: Wed, 13 Aug 2025 23:43:03 +0200
+Subject: [PATCH 1/4] net: dsa: Move KS8995 to the DSA subsystem
+
+By reading the datasheets for the KS8995 it is obvious that this
+is a 100 Mbit DSA switch.
+
+Let us start the refactoring by moving it to the DSA subsystem to
+preserve development history.
+
+Verified that the chip still probes the same after this patch
+provided CONFIG_HAVE_NET_DSA, CONFIG_NET_DSA and CONFIG_DSA_KS8995
+are selected.
+
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250813-ks8995-to-dsa-v1-1-75c359ede3a5@linaro.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/Kconfig                        | 7 +++++++
+ drivers/net/dsa/Makefile                       | 1 +
+ drivers/net/{phy/spi_ks8995.c => dsa/ks8995.c} | 0
+ drivers/net/phy/Kconfig                        | 4 ----
+ drivers/net/phy/Makefile                       | 1 -
+ 5 files changed, 8 insertions(+), 5 deletions(-)
+ rename drivers/net/{phy/spi_ks8995.c => dsa/ks8995.c} (100%)
+
+--- a/drivers/net/dsa/Kconfig
++++ b/drivers/net/dsa/Kconfig
+@@ -100,6 +100,13 @@ config NET_DSA_RZN1_A5PSW
+         This driver supports the A5PSW switch, which is embedded in Renesas
+         RZ/N1 SoC.
++config NET_DSA_KS8995
++      tristate "Micrel KS8995 family 5-ports 10/100 Ethernet switches"
++      depends on SPI
++      help
++        This driver supports the Micrel KS8995 family of 10/100 Mbit ethernet
++        switches, managed over SPI.
++
+ config NET_DSA_SMSC_LAN9303
+       tristate
+       select NET_DSA_TAG_LAN9303
+--- a/drivers/net/dsa/Makefile
++++ b/drivers/net/dsa/Makefile
+@@ -5,6 +5,7 @@ obj-$(CONFIG_NET_DSA_LOOP)     += dsa_loop.o
+ ifdef CONFIG_NET_DSA_LOOP
+ obj-$(CONFIG_FIXED_PHY)               += dsa_loop_bdinfo.o
+ endif
++obj-$(CONFIG_NET_DSA_KS8995)  += ks8995.o
+ 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
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -443,7 +443,3 @@ config XILINX_GMII2RGMII
+         Ethernet physical media devices and the Gigabit Ethernet controller.
+ endif # PHYLIB
+-
+-config MICREL_KS8995MA
+-      tristate "Micrel KS8995MA 5-ports 10/100 managed Ethernet switch"
+-      depends on SPI
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -77,7 +77,6 @@ obj-$(CONFIG_MARVELL_88X2222_PHY)    += mar
+ obj-$(CONFIG_MAXLINEAR_GPHY)  += mxl-gpy.o
+ obj-y                         += mediatek/
+ obj-$(CONFIG_MESON_GXL_PHY)   += meson-gxl.o
+-obj-$(CONFIG_MICREL_KS8995MA) += spi_ks8995.o
+ obj-$(CONFIG_MICREL_PHY)      += micrel.o
+ obj-$(CONFIG_MICROCHIP_PHY)   += microchip.o
+ obj-$(CONFIG_MICROCHIP_T1_PHY)        += microchip_t1.o
+--- /dev/null
++++ b/drivers/net/dsa/ks8995.c
+@@ -0,0 +1,506 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * SPI driver for Micrel/Kendin KS8995M and KSZ8864RMN ethernet switches
++ *
++ * Copyright (C) 2008 Gabor Juhos <juhosg at openwrt.org>
++ *
++ * This file was based on: drivers/spi/at25.c
++ *     Copyright (C) 2006 David Brownell
++ */
++
++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
++
++#include <linux/types.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/delay.h>
++#include <linux/device.h>
++#include <linux/gpio/consumer.h>
++#include <linux/of.h>
++
++#include <linux/spi/spi.h>
++
++#define DRV_VERSION           "0.1.1"
++#define DRV_DESC              "Micrel KS8995 Ethernet switch SPI driver"
++
++/* ------------------------------------------------------------------------ */
++
++#define KS8995_REG_ID0                0x00    /* Chip ID0 */
++#define KS8995_REG_ID1                0x01    /* Chip ID1 */
++
++#define KS8995_REG_GC0                0x02    /* Global Control 0 */
++#define KS8995_REG_GC1                0x03    /* Global Control 1 */
++#define KS8995_REG_GC2                0x04    /* Global Control 2 */
++#define KS8995_REG_GC3                0x05    /* Global Control 3 */
++#define KS8995_REG_GC4                0x06    /* Global Control 4 */
++#define KS8995_REG_GC5                0x07    /* Global Control 5 */
++#define KS8995_REG_GC6                0x08    /* Global Control 6 */
++#define KS8995_REG_GC7                0x09    /* Global Control 7 */
++#define KS8995_REG_GC8                0x0a    /* Global Control 8 */
++#define KS8995_REG_GC9                0x0b    /* Global Control 9 */
++
++#define KS8995_REG_PC(p, r)   ((0x10 * p) + r)         /* Port Control */
++#define KS8995_REG_PS(p, r)   ((0x10 * p) + r + 0xe)  /* Port Status */
++
++#define KS8995_REG_TPC0               0x60    /* TOS Priority Control 0 */
++#define KS8995_REG_TPC1               0x61    /* TOS Priority Control 1 */
++#define KS8995_REG_TPC2               0x62    /* TOS Priority Control 2 */
++#define KS8995_REG_TPC3               0x63    /* TOS Priority Control 3 */
++#define KS8995_REG_TPC4               0x64    /* TOS Priority Control 4 */
++#define KS8995_REG_TPC5               0x65    /* TOS Priority Control 5 */
++#define KS8995_REG_TPC6               0x66    /* TOS Priority Control 6 */
++#define KS8995_REG_TPC7               0x67    /* TOS Priority Control 7 */
++
++#define KS8995_REG_MAC0               0x68    /* MAC address 0 */
++#define KS8995_REG_MAC1               0x69    /* MAC address 1 */
++#define KS8995_REG_MAC2               0x6a    /* MAC address 2 */
++#define KS8995_REG_MAC3               0x6b    /* MAC address 3 */
++#define KS8995_REG_MAC4               0x6c    /* MAC address 4 */
++#define KS8995_REG_MAC5               0x6d    /* MAC address 5 */
++
++#define KS8995_REG_IAC0               0x6e    /* Indirect Access Control 0 */
++#define KS8995_REG_IAC1               0x6f    /* Indirect Access Control 0 */
++#define KS8995_REG_IAD7               0x70    /* Indirect Access Data 7 */
++#define KS8995_REG_IAD6               0x71    /* Indirect Access Data 6 */
++#define KS8995_REG_IAD5               0x72    /* Indirect Access Data 5 */
++#define KS8995_REG_IAD4               0x73    /* Indirect Access Data 4 */
++#define KS8995_REG_IAD3               0x74    /* Indirect Access Data 3 */
++#define KS8995_REG_IAD2               0x75    /* Indirect Access Data 2 */
++#define KS8995_REG_IAD1               0x76    /* Indirect Access Data 1 */
++#define KS8995_REG_IAD0               0x77    /* Indirect Access Data 0 */
++
++#define KSZ8864_REG_ID1               0xfe    /* Chip ID in bit 7 */
++
++#define KS8995_REGS_SIZE      0x80
++#define KSZ8864_REGS_SIZE     0x100
++#define KSZ8795_REGS_SIZE     0x100
++
++#define ID1_CHIPID_M          0xf
++#define ID1_CHIPID_S          4
++#define ID1_REVISION_M                0x7
++#define ID1_REVISION_S                1
++#define ID1_START_SW          1       /* start the switch */
++
++#define FAMILY_KS8995         0x95
++#define FAMILY_KSZ8795                0x87
++#define CHIPID_M              0
++#define KS8995_CHIP_ID                0x00
++#define KSZ8864_CHIP_ID               0x01
++#define KSZ8795_CHIP_ID               0x09
++
++#define KS8995_CMD_WRITE      0x02U
++#define KS8995_CMD_READ               0x03U
++
++#define KS8995_RESET_DELAY    10 /* usec */
++
++enum ks8995_chip_variant {
++      ks8995,
++      ksz8864,
++      ksz8795,
++      max_variant
++};
++
++struct ks8995_chip_params {
++      char *name;
++      int family_id;
++      int chip_id;
++      int regs_size;
++      int addr_width;
++      int addr_shift;
++};
++
++static const struct ks8995_chip_params ks8995_chip[] = {
++      [ks8995] = {
++              .name = "KS8995MA",
++              .family_id = FAMILY_KS8995,
++              .chip_id = KS8995_CHIP_ID,
++              .regs_size = KS8995_REGS_SIZE,
++              .addr_width = 8,
++              .addr_shift = 0,
++      },
++      [ksz8864] = {
++              .name = "KSZ8864RMN",
++              .family_id = FAMILY_KS8995,
++              .chip_id = KSZ8864_CHIP_ID,
++              .regs_size = KSZ8864_REGS_SIZE,
++              .addr_width = 8,
++              .addr_shift = 0,
++      },
++      [ksz8795] = {
++              .name = "KSZ8795CLX",
++              .family_id = FAMILY_KSZ8795,
++              .chip_id = KSZ8795_CHIP_ID,
++              .regs_size = KSZ8795_REGS_SIZE,
++              .addr_width = 12,
++              .addr_shift = 1,
++      },
++};
++
++struct ks8995_switch {
++      struct spi_device       *spi;
++      struct mutex            lock;
++      struct gpio_desc        *reset_gpio;
++      struct bin_attribute    regs_attr;
++      const struct ks8995_chip_params *chip;
++      int                     revision_id;
++};
++
++static const struct spi_device_id ks8995_id[] = {
++      {"ks8995", ks8995},
++      {"ksz8864", ksz8864},
++      {"ksz8795", ksz8795},
++      { }
++};
++MODULE_DEVICE_TABLE(spi, ks8995_id);
++
++static const struct of_device_id ks8895_spi_of_match[] = {
++      { .compatible = "micrel,ks8995" },
++      { .compatible = "micrel,ksz8864" },
++      { .compatible = "micrel,ksz8795" },
++      { },
++};
++MODULE_DEVICE_TABLE(of, ks8895_spi_of_match);
++
++static inline u8 get_chip_id(u8 val)
++{
++      return (val >> ID1_CHIPID_S) & ID1_CHIPID_M;
++}
++
++static inline u8 get_chip_rev(u8 val)
++{
++      return (val >> ID1_REVISION_S) & ID1_REVISION_M;
++}
++
++/* create_spi_cmd - create a chip specific SPI command header
++ * @ks: pointer to switch instance
++ * @cmd: SPI command for switch
++ * @address: register address for command
++ *
++ * Different chip families use different bit pattern to address the switches
++ * registers:
++ *
++ * KS8995: 8bit command + 8bit address
++ * KSZ8795: 3bit command + 12bit address + 1bit TR (?)
++ */
++static inline __be16 create_spi_cmd(struct ks8995_switch *ks, int cmd,
++                                  unsigned address)
++{
++      u16 result = cmd;
++
++      /* make room for address (incl. address shift) */
++      result <<= ks->chip->addr_width + ks->chip->addr_shift;
++      /* add address */
++      result |= address << ks->chip->addr_shift;
++      /* SPI protocol needs big endian */
++      return cpu_to_be16(result);
++}
++/* ------------------------------------------------------------------------ */
++static int ks8995_read(struct ks8995_switch *ks, char *buf,
++               unsigned offset, size_t count)
++{
++      __be16 cmd;
++      struct spi_transfer t[2];
++      struct spi_message m;
++      int err;
++
++      cmd = create_spi_cmd(ks, KS8995_CMD_READ, offset);
++      spi_message_init(&m);
++
++      memset(&t, 0, sizeof(t));
++
++      t[0].tx_buf = &cmd;
++      t[0].len = sizeof(cmd);
++      spi_message_add_tail(&t[0], &m);
++
++      t[1].rx_buf = buf;
++      t[1].len = count;
++      spi_message_add_tail(&t[1], &m);
++
++      mutex_lock(&ks->lock);
++      err = spi_sync(ks->spi, &m);
++      mutex_unlock(&ks->lock);
++
++      return err ? err : count;
++}
++
++static int ks8995_write(struct ks8995_switch *ks, char *buf,
++               unsigned offset, size_t count)
++{
++      __be16 cmd;
++      struct spi_transfer t[2];
++      struct spi_message m;
++      int err;
++
++      cmd = create_spi_cmd(ks, KS8995_CMD_WRITE, offset);
++      spi_message_init(&m);
++
++      memset(&t, 0, sizeof(t));
++
++      t[0].tx_buf = &cmd;
++      t[0].len = sizeof(cmd);
++      spi_message_add_tail(&t[0], &m);
++
++      t[1].tx_buf = buf;
++      t[1].len = count;
++      spi_message_add_tail(&t[1], &m);
++
++      mutex_lock(&ks->lock);
++      err = spi_sync(ks->spi, &m);
++      mutex_unlock(&ks->lock);
++
++      return err ? err : count;
++}
++
++static inline int ks8995_read_reg(struct ks8995_switch *ks, u8 addr, u8 *buf)
++{
++      return ks8995_read(ks, buf, addr, 1) != 1;
++}
++
++static inline int ks8995_write_reg(struct ks8995_switch *ks, u8 addr, u8 val)
++{
++      char buf = val;
++
++      return ks8995_write(ks, &buf, addr, 1) != 1;
++}
++
++/* ------------------------------------------------------------------------ */
++
++static int ks8995_stop(struct ks8995_switch *ks)
++{
++      return ks8995_write_reg(ks, KS8995_REG_ID1, 0);
++}
++
++static int ks8995_start(struct ks8995_switch *ks)
++{
++      return ks8995_write_reg(ks, KS8995_REG_ID1, 1);
++}
++
++static int ks8995_reset(struct ks8995_switch *ks)
++{
++      int err;
++
++      err = ks8995_stop(ks);
++      if (err)
++              return err;
++
++      udelay(KS8995_RESET_DELAY);
++
++      return ks8995_start(ks);
++}
++
++static ssize_t ks8995_registers_read(struct file *filp, struct kobject *kobj,
++      struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
++{
++      struct device *dev;
++      struct ks8995_switch *ks8995;
++
++      dev = kobj_to_dev(kobj);
++      ks8995 = dev_get_drvdata(dev);
++
++      return ks8995_read(ks8995, buf, off, count);
++}
++
++static ssize_t ks8995_registers_write(struct file *filp, struct kobject *kobj,
++      struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
++{
++      struct device *dev;
++      struct ks8995_switch *ks8995;
++
++      dev = kobj_to_dev(kobj);
++      ks8995 = dev_get_drvdata(dev);
++
++      return ks8995_write(ks8995, buf, off, count);
++}
++
++/* ks8995_get_revision - get chip revision
++ * @ks: pointer to switch instance
++ *
++ * Verify chip family and id and get chip revision.
++ */
++static int ks8995_get_revision(struct ks8995_switch *ks)
++{
++      int err;
++      u8 id0, id1, ksz8864_id;
++
++      /* read family id */
++      err = ks8995_read_reg(ks, KS8995_REG_ID0, &id0);
++      if (err) {
++              err = -EIO;
++              goto err_out;
++      }
++
++      /* verify family id */
++      if (id0 != ks->chip->family_id) {
++              dev_err(&ks->spi->dev, "chip family id mismatch: expected 0x%02x but 0x%02x read\n",
++                      ks->chip->family_id, id0);
++              err = -ENODEV;
++              goto err_out;
++      }
++
++      switch (ks->chip->family_id) {
++      case FAMILY_KS8995:
++              /* try reading chip id at CHIP ID1 */
++              err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
++              if (err) {
++                      err = -EIO;
++                      goto err_out;
++              }
++
++              /* verify chip id */
++              if ((get_chip_id(id1) == CHIPID_M) &&
++                  (get_chip_id(id1) == ks->chip->chip_id)) {
++                      /* KS8995MA */
++                      ks->revision_id = get_chip_rev(id1);
++              } else if (get_chip_id(id1) != CHIPID_M) {
++                      /* KSZ8864RMN */
++                      err = ks8995_read_reg(ks, KS8995_REG_ID1, &ksz8864_id);
++                      if (err) {
++                              err = -EIO;
++                              goto err_out;
++                      }
++
++                      if ((ksz8864_id & 0x80) &&
++                          (ks->chip->chip_id == KSZ8864_CHIP_ID)) {
++                              ks->revision_id = get_chip_rev(id1);
++                      }
++
++              } else {
++                      dev_err(&ks->spi->dev, "unsupported chip id for KS8995 family: 0x%02x\n",
++                              id1);
++                      err = -ENODEV;
++              }
++              break;
++      case FAMILY_KSZ8795:
++              /* try reading chip id at CHIP ID1 */
++              err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
++              if (err) {
++                      err = -EIO;
++                      goto err_out;
++              }
++
++              if (get_chip_id(id1) == ks->chip->chip_id) {
++                      ks->revision_id = get_chip_rev(id1);
++              } else {
++                      dev_err(&ks->spi->dev, "unsupported chip id for KSZ8795 family: 0x%02x\n",
++                              id1);
++                      err = -ENODEV;
++              }
++              break;
++      default:
++              dev_err(&ks->spi->dev, "unsupported family id: 0x%02x\n", id0);
++              err = -ENODEV;
++              break;
++      }
++err_out:
++      return err;
++}
++
++static const struct bin_attribute ks8995_registers_attr = {
++      .attr = {
++              .name   = "registers",
++              .mode   = 0600,
++      },
++      .size   = KS8995_REGS_SIZE,
++      .read   = ks8995_registers_read,
++      .write  = ks8995_registers_write,
++};
++
++/* ------------------------------------------------------------------------ */
++static int ks8995_probe(struct spi_device *spi)
++{
++      struct ks8995_switch *ks;
++      int err;
++      int variant = spi_get_device_id(spi)->driver_data;
++
++      if (variant >= max_variant) {
++              dev_err(&spi->dev, "bad chip variant %d\n", variant);
++              return -ENODEV;
++      }
++
++      ks = devm_kzalloc(&spi->dev, sizeof(*ks), GFP_KERNEL);
++      if (!ks)
++              return -ENOMEM;
++
++      mutex_init(&ks->lock);
++      ks->spi = spi;
++      ks->chip = &ks8995_chip[variant];
++
++      ks->reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset",
++                                               GPIOD_OUT_HIGH);
++      err = PTR_ERR_OR_ZERO(ks->reset_gpio);
++      if (err) {
++              dev_err(&spi->dev,
++                      "failed to get reset gpio: %d\n", err);
++              return err;
++      }
++
++      err = gpiod_set_consumer_name(ks->reset_gpio, "switch-reset");
++      if (err)
++              return err;
++
++      /* de-assert switch reset */
++      /* FIXME: this likely requires a delay */
++      gpiod_set_value_cansleep(ks->reset_gpio, 0);
++
++      spi_set_drvdata(spi, ks);
++
++      spi->mode = SPI_MODE_0;
++      spi->bits_per_word = 8;
++      err = spi_setup(spi);
++      if (err) {
++              dev_err(&spi->dev, "spi_setup failed, err=%d\n", err);
++              return err;
++      }
++
++      err = ks8995_get_revision(ks);
++      if (err)
++              return err;
++
++      memcpy(&ks->regs_attr, &ks8995_registers_attr, sizeof(ks->regs_attr));
++      ks->regs_attr.size = ks->chip->regs_size;
++
++      err = ks8995_reset(ks);
++      if (err)
++              return err;
++
++      sysfs_attr_init(&ks->regs_attr.attr);
++      err = sysfs_create_bin_file(&spi->dev.kobj, &ks->regs_attr);
++      if (err) {
++              dev_err(&spi->dev, "unable to create sysfs file, err=%d\n",
++                                  err);
++              return err;
++      }
++
++      dev_info(&spi->dev, "%s device found, Chip ID:%x, Revision:%x\n",
++               ks->chip->name, ks->chip->chip_id, ks->revision_id);
++
++      return 0;
++}
++
++static void ks8995_remove(struct spi_device *spi)
++{
++      struct ks8995_switch *ks = spi_get_drvdata(spi);
++
++      sysfs_remove_bin_file(&spi->dev.kobj, &ks->regs_attr);
++
++      /* assert reset */
++      gpiod_set_value_cansleep(ks->reset_gpio, 1);
++}
++
++/* ------------------------------------------------------------------------ */
++static struct spi_driver ks8995_driver = {
++      .driver = {
++              .name       = "spi-ks8995",
++              .of_match_table = ks8895_spi_of_match,
++      },
++      .probe    = ks8995_probe,
++      .remove   = ks8995_remove,
++      .id_table = ks8995_id,
++};
++
++module_spi_driver(ks8995_driver);
++
++MODULE_DESCRIPTION(DRV_DESC);
++MODULE_VERSION(DRV_VERSION);
++MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>");
++MODULE_LICENSE("GPL v2");
+--- a/drivers/net/phy/spi_ks8995.c
++++ /dev/null
+@@ -1,506 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-/*
+- * SPI driver for Micrel/Kendin KS8995M and KSZ8864RMN ethernet switches
+- *
+- * Copyright (C) 2008 Gabor Juhos <juhosg at openwrt.org>
+- *
+- * This file was based on: drivers/spi/at25.c
+- *     Copyright (C) 2006 David Brownell
+- */
+-
+-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+-
+-#include <linux/types.h>
+-#include <linux/kernel.h>
+-#include <linux/module.h>
+-#include <linux/delay.h>
+-#include <linux/device.h>
+-#include <linux/gpio/consumer.h>
+-#include <linux/of.h>
+-
+-#include <linux/spi/spi.h>
+-
+-#define DRV_VERSION           "0.1.1"
+-#define DRV_DESC              "Micrel KS8995 Ethernet switch SPI driver"
+-
+-/* ------------------------------------------------------------------------ */
+-
+-#define KS8995_REG_ID0                0x00    /* Chip ID0 */
+-#define KS8995_REG_ID1                0x01    /* Chip ID1 */
+-
+-#define KS8995_REG_GC0                0x02    /* Global Control 0 */
+-#define KS8995_REG_GC1                0x03    /* Global Control 1 */
+-#define KS8995_REG_GC2                0x04    /* Global Control 2 */
+-#define KS8995_REG_GC3                0x05    /* Global Control 3 */
+-#define KS8995_REG_GC4                0x06    /* Global Control 4 */
+-#define KS8995_REG_GC5                0x07    /* Global Control 5 */
+-#define KS8995_REG_GC6                0x08    /* Global Control 6 */
+-#define KS8995_REG_GC7                0x09    /* Global Control 7 */
+-#define KS8995_REG_GC8                0x0a    /* Global Control 8 */
+-#define KS8995_REG_GC9                0x0b    /* Global Control 9 */
+-
+-#define KS8995_REG_PC(p, r)   ((0x10 * p) + r)         /* Port Control */
+-#define KS8995_REG_PS(p, r)   ((0x10 * p) + r + 0xe)  /* Port Status */
+-
+-#define KS8995_REG_TPC0               0x60    /* TOS Priority Control 0 */
+-#define KS8995_REG_TPC1               0x61    /* TOS Priority Control 1 */
+-#define KS8995_REG_TPC2               0x62    /* TOS Priority Control 2 */
+-#define KS8995_REG_TPC3               0x63    /* TOS Priority Control 3 */
+-#define KS8995_REG_TPC4               0x64    /* TOS Priority Control 4 */
+-#define KS8995_REG_TPC5               0x65    /* TOS Priority Control 5 */
+-#define KS8995_REG_TPC6               0x66    /* TOS Priority Control 6 */
+-#define KS8995_REG_TPC7               0x67    /* TOS Priority Control 7 */
+-
+-#define KS8995_REG_MAC0               0x68    /* MAC address 0 */
+-#define KS8995_REG_MAC1               0x69    /* MAC address 1 */
+-#define KS8995_REG_MAC2               0x6a    /* MAC address 2 */
+-#define KS8995_REG_MAC3               0x6b    /* MAC address 3 */
+-#define KS8995_REG_MAC4               0x6c    /* MAC address 4 */
+-#define KS8995_REG_MAC5               0x6d    /* MAC address 5 */
+-
+-#define KS8995_REG_IAC0               0x6e    /* Indirect Access Control 0 */
+-#define KS8995_REG_IAC1               0x6f    /* Indirect Access Control 0 */
+-#define KS8995_REG_IAD7               0x70    /* Indirect Access Data 7 */
+-#define KS8995_REG_IAD6               0x71    /* Indirect Access Data 6 */
+-#define KS8995_REG_IAD5               0x72    /* Indirect Access Data 5 */
+-#define KS8995_REG_IAD4               0x73    /* Indirect Access Data 4 */
+-#define KS8995_REG_IAD3               0x74    /* Indirect Access Data 3 */
+-#define KS8995_REG_IAD2               0x75    /* Indirect Access Data 2 */
+-#define KS8995_REG_IAD1               0x76    /* Indirect Access Data 1 */
+-#define KS8995_REG_IAD0               0x77    /* Indirect Access Data 0 */
+-
+-#define KSZ8864_REG_ID1               0xfe    /* Chip ID in bit 7 */
+-
+-#define KS8995_REGS_SIZE      0x80
+-#define KSZ8864_REGS_SIZE     0x100
+-#define KSZ8795_REGS_SIZE     0x100
+-
+-#define ID1_CHIPID_M          0xf
+-#define ID1_CHIPID_S          4
+-#define ID1_REVISION_M                0x7
+-#define ID1_REVISION_S                1
+-#define ID1_START_SW          1       /* start the switch */
+-
+-#define FAMILY_KS8995         0x95
+-#define FAMILY_KSZ8795                0x87
+-#define CHIPID_M              0
+-#define KS8995_CHIP_ID                0x00
+-#define KSZ8864_CHIP_ID               0x01
+-#define KSZ8795_CHIP_ID               0x09
+-
+-#define KS8995_CMD_WRITE      0x02U
+-#define KS8995_CMD_READ               0x03U
+-
+-#define KS8995_RESET_DELAY    10 /* usec */
+-
+-enum ks8995_chip_variant {
+-      ks8995,
+-      ksz8864,
+-      ksz8795,
+-      max_variant
+-};
+-
+-struct ks8995_chip_params {
+-      char *name;
+-      int family_id;
+-      int chip_id;
+-      int regs_size;
+-      int addr_width;
+-      int addr_shift;
+-};
+-
+-static const struct ks8995_chip_params ks8995_chip[] = {
+-      [ks8995] = {
+-              .name = "KS8995MA",
+-              .family_id = FAMILY_KS8995,
+-              .chip_id = KS8995_CHIP_ID,
+-              .regs_size = KS8995_REGS_SIZE,
+-              .addr_width = 8,
+-              .addr_shift = 0,
+-      },
+-      [ksz8864] = {
+-              .name = "KSZ8864RMN",
+-              .family_id = FAMILY_KS8995,
+-              .chip_id = KSZ8864_CHIP_ID,
+-              .regs_size = KSZ8864_REGS_SIZE,
+-              .addr_width = 8,
+-              .addr_shift = 0,
+-      },
+-      [ksz8795] = {
+-              .name = "KSZ8795CLX",
+-              .family_id = FAMILY_KSZ8795,
+-              .chip_id = KSZ8795_CHIP_ID,
+-              .regs_size = KSZ8795_REGS_SIZE,
+-              .addr_width = 12,
+-              .addr_shift = 1,
+-      },
+-};
+-
+-struct ks8995_switch {
+-      struct spi_device       *spi;
+-      struct mutex            lock;
+-      struct gpio_desc        *reset_gpio;
+-      struct bin_attribute    regs_attr;
+-      const struct ks8995_chip_params *chip;
+-      int                     revision_id;
+-};
+-
+-static const struct spi_device_id ks8995_id[] = {
+-      {"ks8995", ks8995},
+-      {"ksz8864", ksz8864},
+-      {"ksz8795", ksz8795},
+-      { }
+-};
+-MODULE_DEVICE_TABLE(spi, ks8995_id);
+-
+-static const struct of_device_id ks8895_spi_of_match[] = {
+-      { .compatible = "micrel,ks8995" },
+-      { .compatible = "micrel,ksz8864" },
+-      { .compatible = "micrel,ksz8795" },
+-      { },
+-};
+-MODULE_DEVICE_TABLE(of, ks8895_spi_of_match);
+-
+-static inline u8 get_chip_id(u8 val)
+-{
+-      return (val >> ID1_CHIPID_S) & ID1_CHIPID_M;
+-}
+-
+-static inline u8 get_chip_rev(u8 val)
+-{
+-      return (val >> ID1_REVISION_S) & ID1_REVISION_M;
+-}
+-
+-/* create_spi_cmd - create a chip specific SPI command header
+- * @ks: pointer to switch instance
+- * @cmd: SPI command for switch
+- * @address: register address for command
+- *
+- * Different chip families use different bit pattern to address the switches
+- * registers:
+- *
+- * KS8995: 8bit command + 8bit address
+- * KSZ8795: 3bit command + 12bit address + 1bit TR (?)
+- */
+-static inline __be16 create_spi_cmd(struct ks8995_switch *ks, int cmd,
+-                                  unsigned address)
+-{
+-      u16 result = cmd;
+-
+-      /* make room for address (incl. address shift) */
+-      result <<= ks->chip->addr_width + ks->chip->addr_shift;
+-      /* add address */
+-      result |= address << ks->chip->addr_shift;
+-      /* SPI protocol needs big endian */
+-      return cpu_to_be16(result);
+-}
+-/* ------------------------------------------------------------------------ */
+-static int ks8995_read(struct ks8995_switch *ks, char *buf,
+-               unsigned offset, size_t count)
+-{
+-      __be16 cmd;
+-      struct spi_transfer t[2];
+-      struct spi_message m;
+-      int err;
+-
+-      cmd = create_spi_cmd(ks, KS8995_CMD_READ, offset);
+-      spi_message_init(&m);
+-
+-      memset(&t, 0, sizeof(t));
+-
+-      t[0].tx_buf = &cmd;
+-      t[0].len = sizeof(cmd);
+-      spi_message_add_tail(&t[0], &m);
+-
+-      t[1].rx_buf = buf;
+-      t[1].len = count;
+-      spi_message_add_tail(&t[1], &m);
+-
+-      mutex_lock(&ks->lock);
+-      err = spi_sync(ks->spi, &m);
+-      mutex_unlock(&ks->lock);
+-
+-      return err ? err : count;
+-}
+-
+-static int ks8995_write(struct ks8995_switch *ks, char *buf,
+-               unsigned offset, size_t count)
+-{
+-      __be16 cmd;
+-      struct spi_transfer t[2];
+-      struct spi_message m;
+-      int err;
+-
+-      cmd = create_spi_cmd(ks, KS8995_CMD_WRITE, offset);
+-      spi_message_init(&m);
+-
+-      memset(&t, 0, sizeof(t));
+-
+-      t[0].tx_buf = &cmd;
+-      t[0].len = sizeof(cmd);
+-      spi_message_add_tail(&t[0], &m);
+-
+-      t[1].tx_buf = buf;
+-      t[1].len = count;
+-      spi_message_add_tail(&t[1], &m);
+-
+-      mutex_lock(&ks->lock);
+-      err = spi_sync(ks->spi, &m);
+-      mutex_unlock(&ks->lock);
+-
+-      return err ? err : count;
+-}
+-
+-static inline int ks8995_read_reg(struct ks8995_switch *ks, u8 addr, u8 *buf)
+-{
+-      return ks8995_read(ks, buf, addr, 1) != 1;
+-}
+-
+-static inline int ks8995_write_reg(struct ks8995_switch *ks, u8 addr, u8 val)
+-{
+-      char buf = val;
+-
+-      return ks8995_write(ks, &buf, addr, 1) != 1;
+-}
+-
+-/* ------------------------------------------------------------------------ */
+-
+-static int ks8995_stop(struct ks8995_switch *ks)
+-{
+-      return ks8995_write_reg(ks, KS8995_REG_ID1, 0);
+-}
+-
+-static int ks8995_start(struct ks8995_switch *ks)
+-{
+-      return ks8995_write_reg(ks, KS8995_REG_ID1, 1);
+-}
+-
+-static int ks8995_reset(struct ks8995_switch *ks)
+-{
+-      int err;
+-
+-      err = ks8995_stop(ks);
+-      if (err)
+-              return err;
+-
+-      udelay(KS8995_RESET_DELAY);
+-
+-      return ks8995_start(ks);
+-}
+-
+-static ssize_t ks8995_registers_read(struct file *filp, struct kobject *kobj,
+-      struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
+-{
+-      struct device *dev;
+-      struct ks8995_switch *ks8995;
+-
+-      dev = kobj_to_dev(kobj);
+-      ks8995 = dev_get_drvdata(dev);
+-
+-      return ks8995_read(ks8995, buf, off, count);
+-}
+-
+-static ssize_t ks8995_registers_write(struct file *filp, struct kobject *kobj,
+-      struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
+-{
+-      struct device *dev;
+-      struct ks8995_switch *ks8995;
+-
+-      dev = kobj_to_dev(kobj);
+-      ks8995 = dev_get_drvdata(dev);
+-
+-      return ks8995_write(ks8995, buf, off, count);
+-}
+-
+-/* ks8995_get_revision - get chip revision
+- * @ks: pointer to switch instance
+- *
+- * Verify chip family and id and get chip revision.
+- */
+-static int ks8995_get_revision(struct ks8995_switch *ks)
+-{
+-      int err;
+-      u8 id0, id1, ksz8864_id;
+-
+-      /* read family id */
+-      err = ks8995_read_reg(ks, KS8995_REG_ID0, &id0);
+-      if (err) {
+-              err = -EIO;
+-              goto err_out;
+-      }
+-
+-      /* verify family id */
+-      if (id0 != ks->chip->family_id) {
+-              dev_err(&ks->spi->dev, "chip family id mismatch: expected 0x%02x but 0x%02x read\n",
+-                      ks->chip->family_id, id0);
+-              err = -ENODEV;
+-              goto err_out;
+-      }
+-
+-      switch (ks->chip->family_id) {
+-      case FAMILY_KS8995:
+-              /* try reading chip id at CHIP ID1 */
+-              err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
+-              if (err) {
+-                      err = -EIO;
+-                      goto err_out;
+-              }
+-
+-              /* verify chip id */
+-              if ((get_chip_id(id1) == CHIPID_M) &&
+-                  (get_chip_id(id1) == ks->chip->chip_id)) {
+-                      /* KS8995MA */
+-                      ks->revision_id = get_chip_rev(id1);
+-              } else if (get_chip_id(id1) != CHIPID_M) {
+-                      /* KSZ8864RMN */
+-                      err = ks8995_read_reg(ks, KS8995_REG_ID1, &ksz8864_id);
+-                      if (err) {
+-                              err = -EIO;
+-                              goto err_out;
+-                      }
+-
+-                      if ((ksz8864_id & 0x80) &&
+-                          (ks->chip->chip_id == KSZ8864_CHIP_ID)) {
+-                              ks->revision_id = get_chip_rev(id1);
+-                      }
+-
+-              } else {
+-                      dev_err(&ks->spi->dev, "unsupported chip id for KS8995 family: 0x%02x\n",
+-                              id1);
+-                      err = -ENODEV;
+-              }
+-              break;
+-      case FAMILY_KSZ8795:
+-              /* try reading chip id at CHIP ID1 */
+-              err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
+-              if (err) {
+-                      err = -EIO;
+-                      goto err_out;
+-              }
+-
+-              if (get_chip_id(id1) == ks->chip->chip_id) {
+-                      ks->revision_id = get_chip_rev(id1);
+-              } else {
+-                      dev_err(&ks->spi->dev, "unsupported chip id for KSZ8795 family: 0x%02x\n",
+-                              id1);
+-                      err = -ENODEV;
+-              }
+-              break;
+-      default:
+-              dev_err(&ks->spi->dev, "unsupported family id: 0x%02x\n", id0);
+-              err = -ENODEV;
+-              break;
+-      }
+-err_out:
+-      return err;
+-}
+-
+-static const struct bin_attribute ks8995_registers_attr = {
+-      .attr = {
+-              .name   = "registers",
+-              .mode   = 0600,
+-      },
+-      .size   = KS8995_REGS_SIZE,
+-      .read   = ks8995_registers_read,
+-      .write  = ks8995_registers_write,
+-};
+-
+-/* ------------------------------------------------------------------------ */
+-static int ks8995_probe(struct spi_device *spi)
+-{
+-      struct ks8995_switch *ks;
+-      int err;
+-      int variant = spi_get_device_id(spi)->driver_data;
+-
+-      if (variant >= max_variant) {
+-              dev_err(&spi->dev, "bad chip variant %d\n", variant);
+-              return -ENODEV;
+-      }
+-
+-      ks = devm_kzalloc(&spi->dev, sizeof(*ks), GFP_KERNEL);
+-      if (!ks)
+-              return -ENOMEM;
+-
+-      mutex_init(&ks->lock);
+-      ks->spi = spi;
+-      ks->chip = &ks8995_chip[variant];
+-
+-      ks->reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset",
+-                                               GPIOD_OUT_HIGH);
+-      err = PTR_ERR_OR_ZERO(ks->reset_gpio);
+-      if (err) {
+-              dev_err(&spi->dev,
+-                      "failed to get reset gpio: %d\n", err);
+-              return err;
+-      }
+-
+-      err = gpiod_set_consumer_name(ks->reset_gpio, "switch-reset");
+-      if (err)
+-              return err;
+-
+-      /* de-assert switch reset */
+-      /* FIXME: this likely requires a delay */
+-      gpiod_set_value_cansleep(ks->reset_gpio, 0);
+-
+-      spi_set_drvdata(spi, ks);
+-
+-      spi->mode = SPI_MODE_0;
+-      spi->bits_per_word = 8;
+-      err = spi_setup(spi);
+-      if (err) {
+-              dev_err(&spi->dev, "spi_setup failed, err=%d\n", err);
+-              return err;
+-      }
+-
+-      err = ks8995_get_revision(ks);
+-      if (err)
+-              return err;
+-
+-      memcpy(&ks->regs_attr, &ks8995_registers_attr, sizeof(ks->regs_attr));
+-      ks->regs_attr.size = ks->chip->regs_size;
+-
+-      err = ks8995_reset(ks);
+-      if (err)
+-              return err;
+-
+-      sysfs_attr_init(&ks->regs_attr.attr);
+-      err = sysfs_create_bin_file(&spi->dev.kobj, &ks->regs_attr);
+-      if (err) {
+-              dev_err(&spi->dev, "unable to create sysfs file, err=%d\n",
+-                                  err);
+-              return err;
+-      }
+-
+-      dev_info(&spi->dev, "%s device found, Chip ID:%x, Revision:%x\n",
+-               ks->chip->name, ks->chip->chip_id, ks->revision_id);
+-
+-      return 0;
+-}
+-
+-static void ks8995_remove(struct spi_device *spi)
+-{
+-      struct ks8995_switch *ks = spi_get_drvdata(spi);
+-
+-      sysfs_remove_bin_file(&spi->dev.kobj, &ks->regs_attr);
+-
+-      /* assert reset */
+-      gpiod_set_value_cansleep(ks->reset_gpio, 1);
+-}
+-
+-/* ------------------------------------------------------------------------ */
+-static struct spi_driver ks8995_driver = {
+-      .driver = {
+-              .name       = "spi-ks8995",
+-              .of_match_table = ks8895_spi_of_match,
+-      },
+-      .probe    = ks8995_probe,
+-      .remove   = ks8995_remove,
+-      .id_table = ks8995_id,
+-};
+-
+-module_spi_driver(ks8995_driver);
+-
+-MODULE_DESCRIPTION(DRV_DESC);
+-MODULE_VERSION(DRV_VERSION);
+-MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>");
+-MODULE_LICENSE("GPL v2");
diff --git a/target/linux/generic/backport-6.12/783-02-v6.18-net-dsa-ks8995-Add-proper-RESET-delay.patch b/target/linux/generic/backport-6.12/783-02-v6.18-net-dsa-ks8995-Add-proper-RESET-delay.patch
new file mode 100644 (file)
index 0000000..3b0b731
--- /dev/null
@@ -0,0 +1,40 @@
+From 7367bfc8b34d7ad5b0c06e19fe24533db22ed8ea Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linus.walleij@linaro.org>
+Date: Wed, 13 Aug 2025 23:43:04 +0200
+Subject: [PATCH 2/4] net: dsa: ks8995: Add proper RESET delay
+
+According to the datasheet we need to wait 100us before accessing
+any registers in the KS8995 after a reset de-assertion.
+
+Add this delay, if and only if we obtained a GPIO descriptor,
+otherwise it is just a pointless delay.
+
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250813-ks8995-to-dsa-v1-2-75c359ede3a5@linaro.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/ks8995.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/dsa/ks8995.c
++++ b/drivers/net/dsa/ks8995.c
+@@ -438,9 +438,15 @@ static int ks8995_probe(struct spi_devic
+       if (err)
+               return err;
+-      /* de-assert switch reset */
+-      /* FIXME: this likely requires a delay */
+-      gpiod_set_value_cansleep(ks->reset_gpio, 0);
++      if (ks->reset_gpio) {
++              /*
++               * If a reset line was obtained, wait for 100us after
++               * de-asserting RESET before accessing any registers, see
++               * the KS8995MA datasheet, page 44.
++               */
++              gpiod_set_value_cansleep(ks->reset_gpio, 0);
++              udelay(100);
++      }
+       spi_set_drvdata(spi, ks);
diff --git a/target/linux/generic/backport-6.12/783-03-v6.18-net-dsa-ks8995-Delete-sysfs-register-access.patch b/target/linux/generic/backport-6.12/783-03-v6.18-net-dsa-ks8995-Delete-sysfs-register-access.patch
new file mode 100644 (file)
index 0000000..78b9141
--- /dev/null
@@ -0,0 +1,109 @@
+From 0e8d3f3623a45f69b6641ff9921ecd0c5afaa4ca Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linus.walleij@linaro.org>
+Date: Wed, 13 Aug 2025 23:43:05 +0200
+Subject: [PATCH 3/4] net: dsa: ks8995: Delete sysfs register access
+
+There is some sysfs file to read and write registers randomly
+in the ks8995 driver.
+
+The contemporary way to achieve the same thing is to implement
+regmap abstractions, if needed. Delete this and implement
+regmap later if we want to be able to inspect individual registers.
+
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250813-ks8995-to-dsa-v1-3-75c359ede3a5@linaro.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/ks8995.c | 48 ----------------------------------------
+ 1 file changed, 48 deletions(-)
+
+--- a/drivers/net/dsa/ks8995.c
++++ b/drivers/net/dsa/ks8995.c
+@@ -140,7 +140,6 @@ struct ks8995_switch {
+       struct spi_device       *spi;
+       struct mutex            lock;
+       struct gpio_desc        *reset_gpio;
+-      struct bin_attribute    regs_attr;
+       const struct ks8995_chip_params *chip;
+       int                     revision_id;
+ };
+@@ -288,30 +287,6 @@ static int ks8995_reset(struct ks8995_sw
+       return ks8995_start(ks);
+ }
+-static ssize_t ks8995_registers_read(struct file *filp, struct kobject *kobj,
+-      struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
+-{
+-      struct device *dev;
+-      struct ks8995_switch *ks8995;
+-
+-      dev = kobj_to_dev(kobj);
+-      ks8995 = dev_get_drvdata(dev);
+-
+-      return ks8995_read(ks8995, buf, off, count);
+-}
+-
+-static ssize_t ks8995_registers_write(struct file *filp, struct kobject *kobj,
+-      struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
+-{
+-      struct device *dev;
+-      struct ks8995_switch *ks8995;
+-
+-      dev = kobj_to_dev(kobj);
+-      ks8995 = dev_get_drvdata(dev);
+-
+-      return ks8995_write(ks8995, buf, off, count);
+-}
+-
+ /* ks8995_get_revision - get chip revision
+  * @ks: pointer to switch instance
+  *
+@@ -395,16 +370,6 @@ err_out:
+       return err;
+ }
+-static const struct bin_attribute ks8995_registers_attr = {
+-      .attr = {
+-              .name   = "registers",
+-              .mode   = 0600,
+-      },
+-      .size   = KS8995_REGS_SIZE,
+-      .read   = ks8995_registers_read,
+-      .write  = ks8995_registers_write,
+-};
+-
+ /* ------------------------------------------------------------------------ */
+ static int ks8995_probe(struct spi_device *spi)
+ {
+@@ -462,21 +427,10 @@ static int ks8995_probe(struct spi_devic
+       if (err)
+               return err;
+-      memcpy(&ks->regs_attr, &ks8995_registers_attr, sizeof(ks->regs_attr));
+-      ks->regs_attr.size = ks->chip->regs_size;
+-
+       err = ks8995_reset(ks);
+       if (err)
+               return err;
+-      sysfs_attr_init(&ks->regs_attr.attr);
+-      err = sysfs_create_bin_file(&spi->dev.kobj, &ks->regs_attr);
+-      if (err) {
+-              dev_err(&spi->dev, "unable to create sysfs file, err=%d\n",
+-                                  err);
+-              return err;
+-      }
+-
+       dev_info(&spi->dev, "%s device found, Chip ID:%x, Revision:%x\n",
+                ks->chip->name, ks->chip->chip_id, ks->revision_id);
+@@ -487,8 +441,6 @@ static void ks8995_remove(struct spi_dev
+ {
+       struct ks8995_switch *ks = spi_get_drvdata(spi);
+-      sysfs_remove_bin_file(&spi->dev.kobj, &ks->regs_attr);
+-
+       /* assert reset */
+       gpiod_set_value_cansleep(ks->reset_gpio, 1);
+ }
diff --git a/target/linux/generic/backport-6.12/783-04-v6.18-net-dsa-ks8995-Add-basic-switch-set-up.patch b/target/linux/generic/backport-6.12/783-04-v6.18-net-dsa-ks8995-Add-basic-switch-set-up.patch
new file mode 100644 (file)
index 0000000..3454e11
--- /dev/null
@@ -0,0 +1,546 @@
+From 4b486b63e07313e1e3807f9d5e6636aa564e0fbc Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linus.walleij@linaro.org>
+Date: Wed, 13 Aug 2025 23:43:06 +0200
+Subject: [PATCH 4/4] net: dsa: ks8995: Add basic switch set-up
+
+We start to extend the KS8995 driver by simply registering it
+as a DSA device and implementing a few switch callbacks for
+STP set-up and such to begin with.
+
+No special tags or other advanced stuff: we use DSA_TAG_NONE
+and rely on the default set-up in the switch with the special
+DSA tags turned off. This makes the switch wire up properly
+to all its PHY's and simple bridge traffic works properly.
+
+After this the bridge DT bindings are respected, ports and their
+PHYs get connected to the switch and react appropriately through
+the phylib when cables are plugged in etc.
+
+Tested like this in a hacky OpenWrt image:
+
+Bring up conduit interface manually:
+ixp4xx_eth c8009000.ethernet eth0: eth0: link up,
+  speed 100 Mb/s, full duplex
+
+spi-ks8995 spi0.0: enable port 0
+spi-ks8995 spi0.0: set KS8995_REG_PC2 for port 0 to 06
+spi-ks8995 spi0.0 lan1: configuring for phy/mii link mode
+spi-ks8995 spi0.0 lan1: Link is Up - 100Mbps/Full - flow control rx/tx
+
+PING 169.254.1.1 (169.254.1.1): 56 data bytes
+64 bytes from 169.254.1.1: seq=0 ttl=64 time=1.629 ms
+64 bytes from 169.254.1.1: seq=1 ttl=64 time=0.951 ms
+
+I also tested SSH from the device to the host and it works fine.
+
+It also works fine to ping the device from the host and to SSH
+into the device from the host.
+
+This brings the ks8995 driver to a reasonable state where it can
+be used from the current device tree bindings and the existing
+device trees in the kernel.
+
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250813-ks8995-to-dsa-v1-4-75c359ede3a5@linaro.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/Kconfig  |   1 +
+ drivers/net/dsa/ks8995.c | 398 ++++++++++++++++++++++++++++++++++++++-
+ 2 files changed, 396 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/dsa/Kconfig
++++ b/drivers/net/dsa/Kconfig
+@@ -103,6 +103,7 @@ config NET_DSA_RZN1_A5PSW
+ config NET_DSA_KS8995
+       tristate "Micrel KS8995 family 5-ports 10/100 Ethernet switches"
+       depends on SPI
++      select NET_DSA_TAG_NONE
+       help
+         This driver supports the Micrel KS8995 family of 10/100 Mbit ethernet
+         switches, managed over SPI.
+--- a/drivers/net/dsa/ks8995.c
++++ b/drivers/net/dsa/ks8995.c
+@@ -3,6 +3,7 @@
+  * SPI driver for Micrel/Kendin KS8995M and KSZ8864RMN ethernet switches
+  *
+  * Copyright (C) 2008 Gabor Juhos <juhosg at openwrt.org>
++ * Copyright (C) 2025 Linus Walleij <linus.walleij@linaro.org>
+  *
+  * This file was based on: drivers/spi/at25.c
+  *     Copyright (C) 2006 David Brownell
+@@ -10,6 +11,9 @@
+ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
++#include <linux/bits.h>
++#include <linux/if_bridge.h>
++#include <linux/if_vlan.h>
+ #include <linux/types.h>
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+@@ -17,8 +21,8 @@
+ #include <linux/device.h>
+ #include <linux/gpio/consumer.h>
+ #include <linux/of.h>
+-
+ #include <linux/spi/spi.h>
++#include <net/dsa.h>
+ #define DRV_VERSION           "0.1.1"
+ #define DRV_DESC              "Micrel KS8995 Ethernet switch SPI driver"
+@@ -29,18 +33,59 @@
+ #define KS8995_REG_ID1                0x01    /* Chip ID1 */
+ #define KS8995_REG_GC0                0x02    /* Global Control 0 */
++
++#define KS8995_GC0_P5_PHY     BIT(3)  /* Port 5 PHY enabled */
++
+ #define KS8995_REG_GC1                0x03    /* Global Control 1 */
+ #define KS8995_REG_GC2                0x04    /* Global Control 2 */
++
++#define KS8995_GC2_HUGE               BIT(2)  /* Huge packet support */
++#define KS8995_GC2_LEGAL      BIT(1)  /* Legal size override */
++
+ #define KS8995_REG_GC3                0x05    /* Global Control 3 */
+ #define KS8995_REG_GC4                0x06    /* Global Control 4 */
++
++#define KS8995_GC4_10BT               BIT(4)  /* Force switch to 10Mbit */
++#define KS8995_GC4_MII_FLOW   BIT(5)  /* MII full-duplex flow control enable */
++#define KS8995_GC4_MII_HD     BIT(6)  /* MII half-duplex mode enable */
++
+ #define KS8995_REG_GC5                0x07    /* Global Control 5 */
+ #define KS8995_REG_GC6                0x08    /* Global Control 6 */
+ #define KS8995_REG_GC7                0x09    /* Global Control 7 */
+ #define KS8995_REG_GC8                0x0a    /* Global Control 8 */
+ #define KS8995_REG_GC9                0x0b    /* Global Control 9 */
+-#define KS8995_REG_PC(p, r)   ((0x10 * p) + r)         /* Port Control */
+-#define KS8995_REG_PS(p, r)   ((0x10 * p) + r + 0xe)  /* Port Status */
++#define KS8995_GC9_SPECIAL    BIT(0)  /* Special tagging mode (DSA) */
++
++/* In DSA the ports 1-4 are numbered 0-3 and the CPU port is port 4 */
++#define KS8995_REG_PC(p, r)   (0x10 + (0x10 * (p)) + (r)) /* Port Control */
++#define KS8995_REG_PS(p, r)   (0x1e + (0x10 * (p)) + (r)) /* Port Status */
++
++#define KS8995_REG_PC0                0x00    /* Port Control 0 */
++#define KS8995_REG_PC1                0x01    /* Port Control 1 */
++#define KS8995_REG_PC2                0x02    /* Port Control 2 */
++#define KS8995_REG_PC3                0x03    /* Port Control 3 */
++#define KS8995_REG_PC4                0x04    /* Port Control 4 */
++#define KS8995_REG_PC5                0x05    /* Port Control 5 */
++#define KS8995_REG_PC6                0x06    /* Port Control 6 */
++#define KS8995_REG_PC7                0x07    /* Port Control 7 */
++#define KS8995_REG_PC8                0x08    /* Port Control 8 */
++#define KS8995_REG_PC9                0x09    /* Port Control 9 */
++#define KS8995_REG_PC10               0x0a    /* Port Control 10 */
++#define KS8995_REG_PC11               0x0b    /* Port Control 11 */
++#define KS8995_REG_PC12               0x0c    /* Port Control 12 */
++#define KS8995_REG_PC13               0x0d    /* Port Control 13 */
++
++#define KS8995_PC0_TAG_INS    BIT(2)  /* Enable tag insertion on port */
++#define KS8995_PC0_TAG_REM    BIT(1)  /* Enable tag removal on port */
++#define KS8995_PC0_PRIO_EN    BIT(0)  /* Enable priority handling */
++
++#define KS8995_PC2_TXEN               BIT(2)  /* Enable TX on port */
++#define KS8995_PC2_RXEN               BIT(1)  /* Enable RX on port */
++#define KS8995_PC2_LEARN_DIS  BIT(0)  /* Disable learning on port */
++
++#define KS8995_PC13_TXDIS     BIT(6)  /* Disable transmitter */
++#define KS8995_PC13_PWDN      BIT(3)  /* Power down */
+ #define KS8995_REG_TPC0               0x60    /* TOS Priority Control 0 */
+ #define KS8995_REG_TPC1               0x61    /* TOS Priority Control 1 */
+@@ -91,6 +136,8 @@
+ #define KS8995_CMD_WRITE      0x02U
+ #define KS8995_CMD_READ               0x03U
++#define KS8995_CPU_PORT               4
++#define KS8995_NUM_PORTS      5 /* 5 ports including the CPU port */
+ #define KS8995_RESET_DELAY    10 /* usec */
+ enum ks8995_chip_variant {
+@@ -138,10 +185,13 @@ static const struct ks8995_chip_params k
+ struct ks8995_switch {
+       struct spi_device       *spi;
++      struct device           *dev;
++      struct dsa_switch       *ds;
+       struct mutex            lock;
+       struct gpio_desc        *reset_gpio;
+       const struct ks8995_chip_params *chip;
+       int                     revision_id;
++      unsigned int max_mtu[KS8995_NUM_PORTS];
+ };
+ static const struct spi_device_id ks8995_id[] = {
+@@ -370,6 +420,327 @@ err_out:
+       return err;
+ }
++static int ks8995_check_config(struct ks8995_switch *ks)
++{
++      int ret;
++      u8 val;
++
++      ret = ks8995_read_reg(ks, KS8995_REG_GC0, &val);
++      if (ret) {
++              dev_err(ks->dev, "failed to read KS8995_REG_GC0\n");
++              return ret;
++      }
++
++      dev_dbg(ks->dev, "port 5 PHY %senabled\n",
++              (val & KS8995_GC0_P5_PHY) ? "" : "not ");
++
++      val |= KS8995_GC0_P5_PHY;
++      ret = ks8995_write_reg(ks, KS8995_REG_GC0, val);
++      if (ret)
++              dev_err(ks->dev, "failed to set KS8995_REG_GC0\n");
++
++      dev_dbg(ks->dev, "set KS8995_REG_GC0 to 0x%02x\n", val);
++
++      return 0;
++}
++
++static void
++ks8995_mac_config(struct phylink_config *config, unsigned int mode,
++                const struct phylink_link_state *state)
++{
++}
++
++static void
++ks8995_mac_link_up(struct phylink_config *config, struct phy_device *phydev,
++                 unsigned int mode, phy_interface_t interface,
++                 int speed, int duplex, bool tx_pause, bool rx_pause)
++{
++      struct dsa_port *dp = dsa_phylink_to_port(config);
++      struct ks8995_switch *ks = dp->ds->priv;
++      int port = dp->index;
++      int ret;
++      u8 val;
++
++      /* Allow forcing the mode on the fixed CPU port, no autonegotiation.
++       * We assume autonegotiation works on the PHY-facing ports.
++       */
++      if (port != KS8995_CPU_PORT)
++              return;
++
++      dev_dbg(ks->dev, "MAC link up on CPU port (%d)\n", port);
++
++      ret = ks8995_read_reg(ks, KS8995_REG_GC4, &val);
++      if (ret) {
++              dev_err(ks->dev, "failed to read KS8995_REG_GC4\n");
++              return;
++      }
++
++      /* Conjure port config */
++      switch (speed) {
++      case SPEED_10:
++              dev_dbg(ks->dev, "set switch MII to 100Mbit mode\n");
++              val |= KS8995_GC4_10BT;
++              break;
++      case SPEED_100:
++      default:
++              dev_dbg(ks->dev, "set switch MII to 100Mbit mode\n");
++              val &= ~KS8995_GC4_10BT;
++              break;
++      }
++
++      if (duplex == DUPLEX_HALF) {
++              dev_dbg(ks->dev, "set switch MII to half duplex\n");
++              val |= KS8995_GC4_MII_HD;
++      } else {
++              dev_dbg(ks->dev, "set switch MII to full duplex\n");
++              val &= ~KS8995_GC4_MII_HD;
++      }
++
++      dev_dbg(ks->dev, "set KS8995_REG_GC4 to %02x\n", val);
++
++      /* Enable the CPU port */
++      ret = ks8995_write_reg(ks, KS8995_REG_GC4, val);
++      if (ret)
++              dev_err(ks->dev, "failed to set KS8995_REG_GC4\n");
++}
++
++static void
++ks8995_mac_link_down(struct phylink_config *config, unsigned int mode,
++                   phy_interface_t interface)
++{
++      struct dsa_port *dp = dsa_phylink_to_port(config);
++      struct ks8995_switch *ks = dp->ds->priv;
++      int port = dp->index;
++
++      if (port != KS8995_CPU_PORT)
++              return;
++
++      dev_dbg(ks->dev, "MAC link down on CPU port (%d)\n", port);
++
++      /* Disable the CPU port */
++}
++
++static const struct phylink_mac_ops ks8995_phylink_mac_ops = {
++      .mac_config = ks8995_mac_config,
++      .mac_link_up = ks8995_mac_link_up,
++      .mac_link_down = ks8995_mac_link_down,
++};
++
++static enum
++dsa_tag_protocol ks8995_get_tag_protocol(struct dsa_switch *ds,
++                                       int port,
++                                       enum dsa_tag_protocol mp)
++{
++      /* This switch actually uses the 6 byte KS8995 protocol */
++      return DSA_TAG_PROTO_NONE;
++}
++
++static int ks8995_setup(struct dsa_switch *ds)
++{
++      return 0;
++}
++
++static int ks8995_port_enable(struct dsa_switch *ds, int port,
++                            struct phy_device *phy)
++{
++      struct ks8995_switch *ks = ds->priv;
++
++      dev_dbg(ks->dev, "enable port %d\n", port);
++
++      return 0;
++}
++
++static void ks8995_port_disable(struct dsa_switch *ds, int port)
++{
++      struct ks8995_switch *ks = ds->priv;
++
++      dev_dbg(ks->dev, "disable port %d\n", port);
++}
++
++static int ks8995_port_pre_bridge_flags(struct dsa_switch *ds, int port,
++                                      struct switchdev_brport_flags flags,
++                                      struct netlink_ext_ack *extack)
++{
++      /* We support enabling/disabling learning */
++      if (flags.mask & ~(BR_LEARNING))
++              return -EINVAL;
++
++      return 0;
++}
++
++static int ks8995_port_bridge_flags(struct dsa_switch *ds, int port,
++                                  struct switchdev_brport_flags flags,
++                                  struct netlink_ext_ack *extack)
++{
++      struct ks8995_switch *ks = ds->priv;
++      int ret;
++      u8 val;
++
++      if (flags.mask & BR_LEARNING) {
++              ret = ks8995_read_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), &val);
++              if (ret) {
++                      dev_err(ks->dev, "failed to read KS8995_REG_PC2 on port %d\n", port);
++                      return ret;
++              }
++
++              if (flags.val & BR_LEARNING)
++                      val &= ~KS8995_PC2_LEARN_DIS;
++              else
++                      val |= KS8995_PC2_LEARN_DIS;
++
++              ret = ks8995_write_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), val);
++              if (ret) {
++                      dev_err(ks->dev, "failed to write KS8995_REG_PC2 on port %d\n", port);
++                      return ret;
++              }
++      }
++
++      return 0;
++}
++
++static void ks8995_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
++{
++      struct ks8995_switch *ks = ds->priv;
++      int ret;
++      u8 val;
++
++      ret = ks8995_read_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), &val);
++      if (ret) {
++              dev_err(ks->dev, "failed to read KS8995_REG_PC2 on port %d\n", port);
++              return;
++      }
++
++      /* Set the bits for the different STP states in accordance with
++       * the datasheet, pages 36-37 "Spanning tree support".
++       */
++      switch (state) {
++      case BR_STATE_DISABLED:
++      case BR_STATE_BLOCKING:
++      case BR_STATE_LISTENING:
++              val &= ~KS8995_PC2_TXEN;
++              val &= ~KS8995_PC2_RXEN;
++              val |= KS8995_PC2_LEARN_DIS;
++              break;
++      case BR_STATE_LEARNING:
++              val &= ~KS8995_PC2_TXEN;
++              val &= ~KS8995_PC2_RXEN;
++              val &= ~KS8995_PC2_LEARN_DIS;
++              break;
++      case BR_STATE_FORWARDING:
++              val |= KS8995_PC2_TXEN;
++              val |= KS8995_PC2_RXEN;
++              val &= ~KS8995_PC2_LEARN_DIS;
++              break;
++      default:
++              dev_err(ks->dev, "unknown bridge state requested\n");
++              return;
++      }
++
++      ret = ks8995_write_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), val);
++      if (ret) {
++              dev_err(ks->dev, "failed to write KS8995_REG_PC2 on port %d\n", port);
++              return;
++      }
++
++      dev_dbg(ks->dev, "set KS8995_REG_PC2 for port %d to %02x\n", port, val);
++}
++
++static void ks8995_phylink_get_caps(struct dsa_switch *dsa, int port,
++                                  struct phylink_config *config)
++{
++      unsigned long *interfaces = config->supported_interfaces;
++
++      if (port == KS8995_CPU_PORT)
++              __set_bit(PHY_INTERFACE_MODE_MII, interfaces);
++
++      if (port <= 3) {
++              /* Internal PHYs */
++              __set_bit(PHY_INTERFACE_MODE_INTERNAL, interfaces);
++              /* phylib default */
++              __set_bit(PHY_INTERFACE_MODE_MII, interfaces);
++      }
++
++      config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100;
++}
++
++/* Huge packet support up to 1916 byte packages "inclusive"
++ * which means that tags are included. If the bit is not set
++ * it is 1536 bytes "inclusive". We present the length without
++ * tags or ethernet headers. The setting affects all ports.
++ */
++static int ks8995_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
++{
++      struct ks8995_switch *ks = ds->priv;
++      unsigned int max_mtu;
++      int ret;
++      u8 val;
++      int i;
++
++      ks->max_mtu[port] = new_mtu;
++
++      /* Roof out the MTU for the entire switch to the greatest
++       * common denominator: the biggest set for any one port will
++       * be the biggest MTU for the switch.
++       */
++      max_mtu = ETH_DATA_LEN;
++      for (i = 0; i < KS8995_NUM_PORTS; i++) {
++              if (ks->max_mtu[i] > max_mtu)
++                      max_mtu = ks->max_mtu[i];
++      }
++
++      /* Translate to layer 2 size.
++       * Add ethernet and (possible) VLAN headers, and checksum to the size.
++       * For ETH_DATA_LEN (1500 bytes) this will add up to 1522 bytes.
++       */
++      max_mtu += VLAN_ETH_HLEN;
++      max_mtu += ETH_FCS_LEN;
++
++      ret = ks8995_read_reg(ks, KS8995_REG_GC2, &val);
++      if (ret) {
++              dev_err(ks->dev, "failed to read KS8995_REG_GC2\n");
++              return ret;
++      }
++
++      if (max_mtu <= 1522) {
++              val &= ~KS8995_GC2_HUGE;
++              val &= ~KS8995_GC2_LEGAL;
++      } else if (max_mtu > 1522 && max_mtu <= 1536) {
++              /* This accepts packets up to 1536 bytes */
++              val &= ~KS8995_GC2_HUGE;
++              val |= KS8995_GC2_LEGAL;
++      } else {
++              /* This accepts packets up to 1916 bytes */
++              val |= KS8995_GC2_HUGE;
++              val |= KS8995_GC2_LEGAL;
++      }
++
++      dev_dbg(ks->dev, "new max MTU %d bytes (inclusive)\n", max_mtu);
++
++      ret = ks8995_write_reg(ks, KS8995_REG_GC2, val);
++      if (ret)
++              dev_err(ks->dev, "failed to set KS8995_REG_GC2\n");
++
++      return ret;
++}
++
++static int ks8995_get_max_mtu(struct dsa_switch *ds, int port)
++{
++      return 1916 - ETH_HLEN - ETH_FCS_LEN;
++}
++
++static const struct dsa_switch_ops ks8995_ds_ops = {
++      .get_tag_protocol = ks8995_get_tag_protocol,
++      .setup = ks8995_setup,
++      .port_pre_bridge_flags = ks8995_port_pre_bridge_flags,
++      .port_bridge_flags = ks8995_port_bridge_flags,
++      .port_enable = ks8995_port_enable,
++      .port_disable = ks8995_port_disable,
++      .port_stp_state_set = ks8995_port_stp_state_set,
++      .port_change_mtu = ks8995_change_mtu,
++      .port_max_mtu = ks8995_get_max_mtu,
++      .phylink_get_caps = ks8995_phylink_get_caps,
++};
++
+ /* ------------------------------------------------------------------------ */
+ static int ks8995_probe(struct spi_device *spi)
+ {
+@@ -388,6 +759,7 @@ static int ks8995_probe(struct spi_devic
+       mutex_init(&ks->lock);
+       ks->spi = spi;
++      ks->dev = &spi->dev;
+       ks->chip = &ks8995_chip[variant];
+       ks->reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset",
+@@ -434,6 +806,25 @@ static int ks8995_probe(struct spi_devic
+       dev_info(&spi->dev, "%s device found, Chip ID:%x, Revision:%x\n",
+                ks->chip->name, ks->chip->chip_id, ks->revision_id);
++      err = ks8995_check_config(ks);
++      if (err)
++              return err;
++
++      ks->ds = devm_kzalloc(&spi->dev, sizeof(*ks->ds), GFP_KERNEL);
++      if (!ks->ds)
++              return -ENOMEM;
++
++      ks->ds->dev = &spi->dev;
++      ks->ds->num_ports = KS8995_NUM_PORTS;
++      ks->ds->ops = &ks8995_ds_ops;
++      ks->ds->phylink_mac_ops = &ks8995_phylink_mac_ops;
++      ks->ds->priv = ks;
++
++      err = dsa_register_switch(ks->ds);
++      if (err)
++              return dev_err_probe(&spi->dev, err,
++                                   "unable to register DSA switch\n");
++
+       return 0;
+ }
+@@ -441,6 +832,7 @@ static void ks8995_remove(struct spi_dev
+ {
+       struct ks8995_switch *ks = spi_get_drvdata(spi);
++      dsa_unregister_switch(ks->ds);
+       /* assert reset */
+       gpiod_set_value_cansleep(ks->reset_gpio, 1);
+ }
diff --git a/target/linux/generic/backport-6.12/784-01-v6.17-net-phy-realtek-add-error-handling-to-rtl8211f_get_w.patch b/target/linux/generic/backport-6.12/784-01-v6.17-net-phy-realtek-add-error-handling-to-rtl8211f_get_w.patch
new file mode 100644 (file)
index 0000000..5f3b119
--- /dev/null
@@ -0,0 +1,39 @@
+From a9b24b3583ae1da7dbda031f141264f2da260219 Mon Sep 17 00:00:00 2001
+From: Daniel Braunwarth <daniel.braunwarth@kuka.com>
+Date: Tue, 24 Jun 2025 16:17:33 +0200
+Subject: [PATCH] net: phy: realtek: add error handling to rtl8211f_get_wol
+
+We should check if the WOL settings was successfully read from the PHY.
+
+In case this fails we cannot just use the error code and proceed.
+
+Signed-off-by: Daniel Braunwarth <daniel.braunwarth@kuka.com>
+Reported-by: Jon Hunter <jonathanh@nvidia.com>
+Closes: https://lore.kernel.org/baaa083b-9a69-460f-ab35-2a7cb3246ffd@nvidia.com
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250624-realtek_fixes-v1-1-02a0b7c369bc@kuka.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 10 ++++++++--
+ 1 file changed, 8 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -436,9 +436,15 @@ static irqreturn_t rtl8211f_handle_inter
+ static void rtl8211f_get_wol(struct phy_device *dev, struct ethtool_wolinfo *wol)
+ {
++      int wol_events;
++
+       wol->supported = WAKE_MAGIC;
+-      if (phy_read_paged(dev, RTL8211F_WOL_SETTINGS_PAGE, RTL8211F_WOL_SETTINGS_EVENTS)
+-          & RTL8211F_WOL_EVENT_MAGIC)
++
++      wol_events = phy_read_paged(dev, RTL8211F_WOL_SETTINGS_PAGE, RTL8211F_WOL_SETTINGS_EVENTS);
++      if (wol_events < 0)
++              return;
++
++      if (wol_events & RTL8211F_WOL_EVENT_MAGIC)
+               wol->wolopts = WAKE_MAGIC;
+ }
diff --git a/target/linux/generic/backport-6.12/784-02-v6.18-net-phy-realtek-Avoid-PHYCR2-access-if-PHYCR2-not-pr.patch b/target/linux/generic/backport-6.12/784-02-v6.18-net-phy-realtek-Avoid-PHYCR2-access-if-PHYCR2-not-pr.patch
new file mode 100644 (file)
index 0000000..681b234
--- /dev/null
@@ -0,0 +1,61 @@
+From 2c67301584f2671e320236df6bbe75ae09feb4d0 Mon Sep 17 00:00:00 2001
+From: Marek Vasut <marek.vasut@mailbox.org>
+Date: Sat, 11 Oct 2025 13:02:49 +0200
+Subject: [PATCH] net: phy: realtek: Avoid PHYCR2 access if PHYCR2 not present
+
+The driver is currently checking for PHYCR2 register presence in
+rtl8211f_config_init(), but it does so after accessing PHYCR2 to
+disable EEE. This was introduced in commit bfc17c165835 ("net:
+phy: realtek: disable PHY-mode EEE"). Move the PHYCR2 presence
+test before the EEE disablement and simplify the code.
+
+Fixes: bfc17c165835 ("net: phy: realtek: disable PHY-mode EEE")
+Signed-off-by: Marek Vasut <marek.vasut@mailbox.org>
+Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/20251011110309.12664-1-marek.vasut@mailbox.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 23 +++++++++++------------
+ 1 file changed, 11 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -589,26 +589,25 @@ static int rtl8211f_config_init(struct p
+                       str_enabled_disabled(val_rxdly));
+       }
++      if (!priv->has_phycr2)
++              return 0;
++
+       /* Disable PHY-mode EEE so LPI is passed to the MAC */
+       ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2,
+                              RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0);
+       if (ret)
+               return ret;
+-      if (priv->has_phycr2) {
+-              ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE,
+-                                     RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN,
+-                                     priv->phycr2);
+-              if (ret < 0) {
+-                      dev_err(dev, "clkout configuration failed: %pe\n",
+-                              ERR_PTR(ret));
+-                      return ret;
+-              }
+-
+-              return genphy_soft_reset(phydev);
++      ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE,
++                             RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN,
++                             priv->phycr2);
++      if (ret < 0) {
++              dev_err(dev, "clkout configuration failed: %pe\n",
++                      ERR_PTR(ret));
++              return ret;
+       }
+-      return 0;
++      return genphy_soft_reset(phydev);
+ }
+ static int rtl821x_suspend(struct phy_device *phydev)
diff --git a/target/linux/generic/backport-6.12/784-02-v6.18-net-phy-realtek-fix-RTL8211F-wake-on-lan-support.patch b/target/linux/generic/backport-6.12/784-02-v6.18-net-phy-realtek-fix-RTL8211F-wake-on-lan-support.patch
new file mode 100644 (file)
index 0000000..b5c4f8f
--- /dev/null
@@ -0,0 +1,352 @@
+From b826bf795564ddef6402cf2cb522ae035bd117ae Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Wed, 13 Aug 2025 11:04:45 +0100
+Subject: [PATCH] net: phy: realtek: fix RTL8211F wake-on-lan support
+
+Implement Wake-on-Lan for RTL8211F correctly. The existing
+implementation has multiple issues:
+
+1. It assumes that Wake-on-Lan can always be used, whether or not the
+   interrupt is wired, and whether or not the interrupt is capable of
+   waking the system. This breaks the ability for MAC drivers to detect
+   whether the PHY WoL is functional.
+2. switching the interrupt pin in the .set_wol() method to PMEB mode
+   immediately silences link-state interrupts, which breaks phylib
+   when interrupts are being used rather than polling mode.
+3. the code claiming to "reset WOL status" was doing nothing of the
+   sort. Bit 15 in page 0xd8a register 17 controls WoL reset, and
+   needs to be pulsed low to reset the WoL state. This bit was always
+   written as '1', resulting in no reset.
+4. not resetting WoL state results in the PMEB pin remaining asserted,
+   which in turn leads to an interrupt storm. Only resetting the WoL
+   state in .set_wol() is not sufficient.
+5. PMEB mode does not allow software detection of the wake-up event as
+   there is no status bit to indicate we received the WoL packet.
+6. across reboots of at least the Jetson Xavier NX system, the WoL
+   configuration is preserved.
+
+Fix all of these issues by essentially rewriting the support. We:
+1. clear the WoL event enable register at probe time.
+2. detect whether we can support wake-up by having a valid interrupt,
+   and the "wakeup-source" property in DT. If we can, then we mark
+   the MDIO device as wakeup capable, and associate the interrupt
+   with the wakeup source.
+3. arrange for the get_wol() and set_wol() implementations to handle
+   the case where the MDIO device has not been marked as wakeup
+   capable (thereby returning no WoL support, and refusing to enable
+   WoL support.)
+4. avoid switching to PMEB mode, instead using INTB mode with the
+   interrupt enable, reconfiguring the interrupt enables at suspend
+   time, and restoring their original state at resume time (we track
+   the state of the interrupt enable register in .config_intr()
+   register.)
+5. move WoL reset from .set_wol() to the suspend function to ensure
+   that WoL state is cleared prior to suspend. This is necessary
+   after the PME interrupt has been enabled as a second WoL packet
+   will not re-raise a previously cleared PME interrupt.
+6. when a PME interrupt (for wakeup) is asserted, pass this to the
+   PM wakeup so it knows which device woke the system.
+
+This fixes WoL support in the Realtek RTL8211F driver when used on the
+nVidia Jetson Xavier NX platform, and needs to be applied before stmmac
+patches which allow these platforms to forward the ethtool WoL commands
+to the Realtek PHY.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/E1um8Ld-008jxD-Mc@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 172 ++++++++++++++++++++-----
+ 1 file changed, 140 insertions(+), 32 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -10,6 +10,7 @@
+ #include <linux/bitops.h>
+ #include <linux/of.h>
+ #include <linux/phy.h>
++#include <linux/pm_wakeirq.h>
+ #include <linux/netdevice.h>
+ #include <linux/module.h>
+ #include <linux/delay.h>
+@@ -31,6 +32,7 @@
+ #define RTL821x_INER                          0x12
+ #define RTL8211B_INER_INIT                    0x6400
+ #define RTL8211E_INER_LINK_STATUS             BIT(10)
++#define RTL8211F_INER_PME                     BIT(7)
+ #define RTL8211F_INER_LINK_STATUS             BIT(4)
+ #define RTL821x_INSR                          0x13
+@@ -96,17 +98,13 @@
+ #define RTL8211F_RXCR                         0x15
+ #define RTL8211F_RX_DELAY                     BIT(3)
+-/* RTL8211F WOL interrupt configuration */
+-#define RTL8211F_INTBCR_PAGE                  0xd40
+-#define RTL8211F_INTBCR                               0x16
+-#define RTL8211F_INTBCR_INTB_PMEB             BIT(5)
+-
+ /* RTL8211F WOL settings */
+-#define RTL8211F_WOL_SETTINGS_PAGE            0xd8a
++#define RTL8211F_WOL_PAGE             0xd8a
+ #define RTL8211F_WOL_SETTINGS_EVENTS          16
+ #define RTL8211F_WOL_EVENT_MAGIC              BIT(12)
+-#define RTL8211F_WOL_SETTINGS_STATUS          17
+-#define RTL8211F_WOL_STATUS_RESET             (BIT(15) | 0x1fff)
++#define RTL8211F_WOL_RST_RMSQ         17
++#define RTL8211F_WOL_RG_RSTB                  BIT(15)
++#define RTL8211F_WOL_RMSQ                     0x1fff
+ /* RTL8211F Unique phyiscal and multicast address (WOL) */
+ #define RTL8211F_PHYSICAL_ADDR_PAGE           0xd8c
+@@ -172,7 +170,8 @@ struct rtl821x_priv {
+       u16 phycr2;
+       bool has_phycr2;
+       struct clk *clk;
+-      u32 saved_wolopts;
++      /* rtl8211f */
++      u16 iner;
+ };
+ static int rtl821x_read_page(struct phy_device *phydev)
+@@ -255,6 +254,34 @@ static int rtl821x_probe(struct phy_devi
+       return 0;
+ }
++static int rtl8211f_probe(struct phy_device *phydev)
++{
++      struct device *dev = &phydev->mdio.dev;
++      int ret;
++
++      ret = rtl821x_probe(phydev);
++      if (ret < 0)
++              return ret;
++
++      /* Disable all PME events */
++      ret = phy_write_paged(phydev, RTL8211F_WOL_PAGE,
++                            RTL8211F_WOL_SETTINGS_EVENTS, 0);
++      if (ret < 0)
++              return ret;
++
++      /* Mark this PHY as wakeup capable and register the interrupt as a
++       * wakeup IRQ if the PHY is marked as a wakeup source in firmware,
++       * and the interrupt is valid.
++       */
++      if (device_property_read_bool(dev, "wakeup-source") &&
++          phy_interrupt_is_valid(phydev)) {
++              device_set_wakeup_capable(dev, true);
++              devm_pm_set_wake_irq(dev, phydev->irq);
++      }
++
++      return ret;
++}
++
+ static int rtl8201_ack_interrupt(struct phy_device *phydev)
+ {
+       int err;
+@@ -352,6 +379,7 @@ static int rtl8211e_config_intr(struct p
+ static int rtl8211f_config_intr(struct phy_device *phydev)
+ {
++      struct rtl821x_priv *priv = phydev->priv;
+       u16 val;
+       int err;
+@@ -362,8 +390,10 @@ static int rtl8211f_config_intr(struct p
+               val = RTL8211F_INER_LINK_STATUS;
+               err = phy_write_paged(phydev, 0xa42, RTL821x_INER, val);
++              if (err == 0)
++                      priv->iner = val;
+       } else {
+-              val = 0;
++              priv->iner = val = 0;
+               err = phy_write_paged(phydev, 0xa42, RTL821x_INER, val);
+               if (err)
+                       return err;
+@@ -426,21 +456,34 @@ static irqreturn_t rtl8211f_handle_inter
+               return IRQ_NONE;
+       }
+-      if (!(irq_status & RTL8211F_INER_LINK_STATUS))
+-              return IRQ_NONE;
++      if (irq_status & RTL8211F_INER_LINK_STATUS) {
++              phy_trigger_machine(phydev);
++              return IRQ_HANDLED;
++      }
+-      phy_trigger_machine(phydev);
++      if (irq_status & RTL8211F_INER_PME) {
++              pm_wakeup_event(&phydev->mdio.dev, 0);
++              return IRQ_HANDLED;
++      }
+-      return IRQ_HANDLED;
++      return IRQ_NONE;
+ }
+ static void rtl8211f_get_wol(struct phy_device *dev, struct ethtool_wolinfo *wol)
+ {
+       int wol_events;
++      /* If the PHY is not capable of waking the system, then WoL can not
++       * be supported.
++       */
++      if (!device_can_wakeup(&dev->mdio.dev)) {
++              wol->supported = 0;
++              return;
++      }
++
+       wol->supported = WAKE_MAGIC;
+-      wol_events = phy_read_paged(dev, RTL8211F_WOL_SETTINGS_PAGE, RTL8211F_WOL_SETTINGS_EVENTS);
++      wol_events = phy_read_paged(dev, RTL8211F_WOL_PAGE, RTL8211F_WOL_SETTINGS_EVENTS);
+       if (wol_events < 0)
+               return;
+@@ -453,6 +496,9 @@ static int rtl8211f_set_wol(struct phy_d
+       const u8 *mac_addr = dev->attached_dev->dev_addr;
+       int oldpage;
++      if (!device_can_wakeup(&dev->mdio.dev))
++              return -EOPNOTSUPP;
++
+       oldpage = phy_save_page(dev);
+       if (oldpage < 0)
+               goto err;
+@@ -464,25 +510,23 @@ static int rtl8211f_set_wol(struct phy_d
+               __phy_write(dev, RTL8211F_PHYSICAL_ADDR_WORD1, mac_addr[3] << 8 | (mac_addr[2]));
+               __phy_write(dev, RTL8211F_PHYSICAL_ADDR_WORD2, mac_addr[5] << 8 | (mac_addr[4]));
+-              /* Enable magic packet matching and reset WOL status */
+-              rtl821x_write_page(dev, RTL8211F_WOL_SETTINGS_PAGE);
++              /* Enable magic packet matching */
++              rtl821x_write_page(dev, RTL8211F_WOL_PAGE);
+               __phy_write(dev, RTL8211F_WOL_SETTINGS_EVENTS, RTL8211F_WOL_EVENT_MAGIC);
+-              __phy_write(dev, RTL8211F_WOL_SETTINGS_STATUS, RTL8211F_WOL_STATUS_RESET);
+-
+-              /* Enable the WOL interrupt */
+-              rtl821x_write_page(dev, RTL8211F_INTBCR_PAGE);
+-              __phy_set_bits(dev, RTL8211F_INTBCR, RTL8211F_INTBCR_INTB_PMEB);
++              /* Set the maximum packet size, and assert WoL reset */
++              __phy_write(dev, RTL8211F_WOL_RST_RMSQ, RTL8211F_WOL_RMSQ);
+       } else {
+-              /* Disable the WOL interrupt */
+-              rtl821x_write_page(dev, RTL8211F_INTBCR_PAGE);
+-              __phy_clear_bits(dev, RTL8211F_INTBCR, RTL8211F_INTBCR_INTB_PMEB);
+-
+-              /* Disable magic packet matching and reset WOL status */
+-              rtl821x_write_page(dev, RTL8211F_WOL_SETTINGS_PAGE);
++              /* Disable magic packet matching */
++              rtl821x_write_page(dev, RTL8211F_WOL_PAGE);
+               __phy_write(dev, RTL8211F_WOL_SETTINGS_EVENTS, 0);
+-              __phy_write(dev, RTL8211F_WOL_SETTINGS_STATUS, RTL8211F_WOL_STATUS_RESET);
++
++              /* Place WoL in reset */
++              __phy_clear_bits(dev, RTL8211F_WOL_RST_RMSQ,
++                               RTL8211F_WOL_RG_RSTB);
+       }
++      device_set_wakeup_enable(&dev->mdio.dev, !!(wol->wolopts & WAKE_MAGIC));
++
+ err:
+       return phy_restore_page(dev, oldpage, 0);
+ }
+@@ -627,6 +671,52 @@ static int rtl821x_suspend(struct phy_de
+       return ret;
+ }
++static int rtl8211f_suspend(struct phy_device *phydev)
++{
++      u16 wol_rst;
++      int ret;
++
++      ret = rtl821x_suspend(phydev);
++      if (ret < 0)
++              return ret;
++
++      /* If a PME event is enabled, then configure the interrupt for
++       * PME events only, disabling link interrupt. We avoid switching
++       * to PMEB mode as we don't have a status bit for that.
++       */
++      if (device_may_wakeup(&phydev->mdio.dev)) {
++              ret = phy_write_paged(phydev, 0xa42, RTL821x_INER,
++                                    RTL8211F_INER_PME);
++              if (ret < 0)
++                      goto err;
++
++              /* Read the INSR to clear any pending interrupt */
++              phy_read_paged(phydev, RTL8211F_INSR_PAGE, RTL8211F_INSR);
++
++              /* Reset the WoL to ensure that an event is picked up.
++               * Unless we do this, even if we receive another packet,
++               * we may not have a PME interrupt raised.
++               */
++              ret = phy_read_paged(phydev, RTL8211F_WOL_PAGE,
++                                   RTL8211F_WOL_RST_RMSQ);
++              if (ret < 0)
++                      goto err;
++
++              wol_rst = ret & ~RTL8211F_WOL_RG_RSTB;
++              ret = phy_write_paged(phydev, RTL8211F_WOL_PAGE,
++                                    RTL8211F_WOL_RST_RMSQ, wol_rst);
++              if (ret < 0)
++                      goto err;
++
++              wol_rst |= RTL8211F_WOL_RG_RSTB;
++              ret = phy_write_paged(phydev, RTL8211F_WOL_PAGE,
++                                    RTL8211F_WOL_RST_RMSQ, wol_rst);
++      }
++
++err:
++      return ret;
++}
++
+ static int rtl821x_resume(struct phy_device *phydev)
+ {
+       struct rtl821x_priv *priv = phydev->priv;
+@@ -644,6 +734,24 @@ static int rtl821x_resume(struct phy_dev
+       return 0;
+ }
++static int rtl8211f_resume(struct phy_device *phydev)
++{
++      struct rtl821x_priv *priv = phydev->priv;
++      int ret;
++
++      ret = rtl821x_resume(phydev);
++      if (ret < 0)
++              return ret;
++
++      /* If the device was programmed for a PME event, restore the interrupt
++       * enable so phylib can receive link state interrupts.
++       */
++      if (device_may_wakeup(&phydev->mdio.dev))
++              ret = phy_write_paged(phydev, 0xa42, RTL821x_INER, priv->iner);
++
++      return ret;
++}
++
+ static int rtl8211x_led_hw_is_supported(struct phy_device *phydev, u8 index,
+                                       unsigned long rules)
+ {
+@@ -1639,15 +1747,15 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               PHY_ID_MATCH_EXACT(0x001cc916),
+               .name           = "RTL8211F Gigabit Ethernet",
+-              .probe          = rtl821x_probe,
++              .probe          = rtl8211f_probe,
+               .config_init    = &rtl8211f_config_init,
+               .read_status    = rtlgen_read_status,
+               .config_intr    = &rtl8211f_config_intr,
+               .handle_interrupt = rtl8211f_handle_interrupt,
+               .set_wol        = rtl8211f_set_wol,
+               .get_wol        = rtl8211f_get_wol,
+-              .suspend        = rtl821x_suspend,
+-              .resume         = rtl821x_resume,
++              .suspend        = rtl8211f_suspend,
++              .resume         = rtl8211f_resume,
+               .read_page      = rtl821x_read_page,
+               .write_page     = rtl821x_write_page,
+               .flags          = PHY_ALWAYS_CALL_SUSPEND,
diff --git a/target/linux/generic/backport-6.12/784-03-v6.18-net-phy-realtek-fix-rtl8221b-vm-cg-name.patch b/target/linux/generic/backport-6.12/784-03-v6.18-net-phy-realtek-fix-rtl8221b-vm-cg-name.patch
new file mode 100644 (file)
index 0000000..ab319f0
--- /dev/null
@@ -0,0 +1,70 @@
+From ffff5c8fc2af2218a3332b3d5b97654599d50cde Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Thu, 16 Oct 2025 21:22:52 +0200
+Subject: [PATCH] net: phy: realtek: fix rtl8221b-vm-cg name
+
+When splitting the RTL8221B-VM-CG into C22 and C45 variants, the name was
+accidentally changed to RTL8221B-VN-CG. This patch brings back the previous
+part number.
+
+Fixes: ad5ce743a6b0 ("net: phy: realtek: Add driver instances for rtl8221b via Clause 45")
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/20251016192325.2306757-1-olek2@wp.pl
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 16 ++++++++--------
+ 1 file changed, 8 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -154,7 +154,7 @@
+ #define RTL_8211FVD_PHYID                     0x001cc878
+ #define RTL_8221B                             0x001cc840
+ #define RTL_8221B_VB_CG                               0x001cc849
+-#define RTL_8221B_VN_CG                               0x001cc84a
++#define RTL_8221B_VM_CG                               0x001cc84a
+ #define RTL_8251B                             0x001cc862
+ #define RTL_8261C                             0x001cc890
+@@ -1498,16 +1498,16 @@ static int rtl8221b_vb_cg_c45_match_phy_
+       return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, true);
+ }
+-static int rtl8221b_vn_cg_c22_match_phy_device(struct phy_device *phydev,
++static int rtl8221b_vm_cg_c22_match_phy_device(struct phy_device *phydev,
+                                              const struct phy_driver *phydrv)
+ {
+-      return rtlgen_is_c45_match(phydev, RTL_8221B_VN_CG, false);
++      return rtlgen_is_c45_match(phydev, RTL_8221B_VM_CG, false);
+ }
+-static int rtl8221b_vn_cg_c45_match_phy_device(struct phy_device *phydev,
++static int rtl8221b_vm_cg_c45_match_phy_device(struct phy_device *phydev,
+                                              const struct phy_driver *phydrv)
+ {
+-      return rtlgen_is_c45_match(phydev, RTL_8221B_VN_CG, true);
++      return rtlgen_is_c45_match(phydev, RTL_8221B_VM_CG, true);
+ }
+ static int rtl_internal_nbaset_match_phy_device(struct phy_device *phydev,
+@@ -1854,7 +1854,7 @@ static struct phy_driver realtek_drvs[]
+               .suspend        = genphy_c45_pma_suspend,
+               .resume         = rtlgen_c45_resume,
+       }, {
+-              .match_phy_device = rtl8221b_vn_cg_c22_match_phy_device,
++              .match_phy_device = rtl8221b_vm_cg_c22_match_phy_device,
+               .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C22)",
+               .probe          = rtl822x_probe,
+               .get_features   = rtl822x_get_features,
+@@ -1867,8 +1867,8 @@ static struct phy_driver realtek_drvs[]
+               .read_page      = rtl821x_read_page,
+               .write_page     = rtl821x_write_page,
+       }, {
+-              .match_phy_device = rtl8221b_vn_cg_c45_match_phy_device,
+-              .name           = "RTL8221B-VN-CG 2.5Gbps PHY (C45)",
++              .match_phy_device = rtl8221b_vm_cg_c45_match_phy_device,
++              .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C45)",
+               .probe          = rtl822x_probe,
+               .config_init    = rtl822xb_config_init,
+               .get_rate_matching = rtl822xb_get_rate_matching,
diff --git a/target/linux/generic/backport-6.12/784-03-v6.18-net-phy-realtek-support-for-TRIGGER_NETDEV_LINK-on-R.patch b/target/linux/generic/backport-6.12/784-03-v6.18-net-phy-realtek-support-for-TRIGGER_NETDEV_LINK-on-R.patch
new file mode 100644 (file)
index 0000000..dc8d247
--- /dev/null
@@ -0,0 +1,105 @@
+From f63f21e82ecafd288b100ea161247820bf1e92c4 Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Mon, 25 Aug 2025 23:09:49 +0200
+Subject: [PATCH] net: phy: realtek: support for TRIGGER_NETDEV_LINK on
+ RTL8211E and RTL8211F
+
+This patch adds support for the TRIGGER_NETDEV_LINK trigger. It activates
+the LED when a link is established, regardless of the speed.
+
+Tested on Orange Pi PC2 with RTL8211E PHY.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250825211059.143231-1-olek2@wp.pl
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 39 +++++++++++++++++++++-----
+ 1 file changed, 32 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -755,7 +755,8 @@ static int rtl8211f_resume(struct phy_de
+ static int rtl8211x_led_hw_is_supported(struct phy_device *phydev, u8 index,
+                                       unsigned long rules)
+ {
+-      const unsigned long mask = BIT(TRIGGER_NETDEV_LINK_10) |
++      const unsigned long mask = BIT(TRIGGER_NETDEV_LINK) |
++                                 BIT(TRIGGER_NETDEV_LINK_10) |
+                                  BIT(TRIGGER_NETDEV_LINK_100) |
+                                  BIT(TRIGGER_NETDEV_LINK_1000) |
+                                  BIT(TRIGGER_NETDEV_RX) |
+@@ -813,6 +814,12 @@ static int rtl8211f_led_hw_control_get(s
+       if (val & RTL8211F_LEDCR_LINK_1000)
+               __set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++      if ((val & RTL8211F_LEDCR_LINK_10) &&
++          (val & RTL8211F_LEDCR_LINK_100) &&
++          (val & RTL8211F_LEDCR_LINK_1000)) {
++              __set_bit(TRIGGER_NETDEV_LINK, rules);
++      }
++
+       if (val & RTL8211F_LEDCR_ACT_TXRX) {
+               __set_bit(TRIGGER_NETDEV_RX, rules);
+               __set_bit(TRIGGER_NETDEV_TX, rules);
+@@ -830,14 +837,20 @@ static int rtl8211f_led_hw_control_set(s
+       if (index >= RTL8211x_LED_COUNT)
+               return -EINVAL;
+-      if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
++      if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
++          test_bit(TRIGGER_NETDEV_LINK_10, &rules)) {
+               reg |= RTL8211F_LEDCR_LINK_10;
++      }
+-      if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++      if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
++          test_bit(TRIGGER_NETDEV_LINK_100, &rules)) {
+               reg |= RTL8211F_LEDCR_LINK_100;
++      }
+-      if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++      if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
++          test_bit(TRIGGER_NETDEV_LINK_1000, &rules)) {
+               reg |= RTL8211F_LEDCR_LINK_1000;
++      }
+       if (test_bit(TRIGGER_NETDEV_RX, &rules) ||
+           test_bit(TRIGGER_NETDEV_TX, &rules)) {
+@@ -885,6 +898,12 @@ static int rtl8211e_led_hw_control_get(s
+       if (cr2 & RTL8211E_LEDCR2_LINK_1000)
+               __set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++      if ((cr2 & RTL8211E_LEDCR2_LINK_10) &&
++          (cr2 & RTL8211E_LEDCR2_LINK_100) &&
++          (cr2 & RTL8211E_LEDCR2_LINK_1000)) {
++              __set_bit(TRIGGER_NETDEV_LINK, rules);
++      }
++
+       return ret;
+ }
+@@ -912,14 +931,20 @@ static int rtl8211e_led_hw_control_set(s
+       if (ret < 0)
+               return ret;
+-      if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
++      if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
++          test_bit(TRIGGER_NETDEV_LINK_10, &rules)) {
+               cr2 |= RTL8211E_LEDCR2_LINK_10;
++      }
+-      if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++      if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
++          test_bit(TRIGGER_NETDEV_LINK_100, &rules)) {
+               cr2 |= RTL8211E_LEDCR2_LINK_100;
++      }
+-      if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++      if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
++          test_bit(TRIGGER_NETDEV_LINK_1000, &rules)) {
+               cr2 |= RTL8211E_LEDCR2_LINK_1000;
++      }
+       cr2 <<= RTL8211E_LEDCR2_SHIFT * index;
+       ret = rtl821x_modify_ext_page(phydev, RTL8211E_LEDCR_EXT_PAGE,
diff --git a/target/linux/generic/backport-6.12/784-06-v6.19-net-phy-realtek-Add-RTL8224-cable-testing-support.patch b/target/linux/generic/backport-6.12/784-06-v6.19-net-phy-realtek-Add-RTL8224-cable-testing-support.patch
new file mode 100644 (file)
index 0000000..d95211d
--- /dev/null
@@ -0,0 +1,272 @@
+From 61958b33ef0bab1c1874c933cd3910f495526782 Mon Sep 17 00:00:00 2001
+From: Issam Hamdi <ih@simonwunderlich.de>
+Date: Fri, 24 Oct 2025 11:49:00 +0200
+Subject: [PATCH] net: phy: realtek: Add RTL8224 cable testing support
+
+The RTL8224 can detect open pairs and short types (in same pair or some
+other pair). The distance to this problem can be estimated. This is done
+for each of the 4 pairs separately.
+
+It is not meant to be run while there is an active link partner because
+this interferes with the active test pulses.
+
+Output with open 50 m cable:
+
+  Pair A code Open Circuit, source: TDR
+  Pair A, fault length: 51.79m, source: TDR
+  Pair B code Open Circuit, source: TDR
+  Pair B, fault length: 51.28m, source: TDR
+  Pair C code Open Circuit, source: TDR
+  Pair C, fault length: 50.46m, source: TDR
+  Pair D code Open Circuit, source: TDR
+  Pair D, fault length: 51.12m, source: TDR
+
+Terminated cable:
+
+  Pair A code OK, source: TDR
+  Pair B code OK, source: TDR
+  Pair C code OK, source: TDR
+  Pair D code OK, source: TDR
+
+Shorted cable (both short types are at roughly the same distance)
+
+  Pair A code Short to another pair, source: TDR
+  Pair A, fault length: 2.35m, source: TDR
+  Pair B code Short to another pair, source: TDR
+  Pair B, fault length: 2.15m, source: TDR
+  Pair C code OK, source: TDR
+  Pair D code Short within Pair, source: TDR
+  Pair D, fault length: 1.94m, source: TDR
+
+Signed-off-by: Issam Hamdi <ih@simonwunderlich.de>
+Co-developed-by: Sven Eckelmann <se@simonwunderlich.de>
+Signed-off-by: Sven Eckelmann <se@simonwunderlich.de>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20251024-rtl8224-cable-test-v1-1-e3cda89ac98f@simonwunderlich.de
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 187 +++++++++++++++++++++++++
+ 1 file changed, 187 insertions(+)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -8,6 +8,7 @@
+  * Copyright (c) 2004 Freescale Semiconductor, Inc.
+  */
+ #include <linux/bitops.h>
++#include <linux/ethtool_netlink.h>
+ #include <linux/of.h>
+ #include <linux/phy.h>
+ #include <linux/pm_wakeirq.h>
+@@ -127,6 +128,27 @@
+  */
+ #define RTL822X_VND2_C22_REG(reg)             (0xa400 + 2 * (reg))
++#define RTL8224_MII_RTCT                      0x11
++#define RTL8224_MII_RTCT_ENABLE                       BIT(0)
++#define RTL8224_MII_RTCT_PAIR_A                       BIT(4)
++#define RTL8224_MII_RTCT_PAIR_B                       BIT(5)
++#define RTL8224_MII_RTCT_PAIR_C                       BIT(6)
++#define RTL8224_MII_RTCT_PAIR_D                       BIT(7)
++#define RTL8224_MII_RTCT_DONE                 BIT(15)
++
++#define RTL8224_MII_SRAM_ADDR                 0x1b
++#define RTL8224_MII_SRAM_DATA                 0x1c
++
++#define RTL8224_SRAM_RTCT_FAULT(pair)         (0x8026 + (pair) * 4)
++#define RTL8224_SRAM_RTCT_FAULT_BUSY          BIT(0)
++#define RTL8224_SRAM_RTCT_FAULT_OPEN          BIT(3)
++#define RTL8224_SRAM_RTCT_FAULT_SAME_SHORT    BIT(4)
++#define RTL8224_SRAM_RTCT_FAULT_OK            BIT(5)
++#define RTL8224_SRAM_RTCT_FAULT_DONE          BIT(6)
++#define RTL8224_SRAM_RTCT_FAULT_CROSS_SHORT   BIT(7)
++
++#define RTL8224_SRAM_RTCT_LEN(pair)           (0x8028 + (pair) * 4)
++
+ #define RTL8366RB_POWER_SAVE                  0x15
+ #define RTL8366RB_POWER_SAVE_ON                       BIT(12)
+@@ -1453,6 +1475,168 @@ static int rtl822xb_c45_read_status(stru
+       return 0;
+ }
++static int rtl8224_cable_test_start(struct phy_device *phydev)
++{
++      u32 val;
++      int ret;
++
++      /* disable auto-negotiation and force 1000/Full */
++      ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2,
++                           RTL822X_VND2_C22_REG(MII_BMCR),
++                           BMCR_ANENABLE | BMCR_SPEED100 | BMCR_SPEED10,
++                           BMCR_SPEED1000 | BMCR_FULLDPLX);
++      if (ret)
++              return ret;
++
++      mdelay(500);
++
++      /* trigger cable test */
++      val = RTL8224_MII_RTCT_ENABLE;
++      val |= RTL8224_MII_RTCT_PAIR_A;
++      val |= RTL8224_MII_RTCT_PAIR_B;
++      val |= RTL8224_MII_RTCT_PAIR_C;
++      val |= RTL8224_MII_RTCT_PAIR_D;
++
++      return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
++                            RTL822X_VND2_C22_REG(RTL8224_MII_RTCT),
++                            RTL8224_MII_RTCT_DONE, val);
++}
++
++static int rtl8224_sram_read(struct phy_device *phydev, u32 reg)
++{
++      int ret;
++
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
++                          RTL822X_VND2_C22_REG(RTL8224_MII_SRAM_ADDR),
++                          reg);
++      if (ret)
++              return ret;
++
++      return phy_read_mmd(phydev, MDIO_MMD_VEND2,
++                          RTL822X_VND2_C22_REG(RTL8224_MII_SRAM_DATA));
++}
++
++static int rtl8224_pair_len_get(struct phy_device *phydev, u32 pair)
++{
++      int cable_len;
++      u32 reg_len;
++      int ret;
++      u32 cm;
++
++      reg_len = RTL8224_SRAM_RTCT_LEN(pair);
++
++      ret = rtl8224_sram_read(phydev, reg_len);
++      if (ret < 0)
++              return ret;
++
++      cable_len = ret & 0xff00;
++
++      ret = rtl8224_sram_read(phydev, reg_len + 1);
++      if (ret < 0)
++              return ret;
++
++      cable_len |= (ret & 0xff00) >> 8;
++
++      cable_len -= 620;
++      cable_len = max(cable_len, 0);
++
++      cm = cable_len * 100 / 78;
++
++      return cm;
++}
++
++static int rtl8224_cable_test_result_trans(u32 result)
++{
++      if (!(result & RTL8224_SRAM_RTCT_FAULT_DONE))
++              return -EBUSY;
++
++      if (result & RTL8224_SRAM_RTCT_FAULT_OK)
++              return ETHTOOL_A_CABLE_RESULT_CODE_OK;
++
++      if (result & RTL8224_SRAM_RTCT_FAULT_OPEN)
++              return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
++
++      if (result & RTL8224_SRAM_RTCT_FAULT_SAME_SHORT)
++              return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
++
++      if (result & RTL8224_SRAM_RTCT_FAULT_BUSY)
++              return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
++
++      if (result & RTL8224_SRAM_RTCT_FAULT_CROSS_SHORT)
++              return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
++
++      return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
++}
++
++static int rtl8224_cable_test_report_pair(struct phy_device *phydev, unsigned int pair)
++{
++      int fault_rslt;
++      int ret;
++
++      ret = rtl8224_sram_read(phydev, RTL8224_SRAM_RTCT_FAULT(pair));
++      if (ret < 0)
++              return ret;
++
++      fault_rslt = rtl8224_cable_test_result_trans(ret);
++      if (fault_rslt < 0)
++              return 0;
++
++      ret = ethnl_cable_test_result(phydev, pair, fault_rslt);
++      if (ret < 0)
++              return ret;
++
++      switch (fault_rslt) {
++      case ETHTOOL_A_CABLE_RESULT_CODE_OPEN:
++      case ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT:
++      case ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT:
++              ret = rtl8224_pair_len_get(phydev, pair);
++              if (ret < 0)
++                      return ret;
++
++              return ethnl_cable_test_fault_length(phydev, pair, ret);
++      default:
++              return  0;
++      }
++}
++
++static int rtl8224_cable_test_report(struct phy_device *phydev, bool *finished)
++{
++      unsigned int pair;
++      int ret;
++
++      for (pair = ETHTOOL_A_CABLE_PAIR_A; pair <= ETHTOOL_A_CABLE_PAIR_D; pair++) {
++              ret = rtl8224_cable_test_report_pair(phydev, pair);
++              if (ret == -EBUSY) {
++                      *finished = false;
++                      return 0;
++              }
++
++              if (ret < 0)
++                      return ret;
++      }
++
++      return 0;
++}
++
++static int rtl8224_cable_test_get_status(struct phy_device *phydev, bool *finished)
++{
++      int ret;
++
++      *finished = false;
++
++      ret = phy_read_mmd(phydev, MDIO_MMD_VEND2,
++                         RTL822X_VND2_C22_REG(RTL8224_MII_RTCT));
++      if (ret < 0)
++              return ret;
++
++      if (!(ret & RTL8224_MII_RTCT_DONE))
++              return 0;
++
++      *finished = true;
++
++      return rtl8224_cable_test_report(phydev, finished);
++}
++
+ static bool rtlgen_supports_2_5gbps(struct phy_device *phydev)
+ {
+       int val;
+@@ -1930,11 +2114,14 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               PHY_ID_MATCH_EXACT(0x001ccad0),
+               .name           = "RTL8224 2.5Gbps PHY",
++              .flags          = PHY_POLL_CABLE_TEST,
+               .get_features   = rtl822x_c45_get_features,
+               .config_aneg    = rtl822x_c45_config_aneg,
+               .read_status    = rtl822x_c45_read_status,
+               .suspend        = genphy_c45_pma_suspend,
+               .resume         = rtlgen_c45_resume,
++              .cable_test_start = rtl8224_cable_test_start,
++              .cable_test_get_status = rtl8224_cable_test_get_status,
+       }, {
+               PHY_ID_MATCH_EXACT(0x001cc961),
+               .name           = "RTL8366RB Gigabit Ethernet",
diff --git a/target/linux/generic/backport-6.12/784-07-v6.19-net-phy-realtek-add-interrupt-support-for-RTL8221B.patch b/target/linux/generic/backport-6.12/784-07-v6.19-net-phy-realtek-add-interrupt-support-for-RTL8221B.patch
new file mode 100644 (file)
index 0000000..2efa4e9
--- /dev/null
@@ -0,0 +1,105 @@
+From 18aa36238a4d835c1644dcccd63d32c7fdd4b310 Mon Sep 17 00:00:00 2001
+From: Jianhui Zhao <zhaojh329@gmail.com>
+Date: Sun, 2 Nov 2025 16:26:37 +0100
+Subject: [PATCH] net: phy: realtek: add interrupt support for RTL8221B
+
+This commit introduces interrupt support for RTL8221B (C45 mode).
+Interrupts are mapped on the VEND2 page. VEND2 registers are only
+accessible via C45 reads and cannot be accessed by C45 over C22.
+
+Signed-off-by: Jianhui Zhao <zhaojh329@gmail.com>
+[Enable only link state change interrupts]
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20251102152644.1676482-1-olek2@wp.pl
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 56 ++++++++++++++++++++++++++
+ 1 file changed, 56 insertions(+)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -128,6 +128,11 @@
+  */
+ #define RTL822X_VND2_C22_REG(reg)             (0xa400 + 2 * (reg))
++#define RTL8221B_VND2_INER                    0xa4d2
++#define RTL8221B_VND2_INER_LINK_STATUS                BIT(4)
++
++#define RTL8221B_VND2_INSR                    0xa4d4
++
+ #define RTL8224_MII_RTCT                      0x11
+ #define RTL8224_MII_RTCT_ENABLE                       BIT(0)
+ #define RTL8224_MII_RTCT_PAIR_A                       BIT(4)
+@@ -1880,6 +1885,53 @@ static irqreturn_t rtl9000a_handle_inter
+       return IRQ_HANDLED;
+ }
++static int rtl8221b_ack_interrupt(struct phy_device *phydev)
++{
++      int err;
++
++      err = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR);
++
++      return (err < 0) ? err : 0;
++}
++
++static int rtl8221b_config_intr(struct phy_device *phydev)
++{
++      int err;
++
++      if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
++              err = rtl8221b_ack_interrupt(phydev);
++              if (err)
++                      return err;
++
++              err = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER,
++                                  RTL8221B_VND2_INER_LINK_STATUS);
++      } else {
++              err = phy_write_mmd(phydev, MDIO_MMD_VEND2,
++                                  RTL8221B_VND2_INER, 0);
++              if (err)
++                      return err;
++
++              err = rtl8221b_ack_interrupt(phydev);
++      }
++
++      return err;
++}
++
++static irqreturn_t rtl8221b_handle_interrupt(struct phy_device *phydev)
++{
++      int err;
++
++      err = rtl8221b_ack_interrupt(phydev);
++      if (err) {
++              phy_error(phydev);
++              return IRQ_NONE;
++      }
++
++      phy_trigger_machine(phydev);
++
++      return IRQ_HANDLED;
++}
++
+ static struct phy_driver realtek_drvs[] = {
+       {
+               PHY_ID_MATCH_EXACT(0x00008201),
+@@ -2054,6 +2106,8 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               .match_phy_device = rtl8221b_vb_cg_c45_match_phy_device,
+               .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C45)",
++              .config_intr    = rtl8221b_config_intr,
++              .handle_interrupt = rtl8221b_handle_interrupt,
+               .probe          = rtl822x_probe,
+               .config_init    = rtl822xb_config_init,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+@@ -2078,6 +2132,8 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               .match_phy_device = rtl8221b_vm_cg_c45_match_phy_device,
+               .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C45)",
++              .config_intr    = rtl8221b_config_intr,
++              .handle_interrupt = rtl8221b_handle_interrupt,
+               .probe          = rtl822x_probe,
+               .config_init    = rtl822xb_config_init,
+               .get_rate_matching = rtl822xb_get_rate_matching,
diff --git a/target/linux/generic/backport-6.12/784-08-v6.19-net-phy-realtek-create-rtl8211f_config_rgmii_delay.patch b/target/linux/generic/backport-6.12/784-08-v6.19-net-phy-realtek-create-rtl8211f_config_rgmii_delay.patch
new file mode 100644 (file)
index 0000000..0c523ee
--- /dev/null
@@ -0,0 +1,128 @@
+From 8e982441ba601d982dd0739972115d85ae01d99b Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Tue, 18 Nov 2025 01:40:28 +0200
+Subject: [PATCH] net: phy: realtek: create rtl8211f_config_rgmii_delay()
+
+The control flow in rtl8211f_config_init() has some pitfalls which were
+probably unintended. Specifically it has an early return:
+
+       switch (phydev->interface) {
+       ...
+       default: /* the rest of the modes imply leaving delay as is. */
+               return 0;
+       }
+
+which exits the entire config_init() function. This means it also skips
+doing things such as disabling CLKOUT or disabling PHY-mode EEE.
+
+For the RTL8211FS, which uses PHY_INTERFACE_MODE_SGMII, this might be a
+problem. However, I don't know that it is, so there is no Fixes: tag.
+The issue was observed through code inspection.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20251117234033.345679-2-vladimir.oltean@nxp.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 65 +++++++++++++++-----------
+ 1 file changed, 39 insertions(+), 26 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -587,22 +587,11 @@ static int rtl8211c_config_init(struct p
+                           CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER);
+ }
+-static int rtl8211f_config_init(struct phy_device *phydev)
++static int rtl8211f_config_rgmii_delay(struct phy_device *phydev)
+ {
+-      struct rtl821x_priv *priv = phydev->priv;
+-      struct device *dev = &phydev->mdio.dev;
+       u16 val_txdly, val_rxdly;
+       int ret;
+-      ret = phy_modify_paged_changed(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1,
+-                                     RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF,
+-                                     priv->phycr1);
+-      if (ret < 0) {
+-              dev_err(dev, "aldps mode  configuration failed: %pe\n",
+-                      ERR_PTR(ret));
+-              return ret;
+-      }
+-
+       switch (phydev->interface) {
+       case PHY_INTERFACE_MODE_RGMII:
+               val_txdly = 0;
+@@ -632,34 +621,58 @@ static int rtl8211f_config_init(struct p
+                                      RTL8211F_TXCR, RTL8211F_TX_DELAY,
+                                      val_txdly);
+       if (ret < 0) {
+-              dev_err(dev, "Failed to update the TX delay register\n");
++              phydev_err(phydev, "Failed to update the TX delay register: %pe\n",
++                         ERR_PTR(ret));
+               return ret;
+       } else if (ret) {
+-              dev_dbg(dev,
+-                      "%s 2ns TX delay (and changing the value from pin-strapping RXD1 or the bootloader)\n",
+-                      str_enable_disable(val_txdly));
++              phydev_dbg(phydev,
++                         "%s 2ns TX delay (and changing the value from pin-strapping RXD1 or the bootloader)\n",
++                         str_enable_disable(val_txdly));
+       } else {
+-              dev_dbg(dev,
+-                      "2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n",
+-                      str_enabled_disabled(val_txdly));
++              phydev_dbg(phydev,
++                         "2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n",
++                         str_enabled_disabled(val_txdly));
+       }
+       ret = phy_modify_paged_changed(phydev, RTL8211F_RGMII_PAGE,
+                                      RTL8211F_RXCR, RTL8211F_RX_DELAY,
+                                      val_rxdly);
+       if (ret < 0) {
+-              dev_err(dev, "Failed to update the RX delay register\n");
++              phydev_err(phydev, "Failed to update the RX delay register: %pe\n",
++                         ERR_PTR(ret));
+               return ret;
+       } else if (ret) {
+-              dev_dbg(dev,
+-                      "%s 2ns RX delay (and changing the value from pin-strapping RXD0 or the bootloader)\n",
+-                      str_enable_disable(val_rxdly));
++              phydev_dbg(phydev,
++                         "%s 2ns RX delay (and changing the value from pin-strapping RXD0 or the bootloader)\n",
++                         str_enable_disable(val_rxdly));
+       } else {
+-              dev_dbg(dev,
+-                      "2ns RX delay was already %s (by pin-strapping RXD0 or bootloader configuration)\n",
+-                      str_enabled_disabled(val_rxdly));
++              phydev_dbg(phydev,
++                         "2ns RX delay was already %s (by pin-strapping RXD0 or bootloader configuration)\n",
++                         str_enabled_disabled(val_rxdly));
+       }
++      return 0;
++}
++
++static int rtl8211f_config_init(struct phy_device *phydev)
++{
++      struct rtl821x_priv *priv = phydev->priv;
++      struct device *dev = &phydev->mdio.dev;
++      int ret;
++
++      ret = phy_modify_paged_changed(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1,
++                                     RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF,
++                                     priv->phycr1);
++      if (ret < 0) {
++              dev_err(dev, "aldps mode  configuration failed: %pe\n",
++                      ERR_PTR(ret));
++              return ret;
++      }
++
++      ret = rtl8211f_config_rgmii_delay(phydev);
++      if (ret)
++              return ret;
++
+       if (!priv->has_phycr2)
+               return 0;
diff --git a/target/linux/generic/backport-6.12/784-09-v6.19-net-phy-realtek-eliminate-priv-phycr2-variable.patch b/target/linux/generic/backport-6.12/784-09-v6.19-net-phy-realtek-eliminate-priv-phycr2-variable.patch
new file mode 100644 (file)
index 0000000..1c67961
--- /dev/null
@@ -0,0 +1,101 @@
+From 27033d06917758d47162581da7e9de8004049dee Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Tue, 18 Nov 2025 01:40:29 +0200
+Subject: [PATCH] net: phy: realtek: eliminate priv->phycr2 variable
+
+The RTL8211F(D)(I)-VD-CG PHY also has support for disabling the CLKOUT,
+and we'd like to introduce the "realtek,clkout-disable" property for
+that.
+
+But it isn't done through the PHYCR2 register, and it becomes awkward to
+have the driver pretend that it is. So just replace the machine-level
+"u16 phycr2" variable with a logical "bool disable_clk_out", which
+scales better to the other PHY as well.
+
+The change is a complete functional equivalent. Before, if the device
+tree property was absent, priv->phycr2 would contain the RTL8211F_CLKOUT_EN
+bit as read from hardware. Now, we don't save priv->phycr2, but we just
+don't call phy_modify_paged() on it. Also, we can simply call
+phy_modify_paged() with the "set" argument to 0.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Link: https://patch.msgid.link/20251117234033.345679-3-vladimir.oltean@nxp.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 38 ++++++++++++++++----------
+ 1 file changed, 23 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -194,8 +194,8 @@ MODULE_LICENSE("GPL");
+ struct rtl821x_priv {
+       u16 phycr1;
+-      u16 phycr2;
+       bool has_phycr2;
++      bool disable_clk_out;
+       struct clk *clk;
+       /* rtl8211f */
+       u16 iner;
+@@ -266,15 +266,8 @@ static int rtl821x_probe(struct phy_devi
+               priv->phycr1 |= RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF;
+       priv->has_phycr2 = !(phy_id == RTL_8211FVD_PHYID);
+-      if (priv->has_phycr2) {
+-              ret = phy_read_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2);
+-              if (ret < 0)
+-                      return ret;
+-
+-              priv->phycr2 = ret & RTL8211F_CLKOUT_EN;
+-              if (of_property_read_bool(dev->of_node, "realtek,clkout-disable"))
+-                      priv->phycr2 &= ~RTL8211F_CLKOUT_EN;
+-      }
++      priv->disable_clk_out = of_property_read_bool(dev->of_node,
++                                                    "realtek,clkout-disable");
+       phydev->priv = priv;
+@@ -654,6 +647,23 @@ static int rtl8211f_config_rgmii_delay(s
+       return 0;
+ }
++static int rtl8211f_config_clk_out(struct phy_device *phydev)
++{
++      struct rtl821x_priv *priv = phydev->priv;
++      int ret;
++
++      /* The value is preserved if the device tree property is absent */
++      if (!priv->disable_clk_out)
++              return 0;
++
++      ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE,
++                             RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN, 0);
++      if (ret)
++              return ret;
++
++      return genphy_soft_reset(phydev);
++}
++
+ static int rtl8211f_config_init(struct phy_device *phydev)
+ {
+       struct rtl821x_priv *priv = phydev->priv;
+@@ -682,16 +692,14 @@ static int rtl8211f_config_init(struct p
+       if (ret)
+               return ret;
+-      ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE,
+-                             RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN,
+-                             priv->phycr2);
+-      if (ret < 0) {
++      ret = rtl8211f_config_clk_out(phydev);
++      if (ret) {
+               dev_err(dev, "clkout configuration failed: %pe\n",
+                       ERR_PTR(ret));
+               return ret;
+       }
+-      return genphy_soft_reset(phydev);
++      return 0;
+ }
+ static int rtl821x_suspend(struct phy_device *phydev)
diff --git a/target/linux/generic/backport-6.12/784-10-v6.19-net-phy-realtek-eliminate-has_phycr2-variable.patch b/target/linux/generic/backport-6.12/784-10-v6.19-net-phy-realtek-eliminate-has_phycr2-variable.patch
new file mode 100644 (file)
index 0000000..a78cb3d
--- /dev/null
@@ -0,0 +1,55 @@
+From 910ac7bfb1af1ae4cd141ef80e03a6729213c189 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Tue, 18 Nov 2025 01:40:30 +0200
+Subject: [PATCH] net: phy: realtek: eliminate has_phycr2 variable
+
+This variable is assigned in rtl821x_probe() and used in
+rtl8211f_config_init(), which is more complex than it needs to be.
+Simply testing the same condition from rtl821x_probe() in
+rtl8211f_config_init() yields the same result (the PHY driver ID is a
+runtime invariant), but with one temporary variable less.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20251117234033.345679-4-vladimir.oltean@nxp.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -194,7 +194,6 @@ MODULE_LICENSE("GPL");
+ struct rtl821x_priv {
+       u16 phycr1;
+-      bool has_phycr2;
+       bool disable_clk_out;
+       struct clk *clk;
+       /* rtl8211f */
+@@ -245,7 +244,6 @@ static int rtl821x_probe(struct phy_devi
+ {
+       struct device *dev = &phydev->mdio.dev;
+       struct rtl821x_priv *priv;
+-      u32 phy_id = phydev->drv->phy_id;
+       int ret;
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+@@ -265,7 +263,6 @@ static int rtl821x_probe(struct phy_devi
+       if (of_property_read_bool(dev->of_node, "realtek,aldps-enable"))
+               priv->phycr1 |= RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF;
+-      priv->has_phycr2 = !(phy_id == RTL_8211FVD_PHYID);
+       priv->disable_clk_out = of_property_read_bool(dev->of_node,
+                                                     "realtek,clkout-disable");
+@@ -683,7 +680,8 @@ static int rtl8211f_config_init(struct p
+       if (ret)
+               return ret;
+-      if (!priv->has_phycr2)
++      /* RTL8211FVD has no PHYCR2 register */
++      if (phydev->drv->phy_id == RTL_8211FVD_PHYID)
+               return 0;
+       /* Disable PHY-mode EEE so LPI is passed to the MAC */
diff --git a/target/linux/generic/backport-6.12/784-11-v6.19-net-phy-realtek-allow-CLKOUT-to-be-disabled-on-RTL82.patch b/target/linux/generic/backport-6.12/784-11-v6.19-net-phy-realtek-allow-CLKOUT-to-be-disabled-on-RTL82.patch
new file mode 100644 (file)
index 0000000..2a3239d
--- /dev/null
@@ -0,0 +1,101 @@
+From e1a31c41bef678afe0d99b7f0dc3711a80c68447 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Tue, 18 Nov 2025 01:40:31 +0200
+Subject: [PATCH] net: phy: realtek: allow CLKOUT to be disabled on
+ RTL8211F(D)(I)-VD-CG
+
+Add CLKOUT disable support for RTL8211F(D)(I)-VD-CG. Like with other PHY
+variants, this feature might be requested by customers when the clock
+output is not used, in order to reduce electromagnetic interference (EMI).
+
+In the common driver, the CLKOUT configuration is done through PHYCR2.
+The RTL_8211FVD_PHYID is singled out as not having that register, and
+execution in rtl8211f_config_init() returns early after commit
+2c67301584f2 ("net: phy: realtek: Avoid PHYCR2 access if PHYCR2 not
+present").
+
+But actually CLKOUT is configured through a different register for this
+PHY. Instead of pretending this is PHYCR2 (which it is not), just add
+some code for modifying this register inside the rtl8211f_disable_clk_out()
+function, and move that outside the code portion that runs only if
+PHYCR2 exists.
+
+In practice this reorders the PHYCR2 writes to disable PHY-mode EEE and
+to disable the CLKOUT for the normal RTL8211F variants, but this should
+be perfectly fine.
+
+It was not noted that RTL8211F(D)(I)-VD-CG would need a genphy_soft_reset()
+call after disabling the CLKOUT. Despite that, we do it out of caution
+and for symmetry with the other RTL8211F models.
+
+Co-developed-by: Clark Wang <xiaoning.wang@nxp.com>
+Signed-off-by: Clark Wang <xiaoning.wang@nxp.com>
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20251117234033.345679-5-vladimir.oltean@nxp.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 31 ++++++++++++++++++--------
+ 1 file changed, 22 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -90,6 +90,14 @@
+ #define RTL8211F_LEDCR_MASK                   GENMASK(4, 0)
+ #define RTL8211F_LEDCR_SHIFT                  5
++/* RTL8211F(D)(I)-VD-CG CLKOUT configuration is specified via magic values
++ * to undocumented register pages. The names here do not reflect the datasheet.
++ * Unlike other PHY models, CLKOUT configuration does not go through PHYCR2.
++ */
++#define RTL8211FVD_CLKOUT_PAGE                        0xd05
++#define RTL8211FVD_CLKOUT_REG                 0x11
++#define RTL8211FVD_CLKOUT_EN                  BIT(8)
++
+ /* RTL8211F RGMII configuration */
+ #define RTL8211F_RGMII_PAGE                   0xd08
+@@ -653,8 +661,13 @@ static int rtl8211f_config_clk_out(struc
+       if (!priv->disable_clk_out)
+               return 0;
+-      ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE,
+-                             RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN, 0);
++      if (phydev->drv->phy_id == RTL_8211FVD_PHYID)
++              ret = phy_modify_paged(phydev, RTL8211FVD_CLKOUT_PAGE,
++                                     RTL8211FVD_CLKOUT_REG,
++                                     RTL8211FVD_CLKOUT_EN, 0);
++      else
++              ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE,
++                                     RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN, 0);
+       if (ret)
+               return ret;
+@@ -680,6 +693,13 @@ static int rtl8211f_config_init(struct p
+       if (ret)
+               return ret;
++      ret = rtl8211f_config_clk_out(phydev);
++      if (ret) {
++              dev_err(dev, "clkout configuration failed: %pe\n",
++                      ERR_PTR(ret));
++              return ret;
++      }
++
+       /* RTL8211FVD has no PHYCR2 register */
+       if (phydev->drv->phy_id == RTL_8211FVD_PHYID)
+               return 0;
+@@ -690,13 +710,6 @@ static int rtl8211f_config_init(struct p
+       if (ret)
+               return ret;
+-      ret = rtl8211f_config_clk_out(phydev);
+-      if (ret) {
+-              dev_err(dev, "clkout configuration failed: %pe\n",
+-                      ERR_PTR(ret));
+-              return ret;
+-      }
+-
+       return 0;
+ }
diff --git a/target/linux/generic/backport-6.12/784-12-v6.19-net-phy-realtek-eliminate-priv-phycr1-variable.patch b/target/linux/generic/backport-6.12/784-12-v6.19-net-phy-realtek-eliminate-priv-phycr1-variable.patch
new file mode 100644 (file)
index 0000000..a2e5abe
--- /dev/null
@@ -0,0 +1,107 @@
+From bb78b71faf60d11a15f07e3390fcfd31e5e523bb Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Tue, 18 Nov 2025 01:40:32 +0200
+Subject: [PATCH] net: phy: realtek: eliminate priv->phycr1 variable
+
+Previous changes have replaced the machine-level priv->phycr2 with a
+high-level priv->disable_clk_out. This created a discrepancy with
+priv->phycr1 which is resolved here, for uniformity.
+
+One advantage of this new implementation is that we don't read
+priv->phycr1 in rtl821x_probe() if we're never going to modify it.
+
+We never test the positive return code from phy_modify_mmd_changed(), so
+we could just as well use phy_modify_mmd().
+
+I took the ALDPS feature description from commit d90db36a9e74 ("net:
+phy: realtek: add dt property to enable ALDPS mode") and transformed it
+into a function comment - the feature is sufficiently non-obvious to
+deserve that.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20251117234033.345679-6-vladimir.oltean@nxp.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 44 ++++++++++++++++----------
+ 1 file changed, 28 insertions(+), 16 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -201,7 +201,7 @@ MODULE_AUTHOR("Johnson Leung");
+ MODULE_LICENSE("GPL");
+ struct rtl821x_priv {
+-      u16 phycr1;
++      bool enable_aldps;
+       bool disable_clk_out;
+       struct clk *clk;
+       /* rtl8211f */
+@@ -252,7 +252,6 @@ static int rtl821x_probe(struct phy_devi
+ {
+       struct device *dev = &phydev->mdio.dev;
+       struct rtl821x_priv *priv;
+-      int ret;
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+@@ -263,14 +262,8 @@ static int rtl821x_probe(struct phy_devi
+               return dev_err_probe(dev, PTR_ERR(priv->clk),
+                                    "failed to get phy clock\n");
+-      ret = phy_read_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1);
+-      if (ret < 0)
+-              return ret;
+-
+-      priv->phycr1 = ret & (RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF);
+-      if (of_property_read_bool(dev->of_node, "realtek,aldps-enable"))
+-              priv->phycr1 |= RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF;
+-
++      priv->enable_aldps = of_property_read_bool(dev->of_node,
++                                                 "realtek,aldps-enable");
+       priv->disable_clk_out = of_property_read_bool(dev->of_node,
+                                                     "realtek,clkout-disable");
+@@ -674,17 +667,36 @@ static int rtl8211f_config_clk_out(struc
+       return genphy_soft_reset(phydev);
+ }
+-static int rtl8211f_config_init(struct phy_device *phydev)
++/* Advance Link Down Power Saving (ALDPS) mode changes crystal/clock behaviour,
++ * which causes the RXC clock signal to stop for tens to hundreds of
++ * milliseconds.
++ *
++ * Some MACs need the RXC clock to support their internal RX logic, so ALDPS is
++ * only enabled based on an opt-in device tree property.
++ */
++static int rtl8211f_config_aldps(struct phy_device *phydev)
+ {
+       struct rtl821x_priv *priv = phydev->priv;
++      u16 mask = RTL8211F_ALDPS_PLL_OFF |
++                 RTL8211F_ALDPS_ENABLE |
++                 RTL8211F_ALDPS_XTAL_OFF;
++
++      /* The value is preserved if the device tree property is absent */
++      if (!priv->enable_aldps)
++              return 0;
++
++      return phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1,
++                              mask, mask);
++}
++
++static int rtl8211f_config_init(struct phy_device *phydev)
++{
+       struct device *dev = &phydev->mdio.dev;
+       int ret;
+-      ret = phy_modify_paged_changed(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1,
+-                                     RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF,
+-                                     priv->phycr1);
+-      if (ret < 0) {
+-              dev_err(dev, "aldps mode  configuration failed: %pe\n",
++      ret = rtl8211f_config_aldps(phydev);
++      if (ret) {
++              dev_err(dev, "aldps mode configuration failed: %pe\n",
+                       ERR_PTR(ret));
+               return ret;
+       }
diff --git a/target/linux/generic/backport-6.12/784-13-v6.19-net-phy-realtek-create-rtl8211f_config_phy_eee-helpe.patch b/target/linux/generic/backport-6.12/784-13-v6.19-net-phy-realtek-create-rtl8211f_config_phy_eee-helpe.patch
new file mode 100644 (file)
index 0000000..54ec217
--- /dev/null
@@ -0,0 +1,58 @@
+From 4465ae435ddc0162d5033a543658449d53d46d08 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Tue, 18 Nov 2025 01:40:33 +0200
+Subject: [PATCH] net: phy: realtek: create rtl8211f_config_phy_eee() helper
+
+To simplify the rtl8211f_config_init() control flow and get rid of
+"early" returns for PHYs where the PHYCR2 register is absent, move the
+entire logic sub-block that deals with disabling PHY-mode EEE to a
+separate function. There, it is much more obvious what the early
+"return 0" skips, and it becomes more difficult to accidentally skip
+unintended stuff.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Link: https://patch.msgid.link/20251117234033.345679-7-vladimir.oltean@nxp.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 23 ++++++++++++-----------
+ 1 file changed, 12 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -689,6 +689,17 @@ static int rtl8211f_config_aldps(struct
+                               mask, mask);
+ }
++static int rtl8211f_config_phy_eee(struct phy_device *phydev)
++{
++      /* RTL8211FVD has no PHYCR2 register */
++      if (phydev->drv->phy_id == RTL_8211FVD_PHYID)
++              return 0;
++
++      /* Disable PHY-mode EEE so LPI is passed to the MAC */
++      return phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2,
++                              RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0);
++}
++
+ static int rtl8211f_config_init(struct phy_device *phydev)
+ {
+       struct device *dev = &phydev->mdio.dev;
+@@ -712,17 +723,7 @@ static int rtl8211f_config_init(struct p
+               return ret;
+       }
+-      /* RTL8211FVD has no PHYCR2 register */
+-      if (phydev->drv->phy_id == RTL_8211FVD_PHYID)
+-              return 0;
+-
+-      /* Disable PHY-mode EEE so LPI is passed to the MAC */
+-      ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2,
+-                             RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0);
+-      if (ret)
+-              return ret;
+-
+-      return 0;
++      return rtl8211f_config_phy_eee(phydev);
+ }
+ static int rtl821x_suspend(struct phy_device *phydev)
diff --git a/target/linux/generic/backport-6.12/785-v6.18-r8169-fix-RTL8127-hang-on-suspend-shutdown.patch b/target/linux/generic/backport-6.12/785-v6.18-r8169-fix-RTL8127-hang-on-suspend-shutdown.patch
new file mode 100644 (file)
index 0000000..269fc89
--- /dev/null
@@ -0,0 +1,50 @@
+From ae1737e7339b513f8c2fc21b500a0fc215d155c3 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Sat, 22 Nov 2025 15:23:02 +0100
+Subject: r8169: fix RTL8127 hang on suspend/shutdown
+
+There have been reports that RTL8127 hangs on suspend and shutdown,
+partially disappearing from lspci until power-cycling.
+According to Realtek disabling PLL's when switching to D3 should be
+avoided on that chip version. Fix this by aligning disabling PLL's
+with the vendor drivers, what in addition results in PLL's not being
+disabled when switching to D3hot on other chip versions.
+
+Fixes: f24f7b2f3af9 ("r8169: add support for RTL8127A")
+Tested-by: Fabio Baltieri <fabio.baltieri@gmail.com>
+Cc: stable@vger.kernel.org
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/d7faae7e-66bc-404a-a432-3a496600575f@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/realtek/r8169_main.c | 19 ++++++++++++++-----
+ 1 file changed, 14 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -1516,11 +1516,20 @@ static enum rtl_dash_type rtl_get_dash_t
+ static void rtl_set_d3_pll_down(struct rtl8169_private *tp, bool enable)
+ {
+-      if (tp->mac_version >= RTL_GIGA_MAC_VER_25 &&
+-          tp->mac_version != RTL_GIGA_MAC_VER_28 &&
+-          tp->mac_version != RTL_GIGA_MAC_VER_31 &&
+-          tp->mac_version != RTL_GIGA_MAC_VER_38)
+-              r8169_mod_reg8_cond(tp, PMCH, D3_NO_PLL_DOWN, !enable);
++      switch (tp->mac_version) {
++      case RTL_GIGA_MAC_VER_02 ... RTL_GIGA_MAC_VER_24:
++      case RTL_GIGA_MAC_VER_28:
++      case RTL_GIGA_MAC_VER_31:
++      case RTL_GIGA_MAC_VER_38:
++              break;
++      case RTL_GIGA_MAC_VER_80:
++              r8169_mod_reg8_cond(tp, PMCH, D3_NO_PLL_DOWN, true);
++              break;
++      default:
++              r8169_mod_reg8_cond(tp, PMCH, D3HOT_NO_PLL_DOWN, true);
++              r8169_mod_reg8_cond(tp, PMCH, D3COLD_NO_PLL_DOWN, !enable);
++              break;
++      }
+ }
+ static void rtl_reset_packet_filter(struct rtl8169_private *tp)
diff --git a/target/linux/generic/backport-6.12/786-01-v6.18-net-phy-introduce-phy_id_compare_vendor-PHY-ID-helpe.patch b/target/linux/generic/backport-6.12/786-01-v6.18-net-phy-introduce-phy_id_compare_vendor-PHY-ID-helpe.patch
new file mode 100644 (file)
index 0000000..a69a564
--- /dev/null
@@ -0,0 +1,58 @@
+From 1abe21ef1adf0c5b6dbb5878c2fa4573df8d29fc Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sat, 23 Aug 2025 15:44:28 +0200
+Subject: net: phy: introduce phy_id_compare_vendor() PHY ID helper
+
+Introduce phy_id_compare_vendor() PHY ID helper to compare a PHY ID with
+the PHY ID Vendor using the generic PHY ID Vendor mask.
+
+While at it also rework the PHY_ID_MATCH macro and move the mask to
+dedicated define so that PHY driver can make use of the mask if needed.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20250823134431.4854-1-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ include/linux/phy.h | 23 ++++++++++++++++++++---
+ 1 file changed, 20 insertions(+), 3 deletions(-)
+
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1266,9 +1266,13 @@ struct phy_driver {
+ #define PHY_ANY_ID "MATCH ANY PHY"
+ #define PHY_ANY_UID 0xffffffff
+-#define PHY_ID_MATCH_EXACT(id) .phy_id = (id), .phy_id_mask = GENMASK(31, 0)
+-#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)
++#define PHY_ID_MATCH_EXTACT_MASK GENMASK(31, 0)
++#define PHY_ID_MATCH_MODEL_MASK GENMASK(31, 4)
++#define PHY_ID_MATCH_VENDOR_MASK GENMASK(31, 10)
++
++#define PHY_ID_MATCH_EXACT(id) .phy_id = (id), .phy_id_mask = PHY_ID_MATCH_EXTACT_MASK
++#define PHY_ID_MATCH_MODEL(id) .phy_id = (id), .phy_id_mask = PHY_ID_MATCH_MODEL_MASK
++#define PHY_ID_MATCH_VENDOR(id) .phy_id = (id), .phy_id_mask = PHY_ID_MATCH_VENDOR_MASK
+ /**
+  * phy_id_compare - compare @id1 with @id2 taking account of @mask
+@@ -1298,6 +1302,19 @@ static inline bool phy_id_compare_model(
+ }
+ /**
++ * phy_id_compare_vendor - compare @id with @vendor mask
++ * @id: PHY ID
++ * @vendor_mask: PHY Vendor mask
++ *
++ * Return: true if the bits from @id match @vendor using the
++ *       generic PHY Vendor mask.
++ */
++static inline bool phy_id_compare_vendor(u32 id, u32 vendor_mask)
++{
++      return phy_id_compare(id, vendor_mask, PHY_ID_MATCH_VENDOR_MASK);
++}
++
++/**
+  * phydev_id_compare - compare @id with the PHY's Clause 22 ID
+  * @phydev: the PHY device
+  * @id: the PHY ID to be matched
diff --git a/target/linux/generic/backport-6.12/786-02-v6.18-net-phy-as21xxx-better-handle-PHY-HW-reset-on-soft-r.patch b/target/linux/generic/backport-6.12/786-02-v6.18-net-phy-as21xxx-better-handle-PHY-HW-reset-on-soft-r.patch
new file mode 100644 (file)
index 0000000..5b00da4
--- /dev/null
@@ -0,0 +1,45 @@
+From b4d5cd20507b252c746fa6971d82ac96f3b3e5b7 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sat, 23 Aug 2025 15:44:29 +0200
+Subject: net: phy: as21xxx: better handle PHY HW reset on soft-reboot
+
+On soft-reboot, with a reset GPIO defined for an Aeonsemi PHY, the
+special match_phy_device fails to correctly identify that the PHY
+needs to load the firmware again.
+
+This is caused by the fact that PHY ID is read BEFORE the PHY reset
+GPIO (if present) is asserted, so we can be in the scenario where the
+phydev have the previous PHY ID (with the PHY firmware loaded) but
+after reset the generic AS21xxx PHY is present in the PHY ID registers.
+
+To better handle this, skip reading the PHY ID register only for the PHY
+that are not AS21xxx (by matching for the Aeonsemi Vendor) and always
+read the PHY ID for the other case to handle both firmware already
+loaded or an HW reset.
+
+Fixes: 830877d89edc ("net: phy: Add support for Aeonsemi AS21xxx PHYs")
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Link: https://patch.msgid.link/20250823134431.4854-2-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/as21xxx.c | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/as21xxx.c
++++ b/drivers/net/phy/as21xxx.c
+@@ -884,11 +884,12 @@ static int as21xxx_match_phy_device(stru
+       u32 phy_id;
+       int ret;
+-      /* Skip PHY that are not AS21xxx or already have firmware loaded */
+-      if (phydev->c45_ids.device_ids[MDIO_MMD_PCS] != PHY_ID_AS21XXX)
++      /* Skip PHY that are not AS21xxx */
++      if (!phy_id_compare_vendor(phydev->c45_ids.device_ids[MDIO_MMD_PCS],
++                                 PHY_VENDOR_AEONSEMI))
+               return genphy_match_phy_device(phydev, phydrv);
+-      /* Read PHY ID to handle firmware just loaded */
++      /* Read PHY ID to handle firmware loaded or HW reset */
+       ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_PHYSID1);
+       if (ret < 0)
+               return ret;
diff --git a/target/linux/generic/backport-6.12/787-v6.19-net-phy-RTL8211FVD-Restore-disabling-of-PHY-mode-EEE.patch b/target/linux/generic/backport-6.12/787-v6.19-net-phy-RTL8211FVD-Restore-disabling-of-PHY-mode-EEE.patch
new file mode 100644 (file)
index 0000000..37290c4
--- /dev/null
@@ -0,0 +1,61 @@
+From 4f0638b12451112de4138689fa679315c8d388dc Mon Sep 17 00:00:00 2001
+From: Ivan Galkin <ivan.galkin@axis.com>
+Date: Tue, 2 Dec 2025 10:07:42 +0100
+Subject: net: phy: RTL8211FVD: Restore disabling of PHY-mode EEE
+
+When support for RTL8211F(D)(I)-VD-CG was introduced in commit
+bb726b753f75 ("net: phy: realtek: add support for RTL8211F(D)(I)-VD-CG")
+the implementation assumed that this PHY model doesn't have the
+control register PHYCR2 (Page 0xa43 Address 0x19). This
+assumption was based on the differences in CLKOUT configurations
+between RTL8211FVD and the remaining RTL8211F PHYs. In the latter
+commit 2c67301584f2
+("net: phy: realtek: Avoid PHYCR2 access if PHYCR2 not present")
+this assumption was expanded to the PHY-mode EEE.
+
+I performed tests on RTL8211FI-VD-CG and confirmed that disabling
+PHY-mode EEE works correctly and is uniform with other PHYs
+supported by the driver. To validate the correctness,
+I contacted Realtek support. Realtek confirmed that PHY-mode EEE on
+RTL8211F(D)(I)-VD-CG is configured via Page 0xa43 Address 0x19 bit 5.
+
+Moreover, Realtek informed me that the most recent datasheet
+for RTL8211F(D)(I)-VD-CG v1.1 is incomplete and the naming of
+control registers is partly inconsistent. The errata I
+received from Realtek corrects the naming as follows:
+
+| Register                | Datasheet v1.1 | Errata |
+|-------------------------|----------------|--------|
+| Page 0xa44 Address 0x11 | PHYCR2         | PHYCR3 |
+| Page 0xa43 Address 0x19 | N/A            | PHYCR2 |
+
+This information confirms that the supposedly missing control register,
+PHYCR2, exists in the RTL8211F(D)(I)-VD-CG under the same address and
+the same name. It controls widely the same configs as other PHYs from
+the RTL8211F series (e.g. PHY-mode EEE). Clock out configuration is an
+exception.
+
+Given all this information, restore disabling of the PHY-mode EEE.
+
+Fixes: 2c67301584f2 ("net: phy: realtek: Avoid PHYCR2 access if PHYCR2 not present")
+Signed-off-by: Ivan Galkin <ivan.galkin@axis.com>
+Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Link: https://patch.msgid.link/20251202-phy_eee-v1-1-fe0bf6ab3df0@axis.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek/realtek_main.c | 4 ----
+ 1 file changed, 4 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -691,10 +691,6 @@ static int rtl8211f_config_aldps(struct
+ static int rtl8211f_config_phy_eee(struct phy_device *phydev)
+ {
+-      /* RTL8211FVD has no PHYCR2 register */
+-      if (phydev->drv->phy_id == RTL_8211FVD_PHYID)
+-              return 0;
+-
+       /* Disable PHY-mode EEE so LPI is passed to the MAC */
+       return phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2,
+                               RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0);
diff --git a/target/linux/generic/backport-6.12/788-v7.0-net-phy-realtek-fix-whitespace-in-struct-phy_driver-.patch b/target/linux/generic/backport-6.12/788-v7.0-net-phy-realtek-fix-whitespace-in-struct-phy_driver-.patch
new file mode 100644 (file)
index 0000000..e577166
--- /dev/null
@@ -0,0 +1,229 @@
+From 50326b48f0cfbd9324668c5d2e08b39b59dc199f Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 5 Jan 2026 16:37:54 +0000
+Subject: [PATCH 1/5] net: phy: realtek: fix whitespace in struct phy_driver
+ initializers
+
+Consistently use tabs instead of spaces in struct phy_driver
+initializers.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
+Link: https://patch.msgid.link/42b0fac53c5c5646707ce3f3a6dacd2bc082a5b2.1767630451.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek/realtek_main.c | 146 ++++++++++++-------------
+ 1 file changed, 73 insertions(+), 73 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -1976,7 +1976,7 @@ static irqreturn_t rtl8221b_handle_inter
+ static struct phy_driver realtek_drvs[] = {
+       {
+               PHY_ID_MATCH_EXACT(0x00008201),
+-              .name           = "RTL8201CP Ethernet",
++              .name           = "RTL8201CP Ethernet",
+               .read_page      = rtl821x_read_page,
+               .write_page     = rtl821x_write_page,
+       }, {
+@@ -2102,7 +2102,7 @@ static struct phy_driver realtek_drvs[]
+               .name           = "RTL8226B_RTL8221B 2.5Gbps PHY",
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
+-              .config_init    = rtl822xb_config_init,
++              .config_init    = rtl822xb_config_init,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+               .read_status    = rtl822xb_read_status,
+               .suspend        = genphy_suspend,
+@@ -2111,112 +2111,112 @@ static struct phy_driver realtek_drvs[]
+               .write_page     = rtl821x_write_page,
+       }, {
+               PHY_ID_MATCH_EXACT(0x001cc838),
+-              .name           = "RTL8226-CG 2.5Gbps PHY",
+-              .soft_reset     = rtl822x_c45_soft_reset,
+-              .get_features   = rtl822x_c45_get_features,
+-              .config_aneg    = rtl822x_c45_config_aneg,
+-              .config_init    = rtl822x_config_init,
+-              .read_status    = rtl822xb_c45_read_status,
+-              .suspend        = genphy_c45_pma_suspend,
+-              .resume         = rtlgen_c45_resume,
++              .name           = "RTL8226-CG 2.5Gbps PHY",
++              .soft_reset     = rtl822x_c45_soft_reset,
++              .get_features   = rtl822x_c45_get_features,
++              .config_aneg    = rtl822x_c45_config_aneg,
++              .config_init    = rtl822x_config_init,
++              .read_status    = rtl822xb_c45_read_status,
++              .suspend        = genphy_c45_pma_suspend,
++              .resume         = rtlgen_c45_resume,
+       }, {
+               PHY_ID_MATCH_EXACT(0x001cc848),
+-              .name           = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY",
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .config_init    = rtl822xb_config_init,
++              .name           = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY",
++              .get_features   = rtl822x_get_features,
++              .config_aneg    = rtl822x_config_aneg,
++              .config_init    = rtl822xb_config_init,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+-              .read_status    = rtl822xb_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
++              .read_status    = rtl822xb_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
+       }, {
+               .match_phy_device = rtl8221b_vb_cg_c22_match_phy_device,
+-              .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C22)",
++              .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C22)",
+               .probe          = rtl822x_probe,
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .config_init    = rtl822xb_config_init,
++              .get_features   = rtl822x_get_features,
++              .config_aneg    = rtl822x_config_aneg,
++              .config_init    = rtl822xb_config_init,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+-              .read_status    = rtl822xb_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
++              .read_status    = rtl822xb_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
+       }, {
+               .match_phy_device = rtl8221b_vb_cg_c45_match_phy_device,
+-              .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C45)",
++              .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C45)",
+               .config_intr    = rtl8221b_config_intr,
+               .handle_interrupt = rtl8221b_handle_interrupt,
+               .probe          = rtl822x_probe,
+-              .config_init    = rtl822xb_config_init,
++              .config_init    = rtl822xb_config_init,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+-              .get_features   = rtl822x_c45_get_features,
+-              .config_aneg    = rtl822x_c45_config_aneg,
+-              .read_status    = rtl822xb_c45_read_status,
+-              .suspend        = genphy_c45_pma_suspend,
+-              .resume         = rtlgen_c45_resume,
++              .get_features   = rtl822x_c45_get_features,
++              .config_aneg    = rtl822x_c45_config_aneg,
++              .read_status    = rtl822xb_c45_read_status,
++              .suspend        = genphy_c45_pma_suspend,
++              .resume         = rtlgen_c45_resume,
+       }, {
+               .match_phy_device = rtl8221b_vm_cg_c22_match_phy_device,
+-              .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C22)",
++              .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C22)",
+               .probe          = rtl822x_probe,
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .config_init    = rtl822xb_config_init,
++              .get_features   = rtl822x_get_features,
++              .config_aneg    = rtl822x_config_aneg,
++              .config_init    = rtl822xb_config_init,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+-              .read_status    = rtl822xb_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
++              .read_status    = rtl822xb_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
+       }, {
+               .match_phy_device = rtl8221b_vm_cg_c45_match_phy_device,
+-              .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C45)",
++              .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C45)",
+               .config_intr    = rtl8221b_config_intr,
+               .handle_interrupt = rtl8221b_handle_interrupt,
+               .probe          = rtl822x_probe,
+-              .config_init    = rtl822xb_config_init,
++              .config_init    = rtl822xb_config_init,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+-              .get_features   = rtl822x_c45_get_features,
+-              .config_aneg    = rtl822x_c45_config_aneg,
+-              .read_status    = rtl822xb_c45_read_status,
+-              .suspend        = genphy_c45_pma_suspend,
+-              .resume         = rtlgen_c45_resume,
++              .get_features   = rtl822x_c45_get_features,
++              .config_aneg    = rtl822x_c45_config_aneg,
++              .read_status    = rtl822xb_c45_read_status,
++              .suspend        = genphy_c45_pma_suspend,
++              .resume         = rtlgen_c45_resume,
+       }, {
+               .match_phy_device = rtl8251b_c45_match_phy_device,
+-              .name           = "RTL8251B 5Gbps PHY",
++              .name           = "RTL8251B 5Gbps PHY",
+               .probe          = rtl822x_probe,
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .read_status    = rtl822x_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
++              .get_features   = rtl822x_get_features,
++              .config_aneg    = rtl822x_config_aneg,
++              .read_status    = rtl822x_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
+       }, {
+               .match_phy_device = rtl_internal_nbaset_match_phy_device,
+-              .name           = "Realtek Internal NBASE-T PHY",
++              .name           = "Realtek Internal NBASE-T PHY",
+               .flags          = PHY_IS_INTERNAL,
+               .probe          = rtl822x_probe,
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .read_status    = rtl822x_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
++              .get_features   = rtl822x_get_features,
++              .config_aneg    = rtl822x_config_aneg,
++              .read_status    = rtl822x_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
+               .read_mmd       = rtl822x_read_mmd,
+               .write_mmd      = rtl822x_write_mmd,
+       }, {
+               PHY_ID_MATCH_EXACT(0x001ccad0),
+               .name           = "RTL8224 2.5Gbps PHY",
+               .flags          = PHY_POLL_CABLE_TEST,
+-              .get_features   = rtl822x_c45_get_features,
+-              .config_aneg    = rtl822x_c45_config_aneg,
+-              .read_status    = rtl822x_c45_read_status,
+-              .suspend        = genphy_c45_pma_suspend,
+-              .resume         = rtlgen_c45_resume,
++              .get_features   = rtl822x_c45_get_features,
++              .config_aneg    = rtl822x_c45_config_aneg,
++              .read_status    = rtl822x_c45_read_status,
++              .suspend        = genphy_c45_pma_suspend,
++              .resume         = rtlgen_c45_resume,
+               .cable_test_start = rtl8224_cable_test_start,
+               .cable_test_get_status = rtl8224_cable_test_get_status,
+       }, {
+@@ -2235,7 +2235,7 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               PHY_ID_MATCH_EXACT(0x001ccb00),
+               .name           = "RTL9000AA_RTL9000AN Ethernet",
+-              .features       = PHY_BASIC_T1_FEATURES,
++              .features       = PHY_BASIC_T1_FEATURES,
+               .config_init    = rtl9000a_config_init,
+               .config_aneg    = rtl9000a_config_aneg,
+               .read_status    = rtl9000a_read_status,
diff --git a/target/linux/generic/backport-6.12/789-v7.0-net-phy-realtek-implement-configuring-in-band-an.patch b/target/linux/generic/backport-6.12/789-v7.0-net-phy-realtek-implement-configuring-in-band-an.patch
new file mode 100644 (file)
index 0000000..16e6de2
--- /dev/null
@@ -0,0 +1,148 @@
+From 10fbd71fc5f9bef5018a35103d493307683ddca1 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 5 Jan 2026 16:38:12 +0000
+Subject: [PATCH 2/5] net: phy: realtek: implement configuring in-band an
+
+Implement the inband_caps() and config_inband() PHY driver methods to
+allow configuring the use of in-band-status with SGMII and 2500Base-X on
+RTL8226 and RTL8221B 2.5GE PHYs.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/82a78a06d67be19e856d646cf880b2021ea9d837.1767630451.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek/realtek_main.c | 67 ++++++++++++++++++++++++++
+ 1 file changed, 67 insertions(+)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -131,6 +131,15 @@
+ #define RTL822X_VND1_SERDES_CTRL3_MODE_SGMII                  0x02
+ #define RTL822X_VND1_SERDES_CTRL3_MODE_2500BASEX              0x16
++#define RTL822X_VND1_SERDES_CMD                       0x7587
++#define  RTL822X_VND1_SERDES_CMD_WRITE                BIT(1)
++#define  RTL822X_VND1_SERDES_CMD_BUSY         BIT(0)
++#define RTL822X_VND1_SERDES_ADDR              0x7588
++#define  RTL822X_VND1_SERDES_ADDR_AUTONEG     0x2
++#define   RTL822X_VND1_SERDES_INBAND_DISABLE  0x71d0
++#define   RTL822X_VND1_SERDES_INBAND_ENABLE   0x70d0
++#define RTL822X_VND1_SERDES_DATA              0x7589
++
+ /* RTL822X_VND2_XXXXX registers are only accessible when phydev->is_c45
+  * is set, they cannot be accessed by C45-over-C22.
+  */
+@@ -1308,6 +1317,50 @@ static int rtl822xb_config_init(struct p
+       return rtl822x_set_serdes_option_mode(phydev, false);
+ }
++static int rtl822x_serdes_write(struct phy_device *phydev, u16 reg, u16 val)
++{
++      int ret, poll;
++
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_ADDR, reg);
++      if (ret < 0)
++              return ret;
++
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_DATA, val);
++      if (ret < 0)
++              return ret;
++
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_CMD,
++                          RTL822X_VND1_SERDES_CMD_WRITE |
++                          RTL822X_VND1_SERDES_CMD_BUSY);
++      if (ret < 0)
++              return ret;
++
++      return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
++                                       RTL822X_VND1_SERDES_CMD, poll,
++                                       !(poll & RTL822X_VND1_SERDES_CMD_BUSY),
++                                       500, 100000, false);
++}
++
++static int rtl822x_config_inband(struct phy_device *phydev, unsigned int modes)
++{
++      return rtl822x_serdes_write(phydev, RTL822X_VND1_SERDES_ADDR_AUTONEG,
++                                  (modes != LINK_INBAND_DISABLE) ?
++                                  RTL822X_VND1_SERDES_INBAND_ENABLE :
++                                  RTL822X_VND1_SERDES_INBAND_DISABLE);
++}
++
++static unsigned int rtl822x_inband_caps(struct phy_device *phydev,
++                                      phy_interface_t interface)
++{
++      switch (interface) {
++      case PHY_INTERFACE_MODE_2500BASEX:
++      case PHY_INTERFACE_MODE_SGMII:
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++      default:
++              return 0;
++      }
++}
++
+ static int rtl822xb_get_rate_matching(struct phy_device *phydev,
+                                     phy_interface_t iface)
+ {
+@@ -2103,6 +2156,8 @@ static struct phy_driver realtek_drvs[]
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
+               .config_init    = rtl822xb_config_init,
++              .inband_caps    = rtl822x_inband_caps,
++              .config_inband  = rtl822x_config_inband,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+               .read_status    = rtl822xb_read_status,
+               .suspend        = genphy_suspend,
+@@ -2116,6 +2171,8 @@ static struct phy_driver realtek_drvs[]
+               .get_features   = rtl822x_c45_get_features,
+               .config_aneg    = rtl822x_c45_config_aneg,
+               .config_init    = rtl822x_config_init,
++              .inband_caps    = rtl822x_inband_caps,
++              .config_inband  = rtl822x_config_inband,
+               .read_status    = rtl822xb_c45_read_status,
+               .suspend        = genphy_c45_pma_suspend,
+               .resume         = rtlgen_c45_resume,
+@@ -2125,6 +2182,8 @@ static struct phy_driver realtek_drvs[]
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
+               .config_init    = rtl822xb_config_init,
++              .inband_caps    = rtl822x_inband_caps,
++              .config_inband  = rtl822x_config_inband,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+               .read_status    = rtl822xb_read_status,
+               .suspend        = genphy_suspend,
+@@ -2138,6 +2197,8 @@ static struct phy_driver realtek_drvs[]
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
+               .config_init    = rtl822xb_config_init,
++              .inband_caps    = rtl822x_inband_caps,
++              .config_inband  = rtl822x_config_inband,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+               .read_status    = rtl822xb_read_status,
+               .suspend        = genphy_suspend,
+@@ -2151,6 +2212,8 @@ static struct phy_driver realtek_drvs[]
+               .handle_interrupt = rtl8221b_handle_interrupt,
+               .probe          = rtl822x_probe,
+               .config_init    = rtl822xb_config_init,
++              .inband_caps    = rtl822x_inband_caps,
++              .config_inband  = rtl822x_config_inband,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+               .get_features   = rtl822x_c45_get_features,
+               .config_aneg    = rtl822x_c45_config_aneg,
+@@ -2164,6 +2227,8 @@ static struct phy_driver realtek_drvs[]
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
+               .config_init    = rtl822xb_config_init,
++              .inband_caps    = rtl822x_inband_caps,
++              .config_inband  = rtl822x_config_inband,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+               .read_status    = rtl822xb_read_status,
+               .suspend        = genphy_suspend,
+@@ -2177,6 +2242,8 @@ static struct phy_driver realtek_drvs[]
+               .handle_interrupt = rtl8221b_handle_interrupt,
+               .probe          = rtl822x_probe,
+               .config_init    = rtl822xb_config_init,
++              .inband_caps    = rtl822x_inband_caps,
++              .config_inband  = rtl822x_config_inband,
+               .get_rate_matching = rtl822xb_get_rate_matching,
+               .get_features   = rtl822x_c45_get_features,
+               .config_aneg    = rtl822x_c45_config_aneg,
diff --git a/target/linux/generic/backport-6.12/790-v7.0-net-phy-realtek-use-paged-access-for-MDIO_MMD_VEND2-.patch b/target/linux/generic/backport-6.12/790-v7.0-net-phy-realtek-use-paged-access-for-MDIO_MMD_VEND2-.patch
new file mode 100644 (file)
index 0000000..d84fb6a
--- /dev/null
@@ -0,0 +1,172 @@
+From 1850ec20d6e71218c02329e5975591075dc972d3 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 5 Jan 2026 16:38:59 +0000
+Subject: [PATCH 4/5] net: phy: realtek: use paged access for MDIO_MMD_VEND2 in
+ C22 mode
+
+RTL822x cannot access MDIO_MMD_VEND2 via MII_MMD_CTRL/MII_MMD_DATA. A
+mapping to use paged access needs to be used instead. All other MMD
+devices can be accessed as usual.
+
+Implement phy_read_mmd and phy_write_mmd using paged access for
+MDIO_MMD_VEND2 in Clause-22 mode instead of relying on
+MII_MMD_CTRL/MII_MMD_DATA. This allows eg. rtl822x_config_aneg to work
+as expected in case the MDIO bus doesn't support Clause-45 access.
+
+Suggested-by: Bevan Weiss <bevan.weiss@gmail.com>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
+Link: https://patch.msgid.link/25aab7f02dac7c6022171455523e3db1435b0881.1767630451.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek/realtek_main.c | 91 +++++++++++++++++++++++++-
+ 1 file changed, 88 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -140,9 +140,8 @@
+ #define   RTL822X_VND1_SERDES_INBAND_ENABLE   0x70d0
+ #define RTL822X_VND1_SERDES_DATA              0x7589
+-/* RTL822X_VND2_XXXXX registers are only accessible when phydev->is_c45
+- * is set, they cannot be accessed by C45-over-C22.
+- */
++#define RTL822X_VND2_TO_PAGE(reg)             ((reg) >> 4)
++#define RTL822X_VND2_TO_PAGE_REG(reg)         (16 + (((reg) & GENMASK(3, 0)) >> 1))
+ #define RTL822X_VND2_C22_REG(reg)             (0xa400 + 2 * (reg))
+ #define RTL8221B_VND2_INER                    0xa4d2
+@@ -1247,6 +1246,79 @@ static int rtl822x_probe(struct phy_devi
+       return 0;
+ }
++/* RTL822x cannot access MDIO_MMD_VEND2 via MII_MMD_CTRL/MII_MMD_DATA.
++ * A mapping to use paged access needs to be used instead.
++ * All other MMD devices can be accessed as usual.
++ */
++static int rtl822xb_read_mmd(struct phy_device *phydev, int devnum, u16 reg)
++{
++      int oldpage, ret, read_ret;
++      u16 page;
++
++      /* Use default method for all MMDs except MDIO_MMD_VEND2 or in case
++       * Clause-45 access is available
++       */
++      if (devnum != MDIO_MMD_VEND2 || phydev->is_c45)
++              return mmd_phy_read(phydev->mdio.bus, phydev->mdio.addr,
++                                  phydev->is_c45, devnum, reg);
++
++      /* Use paged access for MDIO_MMD_VEND2 over Clause-22 */
++      page = RTL822X_VND2_TO_PAGE(reg);
++      oldpage = __phy_read(phydev, RTL821x_PAGE_SELECT);
++      if (oldpage < 0)
++              return oldpage;
++
++      if (oldpage != page) {
++              ret = __phy_write(phydev, RTL821x_PAGE_SELECT, page);
++              if (ret < 0)
++                      return ret;
++      }
++
++      read_ret = __phy_read(phydev, RTL822X_VND2_TO_PAGE_REG(reg));
++      if (oldpage != page) {
++              ret = __phy_write(phydev, RTL821x_PAGE_SELECT, oldpage);
++              if (ret < 0)
++                      return ret;
++      }
++
++      return read_ret;
++}
++
++static int rtl822xb_write_mmd(struct phy_device *phydev, int devnum, u16 reg,
++                            u16 val)
++{
++      int oldpage, ret, write_ret;
++      u16 page;
++
++      /* Use default method for all MMDs except MDIO_MMD_VEND2 or in case
++       * Clause-45 access is available
++       */
++      if (devnum != MDIO_MMD_VEND2 || phydev->is_c45)
++              return mmd_phy_write(phydev->mdio.bus, phydev->mdio.addr,
++                                   phydev->is_c45, devnum, reg, val);
++
++      /* Use paged access for MDIO_MMD_VEND2 over Clause-22 */
++      page = RTL822X_VND2_TO_PAGE(reg);
++      oldpage = __phy_read(phydev, RTL821x_PAGE_SELECT);
++      if (oldpage < 0)
++              return oldpage;
++
++      if (oldpage != page) {
++              ret = __phy_write(phydev, RTL821x_PAGE_SELECT, page);
++              if (ret < 0)
++                      return ret;
++      }
++
++      write_ret = __phy_write(phydev,  RTL822X_VND2_TO_PAGE_REG(reg), val);
++      if (oldpage != page) {
++              ret = __phy_write(phydev, RTL821x_PAGE_SELECT, oldpage);
++              if (ret < 0)
++                      return ret;
++      }
++
++      return write_ret;
++}
++
+ static int rtl822x_set_serdes_option_mode(struct phy_device *phydev, bool gen1)
+ {
+       bool has_2500, has_sgmii;
+@@ -2150,6 +2222,8 @@ static struct phy_driver realtek_drvs[]
+               .resume         = rtlgen_resume,
+               .read_page      = rtl821x_read_page,
+               .write_page     = rtl821x_write_page,
++              .read_mmd       = rtl822xb_read_mmd,
++              .write_mmd      = rtl822xb_write_mmd,
+       }, {
+               .match_phy_device = rtl8221b_match_phy_device,
+               .name           = "RTL8226B_RTL8221B 2.5Gbps PHY",
+@@ -2164,6 +2238,8 @@ static struct phy_driver realtek_drvs[]
+               .resume         = rtlgen_resume,
+               .read_page      = rtl821x_read_page,
+               .write_page     = rtl821x_write_page,
++              .read_mmd       = rtl822xb_read_mmd,
++              .write_mmd      = rtl822xb_write_mmd,
+       }, {
+               PHY_ID_MATCH_EXACT(0x001cc838),
+               .name           = "RTL8226-CG 2.5Gbps PHY",
+@@ -2176,6 +2252,8 @@ static struct phy_driver realtek_drvs[]
+               .read_status    = rtl822xb_c45_read_status,
+               .suspend        = genphy_c45_pma_suspend,
+               .resume         = rtlgen_c45_resume,
++              .read_mmd       = rtl822xb_read_mmd,
++              .write_mmd      = rtl822xb_write_mmd,
+       }, {
+               PHY_ID_MATCH_EXACT(0x001cc848),
+               .name           = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY",
+@@ -2190,6 +2268,8 @@ static struct phy_driver realtek_drvs[]
+               .resume         = rtlgen_resume,
+               .read_page      = rtl821x_read_page,
+               .write_page     = rtl821x_write_page,
++              .read_mmd       = rtl822xb_read_mmd,
++              .write_mmd      = rtl822xb_write_mmd,
+       }, {
+               .match_phy_device = rtl8221b_vb_cg_c22_match_phy_device,
+               .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C22)",
+@@ -2205,6 +2285,8 @@ static struct phy_driver realtek_drvs[]
+               .resume         = rtlgen_resume,
+               .read_page      = rtl821x_read_page,
+               .write_page     = rtl821x_write_page,
++              .read_mmd       = rtl822xb_read_mmd,
++              .write_mmd      = rtl822xb_write_mmd,
+       }, {
+               .match_phy_device = rtl8221b_vb_cg_c45_match_phy_device,
+               .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C45)",
+@@ -2235,6 +2317,8 @@ static struct phy_driver realtek_drvs[]
+               .resume         = rtlgen_resume,
+               .read_page      = rtl821x_read_page,
+               .write_page     = rtl821x_write_page,
++              .read_mmd       = rtl822xb_read_mmd,
++              .write_mmd      = rtl822xb_write_mmd,
+       }, {
+               .match_phy_device = rtl8221b_vm_cg_c45_match_phy_device,
+               .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C45)",
diff --git a/target/linux/generic/backport-6.12/791-v7.0-net-phy-realtek-get-rid-of-magic-number-in-rtlgen_re.patch b/target/linux/generic/backport-6.12/791-v7.0-net-phy-realtek-get-rid-of-magic-number-in-rtlgen_re.patch
new file mode 100644 (file)
index 0000000..992e660
--- /dev/null
@@ -0,0 +1,30 @@
+From d8489935f5979b72e73de585037fe6fde7088bc3 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 5 Jan 2026 16:39:26 +0000
+Subject: [PATCH 5/5] net: phy: realtek: get rid of magic number in
+ rtlgen_read_status()
+
+Use newly introduced helper macros RTL822X_VND2_TO_PAGE and
+RTL822X_VND2_TO_PAGE_REG to access RTL_VEND2_PHYSR register over Clause-22
+paged access instead of using magic numbers.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
+Link: https://patch.msgid.link/a53d4577335fdda4d363db9bc4bf614fd3a56c9b.1767630451.git.daniel@makrotopia.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/phy/realtek/realtek_main.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -1153,7 +1153,8 @@ static int rtlgen_read_status(struct phy
+       if (!phydev->link)
+               return 0;
+-      val = phy_read_paged(phydev, 0xa43, 0x12);
++      val = phy_read_paged(phydev, RTL822X_VND2_TO_PAGE(RTL_VND2_PHYSR),
++                           RTL822X_VND2_TO_PAGE_REG(RTL_VND2_PHYSR));
+       if (val < 0)
+               return val;
diff --git a/target/linux/generic/backport-6.12/792-v7.0-net-phy-realtek-add-dummy-PHY-driver-for-RTL8127ATF.patch b/target/linux/generic/backport-6.12/792-v7.0-net-phy-realtek-add-dummy-PHY-driver-for-RTL8127ATF.patch
new file mode 100644 (file)
index 0000000..85b4111
--- /dev/null
@@ -0,0 +1,123 @@
+From c4277d21ab694c7964a48759a5452e5bbbe12965 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Sat, 10 Jan 2026 16:14:05 +0100
+Subject: [PATCH] net: phy: realtek: add dummy PHY driver for RTL8127ATF
+
+RTL8127ATF supports a SFP+ port for fiber modules (10GBASE-SR/LR/ER/ZR and
+DAC). The list of supported modes was provided by Realtek. According to the
+r8127 vendor driver also 1G modules are supported, but this needs some more
+complexity in the driver, and only 10G mode has been tested so far.
+Therefore mainline support will be limited to 10G for now.
+The SFP port signals are hidden in the chip IP and driven by firmware.
+Therefore mainline SFP support can't be used here.
+This PHY driver is used by the RTL8127ATF support in r8169.
+RTL8127ATF reports the same PHY ID as the TP version. Therefore use a dummy
+PHY ID.  This PHY driver is used by the RTL8127ATF support in r8169.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Link: https://patch.msgid.link/e3d55162-210a-4fab-9abf-99c6954eee10@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ MAINTAINERS                            |  1 +
+ drivers/net/phy/realtek/realtek_main.c | 54 ++++++++++++++++++++++++++
+ include/net/phy/realtek_phy.h          |  7 ++++
+ 3 files changed, 62 insertions(+)
+ create mode 100644 include/net/phy/realtek_phy.h
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -8459,6 +8459,7 @@ F:       include/linux/phy_link_topology.h
+ F:    include/linux/phylib_stubs.h
+ F:    include/linux/platform_data/mdio-bcm-unimac.h
+ F:    include/linux/platform_data/mdio-gpio.h
++F:    include/net/phy/
+ F:    include/trace/events/mdio.h
+ F:    include/uapi/linux/mdio.h
+ F:    include/uapi/linux/mii.h
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -17,6 +17,7 @@
+ #include <linux/delay.h>
+ #include <linux/clk.h>
+ #include <linux/string_choices.h>
++#include <net/phy/realtek_phy.h>
+ #include "realtek.h"
+@@ -2099,6 +2100,45 @@ static irqreturn_t rtl8221b_handle_inter
+       return IRQ_HANDLED;
+ }
++static int rtlgen_sfp_get_features(struct phy_device *phydev)
++{
++      linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
++                       phydev->supported);
++
++      /* set default mode */
++      phydev->speed = SPEED_10000;
++      phydev->duplex = DUPLEX_FULL;
++
++      phydev->port = PORT_FIBRE;
++
++      return 0;
++}
++
++static int rtlgen_sfp_read_status(struct phy_device *phydev)
++{
++      int val, err;
++
++      err = genphy_update_link(phydev);
++      if (err)
++              return err;
++
++      if (!phydev->link)
++              return 0;
++
++      val = rtlgen_read_vend2(phydev, RTL_VND2_PHYSR);
++      if (val < 0)
++              return val;
++
++      rtlgen_decode_physr(phydev, val);
++
++      return 0;
++}
++
++static int rtlgen_sfp_config_aneg(struct phy_device *phydev)
++{
++      return 0;
++}
++
+ static struct phy_driver realtek_drvs[] = {
+       {
+               PHY_ID_MATCH_EXACT(0x00008201),
+@@ -2357,6 +2397,20 @@ static struct phy_driver realtek_drvs[]
+               .suspend        = genphy_suspend,
+               .resume         = rtlgen_resume,
+               .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++              .read_mmd       = rtl822x_read_mmd,
++              .write_mmd      = rtl822x_write_mmd,
++      }, {
++              PHY_ID_MATCH_EXACT(PHY_ID_RTL_DUMMY_SFP),
++              .name           = "Realtek SFP PHY Mode",
++              .flags          = PHY_IS_INTERNAL,
++              .probe          = rtl822x_probe,
++              .get_features   = rtlgen_sfp_get_features,
++              .config_aneg    = rtlgen_sfp_config_aneg,
++              .read_status    = rtlgen_sfp_read_status,
++              .suspend        = genphy_suspend,
++              .resume         = rtlgen_resume,
++              .read_page      = rtl821x_read_page,
+               .write_page     = rtl821x_write_page,
+               .read_mmd       = rtl822x_read_mmd,
+               .write_mmd      = rtl822x_write_mmd,
+--- /dev/null
++++ b/include/net/phy/realtek_phy.h
+@@ -0,0 +1,7 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++#ifndef _REALTEK_PHY_H
++#define _REALTEK_PHY_H
++
++#define       PHY_ID_RTL_DUMMY_SFP    0x001ccbff
++
++#endif /* _REALTEK_PHY_H */
diff --git a/target/linux/generic/backport-6.12/793-v7.0-net-phy-realtek-fix-in-band-capabilities-for-2.5G-PH.patch b/target/linux/generic/backport-6.12/793-v7.0-net-phy-realtek-fix-in-band-capabilities-for-2.5G-PH.patch
new file mode 100644 (file)
index 0000000..7e44ea9
--- /dev/null
@@ -0,0 +1,35 @@
+From 8744b63e8a9ac4a3c30b557ca6bc115851a980e9 Mon Sep 17 00:00:00 2001
+From: Jan Hoffmann <jan@3e8.eu>
+Date: Tue, 13 Jan 2026 21:55:44 +0100
+Subject: [PATCH] net: phy: realtek: fix in-band capabilities for 2.5G PHYs
+
+It looks like the configuration of in-band AN only affects SGMII, and it
+is always disabled for 2500Base-X. Adjust the reported capabilities
+accordingly.
+
+This is based on testing using OpenWrt on Zyxel XGS1010-12 rev A1 with
+RTL8226-CG, and Zyxel XGS1210-12 rev B1 with RTL8221B-VB-CG. On these
+devices, 2500Base-X in-band AN is known to work with some SFP modules
+(containing an unknown PHY). However, with the built-in Realtek PHYs,
+no auto-negotiation takes place, irrespective of the configuration of
+the PHY.
+
+Fixes: 10fbd71fc5f9b ("net: phy: realtek: implement configuring in-band an")
+Signed-off-by: Jan Hoffmann <jan@3e8.eu>
+Reviewed-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/20260113205557.503409-1-jan@3e8.eu
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -1428,6 +1428,7 @@ static unsigned int rtl822x_inband_caps(
+ {
+       switch (interface) {
+       case PHY_INTERFACE_MODE_2500BASEX:
++              return LINK_INBAND_DISABLE;
+       case PHY_INTERFACE_MODE_SGMII:
+               return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+       default:
diff --git a/target/linux/generic/backport-6.12/794-v7.0-net-phy-realtek-support-interrupt-also-for-C22-varia.patch b/target/linux/generic/backport-6.12/794-v7.0-net-phy-realtek-support-interrupt-also-for-C22-varia.patch
new file mode 100644 (file)
index 0000000..ea3347d
--- /dev/null
@@ -0,0 +1,38 @@
+From 84fb8b93fae2a4c53323b2bf6d81e7ddcc8e7cf4 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 13 Jan 2026 03:44:00 +0000
+Subject: [PATCH 1/5] net: phy: realtek: support interrupt also for C22
+ variants
+
+Now that access to MDIO_MMD_VEND2 works transparently also in Clause-22
+mode, add interrupt support also for the C22 variants of the
+RTL8221B-VB-CG and RTL8221B-VM-CG. This results in the C22 and C45
+driver instances now having all the same features implemented.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/7620084b1de01580edc2d0e1b9548507fb4643a8.1768275364.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -2315,6 +2315,8 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               .match_phy_device = rtl8221b_vb_cg_c22_match_phy_device,
+               .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C22)",
++              .config_intr    = rtl8221b_config_intr,
++              .handle_interrupt = rtl8221b_handle_interrupt,
+               .probe          = rtl822x_probe,
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
+@@ -2347,6 +2349,8 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               .match_phy_device = rtl8221b_vm_cg_c22_match_phy_device,
+               .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C22)",
++              .config_intr    = rtl8221b_config_intr,
++              .handle_interrupt = rtl8221b_handle_interrupt,
+               .probe          = rtl822x_probe,
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
diff --git a/target/linux/generic/backport-6.12/795-v7.0-net-phy-realtek-simplify-C22-reg-access-via-MDIO_MMD.patch b/target/linux/generic/backport-6.12/795-v7.0-net-phy-realtek-simplify-C22-reg-access-via-MDIO_MMD.patch
new file mode 100644 (file)
index 0000000..a67b0ef
--- /dev/null
@@ -0,0 +1,61 @@
+From 2809a1c4340437f2ff6b73c0094d7ca51b575e1b Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 13 Jan 2026 03:44:17 +0000
+Subject: [PATCH 2/5] net: phy: realtek: simplify C22 reg access via
+ MDIO_MMD_VEND2
+
+RealTek 2.5GE PHYs have all standard Clause-22 registers mapped also
+inside MDIO_MMD_VEND2 at offset 0xa400. This is used mainly in case the
+PHY is connected to a Clause-45-only bus. The RTL8221B is frequently
+used in copper SFP module which uses the RollBall MDIO-over-I2C
+method which *only* supports Clause-45, for example.
+
+In order to support using the PHY on Clause-45-only busses, the PHY
+driver has previously been split into a C22-only and C45-only instances,
+creating quite a bit of redundancy and confusion.
+
+In preparation of reunifying the two driver instances, add support for
+translating MDIO_MMD_VEND2 registers 0xa400 to 0xa43c back to Clause-22
+registers 0 to 30 in case the PHY is accessed on a Clause-22 bus.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/fd49d86bd0445b76269fd3ea456c709c2066683f.1768275364.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -143,6 +143,7 @@
+ #define RTL822X_VND2_TO_PAGE(reg)             ((reg) >> 4)
+ #define RTL822X_VND2_TO_PAGE_REG(reg)         (16 + (((reg) & GENMASK(3, 0)) >> 1))
++#define RTL822X_VND2_TO_C22_REG(reg)          (((reg) - 0xa400) / 2)
+ #define RTL822X_VND2_C22_REG(reg)             (0xa400 + 2 * (reg))
+ #define RTL8221B_VND2_INER                    0xa4d2
+@@ -1264,6 +1265,11 @@ static int rtl822xb_read_mmd(struct phy_
+               return mmd_phy_read(phydev->mdio.bus, phydev->mdio.addr,
+                                   phydev->is_c45, devnum, reg);
++      /* Simplify access to C22-registers addressed inside MDIO_MMD_VEND2 */
++      if (reg >= RTL822X_VND2_C22_REG(0) &&
++          reg <= RTL822X_VND2_C22_REG(30))
++              return __phy_read(phydev, RTL822X_VND2_TO_C22_REG(reg));
++
+       /* Use paged access for MDIO_MMD_VEND2 over Clause-22 */
+       page = RTL822X_VND2_TO_PAGE(reg);
+       oldpage = __phy_read(phydev, RTL821x_PAGE_SELECT);
+@@ -1299,6 +1305,11 @@ static int rtl822xb_write_mmd(struct phy
+               return mmd_phy_write(phydev->mdio.bus, phydev->mdio.addr,
+                                    phydev->is_c45, devnum, reg, val);
++      /* Simplify access to C22-registers addressed inside MDIO_MMD_VEND2 */
++      if (reg >= RTL822X_VND2_C22_REG(0) &&
++          reg <= RTL822X_VND2_C22_REG(30))
++              return __phy_write(phydev, RTL822X_VND2_TO_C22_REG(reg), val);
++
+       /* Use paged access for MDIO_MMD_VEND2 over Clause-22 */
+       page = RTL822X_VND2_TO_PAGE(reg);
+       oldpage = __phy_read(phydev, RTL821x_PAGE_SELECT);
diff --git a/target/linux/generic/backport-6.12/796-v7.0-net-phy-realtek-reunify-C22-and-C45-drivers.patch b/target/linux/generic/backport-6.12/796-v7.0-net-phy-realtek-reunify-C22-and-C45-drivers.patch
new file mode 100644 (file)
index 0000000..5bca303
--- /dev/null
@@ -0,0 +1,133 @@
+From 85f75da86a0adc4798d3675aab3365e721a1dbf5 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 13 Jan 2026 03:44:25 +0000
+Subject: [PATCH 3/5] net: phy: realtek: reunify C22 and C45 drivers
+
+Reunify the split C22/C45 drivers for the RTL8221B-VB-CG 2.5Gbps and
+RTL8221B-VM-CG 2.5Gbps PHYs back into a single driver.
+
+This is possible now by using all the driver operations previously used
+by the C45 driver, as transparent access to all MMDs including
+MDIO_MMD_VEND2 is now possible also over Clause-22 MDIO.
+
+The unified driver will still only use Clause-45 access on any Clause-45
+capable busses while still working fine on Clause-22 busses.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/bffcb85fdc20e07056976962d3caaa1be5d0ddb0.1768275364.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 72 ++++++--------------------
+ 1 file changed, 16 insertions(+), 56 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -1880,28 +1880,18 @@ static int rtl8221b_match_phy_device(str
+       return phydev->phy_id == RTL_8221B && rtlgen_supports_mmd(phydev);
+ }
+-static int rtl8221b_vb_cg_c22_match_phy_device(struct phy_device *phydev,
+-                                             const struct phy_driver *phydrv)
++static int rtl8221b_vb_cg_match_phy_device(struct phy_device *phydev,
++                                         const struct phy_driver *phydrv)
+ {
+-      return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, false);
++      return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, true) ||
++             rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, false);
+ }
+-static int rtl8221b_vb_cg_c45_match_phy_device(struct phy_device *phydev,
+-                                             const struct phy_driver *phydrv)
++static int rtl8221b_vm_cg_match_phy_device(struct phy_device *phydev,
++                                         const struct phy_driver *phydrv)
+ {
+-      return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, true);
+-}
+-
+-static int rtl8221b_vm_cg_c22_match_phy_device(struct phy_device *phydev,
+-                                             const struct phy_driver *phydrv)
+-{
+-      return rtlgen_is_c45_match(phydev, RTL_8221B_VM_CG, false);
+-}
+-
+-static int rtl8221b_vm_cg_c45_match_phy_device(struct phy_device *phydev,
+-                                             const struct phy_driver *phydrv)
+-{
+-      return rtlgen_is_c45_match(phydev, RTL_8221B_VM_CG, true);
++      return rtlgen_is_c45_match(phydev, RTL_8221B_VM_CG, true) ||
++             rtlgen_is_c45_match(phydev, RTL_8221B_VM_CG, false);
+ }
+ static int rtl_internal_nbaset_match_phy_device(struct phy_device *phydev,
+@@ -2324,27 +2314,8 @@ static struct phy_driver realtek_drvs[]
+               .read_mmd       = rtl822xb_read_mmd,
+               .write_mmd      = rtl822xb_write_mmd,
+       }, {
+-              .match_phy_device = rtl8221b_vb_cg_c22_match_phy_device,
+-              .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C22)",
+-              .config_intr    = rtl8221b_config_intr,
+-              .handle_interrupt = rtl8221b_handle_interrupt,
+-              .probe          = rtl822x_probe,
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .config_init    = rtl822xb_config_init,
+-              .inband_caps    = rtl822x_inband_caps,
+-              .config_inband  = rtl822x_config_inband,
+-              .get_rate_matching = rtl822xb_get_rate_matching,
+-              .read_status    = rtl822xb_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+-              .read_page      = rtl821x_read_page,
+-              .write_page     = rtl821x_write_page,
+-              .read_mmd       = rtl822xb_read_mmd,
+-              .write_mmd      = rtl822xb_write_mmd,
+-      }, {
+-              .match_phy_device = rtl8221b_vb_cg_c45_match_phy_device,
+-              .name           = "RTL8221B-VB-CG 2.5Gbps PHY (C45)",
++              .match_phy_device = rtl8221b_vb_cg_match_phy_device,
++              .name           = "RTL8221B-VB-CG 2.5Gbps PHY",
+               .config_intr    = rtl8221b_config_intr,
+               .handle_interrupt = rtl8221b_handle_interrupt,
+               .probe          = rtl822x_probe,
+@@ -2357,28 +2328,13 @@ static struct phy_driver realtek_drvs[]
+               .read_status    = rtl822xb_c45_read_status,
+               .suspend        = genphy_c45_pma_suspend,
+               .resume         = rtlgen_c45_resume,
+-      }, {
+-              .match_phy_device = rtl8221b_vm_cg_c22_match_phy_device,
+-              .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C22)",
+-              .config_intr    = rtl8221b_config_intr,
+-              .handle_interrupt = rtl8221b_handle_interrupt,
+-              .probe          = rtl822x_probe,
+-              .get_features   = rtl822x_get_features,
+-              .config_aneg    = rtl822x_config_aneg,
+-              .config_init    = rtl822xb_config_init,
+-              .inband_caps    = rtl822x_inband_caps,
+-              .config_inband  = rtl822x_config_inband,
+-              .get_rate_matching = rtl822xb_get_rate_matching,
+-              .read_status    = rtl822xb_read_status,
+-              .suspend        = genphy_suspend,
+-              .resume         = rtlgen_resume,
+               .read_page      = rtl821x_read_page,
+               .write_page     = rtl821x_write_page,
+               .read_mmd       = rtl822xb_read_mmd,
+               .write_mmd      = rtl822xb_write_mmd,
+       }, {
+-              .match_phy_device = rtl8221b_vm_cg_c45_match_phy_device,
+-              .name           = "RTL8221B-VM-CG 2.5Gbps PHY (C45)",
++              .match_phy_device = rtl8221b_vm_cg_match_phy_device,
++              .name           = "RTL8221B-VM-CG 2.5Gbps PHY",
+               .config_intr    = rtl8221b_config_intr,
+               .handle_interrupt = rtl8221b_handle_interrupt,
+               .probe          = rtl822x_probe,
+@@ -2391,6 +2347,10 @@ static struct phy_driver realtek_drvs[]
+               .read_status    = rtl822xb_c45_read_status,
+               .suspend        = genphy_c45_pma_suspend,
+               .resume         = rtlgen_c45_resume,
++              .read_page      = rtl821x_read_page,
++              .write_page     = rtl821x_write_page,
++              .read_mmd       = rtl822xb_read_mmd,
++              .write_mmd      = rtl822xb_write_mmd,
+       }, {
+               .match_phy_device = rtl8251b_c45_match_phy_device,
+               .name           = "RTL8251B 5Gbps PHY",
diff --git a/target/linux/generic/backport-6.12/797-v7.0-net-phy-realtek-demystify-PHYSR-register-location.patch b/target/linux/generic/backport-6.12/797-v7.0-net-phy-realtek-demystify-PHYSR-register-location.patch
new file mode 100644 (file)
index 0000000..185e0fd
--- /dev/null
@@ -0,0 +1,90 @@
+From 46ff862d376cfadf0f9e36a6edce41a003175708 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 13 Jan 2026 03:44:33 +0000
+Subject: [PATCH 4/5] net: phy: realtek: demystify PHYSR register location
+
+Turns out that register address RTL_VND2_PHYSR (0xa434) maps to
+Clause-22 register MII_RESV2. Use that to get rid of yet another magic
+number, and rename access macros accordingly.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/6ed246e0aa3ca8038d2fa432d51518959fb89b6b.1768275364.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 26 +++++++++++++-------------
+ 1 file changed, 13 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -178,12 +178,12 @@
+ #define RTL9000A_GINMR                                0x14
+ #define RTL9000A_GINMR_LINK_STATUS            BIT(4)
+-#define RTL_VND2_PHYSR                                0xa434
+-#define RTL_VND2_PHYSR_DUPLEX                 BIT(3)
+-#define RTL_VND2_PHYSR_SPEEDL                 GENMASK(5, 4)
+-#define RTL_VND2_PHYSR_SPEEDH                 GENMASK(10, 9)
+-#define RTL_VND2_PHYSR_MASTER                 BIT(11)
+-#define RTL_VND2_PHYSR_SPEED_MASK             (RTL_VND2_PHYSR_SPEEDL | RTL_VND2_PHYSR_SPEEDH)
++#define RTL_PHYSR                             MII_RESV2
++#define RTL_PHYSR_DUPLEX                      BIT(3)
++#define RTL_PHYSR_SPEEDL                      GENMASK(5, 4)
++#define RTL_PHYSR_SPEEDH                      GENMASK(10, 9)
++#define RTL_PHYSR_MASTER                      BIT(11)
++#define RTL_PHYSR_SPEED_MASK                  (RTL_PHYSR_SPEEDL | RTL_PHYSR_SPEEDH)
+ #define       RTL_MDIO_PCS_EEE_ABLE                   0xa5c4
+ #define       RTL_MDIO_AN_EEE_ADV                     0xa5d0
+@@ -1102,12 +1102,12 @@ static void rtlgen_decode_physr(struct p
+        * 0: Half Duplex
+        * 1: Full Duplex
+        */
+-      if (val & RTL_VND2_PHYSR_DUPLEX)
++      if (val & RTL_PHYSR_DUPLEX)
+               phydev->duplex = DUPLEX_FULL;
+       else
+               phydev->duplex = DUPLEX_HALF;
+-      switch (val & RTL_VND2_PHYSR_SPEED_MASK) {
++      switch (val & RTL_PHYSR_SPEED_MASK) {
+       case 0x0000:
+               phydev->speed = SPEED_10;
+               break;
+@@ -1135,7 +1135,7 @@ static void rtlgen_decode_physr(struct p
+        * 1: Master Mode
+        */
+       if (phydev->speed >= 1000) {
+-              if (val & RTL_VND2_PHYSR_MASTER)
++              if (val & RTL_PHYSR_MASTER)
+                       phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
+               else
+                       phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
+@@ -1155,8 +1155,7 @@ static int rtlgen_read_status(struct phy
+       if (!phydev->link)
+               return 0;
+-      val = phy_read_paged(phydev, RTL822X_VND2_TO_PAGE(RTL_VND2_PHYSR),
+-                           RTL822X_VND2_TO_PAGE_REG(RTL_VND2_PHYSR));
++      val = phy_read(phydev, RTL_PHYSR);
+       if (val < 0)
+               return val;
+@@ -1623,7 +1622,8 @@ static int rtl822x_c45_read_status(struc
+       }
+       /* Read actual speed from vendor register. */
+-      val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL_VND2_PHYSR);
++      val = phy_read_mmd(phydev, MDIO_MMD_VEND2,
++                         RTL822X_VND2_C22_REG(RTL_PHYSR));
+       if (val < 0)
+               return val;
+@@ -2127,7 +2127,7 @@ static int rtlgen_sfp_read_status(struct
+       if (!phydev->link)
+               return 0;
+-      val = rtlgen_read_vend2(phydev, RTL_VND2_PHYSR);
++      val = phy_read(phydev, RTL_PHYSR);
+       if (val < 0)
+               return val;
diff --git a/target/linux/generic/backport-6.12/798-v7.0-net-phy-realtek-simplify-bogus-paged-operations.patch b/target/linux/generic/backport-6.12/798-v7.0-net-phy-realtek-simplify-bogus-paged-operations.patch
new file mode 100644 (file)
index 0000000..f03ad66
--- /dev/null
@@ -0,0 +1,93 @@
+From 650e55f224a575cdb18c984b95036109519502d1 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 13 Jan 2026 03:44:42 +0000
+Subject: [PATCH 5/5] net: phy: realtek: simplify bogus paged operations
+
+Only registers 0x10~0x17 are affected by the value in the page
+selection register 0x1f. Hence there is no point in using paged
+operations when accessing any other registers.
+Simplify the driver by using the normal phy_read and phy_write
+operations for registers which are anyway not affected by paging.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/0c5cbb66ce3e72a011d76f8c3d61ebcac44483bb.1768275364.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 19 ++++++++-----------
+ 1 file changed, 8 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -67,7 +67,6 @@
+ #define RTL8211E_DELAY_MASK                   GENMASK(13, 11)
+ /* RTL8211F PHY configuration */
+-#define RTL8211F_PHYCR_PAGE                   0xa43
+ #define RTL8211F_PHYCR1                               0x18
+ #define RTL8211F_ALDPS_PLL_OFF                        BIT(1)
+ #define RTL8211F_ALDPS_ENABLE                 BIT(2)
+@@ -77,7 +76,6 @@
+ #define RTL8211F_CLKOUT_EN                    BIT(0)
+ #define RTL8211F_PHYCR2_PHY_EEE_ENABLE                BIT(5)
+-#define RTL8211F_INSR_PAGE                    0xa43
+ #define RTL8211F_INSR                         0x1d
+ /* RTL8211F LED configuration */
+@@ -332,7 +330,7 @@ static int rtl8211f_ack_interrupt(struct
+ {
+       int err;
+-      err = phy_read_paged(phydev, RTL8211F_INSR_PAGE, RTL8211F_INSR);
++      err = phy_read(phydev, RTL8211F_INSR);
+       return (err < 0) ? err : 0;
+ }
+@@ -478,7 +476,7 @@ static irqreturn_t rtl8211f_handle_inter
+ {
+       int irq_status;
+-      irq_status = phy_read_paged(phydev, RTL8211F_INSR_PAGE, RTL8211F_INSR);
++      irq_status = phy_read(phydev, RTL8211F_INSR);
+       if (irq_status < 0) {
+               phy_error(phydev);
+               return IRQ_NONE;
+@@ -669,8 +667,8 @@ static int rtl8211f_config_clk_out(struc
+                                      RTL8211FVD_CLKOUT_REG,
+                                      RTL8211FVD_CLKOUT_EN, 0);
+       else
+-              ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE,
+-                                     RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN, 0);
++              ret = phy_modify(phydev, RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN,
++                               0);
+       if (ret)
+               return ret;
+@@ -695,15 +693,14 @@ static int rtl8211f_config_aldps(struct
+       if (!priv->enable_aldps)
+               return 0;
+-      return phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1,
+-                              mask, mask);
++      return phy_modify(phydev, RTL8211F_PHYCR1, mask, mask);
+ }
+ static int rtl8211f_config_phy_eee(struct phy_device *phydev)
+ {
+       /* Disable PHY-mode EEE so LPI is passed to the MAC */
+-      return phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2,
+-                              RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0);
++      return phy_modify(phydev, RTL8211F_PHYCR2,
++                        RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0);
+ }
+ static int rtl8211f_config_init(struct phy_device *phydev)
+@@ -769,7 +766,7 @@ static int rtl8211f_suspend(struct phy_d
+                       goto err;
+               /* Read the INSR to clear any pending interrupt */
+-              phy_read_paged(phydev, RTL8211F_INSR_PAGE, RTL8211F_INSR);
++              phy_read(phydev, RTL8211F_INSR);
+               /* Reset the WoL to ensure that an event is picked up.
+                * Unless we do this, even if we receive another packet,
diff --git a/target/linux/generic/backport-6.12/799-v7.0-mtd-physmap-core-Prioritize-ofparts-for-OF-probe.patch b/target/linux/generic/backport-6.12/799-v7.0-mtd-physmap-core-Prioritize-ofparts-for-OF-probe.patch
new file mode 100644 (file)
index 0000000..4aac643
--- /dev/null
@@ -0,0 +1,33 @@
+From cd3303e9081050ad09b031bc5b58c89fe6902f86 Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linusw@kernel.org>
+Date: Thu, 29 Jan 2026 00:48:36 +0100
+Subject: [PATCH] mtd: physmap-core: Prioritize ofparts for OF probe
+
+Place the ofparts before RedBoot partitions in the OF probe
+path: this makes it possible to override any existing
+RedBoot partitions with fixed-partitions which may be necessary,
+such as when you need to repartition the flash from Linux'
+point of view but not necessary from the bootloaders point
+of view.
+
+This happens when a device such as Raidsonic IB-4220-B has
+three partitions named "Kern", "Ramdisk" and "Application"
+that we want to merge into one for more efficient use
+of the flash memory in OpenWrt.
+
+Signed-off-by: Linus Walleij <linusw@kernel.org>
+---
+ drivers/mtd/maps/physmap-core.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/mtd/maps/physmap-core.c
++++ b/drivers/mtd/maps/physmap-core.c
+@@ -268,7 +268,7 @@ static const struct of_device_id of_flas
+ MODULE_DEVICE_TABLE(of, of_flash_match);
+ static const char * const of_default_part_probes[] = {
+-      "cmdlinepart", "RedBoot", "ofpart", "ofoldpart", NULL
++      "cmdlinepart", "ofpart", "ofoldpart", "RedBoot", NULL
+ };
+ static const char * const *of_get_part_probes(struct platform_device *dev)
diff --git a/target/linux/generic/backport-6.12/800-v6.13-hwmon-Add-static-visibility-member-to-struct-hwmon_o.patch b/target/linux/generic/backport-6.12/800-v6.13-hwmon-Add-static-visibility-member-to-struct-hwmon_o.patch
new file mode 100644 (file)
index 0000000..0c9f920
--- /dev/null
@@ -0,0 +1,88 @@
+From 79bc0af904db647979c735563299c9b0d820e432 Mon Sep 17 00:00:00 2001
+From: Heiner Kallweit <hkallweit1@gmail.com>
+Date: Thu, 10 Oct 2024 21:35:42 +0200
+Subject: [PATCH] hwmon: Add static visibility member to struct hwmon_ops
+
+Several drivers return the same static value in their is_visible
+callback, what results in code duplication. Therefore add an option
+for drivers to specify a static visibility directly.
+
+Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
+Message-ID: <89690b81-2c73-47ae-9ae9-45c77b45ca0c@gmail.com>
+groeck: Renamed hwmon_ops_is_visible -> hwmon_is_visible
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+ drivers/hwmon/hwmon.c | 19 +++++++++++++++----
+ include/linux/hwmon.h |  5 ++++-
+ 2 files changed, 19 insertions(+), 5 deletions(-)
+
+--- a/drivers/hwmon/hwmon.c
++++ b/drivers/hwmon/hwmon.c
+@@ -145,6 +145,17 @@ static const struct class hwmon_class =
+ static DEFINE_IDA(hwmon_ida);
++static umode_t hwmon_is_visible(const struct hwmon_ops *ops,
++                              const void *drvdata,
++                              enum hwmon_sensor_types type,
++                              u32 attr, int channel)
++{
++      if (ops->visible)
++              return ops->visible;
++
++      return ops->is_visible(drvdata, type, attr, channel);
++}
++
+ /* Thermal zone handling */
+ /*
+@@ -267,8 +278,8 @@ static int hwmon_thermal_register_sensor
+                       int err;
+                       if (!(info[i]->config[j] & HWMON_T_INPUT) ||
+-                          !chip->ops->is_visible(drvdata, hwmon_temp,
+-                                                 hwmon_temp_input, j))
++                          !hwmon_is_visible(chip->ops, drvdata, hwmon_temp,
++                                            hwmon_temp_input, j))
+                               continue;
+                       err = hwmon_thermal_add_sensor(dev, j);
+@@ -506,7 +517,7 @@ static struct attribute *hwmon_genattr(c
+       const char *name;
+       bool is_string = is_string_attr(type, attr);
+-      mode = ops->is_visible(drvdata, type, attr, index);
++      mode = hwmon_is_visible(ops, drvdata, type, attr, index);
+       if (!mode)
+               return ERR_PTR(-ENOENT);
+@@ -1033,7 +1044,7 @@ hwmon_device_register_with_info(struct d
+       if (!dev || !name || !chip)
+               return ERR_PTR(-EINVAL);
+-      if (!chip->ops || !chip->ops->is_visible || !chip->info)
++      if (!chip->ops || !(chip->ops->visible || chip->ops->is_visible) || !chip->info)
+               return ERR_PTR(-EINVAL);
+       return __hwmon_device_register(dev, name, drvdata, chip, extra_groups);
+--- a/include/linux/hwmon.h
++++ b/include/linux/hwmon.h
+@@ -368,7 +368,9 @@ enum hwmon_intrusion_attributes {
+ /**
+  * struct hwmon_ops - hwmon device operations
+- * @is_visible: Callback to return attribute visibility. Mandatory.
++ * @visible:  Static visibility. If non-zero, 'is_visible' is ignored.
++ * @is_visible: Callback to return attribute visibility. Mandatory unless
++ *            'visible' is non-zero.
+  *            Parameters are:
+  *            @const void *drvdata:
+  *                    Pointer to driver-private data structure passed
+@@ -412,6 +414,7 @@ enum hwmon_intrusion_attributes {
+  *            The function returns 0 on success or a negative error number.
+  */
+ struct hwmon_ops {
++      umode_t visible;
+       umode_t (*is_visible)(const void *drvdata, enum hwmon_sensor_types type,
+                             u32 attr, int channel);
+       int (*read)(struct device *dev, enum hwmon_sensor_types type,
diff --git a/target/linux/generic/backport-6.12/801-01-v6.13-dt-bindings-clocks-add-binding-for-gated-fixed-clock.patch b/target/linux/generic/backport-6.12/801-01-v6.13-dt-bindings-clocks-add-binding-for-gated-fixed-clock.patch
new file mode 100644 (file)
index 0000000..f5ab8a0
--- /dev/null
@@ -0,0 +1,82 @@
+From a4a7cbe36623ffaabbae413c0eacf40d033db71f Mon Sep 17 00:00:00 2001
+From: Heiko Stuebner <heiko@sntech.de>
+Date: Fri, 6 Sep 2024 10:25:07 +0200
+Subject: dt-bindings: clocks: add binding for gated-fixed-clocks
+
+In contrast to fixed clocks that are described as ungateable, boards
+sometimes use additional oscillators for things like PCIe reference
+clocks, that need actual supplies to get enabled and enable-gpios to be
+toggled for them to work.
+
+This adds a binding for such oscillators that are not configurable
+themself, but need to handle supplies for them to work.
+
+In schematics they often can be seen as
+
+         ----------------
+Enable - | 100MHz,3.3V, | - VDD
+         |    3225      |
+   GND - |              | - OUT
+         ----------------
+
+or similar. The enable pin might be separate but can also just be tied
+to the vdd supply, hence it is optional in the binding.
+
+Signed-off-by: Heiko Stuebner <heiko@sntech.de>
+Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
+Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
+Link: https://lore.kernel.org/r/20240906082511.2963890-2-heiko@sntech.de
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/clock/gated-fixed-clock.yaml
+@@ -0,0 +1,49 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/clock/gated-fixed-clock.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Gated Fixed clock
++
++maintainers:
++  - Heiko Stuebner <heiko@sntech.de>
++
++properties:
++  compatible:
++    const: gated-fixed-clock
++
++  "#clock-cells":
++    const: 0
++
++  clock-frequency: true
++
++  clock-output-names:
++    maxItems: 1
++
++  enable-gpios:
++    description:
++      Contains a single GPIO specifier for the GPIO that enables and disables
++      the oscillator.
++    maxItems: 1
++
++  vdd-supply:
++    description: handle of the regulator that provides the supply voltage
++
++required:
++  - compatible
++  - "#clock-cells"
++  - clock-frequency
++  - vdd-supply
++
++additionalProperties: false
++
++examples:
++  - |
++    clock-1000000000 {
++      compatible = "gated-fixed-clock";
++      #clock-cells = <0>;
++      clock-frequency = <1000000000>;
++      vdd-supply = <&reg_vdd>;
++    };
++...
diff --git a/target/linux/generic/backport-6.12/801-02-v6.13-clk-clk-gpio-update-documentation-for-gpio-gate-cloc.patch b/target/linux/generic/backport-6.12/801-02-v6.13-clk-clk-gpio-update-documentation-for-gpio-gate-cloc.patch
new file mode 100644 (file)
index 0000000..99e41a1
--- /dev/null
@@ -0,0 +1,27 @@
+From 6cb137c7e99f8307f1f0fcccb1896f2d3b0651d3 Mon Sep 17 00:00:00 2001
+From: Heiko Stuebner <heiko@sntech.de>
+Date: Fri, 6 Sep 2024 10:25:08 +0200
+Subject: clk: clk-gpio: update documentation for gpio-gate clock
+
+The main documentation block seems to be from a time before the driver
+handled sleeping and non-sleeping gpios and with that change it seems
+updating the doc was overlooked. So do that now.
+
+Signed-off-by: Heiko Stuebner <heiko@sntech.de>
+Link: https://lore.kernel.org/r/20240906082511.2963890-3-heiko@sntech.de
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+
+--- a/drivers/clk/clk-gpio.c
++++ b/drivers/clk/clk-gpio.c
+@@ -22,8 +22,9 @@
+  * DOC: basic gpio gated clock which can be enabled and disabled
+  *      with gpio output
+  * Traits of this clock:
+- * prepare - clk_(un)prepare only ensures parent is (un)prepared
+- * enable - clk_enable and clk_disable are functional & control gpio
++ * prepare - clk_(un)prepare are functional and control a gpio that can sleep
++ * enable - clk_enable and clk_disable are functional & control
++ *          non-sleeping gpio
+  * rate - inherits rate from parent.  No clk_set_rate support
+  * parent - fixed parent.  No clk_set_parent support
+  */
diff --git a/target/linux/generic/backport-6.12/801-03-v6.13-clk-clk-gpio-use-dev_err_probe-for-gpio-get-failure.patch b/target/linux/generic/backport-6.12/801-03-v6.13-clk-clk-gpio-use-dev_err_probe-for-gpio-get-failure.patch
new file mode 100644 (file)
index 0000000..1982bb3
--- /dev/null
@@ -0,0 +1,44 @@
+From 36abe81d9c3fa200a57ef2363e93a2991e387e19 Mon Sep 17 00:00:00 2001
+From: Heiko Stuebner <heiko@sntech.de>
+Date: Fri, 6 Sep 2024 10:25:09 +0200
+Subject: clk: clk-gpio: use dev_err_probe for gpio-get failure
+
+This is a real driver and dev_err_probe will hide the distinction between
+EPROBE_DEFER and other errors automatically, so there is no need to
+open-code this.
+
+Signed-off-by: Heiko Stuebner <heiko@sntech.de>
+Link: https://lore.kernel.org/r/20240906082511.2963890-4-heiko@sntech.de
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+
+--- a/drivers/clk/clk-gpio.c
++++ b/drivers/clk/clk-gpio.c
+@@ -200,7 +200,6 @@ static int gpio_clk_driver_probe(struct
+       struct gpio_desc *gpiod;
+       struct clk_hw *hw;
+       bool is_mux;
+-      int ret;
+       is_mux = of_device_is_compatible(node, "gpio-mux-clock");
+@@ -212,17 +211,9 @@ static int gpio_clk_driver_probe(struct
+       gpio_name = is_mux ? "select" : "enable";
+       gpiod = devm_gpiod_get(dev, gpio_name, GPIOD_OUT_LOW);
+-      if (IS_ERR(gpiod)) {
+-              ret = PTR_ERR(gpiod);
+-              if (ret == -EPROBE_DEFER)
+-                      pr_debug("%pOFn: %s: GPIOs not yet available, retry later\n",
+-                                      node, __func__);
+-              else
+-                      pr_err("%pOFn: %s: Can't get '%s' named GPIO property\n",
+-                                      node, __func__,
+-                                      gpio_name);
+-              return ret;
+-      }
++      if (IS_ERR(gpiod))
++              return dev_err_probe(dev, PTR_ERR(gpiod),
++                                   "Can't get '%s' named GPIO property\n", gpio_name);
+       if (is_mux)
+               hw = clk_hw_register_gpio_mux(dev, gpiod);
diff --git a/target/linux/generic/backport-6.12/801-04-v6.13-clk-clk-gpio-add-driver-for-gated-fixed-clocks.patch b/target/linux/generic/backport-6.12/801-04-v6.13-clk-clk-gpio-add-driver-for-gated-fixed-clocks.patch
new file mode 100644 (file)
index 0000000..44da746
--- /dev/null
@@ -0,0 +1,229 @@
+From 4940071d962827467f7297be3b874c96186ca0b7 Mon Sep 17 00:00:00 2001
+From: Heiko Stuebner <heiko@sntech.de>
+Date: Fri, 6 Sep 2024 10:25:10 +0200
+Subject: clk: clk-gpio: add driver for gated-fixed-clocks
+
+In contrast to fixed clocks that are described as ungateable, boards
+sometimes use additional oscillators for things like PCIe reference
+clocks, that need actual supplies to get enabled and enable-gpios to be
+toggled for them to work.
+
+This adds a driver for those generic gated-fixed-clocks
+that can show up in schematics looking like
+
+         ----------------
+Enable - | 100MHz,3.3V, | - VDD
+         |    3225      |
+   GND - |              | - OUT
+         ----------------
+
+The new driver gets grouped together with the existing gpio-gate and
+gpio-mux, as it for one re-uses a lot of the gpio-gate functions
+and also in its core it's just another gpio-controlled clock, just
+with a fixed rate and a regulator-supply added in.
+
+The regulator-API provides function stubs for the !CONFIG_REGULATOR case,
+so no special handling is necessary.
+
+Signed-off-by: Heiko Stuebner <heiko@sntech.de>
+Link: https://lore.kernel.org/r/20240906082511.2963890-5-heiko@sntech.de
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+
+--- a/drivers/clk/clk-gpio.c
++++ b/drivers/clk/clk-gpio.c
+@@ -17,6 +17,7 @@
+ #include <linux/device.h>
+ #include <linux/of.h>
+ #include <linux/platform_device.h>
++#include <linux/regulator/consumer.h>
+ /**
+  * DOC: basic gpio gated clock which can be enabled and disabled
+@@ -239,3 +240,187 @@ static struct platform_driver gpio_clk_d
+       },
+ };
+ builtin_platform_driver(gpio_clk_driver);
++
++/**
++ * DOC: gated fixed clock, controlled with a gpio output and a regulator
++ * Traits of this clock:
++ * prepare - clk_prepare and clk_unprepare are function & control regulator
++ *           optionally a gpio that can sleep
++ * enable - clk_enable and clk_disable are functional & control gpio
++ * rate - rate is fixed and set on clock registration
++ * parent - fixed clock is a root clock and has no parent
++ */
++
++/**
++ * struct clk_gated_fixed - Gateable fixed rate clock
++ * @clk_gpio: instance of clk_gpio for gate-gpio
++ * @supply:   supply regulator
++ * @rate:     fixed rate
++ */
++struct clk_gated_fixed {
++      struct clk_gpio clk_gpio;
++      struct regulator *supply;
++      unsigned long rate;
++};
++
++#define to_clk_gated_fixed(_clk_gpio) container_of(_clk_gpio, struct clk_gated_fixed, clk_gpio)
++
++static unsigned long clk_gated_fixed_recalc_rate(struct clk_hw *hw,
++                                               unsigned long parent_rate)
++{
++      return to_clk_gated_fixed(to_clk_gpio(hw))->rate;
++}
++
++static int clk_gated_fixed_prepare(struct clk_hw *hw)
++{
++      struct clk_gated_fixed *clk = to_clk_gated_fixed(to_clk_gpio(hw));
++
++      if (!clk->supply)
++              return 0;
++
++      return regulator_enable(clk->supply);
++}
++
++static void clk_gated_fixed_unprepare(struct clk_hw *hw)
++{
++      struct clk_gated_fixed *clk = to_clk_gated_fixed(to_clk_gpio(hw));
++
++      if (!clk->supply)
++              return;
++
++      regulator_disable(clk->supply);
++}
++
++static int clk_gated_fixed_is_prepared(struct clk_hw *hw)
++{
++      struct clk_gated_fixed *clk = to_clk_gated_fixed(to_clk_gpio(hw));
++
++      if (!clk->supply)
++              return true;
++
++      return regulator_is_enabled(clk->supply);
++}
++
++/*
++ * Fixed gated clock with non-sleeping gpio.
++ *
++ * Prepare operation turns on the supply regulator
++ * and the enable operation switches the enable-gpio.
++ */
++static const struct clk_ops clk_gated_fixed_ops = {
++      .prepare = clk_gated_fixed_prepare,
++      .unprepare = clk_gated_fixed_unprepare,
++      .is_prepared = clk_gated_fixed_is_prepared,
++      .enable = clk_gpio_gate_enable,
++      .disable = clk_gpio_gate_disable,
++      .is_enabled = clk_gpio_gate_is_enabled,
++      .recalc_rate = clk_gated_fixed_recalc_rate,
++};
++
++static int clk_sleeping_gated_fixed_prepare(struct clk_hw *hw)
++{
++      int ret;
++
++      ret = clk_gated_fixed_prepare(hw);
++      if (ret)
++              return ret;
++
++      ret = clk_sleeping_gpio_gate_prepare(hw);
++      if (ret)
++              clk_gated_fixed_unprepare(hw);
++
++      return ret;
++}
++
++static void clk_sleeping_gated_fixed_unprepare(struct clk_hw *hw)
++{
++      clk_gated_fixed_unprepare(hw);
++      clk_sleeping_gpio_gate_unprepare(hw);
++}
++
++/*
++ * Fixed gated clock with non-sleeping gpio.
++ *
++ * Enabling the supply regulator and switching the enable-gpio happens
++ * both in the prepare step.
++ * is_prepared only needs to check the gpio state, as toggling the
++ * gpio is the last step when preparing.
++ */
++static const struct clk_ops clk_sleeping_gated_fixed_ops = {
++      .prepare = clk_sleeping_gated_fixed_prepare,
++      .unprepare = clk_sleeping_gated_fixed_unprepare,
++      .is_prepared = clk_sleeping_gpio_gate_is_prepared,
++      .recalc_rate = clk_gated_fixed_recalc_rate,
++};
++
++static int clk_gated_fixed_probe(struct platform_device *pdev)
++{
++      struct device *dev = &pdev->dev;
++      struct clk_gated_fixed *clk;
++      const struct clk_ops *ops;
++      const char *clk_name;
++      u32 rate;
++      int ret;
++
++      clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL);
++      if (!clk)
++              return -ENOMEM;
++
++      ret = device_property_read_u32(dev, "clock-frequency", &rate);
++      if (ret)
++              return dev_err_probe(dev, ret, "Failed to get clock-frequency\n");
++      clk->rate = rate;
++
++      ret = device_property_read_string(dev, "clock-output-names", &clk_name);
++      if (ret)
++              clk_name = fwnode_get_name(dev->fwnode);
++
++      clk->supply = devm_regulator_get_optional(dev, "vdd");
++      if (IS_ERR(clk->supply)) {
++              if (PTR_ERR(clk->supply) != -ENODEV)
++                      return dev_err_probe(dev, PTR_ERR(clk->supply),
++                                           "Failed to get regulator\n");
++              clk->supply = NULL;
++      }
++
++      clk->clk_gpio.gpiod = devm_gpiod_get_optional(dev, "enable",
++                                                    GPIOD_OUT_LOW);
++      if (IS_ERR(clk->clk_gpio.gpiod))
++              return dev_err_probe(dev, PTR_ERR(clk->clk_gpio.gpiod),
++                                   "Failed to get gpio\n");
++
++      if (gpiod_cansleep(clk->clk_gpio.gpiod))
++              ops = &clk_sleeping_gated_fixed_ops;
++      else
++              ops = &clk_gated_fixed_ops;
++
++      clk->clk_gpio.hw.init = CLK_HW_INIT_NO_PARENT(clk_name, ops, 0);
++
++      /* register the clock */
++      ret = devm_clk_hw_register(dev, &clk->clk_gpio.hw);
++      if (ret)
++              return dev_err_probe(dev, ret,
++                                   "Failed to register clock\n");
++
++      ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
++                                        &clk->clk_gpio.hw);
++      if (ret)
++              return dev_err_probe(dev, ret,
++                                   "Failed to register clock provider\n");
++
++      return 0;
++}
++
++static const struct of_device_id gated_fixed_clk_match_table[] = {
++      { .compatible = "gated-fixed-clock" },
++      { /* sentinel */ }
++};
++
++static struct platform_driver gated_fixed_clk_driver = {
++      .probe          = clk_gated_fixed_probe,
++      .driver         = {
++              .name   = "gated-fixed-clk",
++              .of_match_table = gated_fixed_clk_match_table,
++      },
++};
++builtin_platform_driver(gated_fixed_clk_driver);
diff --git a/target/linux/generic/backport-6.12/810-v6.14-gpio-regmap-Use-generic-request-free-ops.patch b/target/linux/generic/backport-6.12/810-v6.14-gpio-regmap-Use-generic-request-free-ops.patch
new file mode 100644 (file)
index 0000000..d696acf
--- /dev/null
@@ -0,0 +1,30 @@
+From b0fa00fe38f673c986633c11087274deeb7ce7b0 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Tue, 7 Jan 2025 21:16:20 +0100
+Subject: [PATCH] gpio: regmap: Use generic request/free ops
+
+Set the gpiochip request and free ops to the generic implementations.
+This way a user can provide a gpio-ranges property defined for a pinmux,
+easing muxing of gpio functions. Provided that the pin controller
+implementents the pinmux op .gpio_request_enable(), pins will
+automatically be muxed to their GPIO function when requested.
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+Acked-by: Michael Walle <mwalle@kernel.org>
+Link: https://lore.kernel.org/r/20250107201621.12467-1-sander@svanheule.net
+Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+---
+ drivers/gpio/gpio-regmap.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/gpio/gpio-regmap.c
++++ b/drivers/gpio/gpio-regmap.c
+@@ -276,6 +276,8 @@ struct gpio_regmap *gpio_regmap_register
+       chip->label = config->label ?: dev_name(config->parent);
+       chip->can_sleep = regmap_might_sleep(config->regmap);
++      chip->request = gpiochip_generic_request;
++      chip->free = gpiochip_generic_free;
+       chip->get = gpio_regmap_get;
+       if (gpio->reg_set_base && gpio->reg_clr_base)
+               chip->set = gpio_regmap_set_with_clear;
diff --git a/target/linux/generic/backport-6.12/813-v6.16-pinctrl-aw9523-fix-can_sleep-flag-for-GPIO-chip.patch b/target/linux/generic/backport-6.12/813-v6.16-pinctrl-aw9523-fix-can_sleep-flag-for-GPIO-chip.patch
new file mode 100644 (file)
index 0000000..97b3921
--- /dev/null
@@ -0,0 +1,28 @@
+From 5285b5ed04ab6ad40f7b654eefbccd6ae8cbf415 Mon Sep 17 00:00:00 2001
+From: Milan Krstic <milan.krstic@gmail.com>
+Date: Thu, 3 Jul 2025 14:30:39 +0000
+Subject: [PATCH] pinctrl: aw9523: fix can_sleep flag for GPIO chip
+
+The GPIO expander is connected via I2C, thus the can_sleep flag has to
+be set to true. This fixes spurious "scheduling while atomic" bugs
+in the kernel ringbuffer.
+
+Signed-off-by: David Bauer <mail@david-bauer.net>
+Signed-off-by: Milan Krstic <milan.krstic@gmail.com>
+Link: https://lore.kernel.org/20250703143039.5809-1-milan.krstic@gmail.com
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+---
+ drivers/pinctrl/pinctrl-aw9523.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/pinctrl/pinctrl-aw9523.c
++++ b/drivers/pinctrl/pinctrl-aw9523.c
+@@ -784,7 +784,7 @@ static int aw9523_init_gpiochip(struct a
+       gc->set_config = gpiochip_generic_config;
+       gc->parent = dev;
+       gc->owner = THIS_MODULE;
+-      gc->can_sleep = false;
++      gc->can_sleep = true;
+       return 0;
+ }
diff --git a/target/linux/generic/backport-6.12/820-v6.15-power-supply-sysfs-remove-duplicate-nul-termination-to-fix-build-with-GCC15.patch b/target/linux/generic/backport-6.12/820-v6.15-power-supply-sysfs-remove-duplicate-nul-termination-to-fix-build-with-GCC15.patch
new file mode 100644 (file)
index 0000000..48f9c1c
--- /dev/null
@@ -0,0 +1,39 @@
+From 77f5bb150132bbbcd6bc37ffdc80c9e140e373a4 Mon Sep 17 00:00:00 2001
+From: Kees Cook <kees@kernel.org>
+Date: Wed, 16 Apr 2025 15:27:41 -0700
+Subject: [PATCH] power: supply: sysfs: Remove duplicate NUL termination
+
+GCC 15's new -Wunterminated-string-initialization notices that one of
+the sysfs attr strings would lack the implicit trailing NUL byte during
+initialization:
+
+drivers/power/supply/power_supply_sysfs.c:183:57: warning: initializer-string for array of 'char' truncates NUL terminator but destination lacks 'nonstring' attribute (32 chars into 31 available) [-Wunterminated-string-initialization]
+  183 |         POWER_SUPPLY_ATTR(CHARGE_CONTROL_START_THRESHOLD),
+      |                                                         ^
+drivers/power/supply/power_supply_sysfs.c:36:23: note: in definition of macro '_POWER_SUPPLY_ATTR'
+   36 |         .attr_name = #_name "\0",               \
+      |                       ^~~~~
+drivers/power/supply/power_supply_sysfs.c:183:9: note: in expansion of macro 'POWER_SUPPLY_ATTR'
+  183 |         POWER_SUPPLY_ATTR(CHARGE_CONTROL_START_THRESHOLD),
+      |         ^~~~~~~~~~~~~~~~~
+
+However, the macro used was explicitly adding a trailing NUL byte (which
+is not needed). Remove this to avoid the GCC warning. No binary
+differences are seen after this change (there was always run for a NUL
+byte, it's just that the _second_ NUL byte was getting truncated).
+
+Signed-off-by: Kees Cook <kees@kernel.org>
+Link: https://lore.kernel.org/r/20250416222740.work.569-kees@kernel.org
+Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
+
+--- a/drivers/power/supply/power_supply_sysfs.c
++++ b/drivers/power/supply/power_supply_sysfs.c
+@@ -33,7 +33,7 @@ struct power_supply_attr {
+ [POWER_SUPPLY_PROP_ ## _name] =                       \
+ {                                             \
+       .prop_name = #_name,                    \
+-      .attr_name = #_name "\0",               \
++      .attr_name = #_name,                    \
+       .text_values = _text,                   \
+       .text_values_len = _len,                \
+ }
diff --git a/target/linux/generic/backport-6.12/821-01-v7.0-dt-bindings-gpio-add-gpio-line-mux-controller.patch b/target/linux/generic/backport-6.12/821-01-v7.0-dt-bindings-gpio-add-gpio-line-mux-controller.patch
new file mode 100644 (file)
index 0000000..12e6fed
--- /dev/null
@@ -0,0 +1,128 @@
+From 2a7618ba8698874e9871a8ec5453e0068e94d9e5 Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Sat, 27 Dec 2025 18:01:33 +0000
+Subject: [PATCH] dt-bindings: gpio: add gpio-line-mux controller
+
+Add dt-schema for a gpio-line-mux controller which exposes virtual
+GPIOs for a shared GPIO controlled by a multiplexer, e.g. a gpio-mux.
+
+The gpio-line-mux controller is a gpio-controller, thus has mostly the
+same semantics. However, it requires a mux-control to be specified upon
+which it will operate.
+
+Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
+Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Link: https://lore.kernel.org/r/20251227180134.1262138-2-jelonek.jonas@gmail.com
+Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/gpio/gpio-line-mux.yaml
+@@ -0,0 +1,107 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/gpio/gpio-line-mux.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: GPIO line mux
++
++maintainers:
++  - Jonas Jelonek <jelonek.jonas@gmail.com>
++
++description: |
++  A GPIO controller to provide virtual GPIOs for a 1-to-many input-only mapping
++  backed by a single shared GPIO and a multiplexer. A simple illustrated
++  example is:
++
++            +----- A
++    IN     /
++    <-----o------- B
++        / |\
++        | | +----- C
++        | |  \
++        | |   +--- D
++        | |
++       M1 M0
++
++    MUX CONTROL
++
++     M1 M0   IN
++      0  0   A
++      0  1   B
++      1  0   C
++      1  1   D
++
++  This can be used in case a real GPIO is connected to multiple inputs and
++  controlled by a multiplexer, and another subsystem/driver does not work
++  directly with the multiplexer subsystem.
++
++properties:
++  compatible:
++    const: gpio-line-mux
++
++  gpio-controller: true
++
++  "#gpio-cells":
++    const: 2
++
++  gpio-line-mux-states:
++    description: Mux states corresponding to the virtual GPIOs.
++    $ref: /schemas/types.yaml#/definitions/uint32-array
++
++  gpio-line-names: true
++
++  mux-controls:
++    maxItems: 1
++    description:
++      Phandle to the multiplexer to control access to the GPIOs.
++
++  ngpios: false
++
++  muxed-gpios:
++    maxItems: 1
++    description:
++      GPIO which is the '1' in 1-to-many and is shared by the virtual GPIOs
++      and controlled via the mux.
++
++required:
++  - compatible
++  - gpio-controller
++  - gpio-line-mux-states
++  - mux-controls
++  - muxed-gpios
++
++additionalProperties: false
++
++examples:
++  - |
++    #include <dt-bindings/gpio/gpio.h>
++    #include <dt-bindings/mux/mux.h>
++
++    sfp_gpio_mux: mux-controller-1 {
++        compatible = "gpio-mux";
++        mux-gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>,
++                    <&gpio0 1 GPIO_ACTIVE_HIGH>;
++        #mux-control-cells = <0>;
++        idle-state = <MUX_IDLE_AS_IS>;
++    };
++
++    sfp1_gpio: sfp-gpio-1 {
++        compatible = "gpio-line-mux";
++        gpio-controller;
++        #gpio-cells = <2>;
++
++        mux-controls = <&sfp_gpio_mux>;
++        muxed-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
++
++        gpio-line-mux-states = <0>, <1>, <3>;
++    };
++
++    sfp1: sfp-p1 {
++        compatible = "sff,sfp";
++
++        i2c-bus = <&sfp1_i2c>;
++        los-gpios = <&sfp1_gpio 0 GPIO_ACTIVE_HIGH>;
++        mod-def0-gpios = <&sfp1_gpio 1 GPIO_ACTIVE_LOW>;
++        tx-fault-gpios = <&sfp1_gpio 2 GPIO_ACTIVE_HIGH>;
++    };
diff --git a/target/linux/generic/backport-6.12/821-02-v7.0-gpio-add-gpio-line-mux-driver.patch b/target/linux/generic/backport-6.12/821-02-v7.0-gpio-add-gpio-line-mux-driver.patch
new file mode 100644 (file)
index 0000000..3569615
--- /dev/null
@@ -0,0 +1,209 @@
+From 2b03d9a40cd1fea42fd65d2b66df80edc0f374c8 Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Sat, 27 Dec 2025 18:01:34 +0000
+Subject: [PATCH] gpio: add gpio-line-mux driver
+
+Add a new driver which provides a 1-to-many mapping for a single real
+GPIO using a multiplexer. Each virtual GPIO corresponds to a multiplexer
+state which, if set for the multiplexer, connects the real GPIO to the
+corresponding virtual GPIO.
+
+This can help in various usecases. One practical case is the special
+hardware design of the Realtek-based XS1930-10 switch from Zyxel. It
+features two SFP+ ports/cages whose signals are wired directly to the
+switch SoC. Although Realtek SoCs are short on GPIOs, there are usually
+enough the fit the SFP signals without any hacks.
+
+However, Zyxel did some weird design and connected RX_LOS, MOD_ABS and
+TX_FAULT of one SFP cage onto a single GPIO line controlled by a
+multiplexer (the same for the other SFP cage). The single multiplexer
+controls the lines for both SFP and depending on the state, the
+designated 'signal GPIO lines' are connected to one of the three SFP
+signals.
+
+Because the SFP core/driver doesn't support multiplexer but needs single
+GPIOs for each of the signals, this driver fills the gap between both.
+It registers a gpio_chip, provides multiple virtual GPIOs and sets the
+backing multiplexer accordingly.
+
+Due to several practical issues, this is input-only and doesn't support
+IRQs.
+
+Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
+Reviewed-by: Thomas Richard <thomas.richard@bootlin.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Link: https://lore.kernel.org/r/20251227180134.1262138-3-jelonek.jonas@gmail.com
+Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -9704,6 +9704,12 @@ S:      Maintained
+ F:    Documentation/devicetree/bindings/leds/irled/gpio-ir-tx.yaml
+ F:    drivers/media/rc/gpio-ir-tx.c
++GPIO LINE MUX
++M:    Jonas Jelonek <jelonek.jonas@gmail.com>
++S:    Maintained
++F:    Documentation/devicetree/bindings/gpio/gpio-line-mux.yaml
++F:    drivers/gpio/gpio-line-mux.c
++
+ GPIO MOCKUP DRIVER
+ M:    Bamvor Jian Zhang <bamv2005@gmail.com>
+ L:    linux-gpio@vger.kernel.org
+--- a/drivers/gpio/Kconfig
++++ b/drivers/gpio/Kconfig
+@@ -1866,6 +1866,15 @@ config GPIO_LATCH
+         Say yes here to enable a driver for GPIO multiplexers based on latches
+         connected to other GPIOs.
++config GPIO_LINE_MUX
++      tristate "GPIO line mux driver"
++      depends on OF_GPIO
++      select MULTIPLEXER
++      help
++        Say Y here to support the GPIO line mux, which can provide virtual
++        GPIOs backed by a shared real GPIO and a multiplexer in a 1-to-many
++        fashion.
++
+ config GPIO_MOCKUP
+       tristate "GPIO Testing Driver (DEPRECATED)"
+       select IRQ_SIM
+--- a/drivers/gpio/Makefile
++++ b/drivers/gpio/Makefile
+@@ -84,6 +84,7 @@ obj-$(CONFIG_GPIO_IXP4XX)            += gpio-ixp4x
+ obj-$(CONFIG_GPIO_JANZ_TTL)           += gpio-janz-ttl.o
+ obj-$(CONFIG_GPIO_KEMPLD)             += gpio-kempld.o
+ obj-$(CONFIG_GPIO_LATCH)              += gpio-latch.o
++obj-$(CONFIG_GPIO_LINE_MUX)           += gpio-line-mux.o
+ obj-$(CONFIG_GPIO_LJCA)               += gpio-ljca.o
+ obj-$(CONFIG_GPIO_LOGICVC)            += gpio-logicvc.o
+ obj-$(CONFIG_GPIO_LOONGSON1)          += gpio-loongson1.o
+--- /dev/null
++++ b/drivers/gpio/gpio-line-mux.c
+@@ -0,0 +1,126 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * GPIO line mux which acts as virtual gpiochip and provides a 1-to-many
++ * mapping between virtual GPIOs and a real GPIO + multiplexer.
++ *
++ * Copyright (c) 2025 Jonas Jelonek <jelonek.jonas@gmail.com>
++ */
++
++#include <linux/gpio/consumer.h>
++#include <linux/gpio/driver.h>
++#include <linux/mod_devicetable.h>
++#include <linux/mutex.h>
++#include <linux/mux/consumer.h>
++#include <linux/platform_device.h>
++
++#define MUX_SELECT_DELAY_US   100
++
++struct gpio_lmux {
++      struct gpio_chip gc;
++      struct mux_control *mux;
++      struct gpio_desc *muxed_gpio;
++
++      u32 num_gpio_mux_states;
++      unsigned int gpio_mux_states[] __counted_by(num_gpio_mux_states);
++};
++
++static int gpio_lmux_gpio_get(struct gpio_chip *gc, unsigned int offset)
++{
++      struct gpio_lmux *glm = gpiochip_get_data(gc);
++      int ret;
++
++      if (offset > gc->ngpio)
++              return -EINVAL;
++
++      ret = mux_control_select_delay(glm->mux, glm->gpio_mux_states[offset],
++                                     MUX_SELECT_DELAY_US);
++      if (ret < 0)
++              return ret;
++
++      ret = gpiod_get_raw_value_cansleep(glm->muxed_gpio);
++      mux_control_deselect(glm->mux);
++      return ret;
++}
++
++static int gpio_lmux_gpio_set(struct gpio_chip *gc, unsigned int offset,
++                            int value)
++{
++      return -EOPNOTSUPP;
++}
++
++static int gpio_lmux_gpio_get_direction(struct gpio_chip *gc,
++                                      unsigned int offset)
++{
++      return GPIO_LINE_DIRECTION_IN;
++}
++
++static int gpio_lmux_probe(struct platform_device *pdev)
++{
++      struct device *dev = &pdev->dev;
++      struct gpio_lmux *glm;
++      unsigned int ngpio;
++      size_t size;
++      int ret;
++
++      ngpio = device_property_count_u32(dev, "gpio-line-mux-states");
++      if (!ngpio)
++              return -EINVAL;
++
++      size = struct_size(glm, gpio_mux_states, ngpio);
++      glm = devm_kzalloc(dev, size, GFP_KERNEL);
++      if (!glm)
++              return -ENOMEM;
++
++      glm->gc.base = -1;
++      glm->gc.can_sleep = true;
++      glm->gc.fwnode = dev_fwnode(dev);
++      glm->gc.label = dev_name(dev);
++      glm->gc.ngpio = ngpio;
++      glm->gc.owner = THIS_MODULE;
++      glm->gc.parent = dev;
++
++      glm->gc.get = gpio_lmux_gpio_get;
++      glm->gc.set = gpio_lmux_gpio_set;
++      glm->gc.get_direction = gpio_lmux_gpio_get_direction;
++
++      glm->mux = devm_mux_control_get(dev, NULL);
++      if (IS_ERR(glm->mux))
++              return dev_err_probe(dev, PTR_ERR(glm->mux),
++                                   "could not get mux controller\n");
++
++      glm->muxed_gpio = devm_gpiod_get(dev, "muxed", GPIOD_IN);
++      if (IS_ERR(glm->muxed_gpio))
++              return dev_err_probe(dev, PTR_ERR(glm->muxed_gpio),
++                                   "could not get muxed-gpio\n");
++
++      glm->num_gpio_mux_states = ngpio;
++      ret = device_property_read_u32_array(dev, "gpio-line-mux-states",
++                                           &glm->gpio_mux_states[0], ngpio);
++      if (ret)
++              return dev_err_probe(dev, ret, "could not get mux states\n");
++
++      ret = devm_gpiochip_add_data(dev, &glm->gc, glm);
++      if (ret)
++              return dev_err_probe(dev, ret, "failed to add gpiochip\n");
++
++      return 0;
++}
++
++static const struct of_device_id gpio_lmux_of_match[] = {
++      { .compatible = "gpio-line-mux" },
++      { }
++};
++MODULE_DEVICE_TABLE(of, gpio_lmux_of_match);
++
++static struct platform_driver gpio_lmux_driver = {
++      .driver = {
++              .name = "gpio-line-mux",
++              .of_match_table = gpio_lmux_of_match,
++      },
++      .probe = gpio_lmux_probe,
++};
++module_platform_driver(gpio_lmux_driver);
++
++MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
++MODULE_DESCRIPTION("GPIO line mux driver");
++MODULE_LICENSE("GPL");
diff --git a/target/linux/generic/backport-6.12/821-03-v7.0-gpio-line-mux-remove-bits-already-handled-by-GPIO-co.patch b/target/linux/generic/backport-6.12/821-03-v7.0-gpio-line-mux-remove-bits-already-handled-by-GPIO-co.patch
new file mode 100644 (file)
index 0000000..717421e
--- /dev/null
@@ -0,0 +1,52 @@
+From e034e058897a12bc856f8b22d1796964c742f732 Mon Sep 17 00:00:00 2001
+From: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
+Date: Wed, 7 Jan 2026 09:58:33 +0100
+Subject: [PATCH] gpio: line-mux: remove bits already handled by GPIO core
+
+GPIO core already handles checking the offset against the number of
+GPIOs as well as missing any of the GPIO chip callbacks. Remove the
+unnecessary bits.
+
+Also, the offset check was off-by-one as reported by Dan.
+
+Fixes: 2b03d9a40cd1 ("gpio: add gpio-line-mux driver")
+Reported-by: Dan Carpenter <dan.carpenter@linaro.org>
+Closes: https://lore.kernel.org/all/aV4b6GAGz1zyf8Xy@stanley.mountain/
+Tested-by: Jonas Jelonek <jelonek.jonas@gmail.com>
+Reviewed-by: Jonas Jelonek <jelonek.jonas@gmail.com>
+Link: https://lore.kernel.org/r/20260107085833.17338-1-bartosz.golaszewski@oss.qualcomm.com
+Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
+
+--- a/drivers/gpio/gpio-line-mux.c
++++ b/drivers/gpio/gpio-line-mux.c
+@@ -29,9 +29,6 @@ static int gpio_lmux_gpio_get(struct gpi
+       struct gpio_lmux *glm = gpiochip_get_data(gc);
+       int ret;
+-      if (offset > gc->ngpio)
+-              return -EINVAL;
+-
+       ret = mux_control_select_delay(glm->mux, glm->gpio_mux_states[offset],
+                                      MUX_SELECT_DELAY_US);
+       if (ret < 0)
+@@ -42,12 +39,6 @@ static int gpio_lmux_gpio_get(struct gpi
+       return ret;
+ }
+-static int gpio_lmux_gpio_set(struct gpio_chip *gc, unsigned int offset,
+-                            int value)
+-{
+-      return -EOPNOTSUPP;
+-}
+-
+ static int gpio_lmux_gpio_get_direction(struct gpio_chip *gc,
+                                       unsigned int offset)
+ {
+@@ -80,7 +71,6 @@ static int gpio_lmux_probe(struct platfo
+       glm->gc.parent = dev;
+       glm->gc.get = gpio_lmux_gpio_get;
+-      glm->gc.set = gpio_lmux_gpio_set;
+       glm->gc.get_direction = gpio_lmux_gpio_get_direction;
+       glm->mux = devm_mux_control_get(dev, NULL);
diff --git a/target/linux/generic/backport-6.12/822-v6.19-ALSA-usb-audio-Convert-to-common-field_-get-prep-hel.patch b/target/linux/generic/backport-6.12/822-v6.19-ALSA-usb-audio-Convert-to-common-field_-get-prep-hel.patch
new file mode 100644 (file)
index 0000000..ad4c55e
--- /dev/null
@@ -0,0 +1,28 @@
+From b1cff2f4b2391a13bd3e9263502072df1ee5d035 Mon Sep 17 00:00:00 2001
+From: Geert Uytterhoeven <geert+renesas@glider.be>
+Date: Thu, 6 Nov 2025 14:34:10 +0100
+Subject: [PATCH] ALSA: usb-audio: Convert to common field_{get,prep}() helpers
+
+Drop the driver-specific field_get() and field_prep() macros, in favor
+of the globally available variants from <linux/bitfield.h>.
+
+Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Acked-by: Takashi Iwai <tiwai@suse.de>
+Signed-off-by: Yury Norov (NVIDIA) <yury.norov@gmail.com>
+---
+ sound/usb/mixer_quirks.c | 6 ------
+ 1 file changed, 6 deletions(-)
+
+--- a/sound/usb/mixer_quirks.c
++++ b/sound/usb/mixer_quirks.c
+@@ -3367,10 +3367,6 @@ static int snd_bbfpro_controls_create(st
+ #define RME_DIGIFACE_REGISTER(reg, mask) (((reg) << 16) | (mask))
+ #define RME_DIGIFACE_INVERT BIT(31)
+-/* Nonconst helpers */
+-#define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1))
+-#define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask))
+-
+ static int snd_rme_digiface_write_reg(struct snd_kcontrol *kcontrol, int item, u16 mask, u16 val)
+ {
+       struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
diff --git a/target/linux/generic/backport-6.12/823-v6.19-clk-at91-Convert-to-common-field_-get-prep-helpers.patch b/target/linux/generic/backport-6.12/823-v6.19-clk-at91-Convert-to-common-field_-get-prep-helpers.patch
new file mode 100644 (file)
index 0000000..fa32ad6
--- /dev/null
@@ -0,0 +1,40 @@
+From 0f8407a1f1c795c417e4c7750654a6024a3ec68b Mon Sep 17 00:00:00 2001
+From: Geert Uytterhoeven <geert+renesas@glider.be>
+Date: Thu, 6 Nov 2025 14:34:02 +0100
+Subject: [PATCH] clk: at91: Convert to common field_{get,prep}() helpers
+
+Drop the driver-specific field_get() and field_prep() macros, in favor
+of the globally available variants from <linux/bitfield.h>.
+
+Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Acked-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
+Acked-by: Stephen Boyd <sboyd@kernel.org>
+Acked-by: Claudiu Beznea <claudiu.beznea@tuxon.dev>
+Signed-off-by: Yury Norov (NVIDIA) <yury.norov@gmail.com>
+---
+ drivers/clk/at91/clk-peripheral.c | 1 +
+ drivers/clk/at91/pmc.h            | 5 -----
+ 2 files changed, 1 insertion(+), 5 deletions(-)
+
+--- a/drivers/clk/at91/clk-peripheral.c
++++ b/drivers/clk/at91/clk-peripheral.c
+@@ -3,6 +3,7 @@
+  *  Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+  */
++#include <linux/bitfield.h>
+ #include <linux/bitops.h>
+ #include <linux/clk-provider.h>
+ #include <linux/clkdev.h>
+--- a/drivers/clk/at91/pmc.h
++++ b/drivers/clk/at91/pmc.h
+@@ -116,9 +116,6 @@ struct at91_clk_pms {
+       unsigned int parent;
+ };
+-#define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1))
+-#define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask))
+-
+ #define ndck(a, s) (a[s - 1].id + 1)
+ #define nck(a) (a[ARRAY_SIZE(a) - 1].id + 1)
diff --git a/target/linux/generic/backport-6.12/824-v6.19-iio-mlx90614-Convert-to-common-field_-get-prep-helpe.patch b/target/linux/generic/backport-6.12/824-v6.19-iio-mlx90614-Convert-to-common-field_-get-prep-helpe.patch
new file mode 100644 (file)
index 0000000..8b502ec
--- /dev/null
@@ -0,0 +1,37 @@
+From 1fe1c28a108e4953f083c0106575ee0eccc296ae Mon Sep 17 00:00:00 2001
+From: Geert Uytterhoeven <geert+renesas@glider.be>
+Date: Thu, 6 Nov 2025 14:34:07 +0100
+Subject: [PATCH] iio: mlx90614: Convert to common field_{get,prep}() helpers
+
+Drop the driver-specific field_get() and field_prep() macros, in favor
+of the globally available variants from <linux/bitfield.h>.
+
+Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Acked-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Acked-by: Crt Mori <cmo@melexis.com>
+Signed-off-by: Yury Norov (NVIDIA) <yury.norov@gmail.com>
+---
+ drivers/iio/temperature/mlx90614.c | 7 +------
+ 1 file changed, 1 insertion(+), 6 deletions(-)
+
+--- a/drivers/iio/temperature/mlx90614.c
++++ b/drivers/iio/temperature/mlx90614.c
+@@ -22,6 +22,7 @@
+  * the "wakeup" GPIO is not given, power management will be disabled.
+  */
++#include <linux/bitfield.h>
+ #include <linux/delay.h>
+ #include <linux/err.h>
+ #include <linux/gpio/consumer.h>
+@@ -68,10 +69,6 @@
+ #define MLX90614_CONST_SCALE 20 /* Scale in milliKelvin (0.02 * 1000) */
+ #define MLX90614_CONST_FIR 0x7 /* Fixed value for FIR part of low pass filter */
+-/* Non-constant mask variant of FIELD_GET() and FIELD_PREP() */
+-#define field_get(_mask, _reg)        (((_reg) & (_mask)) >> (ffs(_mask) - 1))
+-#define field_prep(_mask, _val)       (((_val) << (ffs(_mask) - 1)) & (_mask))
+-
+ struct mlx_chip_info {
+       /* EEPROM offsets with 16-bit data, MSB first */
+       /* emissivity correction coefficient */
diff --git a/target/linux/generic/backport-6.12/825-v6.19-pinctrl-ma35-Convert-to-common-field_-get-prep-helpe.patch b/target/linux/generic/backport-6.12/825-v6.19-pinctrl-ma35-Convert-to-common-field_-get-prep-helpe.patch
new file mode 100644 (file)
index 0000000..566a003
--- /dev/null
@@ -0,0 +1,28 @@
+From bb0e7fda87753a973cb4a86c22905b1177f00d4e Mon Sep 17 00:00:00 2001
+From: Geert Uytterhoeven <geert+renesas@glider.be>
+Date: Thu, 6 Nov 2025 14:34:08 +0100
+Subject: [PATCH] pinctrl: ma35: Convert to common field_{get,prep}() helpers
+
+Drop the driver-specific field_get() and field_prep() macros, in favor
+of the globally available variants from <linux/bitfield.h>.
+
+Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: Yury Norov (NVIDIA) <yury.norov@gmail.com>
+---
+ drivers/pinctrl/nuvoton/pinctrl-ma35.c | 6 ------
+ 1 file changed, 6 deletions(-)
+
+--- a/drivers/pinctrl/nuvoton/pinctrl-ma35.c
++++ b/drivers/pinctrl/nuvoton/pinctrl-ma35.c
+@@ -81,10 +81,6 @@
+ #define MVOLT_1800                    0
+ #define MVOLT_3300                    1
+-/* Non-constant mask variant of FIELD_GET() and FIELD_PREP() */
+-#define field_get(_mask, _reg)        (((_reg) & (_mask)) >> (ffs(_mask) - 1))
+-#define field_prep(_mask, _val)       (((_val) << (ffs(_mask) - 1)) & (_mask))
+-
+ static const char * const gpio_group_name[] = {
+       "gpioa", "gpiob", "gpioc", "gpiod", "gpioe", "gpiof", "gpiog",
+       "gpioh", "gpioi", "gpioj", "gpiok", "gpiol", "gpiom", "gpion",
diff --git a/target/linux/generic/backport-6.12/891-v6.14-dt-bindings-leds-Add-LED1202-LED-Controller.patch b/target/linux/generic/backport-6.12/891-v6.14-dt-bindings-leds-Add-LED1202-LED-Controller.patch
new file mode 100644 (file)
index 0000000..9901d13
--- /dev/null
@@ -0,0 +1,160 @@
+From 599b92fd0efa8b7c43e7f58c9dd0f7951f7cbf09 Mon Sep 17 00:00:00 2001
+From: Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
+Date: Wed, 18 Dec 2024 18:33:58 +0000
+Subject: dt-bindings: leds: Add LED1202 LED Controller
+
+The LED1202 is a 12-channel low quiescent current LED driver with:
+  * Supply range from 2.6 V to 5 V
+  * 20 mA current capability per channel
+  * 1.8 V compatible I2C control interface
+  * 8-bit analog dimming individual control
+  * 12-bit local PWM resolution
+  * 8 programmable patterns
+
+If the led node is present in the controller then the channel is
+set to active.
+
+Signed-off-by: Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
+Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Link: https://lore.kernel.org/r/20241218183401.41687-3-vicentiu.galanopulo@remote-tech.co.uk
+Signed-off-by: Lee Jones <lee@kernel.org>
+---
+ .../devicetree/bindings/leds/st,led1202.yaml  | 132 ++++++++++++++++++
+ 1 file changed, 132 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/leds/st,led1202.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/leds/st,led1202.yaml
+@@ -0,0 +1,132 @@
++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/leds/st,led1202.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: ST LED1202 LED controllers
++
++maintainers:
++  - Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
++
++description: |
++  The LED1202 is a 12-channel low quiescent current LED controller
++  programmable via I2C; The output current can be adjusted separately
++  for each channel by 8-bit analog and 12-bit digital dimming control.
++  Datasheet available at
++  https://www.st.com/en/power-management/led1202.html
++
++properties:
++  compatible:
++    const: st,led1202
++
++  reg:
++    maxItems: 1
++
++  "#address-cells":
++    const: 1
++
++  "#size-cells":
++    const: 0
++
++patternProperties:
++  "^led@[0-9a-f]$":
++    type: object
++    $ref: common.yaml#
++    unevaluatedProperties: false
++
++    properties:
++      reg:
++        minimum: 0
++        maximum: 11
++
++    required:
++      - reg
++
++required:
++  - compatible
++  - reg
++  - "#address-cells"
++  - "#size-cells"
++
++additionalProperties: false
++
++examples:
++  - |
++    #include <dt-bindings/leds/common.h>
++
++    i2c {
++        #address-cells = <1>;
++        #size-cells = <0>;
++
++        led-controller@58 {
++            compatible = "st,led1202";
++            reg = <0x58>;
++            #address-cells = <1>;
++            #size-cells = <0>;
++
++            led@0 {
++                reg = <0x0>;
++                function = LED_FUNCTION_STATUS;
++                color = <LED_COLOR_ID_RED>;
++                function-enumerator = <1>;
++            };
++
++            led@1 {
++                reg = <0x1>;
++                function = LED_FUNCTION_STATUS;
++                color = <LED_COLOR_ID_GREEN>;
++                function-enumerator = <2>;
++            };
++
++            led@2 {
++                reg = <0x2>;
++                function = LED_FUNCTION_STATUS;
++                color = <LED_COLOR_ID_BLUE>;
++                function-enumerator = <3>;
++            };
++
++            led@3 {
++                reg = <0x3>;
++                function = LED_FUNCTION_STATUS;
++                color = <LED_COLOR_ID_RED>;
++                function-enumerator = <4>;
++            };
++
++            led@4 {
++                reg = <0x4>;
++                function = LED_FUNCTION_STATUS;
++                color = <LED_COLOR_ID_GREEN>;
++                function-enumerator = <5>;
++            };
++
++            led@5 {
++                reg = <0x5>;
++                function = LED_FUNCTION_STATUS;
++                color = <LED_COLOR_ID_BLUE>;
++                function-enumerator = <6>;
++            };
++
++            led@6 {
++                reg = <0x6>;
++                function = LED_FUNCTION_STATUS;
++                color = <LED_COLOR_ID_RED>;
++                function-enumerator = <7>;
++            };
++
++            led@7 {
++                reg = <0x7>;
++                function = LED_FUNCTION_STATUS;
++                color = <LED_COLOR_ID_GREEN>;
++                function-enumerator = <8>;
++            };
++
++            led@8 {
++                reg = <0x8>;
++                function = LED_FUNCTION_STATUS;
++                color = <LED_COLOR_ID_BLUE>;
++                function-enumerator = <9>;
++            };
++        };
++    };
++...
diff --git a/target/linux/generic/backport-6.12/892-v6.14-leds-Add-LED1202-I2C-driver.patch b/target/linux/generic/backport-6.12/892-v6.14-leds-Add-LED1202-I2C-driver.patch
new file mode 100644 (file)
index 0000000..ee2ea07
--- /dev/null
@@ -0,0 +1,475 @@
+From 939757aafeb9c266dda37657ee5f7a73ffd35ae2 Mon Sep 17 00:00:00 2001
+From: Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
+Date: Wed, 18 Dec 2024 18:33:59 +0000
+Subject: leds: Add LED1202 I2C driver
+
+The output current can be adjusted separately for each channel by 8-bit
+analog (current sink input) and 12-bit digital (PWM) dimming control. The
+LED1202 implements 12 low-side current generators with independent dimming
+control.
+Internal volatile memory allows the user to store up to 8 different patterns,
+each pattern is a particular output configuration in terms of PWM
+duty-cycle (on 4096 steps). Analog dimming (on 256 steps) is per channel but
+common to all patterns. Each device tree LED node will have a corresponding
+entry in /sys/class/leds with the label name. The brightness property
+corresponds to the per channel analog dimming, while the patterns[1-8] to the
+PWM dimming control.
+
+Signed-off-by: Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
+Link: https://lore.kernel.org/r/20241218183401.41687-4-vicentiu.galanopulo@remote-tech.co.uk
+Signed-off-by: Lee Jones <lee@kernel.org>
+---
+ drivers/leds/Kconfig       |  10 +
+ drivers/leds/Makefile      |   1 +
+ drivers/leds/leds-st1202.c | 416 +++++++++++++++++++++++++++++++++++++
+ 3 files changed, 427 insertions(+)
+ create mode 100644 drivers/leds/leds-st1202.c
+
+--- a/drivers/leds/Kconfig
++++ b/drivers/leds/Kconfig
+@@ -931,6 +931,16 @@ config LEDS_LM36274
+         Say Y to enable the LM36274 LED driver for TI LMU devices.
+         This supports the LED device LM36274.
++config LEDS_ST1202
++      tristate "LED Support for STMicroelectronics LED1202 I2C chips"
++      depends on LEDS_CLASS
++      depends on I2C
++      depends on OF
++      select LEDS_TRIGGERS
++      help
++        Say Y to enable support for LEDs connected to LED1202
++        LED driver chips accessed via the I2C bus.
++
+ config LEDS_TPS6105X
+       tristate "LED support for TI TPS6105X"
+       depends on LEDS_CLASS
+--- a/drivers/leds/Makefile
++++ b/drivers/leds/Makefile
+@@ -81,6 +81,7 @@ obj-$(CONFIG_LEDS_POWERNV)           += leds-powe
+ obj-$(CONFIG_LEDS_PWM)                        += leds-pwm.o
+ obj-$(CONFIG_LEDS_REGULATOR)          += leds-regulator.o
+ obj-$(CONFIG_LEDS_SC27XX_BLTC)                += leds-sc27xx-bltc.o
++obj-$(CONFIG_LEDS_ST1202)             += leds-st1202.o
+ obj-$(CONFIG_LEDS_SUN50I_A100)                += leds-sun50i-a100.o
+ obj-$(CONFIG_LEDS_SUNFIRE)            += leds-sunfire.o
+ obj-$(CONFIG_LEDS_SYSCON)             += leds-syscon.o
+--- /dev/null
++++ b/drivers/leds/leds-st1202.c
+@@ -0,0 +1,416 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * LED driver for STMicroelectronics LED1202 chip
++ *
++ * Copyright (C) 2024 Remote-Tech Ltd. UK
++ */
++
++#include <linux/cleanup.h>
++#include <linux/ctype.h>
++#include <linux/delay.h>
++#include <linux/err.h>
++#include <linux/gpio.h>
++#include <linux/i2c.h>
++#include <linux/leds.h>
++#include <linux/module.h>
++#include <linux/slab.h>
++#include <linux/string.h>
++
++#define ST1202_CHAN_DISABLE_ALL            0x00
++#define ST1202_CHAN_ENABLE_HIGH            0x03
++#define ST1202_CHAN_ENABLE_LOW             0x02
++#define ST1202_CONFIG_REG                  0x04
++/* PATS: Pattern sequence feature enable */
++#define ST1202_CONFIG_REG_PATS             BIT(7)
++/* PATSR: Pattern sequence runs (self-clear when sequence is finished) */
++#define ST1202_CONFIG_REG_PATSR            BIT(6)
++#define ST1202_CONFIG_REG_SHFT             BIT(3)
++#define ST1202_DEV_ENABLE                  0x01
++#define ST1202_DEV_ENABLE_ON               BIT(0)
++#define ST1202_DEV_ENABLE_RESET            BIT(7)
++#define ST1202_DEVICE_ID                   0x00
++#define ST1202_ILED_REG0                   0x09
++#define ST1202_MAX_LEDS                    12
++#define ST1202_MAX_PATTERNS                8
++#define ST1202_MILLIS_PATTERN_DUR_MAX      5660
++#define ST1202_MILLIS_PATTERN_DUR_MIN      22
++#define ST1202_PATTERN_DUR                 0x16
++#define ST1202_PATTERN_PWM                 0x1E
++#define ST1202_PATTERN_REP                 0x15
++
++struct st1202_led {
++      struct fwnode_handle *fwnode;
++      struct led_classdev led_cdev;
++      struct st1202_chip *chip;
++      bool is_active;
++      int led_num;
++};
++
++struct st1202_chip {
++      struct i2c_client *client;
++      struct mutex lock;
++      struct st1202_led leds[ST1202_MAX_LEDS];
++};
++
++static struct st1202_led *cdev_to_st1202_led(struct led_classdev *cdev)
++{
++      return container_of(cdev, struct st1202_led, led_cdev);
++}
++
++static int st1202_read_reg(struct st1202_chip *chip, int reg, uint8_t *val)
++{
++      struct device *dev = &chip->client->dev;
++      int ret;
++
++      ret = i2c_smbus_read_byte_data(chip->client, reg);
++      if (ret < 0) {
++              dev_err(dev, "Failed to read register [0x%x]: %d\n", reg, ret);
++              return ret;
++      }
++
++      *val = (uint8_t)ret;
++      return 0;
++}
++
++static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val)
++{
++      struct device *dev = &chip->client->dev;
++      int ret;
++
++      ret = i2c_smbus_write_byte_data(chip->client, reg, val);
++      if (ret != 0)
++              dev_err(dev, "Failed to write %d to register [0x%x]: %d\n", val, reg, ret);
++
++      return ret;
++}
++
++static uint8_t st1202_prescalar_to_miliseconds(unsigned int value)
++{
++      return value / ST1202_MILLIS_PATTERN_DUR_MIN - 1;
++}
++
++static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num,
++                              int pattern, unsigned int value)
++{
++      u8 value_l, value_h;
++      int ret;
++
++      value_l = (u8)value;
++      value_h = (u8)(value >> 8);
++
++      /*
++       *  Datasheet: Register address low = 1Eh + 2*(xh) + 18h*(yh),
++       *  where x is the channel number (led number) in hexadecimal (x = 00h .. 0Bh)
++       *  and y is the pattern number in hexadecimal (y = 00h .. 07h)
++       */
++      ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),
++                              value_l);
++      if (ret != 0)
++              return ret;
++
++      /*
++       * Datasheet: Register address high = 1Eh + 01h + 2(xh) +18h*(yh),
++       * where x is the channel number in hexadecimal (x = 00h .. 0Bh)
++       * and y is the pattern number in hexadecimal (y = 00h .. 07h)
++       */
++      ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + 0x1 + (led_num * 2) + 0x18 * pattern),
++                              value_h);
++      if (ret != 0)
++              return ret;
++
++      return 0;
++}
++
++static int st1202_duration_pattern_write(struct st1202_chip *chip, int pattern,
++                                      unsigned int value)
++{
++      return st1202_write_reg(chip, (ST1202_PATTERN_DUR + pattern),
++                              st1202_prescalar_to_miliseconds(value));
++}
++
++static void st1202_brightness_set(struct led_classdev *led_cdev,
++                              enum led_brightness value)
++{
++      struct st1202_led *led = cdev_to_st1202_led(led_cdev);
++      struct st1202_chip *chip = led->chip;
++
++      guard(mutex)(&chip->lock);
++
++      st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value);
++}
++
++static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev)
++{
++      struct st1202_led *led = cdev_to_st1202_led(led_cdev);
++      struct st1202_chip *chip = led->chip;
++      u8 value = 0;
++
++      guard(mutex)(&chip->lock);
++
++      st1202_read_reg(chip, ST1202_ILED_REG0 + led->led_num, &value);
++
++      return value;
++}
++
++static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active)
++{
++      u8 chan_low, chan_high;
++      int ret;
++
++      guard(mutex)(&chip->lock);
++
++      if (led_num <= 7) {
++              ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_LOW, &chan_low);
++              if (ret < 0)
++                      return ret;
++
++              chan_low = active ? chan_low | BIT(led_num) : chan_low & ~BIT(led_num);
++
++              ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, chan_low);
++              if (ret < 0)
++                      return ret;
++
++      } else {
++              ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_HIGH, &chan_high);
++              if (ret < 0)
++                      return ret;
++
++              chan_high = active ? chan_high | (BIT(led_num) >> 8) :
++                                      chan_high & ~(BIT(led_num) >> 8);
++
++              ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, chan_high);
++              if (ret < 0)
++                      return ret;
++      }
++
++      return 0;
++}
++
++static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value)
++{
++      struct st1202_led *led = cdev_to_st1202_led(ldev);
++      struct st1202_chip *chip = led->chip;
++
++      return st1202_channel_set(chip, led->led_num, value == LED_OFF ? false : true);
++}
++
++static int st1202_led_pattern_clear(struct led_classdev *ldev)
++{
++      struct st1202_led *led = cdev_to_st1202_led(ldev);
++      struct st1202_chip *chip = led->chip;
++      int ret;
++
++      guard(mutex)(&chip->lock);
++
++      for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) {
++              ret = st1202_pwm_pattern_write(chip, led->led_num, patt, LED_OFF);
++              if (ret != 0)
++                      return ret;
++
++              ret = st1202_duration_pattern_write(chip, patt, ST1202_MILLIS_PATTERN_DUR_MIN);
++              if (ret != 0)
++                      return ret;
++      }
++
++      return 0;
++}
++
++static int st1202_led_pattern_set(struct led_classdev *ldev,
++                              struct led_pattern *pattern,
++                              u32 len, int repeat)
++{
++      struct st1202_led *led = cdev_to_st1202_led(ldev);
++      struct st1202_chip *chip = led->chip;
++      int ret;
++
++      if (len > ST1202_MAX_PATTERNS)
++              return -EINVAL;
++
++      guard(mutex)(&chip->lock);
++
++      for (int patt = 0; patt < len; patt++) {
++              if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN ||
++                              pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX)
++                      return -EINVAL;
++
++              ret = st1202_pwm_pattern_write(chip, led->led_num, patt, pattern[patt].brightness);
++              if (ret != 0)
++                      return ret;
++
++              ret = st1202_duration_pattern_write(chip, patt, pattern[patt].delta_t);
++              if (ret != 0)
++                      return ret;
++      }
++
++      ret = st1202_write_reg(chip, ST1202_PATTERN_REP, repeat);
++      if (ret != 0)
++              return ret;
++
++      ret = st1202_write_reg(chip, ST1202_CONFIG_REG, (ST1202_CONFIG_REG_PATSR |
++                                                      ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_SHFT));
++      if (ret != 0)
++              return ret;
++
++      return 0;
++}
++
++static int st1202_dt_init(struct st1202_chip *chip)
++{
++      struct device *dev = &chip->client->dev;
++      struct st1202_led *led;
++      int err, reg;
++
++      for_each_available_child_of_node_scoped(dev_of_node(dev), child) {
++              struct led_init_data init_data = {};
++
++              err = of_property_read_u32(child, "reg", &reg);
++              if (err)
++                      return dev_err_probe(dev, err, "Invalid register\n");
++
++              led = &chip->leds[reg];
++              led->is_active = true;
++              led->fwnode = of_fwnode_handle(child);
++
++              led->led_cdev.max_brightness = U8_MAX;
++              led->led_cdev.brightness_set_blocking = st1202_led_set;
++              led->led_cdev.pattern_set = st1202_led_pattern_set;
++              led->led_cdev.pattern_clear = st1202_led_pattern_clear;
++              led->led_cdev.default_trigger = "pattern";
++
++              init_data.fwnode = led->fwnode;
++              init_data.devicename = "st1202";
++              init_data.default_label = ":";
++
++              err = devm_led_classdev_register_ext(dev, &led->led_cdev, &init_data);
++              if (err < 0)
++                      return dev_err_probe(dev, err, "Failed to register LED class device\n");
++
++              led->led_cdev.brightness_set = st1202_brightness_set;
++              led->led_cdev.brightness_get = st1202_brightness_get;
++      }
++
++      return 0;
++}
++
++static int st1202_setup(struct st1202_chip *chip)
++{
++      int ret;
++
++      guard(mutex)(&chip->lock);
++
++      /*
++       * Once the supply voltage is applied, the LED1202 executes some internal checks,
++       * afterwords it stops the oscillator and puts the internal LDO in quiescent mode.
++       * To start the device, EN bit must be set inside the “Device Enable” register at
++       * address 01h. As soon as EN is set, the LED1202 loads the adjustment parameters
++       * from the internal non-volatile memory and performs an auto-calibration procedure
++       * in order to increase the output current precision.
++       * Such initialization lasts about 6.5 ms.
++       */
++
++      /* Reset the chip during setup */
++      ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_RESET);
++      if (ret < 0)
++              return ret;
++
++      /* Enable phase-shift delay feature */
++      ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT);
++      if (ret < 0)
++              return ret;
++
++      /* Enable the device */
++      ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_ON);
++      if (ret < 0)
++              return ret;
++
++      /* Duration of initialization */
++      usleep_range(6500, 10000);
++
++      /* Deactivate all LEDS (channels) and activate only the ones found in Device Tree */
++      ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, ST1202_CHAN_DISABLE_ALL);
++      if (ret < 0)
++              return ret;
++
++      ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, ST1202_CHAN_DISABLE_ALL);
++      if (ret < 0)
++              return ret;
++
++      ret = st1202_write_reg(chip, ST1202_CONFIG_REG,
++                              ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_PATSR);
++      if (ret < 0)
++              return ret;
++
++      return 0;
++}
++
++static int st1202_probe(struct i2c_client *client)
++{
++      struct st1202_chip *chip;
++      struct st1202_led *led;
++      int ret;
++
++      if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
++              return dev_err_probe(&client->dev, -EIO, "SMBUS Byte Data not Supported\n");
++
++      chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
++      if (!chip)
++              return -ENOMEM;
++
++      devm_mutex_init(&client->dev, &chip->lock);
++      chip->client = client;
++
++      ret = st1202_dt_init(chip);
++      if (ret < 0)
++              return ret;
++
++      ret = st1202_setup(chip);
++      if (ret < 0)
++              return ret;
++
++      for (int i = 0; i < ST1202_MAX_LEDS; i++) {
++              led = &chip->leds[i];
++              led->chip = chip;
++              led->led_num = i;
++
++              if (!led->is_active)
++                      continue;
++
++              ret = st1202_channel_set(led->chip, led->led_num, true);
++              if (ret < 0)
++                      return dev_err_probe(&client->dev, ret,
++                                      "Failed to activate LED channel\n");
++
++              ret = st1202_led_pattern_clear(&led->led_cdev);
++              if (ret < 0)
++                      return dev_err_probe(&client->dev, ret,
++                                      "Failed to clear LED pattern\n");
++      }
++
++      return 0;
++}
++
++static const struct i2c_device_id st1202_id[] = {
++      { "st1202-i2c" },
++      { /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(i2c, st1202_id);
++
++static const struct of_device_id st1202_dt_ids[] = {
++      { .compatible = "st,led1202" },
++      { /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, st1202_dt_ids);
++
++static struct i2c_driver st1202_driver = {
++      .driver = {
++              .name = "leds-st1202",
++              .of_match_table = of_match_ptr(st1202_dt_ids),
++      },
++      .probe = st1202_probe,
++      .id_table = st1202_id,
++};
++module_i2c_driver(st1202_driver);
++
++MODULE_AUTHOR("Remote Tech LTD");
++MODULE_DESCRIPTION("STMicroelectronics LED1202 : 12-channel constant current LED driver");
++MODULE_LICENSE("GPL");
diff --git a/target/linux/generic/backport-6.12/893-v6.14-leds_st1202-Fix-NULL-pointer-access-error.patch b/target/linux/generic/backport-6.12/893-v6.14-leds_st1202-Fix-NULL-pointer-access-error.patch
new file mode 100644 (file)
index 0000000..39d8ddb
--- /dev/null
@@ -0,0 +1,73 @@
+From c72e455b89f216b43cd0dbb518036ec4c98f5c46 Mon Sep 17 00:00:00 2001
+From: Manuel Fombuena <fombuena@outlook.com>
+Date: Tue, 25 Feb 2025 22:01:02 +0000
+Subject: leds: leds-st1202: Fix NULL pointer access on race condition
+
+st1202_dt_init() calls devm_led_classdev_register_ext() before the
+internal data structures are properly set up, so the LEDs become visible
+to user space while being partially initialized, leading to a window
+where trying to access them causes a NULL pointer access.
+
+Move devm_led_classdev_register_ext() from DT initialization
+to the end of the probe function when DT and hardware are fully
+initialized and ready to interact with user space.
+
+Fixes: 259230378c65 ("leds: Add LED1202 I2C driver")
+Signed-off-by: Manuel Fombuena <fombuena@outlook.com>
+Link: https://lore.kernel.org/r/CWLP123MB54732771AC0CE5491B3C84DCC5C32@CWLP123MB5473.GBRP123.PROD.OUTLOOK.COM
+Signed-off-by: Lee Jones <lee@kernel.org>
+---
+ drivers/leds/leds-st1202.c | 21 ++++++++++-----------
+ 1 file changed, 10 insertions(+), 11 deletions(-)
+
+--- a/drivers/leds/leds-st1202.c
++++ b/drivers/leds/leds-st1202.c
+@@ -261,8 +261,6 @@ static int st1202_dt_init(struct st1202_
+       int err, reg;
+       for_each_available_child_of_node_scoped(dev_of_node(dev), child) {
+-              struct led_init_data init_data = {};
+-
+               err = of_property_read_u32(child, "reg", &reg);
+               if (err)
+                       return dev_err_probe(dev, err, "Invalid register\n");
+@@ -276,15 +274,6 @@ static int st1202_dt_init(struct st1202_
+               led->led_cdev.pattern_set = st1202_led_pattern_set;
+               led->led_cdev.pattern_clear = st1202_led_pattern_clear;
+               led->led_cdev.default_trigger = "pattern";
+-
+-              init_data.fwnode = led->fwnode;
+-              init_data.devicename = "st1202";
+-              init_data.default_label = ":";
+-
+-              err = devm_led_classdev_register_ext(dev, &led->led_cdev, &init_data);
+-              if (err < 0)
+-                      return dev_err_probe(dev, err, "Failed to register LED class device\n");
+-
+               led->led_cdev.brightness_set = st1202_brightness_set;
+               led->led_cdev.brightness_get = st1202_brightness_get;
+       }
+@@ -368,6 +357,7 @@ static int st1202_probe(struct i2c_clien
+               return ret;
+       for (int i = 0; i < ST1202_MAX_LEDS; i++) {
++              struct led_init_data init_data = {};
+               led = &chip->leds[i];
+               led->chip = chip;
+               led->led_num = i;
+@@ -384,6 +374,15 @@ static int st1202_probe(struct i2c_clien
+               if (ret < 0)
+                       return dev_err_probe(&client->dev, ret,
+                                       "Failed to clear LED pattern\n");
++
++              init_data.fwnode = led->fwnode;
++              init_data.devicename = "st1202";
++              init_data.default_label = ":";
++
++              ret = devm_led_classdev_register_ext(&client->dev, &led->led_cdev, &init_data);
++              if (ret < 0)
++                      return dev_err_probe(&client->dev, ret,
++                                      "Failed to register LED class device\n");
+       }
+       return 0;
diff --git a/target/linux/generic/backport-6.12/900-v6.14-driver-core-add-a-faux-bus-for-use-when-a-simple-dev.patch b/target/linux/generic/backport-6.12/900-v6.14-driver-core-add-a-faux-bus-for-use-when-a-simple-dev.patch
new file mode 100644 (file)
index 0000000..1ada8a6
--- /dev/null
@@ -0,0 +1,386 @@
+From 35fa2d88ca9481e5caf533d58b99ca259c63b2fe Mon Sep 17 00:00:00 2001
+From: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Date: Mon, 10 Feb 2025 13:30:25 +0100
+Subject: [PATCH] driver core: add a faux bus for use when a simple device/bus
+ is needed
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Many drivers abuse the platform driver/bus system as it provides a
+simple way to create and bind a device to a driver-specific set of
+probe/release functions.  Instead of doing that, and wasting all of the
+memory associated with a platform device, here is a "faux" bus that
+can be used instead.
+
+Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Reviewed-by: Danilo Krummrich <dakr@kernel.org>
+Reviewed-by: Lyude Paul <lyude@redhat.com>
+Reviewed-by: Thomas Weißschuh <thomas.weissschuh@linutronix.de>
+Reviewed-by: Zijun Hu <quic_zijuhu@quicinc.com>
+Link: https://lore.kernel.org/r/2025021026-atlantic-gibberish-3f0c@gregkh
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ Documentation/driver-api/infrastructure.rst |   6 +
+ drivers/base/Makefile                       |   2 +-
+ drivers/base/base.h                         |   1 +
+ drivers/base/faux.c                         | 232 ++++++++++++++++++++
+ drivers/base/init.c                         |   1 +
+ include/linux/device/faux.h                 |  69 ++++++
+ 6 files changed, 310 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/base/faux.c
+ create mode 100644 include/linux/device/faux.h
+
+--- a/Documentation/driver-api/infrastructure.rst
++++ b/Documentation/driver-api/infrastructure.rst
+@@ -41,6 +41,12 @@ Device Drivers Base
+ .. kernel-doc:: drivers/base/class.c
+    :export:
++.. kernel-doc:: include/linux/device/faux.h
++   :internal:
++
++.. kernel-doc:: drivers/base/faux.c
++   :export:
++
+ .. kernel-doc:: drivers/base/node.c
+    :internal:
+--- a/drivers/base/Makefile
++++ b/drivers/base/Makefile
+@@ -6,7 +6,7 @@ obj-y                  := component.o core.o bus.o dd.o
+                          cpu.o firmware.o init.o map.o devres.o \
+                          attribute_container.o transport_class.o \
+                          topology.o container.o property.o cacheinfo.o \
+-                         swnode.o
++                         swnode.o faux.o
+ obj-$(CONFIG_AUXILIARY_BUS) += auxiliary.o
+ obj-$(CONFIG_DEVTMPFS)        += devtmpfs.o
+ obj-y                 += power/
+--- a/drivers/base/base.h
++++ b/drivers/base/base.h
+@@ -138,6 +138,7 @@ int hypervisor_init(void);
+ static inline int hypervisor_init(void) { return 0; }
+ #endif
+ int platform_bus_init(void);
++int faux_bus_init(void);
+ void cpu_dev_init(void);
+ void container_dev_init(void);
+ #ifdef CONFIG_AUXILIARY_BUS
+--- /dev/null
++++ b/drivers/base/faux.c
+@@ -0,0 +1,232 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2025 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
++ * Copyright (c) 2025 The Linux Foundation
++ *
++ * A "simple" faux bus that allows devices to be created and added
++ * automatically to it.  This is to be used whenever you need to create a
++ * device that is not associated with any "real" system resources, and do
++ * not want to have to deal with a bus/driver binding logic.  It is
++ * intended to be very simple, with only a create and a destroy function
++ * available.
++ */
++#include <linux/err.h>
++#include <linux/init.h>
++#include <linux/slab.h>
++#include <linux/string.h>
++#include <linux/container_of.h>
++#include <linux/device/faux.h>
++#include "base.h"
++
++/*
++ * Internal wrapper structure so we can hold a pointer to the
++ * faux_device_ops for this device.
++ */
++struct faux_object {
++      struct faux_device faux_dev;
++      const struct faux_device_ops *faux_ops;
++};
++#define to_faux_object(dev) container_of_const(dev, struct faux_object, faux_dev.dev)
++
++static struct device faux_bus_root = {
++      .init_name      = "faux",
++};
++
++static int faux_match(struct device *dev, const struct device_driver *drv)
++{
++      /* Match always succeeds, we only have one driver */
++      return 1;
++}
++
++static int faux_probe(struct device *dev)
++{
++      struct faux_object *faux_obj = to_faux_object(dev);
++      struct faux_device *faux_dev = &faux_obj->faux_dev;
++      const struct faux_device_ops *faux_ops = faux_obj->faux_ops;
++      int ret = 0;
++
++      if (faux_ops && faux_ops->probe)
++              ret = faux_ops->probe(faux_dev);
++
++      return ret;
++}
++
++static void faux_remove(struct device *dev)
++{
++      struct faux_object *faux_obj = to_faux_object(dev);
++      struct faux_device *faux_dev = &faux_obj->faux_dev;
++      const struct faux_device_ops *faux_ops = faux_obj->faux_ops;
++
++      if (faux_ops && faux_ops->remove)
++              faux_ops->remove(faux_dev);
++}
++
++static const struct bus_type faux_bus_type = {
++      .name           = "faux",
++      .match          = faux_match,
++      .probe          = faux_probe,
++      .remove         = faux_remove,
++};
++
++static struct device_driver faux_driver = {
++      .name           = "faux_driver",
++      .bus            = &faux_bus_type,
++      .probe_type     = PROBE_FORCE_SYNCHRONOUS,
++};
++
++static void faux_device_release(struct device *dev)
++{
++      struct faux_object *faux_obj = to_faux_object(dev);
++
++      kfree(faux_obj);
++}
++
++/**
++ * faux_device_create_with_groups - Create and register with the driver
++ *            core a faux device and populate the device with an initial
++ *            set of sysfs attributes.
++ * @name:     The name of the device we are adding, must be unique for
++ *            all faux devices.
++ * @parent:   Pointer to a potential parent struct device.  If set to
++ *            NULL, the device will be created in the "root" of the faux
++ *            device tree in sysfs.
++ * @faux_ops: struct faux_device_ops that the new device will call back
++ *            into, can be NULL.
++ * @groups:   The set of sysfs attributes that will be created for this
++ *            device when it is registered with the driver core.
++ *
++ * Create a new faux device and register it in the driver core properly.
++ * If present, callbacks in @faux_ops will be called with the device that
++ * for the caller to do something with at the proper time given the
++ * device's lifecycle.
++ *
++ * Note, when this function is called, the functions specified in struct
++ * faux_ops can be called before the function returns, so be prepared for
++ * everything to be properly initialized before that point in time.
++ *
++ * Return:
++ * * NULL if an error happened with creating the device
++ * * pointer to a valid struct faux_device that is registered with sysfs
++ */
++struct faux_device *faux_device_create_with_groups(const char *name,
++                                                 struct device *parent,
++                                                 const struct faux_device_ops *faux_ops,
++                                                 const struct attribute_group **groups)
++{
++      struct faux_object *faux_obj;
++      struct faux_device *faux_dev;
++      struct device *dev;
++      int ret;
++
++      faux_obj = kzalloc(sizeof(*faux_obj), GFP_KERNEL);
++      if (!faux_obj)
++              return NULL;
++
++      /* Save off the callbacks so we can use them in the future */
++      faux_obj->faux_ops = faux_ops;
++
++      /* Initialize the device portion and register it with the driver core */
++      faux_dev = &faux_obj->faux_dev;
++      dev = &faux_dev->dev;
++
++      device_initialize(dev);
++      dev->release = faux_device_release;
++      if (parent)
++              dev->parent = parent;
++      else
++              dev->parent = &faux_bus_root;
++      dev->bus = &faux_bus_type;
++      dev->groups = groups;
++      dev_set_name(dev, "%s", name);
++
++      ret = device_add(dev);
++      if (ret) {
++              pr_err("%s: device_add for faux device '%s' failed with %d\n",
++                     __func__, name, ret);
++              put_device(dev);
++              return NULL;
++      }
++
++      return faux_dev;
++}
++EXPORT_SYMBOL_GPL(faux_device_create_with_groups);
++
++/**
++ * faux_device_create - create and register with the driver core a faux device
++ * @name:     The name of the device we are adding, must be unique for all
++ *            faux devices.
++ * @parent:   Pointer to a potential parent struct device.  If set to
++ *            NULL, the device will be created in the "root" of the faux
++ *            device tree in sysfs.
++ * @faux_ops: struct faux_device_ops that the new device will call back
++ *            into, can be NULL.
++ *
++ * Create a new faux device and register it in the driver core properly.
++ * If present, callbacks in @faux_ops will be called with the device that
++ * for the caller to do something with at the proper time given the
++ * device's lifecycle.
++ *
++ * Note, when this function is called, the functions specified in struct
++ * faux_ops can be called before the function returns, so be prepared for
++ * everything to be properly initialized before that point in time.
++ *
++ * Return:
++ * * NULL if an error happened with creating the device
++ * * pointer to a valid struct faux_device that is registered with sysfs
++ */
++struct faux_device *faux_device_create(const char *name,
++                                     struct device *parent,
++                                     const struct faux_device_ops *faux_ops)
++{
++      return faux_device_create_with_groups(name, parent, faux_ops, NULL);
++}
++EXPORT_SYMBOL_GPL(faux_device_create);
++
++/**
++ * faux_device_destroy - destroy a faux device
++ * @faux_dev: faux device to destroy
++ *
++ * Unregisters and cleans up a device that was created with a call to
++ * faux_device_create()
++ */
++void faux_device_destroy(struct faux_device *faux_dev)
++{
++      struct device *dev = &faux_dev->dev;
++
++      if (!faux_dev)
++              return;
++
++      device_del(dev);
++
++      /* The final put_device() will clean up the memory we allocated for this device. */
++      put_device(dev);
++}
++EXPORT_SYMBOL_GPL(faux_device_destroy);
++
++int __init faux_bus_init(void)
++{
++      int ret;
++
++      ret = device_register(&faux_bus_root);
++      if (ret) {
++              put_device(&faux_bus_root);
++              return ret;
++      }
++
++      ret = bus_register(&faux_bus_type);
++      if (ret)
++              goto error_bus;
++
++      ret = driver_register(&faux_driver);
++      if (ret)
++              goto error_driver;
++
++      return ret;
++
++error_driver:
++      bus_unregister(&faux_bus_type);
++
++error_bus:
++      device_unregister(&faux_bus_root);
++      return ret;
++}
+--- a/drivers/base/init.c
++++ b/drivers/base/init.c
+@@ -32,6 +32,7 @@ void __init driver_init(void)
+       /* These are also core pieces, but must come after the
+        * core core pieces.
+        */
++      faux_bus_init();
+       of_core_init();
+       platform_bus_init();
+       auxiliary_bus_init();
+--- /dev/null
++++ b/include/linux/device/faux.h
+@@ -0,0 +1,69 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/*
++ * Copyright (c) 2025 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
++ * Copyright (c) 2025 The Linux Foundation
++ *
++ * A "simple" faux bus that allows devices to be created and added
++ * automatically to it.  This is to be used whenever you need to create a
++ * device that is not associated with any "real" system resources, and do
++ * not want to have to deal with a bus/driver binding logic.  It is
++ * intended to be very simple, with only a create and a destroy function
++ * available.
++ */
++#ifndef _FAUX_DEVICE_H_
++#define _FAUX_DEVICE_H_
++
++#include <linux/container_of.h>
++#include <linux/device.h>
++
++/**
++ * struct faux_device - a "faux" device
++ * @dev:      internal struct device of the object
++ *
++ * A simple faux device that can be created/destroyed.  To be used when a
++ * driver only needs to have a device to "hang" something off.  This can be
++ * used for downloading firmware or other basic tasks.  Use this instead of
++ * a struct platform_device if the device has no resources assigned to
++ * it at all.
++ */
++struct faux_device {
++      struct device dev;
++};
++#define to_faux_device(x) container_of_const((x), struct faux_device, dev)
++
++/**
++ * struct faux_device_ops - a set of callbacks for a struct faux_device
++ * @probe:    called when a faux device is probed by the driver core
++ *            before the device is fully bound to the internal faux bus
++ *            code.  If probe succeeds, return 0, otherwise return a
++ *            negative error number to stop the probe sequence from
++ *            succeeding.
++ * @remove:   called when a faux device is removed from the system
++ *
++ * Both @probe and @remove are optional, if not needed, set to NULL.
++ */
++struct faux_device_ops {
++      int (*probe)(struct faux_device *faux_dev);
++      void (*remove)(struct faux_device *faux_dev);
++};
++
++struct faux_device *faux_device_create(const char *name,
++                                     struct device *parent,
++                                     const struct faux_device_ops *faux_ops);
++struct faux_device *faux_device_create_with_groups(const char *name,
++                                                 struct device *parent,
++                                                 const struct faux_device_ops *faux_ops,
++                                                 const struct attribute_group **groups);
++void faux_device_destroy(struct faux_device *faux_dev);
++
++static inline void *faux_device_get_drvdata(const struct faux_device *faux_dev)
++{
++      return dev_get_drvdata(&faux_dev->dev);
++}
++
++static inline void faux_device_set_drvdata(struct faux_device *faux_dev, void *data)
++{
++      dev_set_drvdata(&faux_dev->dev, data);
++}
++
++#endif /* _FAUX_DEVICE_H_ */
diff --git a/target/linux/generic/backport-6.12/910-v7.0-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch b/target/linux/generic/backport-6.12/910-v7.0-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch
new file mode 100644 (file)
index 0000000..4957677
--- /dev/null
@@ -0,0 +1,354 @@
+From 030218dedee2628ea3f035d71431e1b7c4191cfb Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Sat, 31 Jan 2026 18:38:47 +0100
+Subject: [PATCH] crypto: testmgr - Add test vectors for
+ authenc(hmac(sha384),cbc(aes))
+
+Test vectors were generated starting from existing CBC(AES) test vectors
+(RFC3602, NIST SP800-38A) and adding HMAC(SHA384) computed with Python
+script. Then, the results were double-checked on Mediatek MT7981 (safexcel)
+and NXP P2020 (talitos). Both platforms pass self-tests.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+---
+ crypto/testmgr.c |   7 ++
+ crypto/testmgr.h | 311 +++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 318 insertions(+)
+
+--- a/crypto/testmgr.c
++++ b/crypto/testmgr.c
+@@ -4453,6 +4453,13 @@ static const struct alg_test_desc alg_te
+               .test = alg_test_null,
+               .fips_allowed = 1,
+       }, {
++              .alg = "authenc(hmac(sha384),cbc(aes))",
++              .generic_driver = "authenc(hmac-sha384-lib,cbc(aes-generic))",
++              .test = alg_test_aead,
++              .suite = {
++                      .aead = __VECS(hmac_sha384_aes_cbc_tv_temp)
++              }
++      }, {
+               .alg = "authenc(hmac(sha384),cbc(des))",
+               .test = alg_test_aead,
+               .suite = {
+--- a/crypto/testmgr.h
++++ b/crypto/testmgr.h
+@@ -17773,6 +17773,317 @@ static const struct aead_testvec hmac_sh
+       },
+ };
++static const struct aead_testvec hmac_sha384_aes_cbc_tv_temp[] = {
++      { /* RFC 3602 Case 1 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x06\xa9\x21\x40\x36\xb8\xa1\x5b"
++                        "\x51\x2e\x03\xd5\x34\x12\x00\x06",
++              .klen   = 8 + 48 + 16,
++              .iv     = "\x3d\xaf\xba\x42\x9d\x9e\xb4\x30"
++                        "\xb4\x22\xda\x80\x2c\x9f\xac\x41",
++              .assoc  = "\x3d\xaf\xba\x42\x9d\x9e\xb4\x30"
++                        "\xb4\x22\xda\x80\x2c\x9f\xac\x41",
++              .alen   = 16,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\xe3\x53\x77\x9c\x10\x79\xae\xb8"
++                        "\x27\x08\x94\x2d\xbe\x77\x18\x1a"
++                        "\x79\x1c\xf1\x22\x95\x80\xe0\x60"
++                        "\x7f\xf9\x92\x60\x83\xbd\x60\x9c"
++                        "\xf6\x62\x8b\xa9\x7d\x56\xe2\xaf"
++                        "\x80\x43\xbc\x41\x4a\x63\x0b\xa0"
++                        "\x16\x25\xe2\xfe\x0a\x96\xf6\xa5"
++                        "\x6c\x0b\xc2\x53\xb4\x27\xd9\x42",
++              .clen   = 16 + 48,
++      }, { /* RFC 3602 Case 2 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
++                        "\x40\x41\x42\x43\x44\x45\x46\x47"
++                        "\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
++                        "\xc2\x86\x69\x6d\x88\x7c\x9a\xa0"
++                        "\x61\x1b\xbb\x3e\x20\x25\xa4\x5a",
++              .klen   = 8 + 48 + 16,
++              .iv     = "\x56\x2e\x17\x99\x6d\x09\x3d\x28"
++                        "\xdd\xb3\xba\x69\x5a\x2e\x6f\x58",
++              .assoc  = "\x56\x2e\x17\x99\x6d\x09\x3d\x28"
++                        "\xdd\xb3\xba\x69\x5a\x2e\x6f\x58",
++              .alen   = 16,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\xd2\x96\xcd\x94\xc2\xcc\xcf\x8a"
++                        "\x3a\x86\x30\x28\xb5\xe1\xdc\x0a"
++                        "\x75\x86\x60\x2d\x25\x3c\xff\xf9"
++                        "\x1b\x82\x66\xbe\xa6\xd6\x1a\xb1"
++                        "\x4e\x5b\xa8\x65\x51\xc6\x58\xaf"
++                        "\x31\x57\x50\x3d\x01\xa1\xa4\x3f"
++                        "\x42\xd1\xd7\x31\x76\x8d\xf8\xc8"
++                        "\xe4\xd2\x7e\xc5\x23\xe7\xc6\x2e"
++                        "\x2d\xfd\x9d\xc1\xac\x50\x1e\xcf"
++                        "\xa0\x10\xeb\x1a\x9c\xb7\xe1\xca",
++              .clen   = 32 + 48,
++      }, { /* RFC 3602 Case 3 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd\xee\xff\x11\x22"
++                        "\x33\x44\x55\x66\x77\x88\x99\xaa"
++                        "\xbb\xcc\xdd\xee\xff\x11\x22\x33"
++                        "\x6c\x3e\xa0\x47\x76\x30\xce\x21"
++                        "\xa2\xce\x33\x4a\xa7\x46\xc2\xcd",
++              .klen   = 8 + 48 + 16,
++              .iv     = "\xc7\x82\xdc\x4c\x09\x8c\x66\xcb"
++                        "\xd9\xcd\x27\xd8\x25\x68\x2c\x81",
++              .assoc  = "\xc7\x82\xdc\x4c\x09\x8c\x66\xcb"
++                        "\xd9\xcd\x27\xd8\x25\x68\x2c\x81",
++              .alen   = 16,
++              .ptext  = "This is a 48-byte message (exactly 3 AES blocks)",
++              .plen   = 48,
++              .ctext  = "\xd0\xa0\x2b\x38\x36\x45\x17\x53"
++                        "\xd4\x93\x66\x5d\x33\xf0\xe8\x86"
++                        "\x2d\xea\x54\xcd\xb2\x93\xab\xc7"
++                        "\x50\x69\x39\x27\x67\x72\xf8\xd5"
++                        "\x02\x1c\x19\x21\x6b\xad\x52\x5c"
++                        "\x85\x79\x69\x5d\x83\xba\x26\x84"
++                        "\xa1\x52\xe7\xda\xf7\x05\xb6\xca"
++                        "\xad\x0f\x51\xed\x5a\xd3\x0f\xdf"
++                        "\xde\xeb\x3f\x31\xed\x3a\x43\x93"
++                        "\x3b\xb7\xca\xc8\x1b\xe7\x3b\x61"
++                        "\x6a\x05\xfd\x2d\x6a\x5c\xb1\x0d"
++                        "\x6e\x7a\xeb\x1c\x84\xec\xdb\xde",
++              .clen   = 48 + 48,
++      }, { /* RFC 3602 Case 4 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd\xee\xff\x11\x22"
++                        "\x33\x44\x55\x66\x77\x88\x99\xaa"
++                        "\xbb\xcc\xdd\xee\xff\x11\x22\x33"
++                        "\x56\xe4\x7a\x38\xc5\x59\x89\x74"
++                        "\xbc\x46\x90\x3d\xba\x29\x03\x49",
++              .klen   = 8 + 48 + 16,
++              .iv     = "\x8c\xe8\x2e\xef\xbe\xa0\xda\x3c"
++                        "\x44\x69\x9e\xd7\xdb\x51\xb7\xd9",
++              .assoc  = "\x8c\xe8\x2e\xef\xbe\xa0\xda\x3c"
++                        "\x44\x69\x9e\xd7\xdb\x51\xb7\xd9",
++              .alen   = 16,
++              .ptext  = "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7"
++                        "\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
++                        "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7"
++                        "\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
++                        "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7"
++                        "\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
++                        "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7"
++                        "\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf",
++              .plen   = 64,
++              .ctext  = "\xc3\x0e\x32\xff\xed\xc0\x77\x4e"
++                        "\x6a\xff\x6a\xf0\x86\x9f\x71\xaa"
++                        "\x0f\x3a\xf0\x7a\x9a\x31\xa9\xc6"
++                        "\x84\xdb\x20\x7e\xb0\xef\x8e\x4e"
++                        "\x35\x90\x7a\xa6\x32\xc3\xff\xdf"
++                        "\x86\x8b\xb7\xb2\x9d\x3d\x46\xad"
++                        "\x83\xce\x9f\x9a\x10\x2e\xe9\x9d"
++                        "\x49\xa5\x3e\x87\xf4\xc3\xda\x55"
++                        "\x85\x7b\x91\xe0\x29\xeb\xd3\x59"
++                        "\x7c\xe3\x67\x14\xbe\x71\x2a\xd2"
++                        "\x8a\x1a\xd2\x35\x78\x6b\x69\xba"
++                        "\x64\xa5\x04\x00\x19\xc3\x4c\xae"
++                        "\x71\xff\x76\x9f\xbb\xc3\x29\x22"
++                        "\xc2\xc6\x51\xf1\xe6\x29\x5e\xa5",
++              .clen   = 64 + 48,
++      }, { /* RFC 3602 Case 5 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd\xee\xff\x11\x22"
++                        "\x33\x44\x55\x66\x77\x88\x99\xaa"
++                        "\xbb\xcc\xdd\xee\xff\x11\x22\x33"
++                        "\x90\xd3\x82\xb4\x10\xee\xba\x7a"
++                        "\xd9\x38\xc4\x6c\xec\x1a\x82\xbf",
++              .klen   = 8 + 48 + 16,
++              .iv     = "\xe9\x6e\x8c\x08\xab\x46\x57\x63"
++                        "\xfd\x09\x8d\x45\xdd\x3f\xf8\x93",
++              .assoc  = "\x00\x00\x43\x21\x00\x00\x00\x01"
++                        "\xe9\x6e\x8c\x08\xab\x46\x57\x63"
++                        "\xfd\x09\x8d\x45\xdd\x3f\xf8\x93",
++              .alen   = 24,
++              .ptext  = "\x08\x00\x0e\xbd\xa7\x0a\x00\x00"
++                        "\x8e\x9c\x08\x3d\xb9\x5b\x07\x00"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x01\x02\x03\x04\x05\x06\x07\x08"
++                        "\x09\x0a\x0b\x0c\x0d\x0e\x0e\x01",
++              .plen   = 80,
++              .ctext  = "\xf6\x63\xc2\x5d\x32\x5c\x18\xc6"
++                        "\xa9\x45\x3e\x19\x4e\x12\x08\x49"
++                        "\xa4\x87\x0b\x66\xcc\x6b\x99\x65"
++                        "\x33\x00\x13\xb4\x89\x8d\xc8\x56"
++                        "\xa4\x69\x9e\x52\x3a\x55\xdb\x08"
++                        "\x0b\x59\xec\x3a\x8e\x4b\x7e\x52"
++                        "\x77\x5b\x07\xd1\xdb\x34\xed\x9c"
++                        "\x53\x8a\xb5\x0c\x55\x1b\x87\x4a"
++                        "\xa2\x69\xad\xd0\x47\xad\x2d\x59"
++                        "\x13\xac\x19\xb7\xcf\xba\xd4\xa6"
++                        "\x57\x5f\xb4\xd7\x74\x6f\x18\x97"
++                        "\xb7\xde\xfc\xf3\x4e\x0d\x29\x4d"
++                        "\xa0\xff\x39\x9e\x2d\xbf\x27\xac"
++                        "\x54\xb9\x8a\x3e\xab\x3b\xac\xd3"
++                        "\x36\x43\x74\xfc\xc2\x64\x81\x8a"
++                        "\x2c\x15\x72\xdf\x3f\x9d\x5b\xa4",
++              .clen   = 80 + 48,
++      }, { /* NIST SP800-38A F.2.3 CBC-AES192.Encrypt */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x18"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd\xee\xff\x11\x22"
++                        "\x33\x44\x55\x66\x77\x88\x99\xaa"
++                        "\xbb\xcc\xdd\xee\xff\x11\x22\x33"
++                        "\x8e\x73\xb0\xf7\xda\x0e\x64\x52"
++                        "\xc8\x10\xf3\x2b\x80\x90\x79\xe5"
++                        "\x62\xf8\xea\xd2\x52\x2c\x6b\x7b",
++              .klen   = 8 + 48 + 24,
++              .iv     = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
++              .assoc  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
++              .alen   = 16,
++              .ptext  = "\x6b\xc1\xbe\xe2\x2e\x40\x9f\x96"
++                        "\xe9\x3d\x7e\x11\x73\x93\x17\x2a"
++                        "\xae\x2d\x8a\x57\x1e\x03\xac\x9c"
++                        "\x9e\xb7\x6f\xac\x45\xaf\x8e\x51"
++                        "\x30\xc8\x1c\x46\xa3\x5c\xe4\x11"
++                        "\xe5\xfb\xc1\x19\x1a\x0a\x52\xef"
++                        "\xf6\x9f\x24\x45\xdf\x4f\x9b\x17"
++                        "\xad\x2b\x41\x7b\xe6\x6c\x37\x10",
++              .plen   = 64,
++              .ctext  = "\x4f\x02\x1d\xb2\x43\xbc\x63\x3d"
++                        "\x71\x78\x18\x3a\x9f\xa0\x71\xe8"
++                        "\xb4\xd9\xad\xa9\xad\x7d\xed\xf4"
++                        "\xe5\xe7\x38\x76\x3f\x69\x14\x5a"
++                        "\x57\x1b\x24\x20\x12\xfb\x7a\xe0"
++                        "\x7f\xa9\xba\xac\x3d\xf1\x02\xe0"
++                        "\x08\xb0\xe2\x79\x88\x59\x88\x81"
++                        "\xd9\x20\xa9\xe6\x4f\x56\x15\xcd"
++                        "\x29\x9b\x42\x47\x0b\xbf\xf3\x54"
++                        "\x54\x95\xb0\x89\xd5\xa0\xc3\x78"
++                        "\x60\x6c\x18\x39\x6d\xc9\xfb\x2a"
++                        "\x34\x1c\xed\x95\x10\x1e\x43\x0a"
++                        "\x72\xce\x26\xbc\x74\xd9\x6f\xa2"
++                        "\xf1\xd9\xd0\xb1\xdf\x3d\x93\x14",
++              .clen   = 64 + 48,
++      }, { /* NIST SP800-38A F.2.5 CBC-AES256.Encrypt */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x20"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd\xee\xff\x11\x22"
++                        "\x33\x44\x55\x66\x77\x88\x99\xaa"
++                        "\xbb\xcc\xdd\xee\xff\x11\x22\x33"
++                        "\x60\x3d\xeb\x10\x15\xca\x71\xbe"
++                        "\x2b\x73\xae\xf0\x85\x7d\x77\x81"
++                        "\x1f\x35\x2c\x07\x3b\x61\x08\xd7"
++                        "\x2d\x98\x10\xa3\x09\x14\xdf\xf4",
++              .klen   = 8 + 48 + 32,
++              .iv     = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
++              .assoc  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
++              .alen   = 16,
++              .ptext  = "\x6b\xc1\xbe\xe2\x2e\x40\x9f\x96"
++                        "\xe9\x3d\x7e\x11\x73\x93\x17\x2a"
++                        "\xae\x2d\x8a\x57\x1e\x03\xac\x9c"
++                        "\x9e\xb7\x6f\xac\x45\xaf\x8e\x51"
++                        "\x30\xc8\x1c\x46\xa3\x5c\xe4\x11"
++                        "\xe5\xfb\xc1\x19\x1a\x0a\x52\xef"
++                        "\xf6\x9f\x24\x45\xdf\x4f\x9b\x17"
++                        "\xad\x2b\x41\x7b\xe6\x6c\x37\x10",
++              .plen   = 64,
++              .ctext  = "\xf5\x8c\x4c\x04\xd6\xe5\xf1\xba"
++                        "\x77\x9e\xab\xfb\x5f\x7b\xfb\xd6"
++                        "\x9c\xfc\x4e\x96\x7e\xdb\x80\x8d"
++                        "\x67\x9f\x77\x7b\xc6\x70\x2c\x7d"
++                        "\x39\xf2\x33\x69\xa9\xd9\xba\xcf"
++                        "\xa5\x30\xe2\x63\x04\x23\x14\x61"
++                        "\xb2\xeb\x05\xe2\xc3\x9b\xe9\xfc"
++                        "\xda\x6c\x19\x07\x8c\x6a\x9d\x1b"
++                        "\x9f\x50\xce\x64\xd9\xa3\xc9\x7a"
++                        "\x15\x3a\x3d\x46\x9a\x90\xf3\x06"
++                        "\x22\xad\xc5\x24\x77\x50\xb8\xfe"
++                        "\xbe\x37\x16\x86\x34\x5f\xaf\x97"
++                        "\x00\x9d\x86\xc8\x32\x4f\x72\x2f"
++                        "\x48\x97\xad\xb6\xb9\x77\x33\xbc",
++              .clen   = 64 + 48,
++      },
++};
++
+ static const struct aead_testvec hmac_sha512_aes_cbc_tv_temp[] = {
+       { /* RFC 3602 Case 1 */
+ #ifdef __LITTLE_ENDIAN
diff --git a/target/linux/generic/backport-6.12/911-v7.0-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch b/target/linux/generic/backport-6.12/911-v7.0-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch
new file mode 100644 (file)
index 0000000..8649c1e
--- /dev/null
@@ -0,0 +1,328 @@
+From a22d48cbe55814061d46db2f87090ba5e36aaf7f Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Sat, 31 Jan 2026 18:43:03 +0100
+Subject: [PATCH] crypto: testmgr - Add test vectors for
+ authenc(hmac(sha224),cbc(aes))
+
+Test vectors were generated starting from existing CBC(AES) test vectors
+(RFC3602, NIST SP800-38A) and adding HMAC(SHA224) computed with Python
+script. Then, the results were double-checked on Mediatek MT7981 (safexcel)
+and NXP P2020 (talitos). Both platforms pass self-tests.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+---
+ crypto/testmgr.c |   7 ++
+ crypto/testmgr.h | 285 +++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 292 insertions(+)
+
+--- a/crypto/testmgr.c
++++ b/crypto/testmgr.c
+@@ -4414,6 +4414,13 @@ static const struct alg_test_desc alg_te
+               .test = alg_test_null,
+               .fips_allowed = 1,
+       }, {
++              .alg = "authenc(hmac(sha224),cbc(aes))",
++              .generic_driver = "authenc(hmac-sha224-lib,cbc(aes-generic))",
++              .test = alg_test_aead,
++              .suite = {
++                      .aead = __VECS(hmac_sha224_aes_cbc_tv_temp)
++              }
++      }, {
+               .alg = "authenc(hmac(sha224),cbc(des))",
+               .test = alg_test_aead,
+               .suite = {
+--- a/crypto/testmgr.h
++++ b/crypto/testmgr.h
+@@ -17490,6 +17490,291 @@ static const struct aead_testvec hmac_sh
+       },
+ };
++static const struct aead_testvec hmac_sha224_aes_cbc_tv_temp[] = {
++      { /* RFC 3602 Case 1 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00"
++                        "\x06\xa9\x21\x40\x36\xb8\xa1\x5b"
++                        "\x51\x2e\x03\xd5\x34\x12\x00\x06",
++              .klen   = 8 + 28 + 16,
++              .iv     = "\x3d\xaf\xba\x42\x9d\x9e\xb4\x30"
++                        "\xb4\x22\xda\x80\x2c\x9f\xac\x41",
++              .assoc  = "\x3d\xaf\xba\x42\x9d\x9e\xb4\x30"
++                        "\xb4\x22\xda\x80\x2c\x9f\xac\x41",
++              .alen   = 16,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\xe3\x53\x77\x9c\x10\x79\xae\xb8"
++                        "\x27\x08\x94\x2d\xbe\x77\x18\x1a"
++                        "\x17\xe8\x00\x76\x70\x71\xd1\x72"
++                        "\xf8\xd0\x91\x51\x67\xf9\xdf\xd6"
++                        "\x0d\x56\x1a\xb3\x52\x19\x85\xae"
++                        "\x46\x74\xb6\x98",
++              .clen   = 16 + 28,
++      }, { /* RFC 3602 Case 2 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b"
++                        "\xc2\x86\x69\x6d\x88\x7c\x9a\xa0"
++                        "\x61\x1b\xbb\x3e\x20\x25\xa4\x5a",
++              .klen   = 8 + 28 + 16,
++              .iv     = "\x56\x2e\x17\x99\x6d\x09\x3d\x28"
++                        "\xdd\xb3\xba\x69\x5a\x2e\x6f\x58",
++              .assoc  = "\x56\x2e\x17\x99\x6d\x09\x3d\x28"
++                        "\xdd\xb3\xba\x69\x5a\x2e\x6f\x58",
++              .alen   = 16,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\xd2\x96\xcd\x94\xc2\xcc\xcf\x8a"
++                        "\x3a\x86\x30\x28\xb5\xe1\xdc\x0a"
++                        "\x75\x86\x60\x2d\x25\x3c\xff\xf9"
++                        "\x1b\x82\x66\xbe\xa6\xd6\x1a\xb1"
++                        "\xa1\x11\xfa\xbb\x1e\x04\x7e\xe7"
++                        "\x4c\x5f\x65\xbf\x68\x8d\x33\x9d"
++                        "\xbc\x74\x9b\xf3\x15\xf3\x8f\x8d"
++                        "\xe8\xaf\x33\xe0",
++
++              .clen   = 32 + 28,
++      }, { /* RFC 3602 Case 3 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd"
++                        "\x6c\x3e\xa0\x47\x76\x30\xce\x21"
++                        "\xa2\xce\x33\x4a\xa7\x46\xc2\xcd",
++              .klen   = 8 + 28 + 16,
++              .iv     = "\xc7\x82\xdc\x4c\x09\x8c\x66\xcb"
++                        "\xd9\xcd\x27\xd8\x25\x68\x2c\x81",
++              .assoc  = "\xc7\x82\xdc\x4c\x09\x8c\x66\xcb"
++                        "\xd9\xcd\x27\xd8\x25\x68\x2c\x81",
++              .alen   = 16,
++              .ptext  = "This is a 48-byte message (exactly 3 AES blocks)",
++              .plen   = 48,
++              .ctext  = "\xd0\xa0\x2b\x38\x36\x45\x17\x53"
++                        "\xd4\x93\x66\x5d\x33\xf0\xe8\x86"
++                        "\x2d\xea\x54\xcd\xb2\x93\xab\xc7"
++                        "\x50\x69\x39\x27\x67\x72\xf8\xd5"
++                        "\x02\x1c\x19\x21\x6b\xad\x52\x5c"
++                        "\x85\x79\x69\x5d\x83\xba\x26\x84"
++                        "\x60\xb3\xca\x0e\xc1\xfe\xf2\x27"
++                        "\x5a\x41\xe4\x99\xa8\x19\x56\xf1"
++                        "\x44\x98\x27\x9f\x99\xb0\x4a\xad"
++                        "\x4d\xc1\x1e\x88",
++              .clen   = 48 + 28,
++      }, { /* RFC 3602 Case 4 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd"
++                        "\x56\xe4\x7a\x38\xc5\x59\x89\x74"
++                        "\xbc\x46\x90\x3d\xba\x29\x03\x49",
++              .klen   = 8 + 28 + 16,
++              .iv     = "\x8c\xe8\x2e\xef\xbe\xa0\xda\x3c"
++                        "\x44\x69\x9e\xd7\xdb\x51\xb7\xd9",
++              .assoc  = "\x8c\xe8\x2e\xef\xbe\xa0\xda\x3c"
++                        "\x44\x69\x9e\xd7\xdb\x51\xb7\xd9",
++              .alen   = 16,
++              .ptext  = "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7"
++                        "\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
++                        "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7"
++                        "\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
++                        "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7"
++                        "\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
++                        "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7"
++                        "\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf",
++              .plen   = 64,
++              .ctext  = "\xc3\x0e\x32\xff\xed\xc0\x77\x4e"
++                        "\x6a\xff\x6a\xf0\x86\x9f\x71\xaa"
++                        "\x0f\x3a\xf0\x7a\x9a\x31\xa9\xc6"
++                        "\x84\xdb\x20\x7e\xb0\xef\x8e\x4e"
++                        "\x35\x90\x7a\xa6\x32\xc3\xff\xdf"
++                        "\x86\x8b\xb7\xb2\x9d\x3d\x46\xad"
++                        "\x83\xce\x9f\x9a\x10\x2e\xe9\x9d"
++                        "\x49\xa5\x3e\x87\xf4\xc3\xda\x55"
++                        "\xbb\xe9\x38\xf8\xb9\xbf\xcb\x7b"
++                        "\xa8\x22\x91\xea\x1e\xaf\x13\xba"
++                        "\x24\x18\x64\x9c\xcb\xb4\xa9\x16"
++                        "\x4b\x83\x9c\xec",
++              .clen   = 64 + 28,
++      }, { /* RFC 3602 Case 5 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd"
++                        "\x90\xd3\x82\xb4\x10\xee\xba\x7a"
++                        "\xd9\x38\xc4\x6c\xec\x1a\x82\xbf",
++              .klen   = 8 + 28 + 16,
++              .iv     = "\xe9\x6e\x8c\x08\xab\x46\x57\x63"
++                        "\xfd\x09\x8d\x45\xdd\x3f\xf8\x93",
++              .assoc  = "\x00\x00\x43\x21\x00\x00\x00\x01"
++                        "\xe9\x6e\x8c\x08\xab\x46\x57\x63"
++                        "\xfd\x09\x8d\x45\xdd\x3f\xf8\x93",
++              .alen   = 24,
++              .ptext  = "\x08\x00\x0e\xbd\xa7\x0a\x00\x00"
++                        "\x8e\x9c\x08\x3d\xb9\x5b\x07\x00"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x01\x02\x03\x04\x05\x06\x07\x08"
++                        "\x09\x0a\x0b\x0c\x0d\x0e\x0e\x01",
++              .plen   = 80,
++              .ctext  = "\xf6\x63\xc2\x5d\x32\x5c\x18\xc6"
++                        "\xa9\x45\x3e\x19\x4e\x12\x08\x49"
++                        "\xa4\x87\x0b\x66\xcc\x6b\x99\x65"
++                        "\x33\x00\x13\xb4\x89\x8d\xc8\x56"
++                        "\xa4\x69\x9e\x52\x3a\x55\xdb\x08"
++                        "\x0b\x59\xec\x3a\x8e\x4b\x7e\x52"
++                        "\x77\x5b\x07\xd1\xdb\x34\xed\x9c"
++                        "\x53\x8a\xb5\x0c\x55\x1b\x87\x4a"
++                        "\xa2\x69\xad\xd0\x47\xad\x2d\x59"
++                        "\x13\xac\x19\xb7\xcf\xba\xd4\xa6"
++                        "\x04\x5e\x83\x45\xc5\x6a\x5b\xe2"
++                        "\x5e\xd8\x59\x06\xbd\xc7\xd2\x9b"
++                        "\x0b\x65\x1f\x31\xc7\xe6\x9c\x39"
++                        "\xa3\x66\xdb\xb8",
++              .clen   = 80 + 28,
++      }, { /* NIST SP800-38A F.2.3 CBC-AES192.Encrypt */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x18"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd"
++                        "\x8e\x73\xb0\xf7\xda\x0e\x64\x52"
++                        "\xc8\x10\xf3\x2b\x80\x90\x79\xe5"
++                        "\x62\xf8\xea\xd2\x52\x2c\x6b\x7b",
++              .klen   = 8 + 28 + 24,
++              .iv     = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
++              .assoc  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
++              .alen   = 16,
++              .ptext  = "\x6b\xc1\xbe\xe2\x2e\x40\x9f\x96"
++                        "\xe9\x3d\x7e\x11\x73\x93\x17\x2a"
++                        "\xae\x2d\x8a\x57\x1e\x03\xac\x9c"
++                        "\x9e\xb7\x6f\xac\x45\xaf\x8e\x51"
++                        "\x30\xc8\x1c\x46\xa3\x5c\xe4\x11"
++                        "\xe5\xfb\xc1\x19\x1a\x0a\x52\xef"
++                        "\xf6\x9f\x24\x45\xdf\x4f\x9b\x17"
++                        "\xad\x2b\x41\x7b\xe6\x6c\x37\x10",
++              .plen   = 64,
++              .ctext  = "\x4f\x02\x1d\xb2\x43\xbc\x63\x3d"
++                        "\x71\x78\x18\x3a\x9f\xa0\x71\xe8"
++                        "\xb4\xd9\xad\xa9\xad\x7d\xed\xf4"
++                        "\xe5\xe7\x38\x76\x3f\x69\x14\x5a"
++                        "\x57\x1b\x24\x20\x12\xfb\x7a\xe0"
++                        "\x7f\xa9\xba\xac\x3d\xf1\x02\xe0"
++                        "\x08\xb0\xe2\x79\x88\x59\x88\x81"
++                        "\xd9\x20\xa9\xe6\x4f\x56\x15\xcd"
++                        "\x67\x35\xcd\x86\x94\x51\x3b\x3a"
++                        "\xaa\x07\xb1\xed\x18\x55\x62\x01"
++                        "\x95\xb2\x53\xb5\x20\x78\x16\xd7"
++                        "\xb8\x49\x7f\x96",
++
++              .clen   = 64 + 28,
++      }, { /* NIST SP800-38A F.2.5 CBC-AES256.Encrypt */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x20"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd"
++                        "\x60\x3d\xeb\x10\x15\xca\x71\xbe"
++                        "\x2b\x73\xae\xf0\x85\x7d\x77\x81"
++                        "\x1f\x35\x2c\x07\x3b\x61\x08\xd7"
++                        "\x2d\x98\x10\xa3\x09\x14\xdf\xf4",
++              .klen   = 8 + 28 + 32,
++              .iv     = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
++              .assoc  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
++              .alen   = 16,
++              .ptext  = "\x6b\xc1\xbe\xe2\x2e\x40\x9f\x96"
++                        "\xe9\x3d\x7e\x11\x73\x93\x17\x2a"
++                        "\xae\x2d\x8a\x57\x1e\x03\xac\x9c"
++                        "\x9e\xb7\x6f\xac\x45\xaf\x8e\x51"
++                        "\x30\xc8\x1c\x46\xa3\x5c\xe4\x11"
++                        "\xe5\xfb\xc1\x19\x1a\x0a\x52\xef"
++                        "\xf6\x9f\x24\x45\xdf\x4f\x9b\x17"
++                        "\xad\x2b\x41\x7b\xe6\x6c\x37\x10",
++              .plen   = 64,
++              .ctext  = "\xf5\x8c\x4c\x04\xd6\xe5\xf1\xba"
++                        "\x77\x9e\xab\xfb\x5f\x7b\xfb\xd6"
++                        "\x9c\xfc\x4e\x96\x7e\xdb\x80\x8d"
++                        "\x67\x9f\x77\x7b\xc6\x70\x2c\x7d"
++                        "\x39\xf2\x33\x69\xa9\xd9\xba\xcf"
++                        "\xa5\x30\xe2\x63\x04\x23\x14\x61"
++                        "\xb2\xeb\x05\xe2\xc3\x9b\xe9\xfc"
++                        "\xda\x6c\x19\x07\x8c\x6a\x9d\x1b"
++                        "\xe0\xe2\x3d\x3f\x55\x24\x2c\x4d"
++                        "\xb9\x13\x2a\xc0\x07\xbb\x3b\xda"
++                        "\xfd\xa4\x51\x32\x3f\x44\xb1\x13"
++                        "\x98\xf9\xbc\xb9",
++              .clen   = 64 + 28,
++      },
++};
++
+ static const struct aead_testvec hmac_sha256_aes_cbc_tv_temp[] = {
+       { /* RFC 3602 Case 1 */
+ #ifdef __LITTLE_ENDIAN
diff --git a/target/linux/generic/backport-6.12/912-v7.0-crypto-testmgr-Add-test-vectors-for-authenc-hmac-md5.patch b/target/linux/generic/backport-6.12/912-v7.0-crypto-testmgr-Add-test-vectors-for-authenc-hmac-md5.patch
new file mode 100644 (file)
index 0000000..0e36006
--- /dev/null
@@ -0,0 +1,102 @@
+From dc8f3d9ae804e8bb47dd49b051fe8b303db3b95c Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Sun, 1 Feb 2026 12:27:08 +0100
+Subject: [PATCH] crypto: testmgr - Add test vectors for
+ authenc(hmac(md5),cbc(des3_ede))
+
+Test vector was generated using a software implementation and then double
+checked using a hardware implementation on NXP P2020 (talitos). The
+encryption part is identical to authenc(hmac(sha1),cbc(des3_ede)),
+only HMAC is different.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+---
+ crypto/testmgr.c |  7 ++++++
+ crypto/testmgr.h | 59 ++++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 66 insertions(+)
+
+--- a/crypto/testmgr.c
++++ b/crypto/testmgr.c
+@@ -4375,6 +4375,13 @@ static const struct alg_test_desc alg_te
+                       .cprng = __VECS(ansi_cprng_aes_tv_template)
+               }
+       }, {
++              .alg = "authenc(hmac(md5),cbc(des3_ede))",
++              .generic_driver = "authenc(hmac-md5-lib,cbc(des3_ede-generic))",
++              .test = alg_test_aead,
++              .suite = {
++                      .aead = __VECS(hmac_md5_des3_ede_cbc_tv_temp)
++              }
++      }, {
+               .alg = "authenc(hmac(md5),ecb(cipher_null))",
+               .test = alg_test_aead,
+               .suite = {
+--- a/crypto/testmgr.h
++++ b/crypto/testmgr.h
+@@ -19021,6 +19021,65 @@ static const struct aead_testvec hmac_sh
+       },
+ };
++static const struct aead_testvec hmac_md5_des3_ede_cbc_tv_temp[] = {
++      { /*Generated with cryptopp*/
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x18"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\xE9\xC0\xFF\x2E\x76\x0B\x64\x24"
++                        "\x44\x4D\x99\x5A\x12\xD6\x40\xC0"
++                        "\xEA\xC2\x84\xE8\x14\x95\xDB\xE8",
++              .klen   = 8 + 16 + 24,
++              .iv     = "\x7D\x33\x88\x93\x0F\x93\xB2\x42",
++              .assoc  = "\x00\x00\x43\x21\x00\x00\x00\x01"
++                        "\x7D\x33\x88\x93\x0F\x93\xB2\x42",
++              .alen   = 16,
++              .ptext  = "\x6f\x54\x20\x6f\x61\x4d\x79\x6e"
++                        "\x53\x20\x63\x65\x65\x72\x73\x74"
++                        "\x54\x20\x6f\x6f\x4d\x20\x6e\x61"
++                        "\x20\x79\x65\x53\x72\x63\x74\x65"
++                        "\x20\x73\x6f\x54\x20\x6f\x61\x4d"
++                        "\x79\x6e\x53\x20\x63\x65\x65\x72"
++                        "\x73\x74\x54\x20\x6f\x6f\x4d\x20"
++                        "\x6e\x61\x20\x79\x65\x53\x72\x63"
++                        "\x74\x65\x20\x73\x6f\x54\x20\x6f"
++                        "\x61\x4d\x79\x6e\x53\x20\x63\x65"
++                        "\x65\x72\x73\x74\x54\x20\x6f\x6f"
++                        "\x4d\x20\x6e\x61\x20\x79\x65\x53"
++                        "\x72\x63\x74\x65\x20\x73\x6f\x54"
++                        "\x20\x6f\x61\x4d\x79\x6e\x53\x20"
++                        "\x63\x65\x65\x72\x73\x74\x54\x20"
++                        "\x6f\x6f\x4d\x20\x6e\x61\x0a\x79",
++              .plen   = 128,
++              .ctext  = "\x0e\x2d\xb6\x97\x3c\x56\x33\xf4"
++                        "\x67\x17\x21\xc7\x6e\x8a\xd5\x49"
++                        "\x74\xb3\x49\x05\xc5\x1c\xd0\xed"
++                        "\x12\x56\x5c\x53\x96\xb6\x00\x7d"
++                        "\x90\x48\xfc\xf5\x8d\x29\x39\xcc"
++                        "\x8a\xd5\x35\x18\x36\x23\x4e\xd7"
++                        "\x76\xd1\xda\x0c\x94\x67\xbb\x04"
++                        "\x8b\xf2\x03\x6c\xa8\xcf\xb6\xea"
++                        "\x22\x64\x47\xaa\x8f\x75\x13\xbf"
++                        "\x9f\xc2\xc3\xf0\xc9\x56\xc5\x7a"
++                        "\x71\x63\x2e\x89\x7b\x1e\x12\xca"
++                        "\xe2\x5f\xaf\xd8\xa4\xf8\xc9\x7a"
++                        "\xd6\xf9\x21\x31\x62\x44\x45\xa6"
++                        "\xd6\xbc\x5a\xd3\x2d\x54\x43\xcc"
++                        "\x9d\xde\xa5\x70\xe9\x42\x45\x8a"
++                        "\x6b\xfa\xb1\x91\x13\xb0\xd9\x19"
++                        "\x99\x09\xfb\x05\x35\xc8\xcc\x38"
++                        "\xc3\x1e\x5e\xe1\xe6\x96\x84\xc8",
++                        .clen = 128 + 16,
++      },
++};
++
+ static const struct aead_testvec hmac_sha1_des3_ede_cbc_tv_temp[] = {
+       { /*Generated with cryptopp*/
+ #ifdef __LITTLE_ENDIAN
diff --git a/target/linux/generic/backport-6.12/913-v7.0-crypto-testmgr-allow-authenc-sha224-rfc3686-variant-.patch b/target/linux/generic/backport-6.12/913-v7.0-crypto-testmgr-allow-authenc-sha224-rfc3686-variant-.patch
new file mode 100644 (file)
index 0000000..7ea115c
--- /dev/null
@@ -0,0 +1,31 @@
+From e1dc530d0c082c903e74bf6e16d8a38843d3d1d7 Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Thu, 1 Jan 2026 16:25:18 +0100
+Subject: [PATCH] crypto: testmgr - allow authenc(sha224,rfc3686) variant in
+ fips mode
+
+The remaining combinations of AES-CTR-RFC3686 and SHA* have already been
+marked as allowed in 8888690ef5f7. This commit does the same for SHA224.
+
+rfc3686(ctr(aes)) is already marked fips compliant,
+so these should be fine.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+---
+ crypto/testmgr.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/crypto/testmgr.c
++++ b/crypto/testmgr.c
+@@ -4440,6 +4440,10 @@ static const struct alg_test_desc alg_te
+                       .aead = __VECS(hmac_sha224_des3_ede_cbc_tv_temp)
+               }
+       }, {
++              .alg = "authenc(hmac(sha224),rfc3686(ctr(aes)))",
++              .test = alg_test_null,
++              .fips_allowed = 1,
++      }, {
+               .alg = "authenc(hmac(sha256),cbc(aes))",
+               .test = alg_test_aead,
+               .fips_allowed = 1,
diff --git a/target/linux/generic/backport-6.12/914-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-md5.patch b/target/linux/generic/backport-6.12/914-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-md5.patch
new file mode 100644 (file)
index 0000000..0c03750
--- /dev/null
@@ -0,0 +1,99 @@
+From 404ba6b46b6e234384b962210a98931f7423f139 Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Sat, 7 Feb 2026 15:51:03 +0100
+Subject: [PATCH] crypto: testmgr - Add test vectors for
+ authenc(hmac(md5),cbc(des))
+
+Test vector was generated using a software implementation and then double
+checked on Mediatek MT7981 (safexcel) and NXP P2020 (talitos). Both
+platforms pass self-tests.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+---
+ crypto/testmgr.c |  7 ++++++
+ crypto/testmgr.h | 57 ++++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 64 insertions(+)
+
+--- a/crypto/testmgr.c
++++ b/crypto/testmgr.c
+@@ -4375,6 +4375,13 @@ static const struct alg_test_desc alg_te
+                       .cprng = __VECS(ansi_cprng_aes_tv_template)
+               }
+       }, {
++              .alg = "authenc(hmac(md5),cbc(des))",
++              .generic_driver = "authenc(hmac-md5-lib,cbc(des-generic))",
++              .test = alg_test_aead,
++              .suite = {
++                      .aead = __VECS(hmac_md5_des_cbc_tv_temp)
++              }
++      }, {
+               .alg = "authenc(hmac(md5),cbc(des3_ede))",
+               .generic_driver = "authenc(hmac-md5-lib,cbc(des3_ede-generic))",
+               .test = alg_test_aead,
+--- a/crypto/testmgr.h
++++ b/crypto/testmgr.h
+@@ -18708,6 +18708,63 @@ static const struct aead_testvec hmac_sh
+       },
+ };
++static const struct aead_testvec hmac_md5_des_cbc_tv_temp[] = {
++      { /*Generated with cryptopp*/
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x08"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\xE9\xC0\xFF\x2E\x76\x0B\x64\x24",
++              .klen   = 8 + 16 + 8,
++              .iv     = "\x7D\x33\x88\x93\x0F\x93\xB2\x42",
++              .assoc  = "\x00\x00\x43\x21\x00\x00\x00\x01"
++                        "\x7D\x33\x88\x93\x0F\x93\xB2\x42",
++              .alen   = 16,
++              .ptext  = "\x6f\x54\x20\x6f\x61\x4d\x79\x6e"
++                        "\x53\x20\x63\x65\x65\x72\x73\x74"
++                        "\x54\x20\x6f\x6f\x4d\x20\x6e\x61"
++                        "\x20\x79\x65\x53\x72\x63\x74\x65"
++                        "\x20\x73\x6f\x54\x20\x6f\x61\x4d"
++                        "\x79\x6e\x53\x20\x63\x65\x65\x72"
++                        "\x73\x74\x54\x20\x6f\x6f\x4d\x20"
++                        "\x6e\x61\x20\x79\x65\x53\x72\x63"
++                        "\x74\x65\x20\x73\x6f\x54\x20\x6f"
++                        "\x61\x4d\x79\x6e\x53\x20\x63\x65"
++                        "\x65\x72\x73\x74\x54\x20\x6f\x6f"
++                        "\x4d\x20\x6e\x61\x20\x79\x65\x53"
++                        "\x72\x63\x74\x65\x20\x73\x6f\x54"
++                        "\x20\x6f\x61\x4d\x79\x6e\x53\x20"
++                        "\x63\x65\x65\x72\x73\x74\x54\x20"
++                        "\x6f\x6f\x4d\x20\x6e\x61\x0a\x79",
++              .plen   = 128,
++              .ctext  = "\x70\xd6\xde\x64\x87\x17\xf1\xe8"
++                        "\x54\x31\x85\x37\xed\x6b\x01\x8d"
++                        "\xe3\xcc\xe0\x1d\x5e\xf3\xfe\xf1"
++                        "\x41\xaa\x33\x91\xa7\x7d\x99\x88"
++                        "\x4d\x85\x6e\x2f\xa3\x69\xf5\x82"
++                        "\x3a\x6f\x25\xcb\x7d\x58\x1f\x9b"
++                        "\xaa\x9c\x11\xd5\x76\x67\xce\xde"
++                        "\x56\xd7\x5a\x80\x69\xea\x3a\x02"
++                        "\xf0\xc7\x7c\xe3\xcb\x40\xe5\x52"
++                        "\xd1\x10\x92\x78\x0b\x8e\x5b\xf1"
++                        "\xe3\x26\x1f\xe1\x15\x41\xc7\xba"
++                        "\x99\xdb\x08\x51\x1c\xd3\x01\xf4"
++                        "\x87\x47\x39\xb8\xd2\xdd\xbd\xfb"
++                        "\x66\x13\xdf\x1c\x01\x44\xf0\x7a"
++                        "\x1a\x6b\x13\xf5\xd5\x0b\xb8\xba"
++                        "\x53\xba\xe1\x76\xe3\x82\x07\x86"
++                        "\x95\x9e\x7d\x37\x1e\x60\xaf\x7c"
++                        "\x53\x12\x61\x68\xef\xb4\x47\xa6",
++                        .clen = 128 + 16,
++      },
++};
++
+ static const struct aead_testvec hmac_sha1_des_cbc_tv_temp[] = {
+       { /*Generated with cryptopp*/
+ #ifdef __LITTLE_ENDIAN
diff --git a/target/linux/generic/backport-6.12/915-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch b/target/linux/generic/backport-6.12/915-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch
new file mode 100644 (file)
index 0000000..c601830
--- /dev/null
@@ -0,0 +1,264 @@
+From 2a2468bf33d3fac5192429207a439d23e615173a Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Thu, 5 Mar 2026 21:08:20 +0100
+Subject: [PATCH 1/5] crypto: testmgr - Add test vectors for
+ authenc(hmac(sha1),rfc3686(ctr(aes)))
+
+Test vectors were generated starting from existing RFC3686(CTR(AES)) test
+vectors and adding HMAC(SHA1) computed with software implementation.
+Then, the results were double-checked on Mediatek MT7986 (safexcel).
+Platform pass self-tests.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+---
+ crypto/testmgr.c |   6 +-
+ crypto/testmgr.h | 221 +++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 226 insertions(+), 1 deletion(-)
+
+--- a/crypto/testmgr.c
++++ b/crypto/testmgr.c
+@@ -4425,8 +4425,12 @@ static const struct alg_test_desc alg_te
+               }
+       }, {
+               .alg = "authenc(hmac(sha1),rfc3686(ctr(aes)))",
+-              .test = alg_test_null,
++              .generic_driver = "authenc(hmac-sha1-lib,rfc3686(ctr(aes-lib)))",
++              .test = alg_test_aead,
+               .fips_allowed = 1,
++              .suite = {
++                      .aead = __VECS(hmac_sha1_aes_ctr_rfc3686_tv_temp)
++              }
+       }, {
+               .alg = "authenc(hmac(sha224),cbc(aes))",
+               .generic_driver = "authenc(hmac-sha224-lib,cbc(aes-generic))",
+--- a/crypto/testmgr.h
++++ b/crypto/testmgr.h
+@@ -17444,6 +17444,227 @@ static const struct aead_testvec hmac_sh
+       },
+ };
++static const struct aead_testvec hmac_sha1_aes_ctr_rfc3686_tv_temp[] = {
++      { /* RFC 3686 Case 1 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00"
++                        "\xae\x68\x52\xf8\x12\x10\x67\xcc"
++                        "\x4b\xf7\xa5\x76\x55\x77\xf3\x9e"
++                        "\x00\x00\x00\x30",
++              .klen   = 8 + 20 + 20,
++              .iv     = "\x00\x00\x00\x00\x00\x00\x00\x00",
++              .assoc  = "\x00\x00\x00\x00\x00\x00\x00\x00",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\xe4\x09\x5d\x4f\xb7\xa7\xb3\x79"
++                        "\x2d\x61\x75\xa3\x26\x13\x11\xb8"
++                        "\x70\xdc\x6b\x62\x43\xa1\x2f\x08"
++                        "\xf1\xec\x93\x7d\x69\xb2\x8e\x1f"
++                        "\x0a\x97\x39\x86",
++              .clen   = 16 + 20,
++      }, { /* RFC 3686 Case 2 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33"
++                        "\x7e\x24\x06\x78\x17\xfa\xe0\xd7"
++                        "\x43\xd6\xce\x1f\x32\x53\x91\x63"
++                        "\x00\x6c\xb6\xdb",
++              .klen   = 8 + 20 + 20,
++              .iv     = "\xc0\x54\x3b\x59\xda\x48\xd9\x0b",
++              .assoc  = "\xc0\x54\x3b\x59\xda\x48\xd9\x0b",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\x51\x04\xa1\x06\x16\x8a\x72\xd9"
++                        "\x79\x0d\x41\xee\x8e\xda\xd3\x88"
++                        "\xeb\x2e\x1e\xfc\x46\xda\x57\xc8"
++                        "\xfc\xe6\x30\xdf\x91\x41\xbe\x28"
++                        "\x6b\x7b\x4d\x39\x36\x1c\x12\x5f"
++                        "\x72\xd2\x88\xb2\x26\xa6\xa6\xb5"
++                        "\x1d\x3a\x49\xa6",
++              .clen   = 32 + 20,
++      }, { /* RFC 3686 Case 3 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55"
++                        "\x76\x91\xbe\x03\x5e\x50\x20\xa8"
++                        "\xac\x6e\x61\x85\x29\xf9\xa0\xdc"
++                        "\x00\xe0\x01\x7b",
++              .klen   = 8 + 20 + 20,
++              .iv     = "\x27\x77\x7f\x3f\x4a\x17\x86\xf0",
++              .assoc  = "\x27\x77\x7f\x3f\x4a\x17\x86\xf0",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
++                        "\x20\x21\x22\x23",
++              .plen   = 36,
++              .ctext  = "\xc1\xcf\x48\xa8\x9f\x2f\xfd\xd9"
++                        "\xcf\x46\x52\xe9\xef\xdb\x72\xd7"
++                        "\x45\x40\xa4\x2b\xde\x6d\x78\x36"
++                        "\xd5\x9a\x5c\xea\xae\xf3\x10\x53"
++                        "\x25\xb2\x07\x2f"
++                        "\x2c\x86\xa0\x90\x8e\xc1\x02\x1d"
++                        "\x51\xdc\xd6\x21\xc7\x30\xcc\x32"
++                        "\x38\x55\x47\x64",
++              .clen   = 36 + 20,
++      }, { /* RFC 3686 Case 4 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x1c"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00"
++                        "\x16\xaf\x5b\x14\x5f\xc9\xf5\x79"
++                        "\xc1\x75\xf9\x3e\x3b\xfb\x0e\xed"
++                        "\x86\x3d\x06\xcc\xfd\xb7\x85\x15"
++                        "\x00\x00\x00\x48",
++              .klen   = 8 + 20 + 28,
++              .iv     = "\x36\x73\x3c\x14\x7d\x6d\x93\xcb",
++              .assoc  = "\x36\x73\x3c\x14\x7d\x6d\x93\xcb",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\x4b\x55\x38\x4f\xe2\x59\xc9\xc8"
++                        "\x4e\x79\x35\xa0\x03\xcb\xe9\x28"
++                        "\xe9\x4e\x49\xf0\x6b\x8d\x58\x2b"
++                        "\x26\x7f\xf3\xab\xeb\x2f\x74\x2f"
++                        "\x45\x43\x64\xc1",
++              .clen   = 16 + 20,
++      }, { /* RFC 3686 Case 5 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x1c"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33"
++                        "\x7c\x5c\xb2\x40\x1b\x3d\xc3\x3c"
++                        "\x19\xe7\x34\x08\x19\xe0\xf6\x9c"
++                        "\x67\x8c\x3d\xb8\xe6\xf6\xa9\x1a"
++                        "\x00\x96\xb0\x3b",
++              .klen   = 8 + 20 + 28,
++              .iv     = "\x02\x0c\x6e\xad\xc2\xcb\x50\x0d",
++              .assoc  = "\x02\x0c\x6e\xad\xc2\xcb\x50\x0d",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\x45\x32\x43\xfc\x60\x9b\x23\x32"
++                        "\x7e\xdf\xaa\xfa\x71\x31\xcd\x9f"
++                        "\x84\x90\x70\x1c\x5a\xd4\xa7\x9c"
++                        "\xfc\x1f\xe0\xff\x42\xf4\xfb\x00"
++                        "\xab\xc4\xfa\x6d\x20\xe1\xce\x72"
++                        "\x0e\x92\x4e\x97\xaa\x4d\x30\x84"
++                        "\xb6\xd8\x4d\x3b",
++              .clen   = 32 + 20,
++      }, { /* RFC 3686 Case 7 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x24"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00"
++                        "\x77\x6b\xef\xf2\x85\x1d\xb0\x6f"
++                        "\x4c\x8a\x05\x42\xc8\x69\x6f\x6c"
++                        "\x6a\x81\xaf\x1e\xec\x96\xb4\xd3"
++                        "\x7f\xc1\xd6\x89\xe6\xc1\xc1\x04"
++                        "\x00\x00\x00\x60",
++              .klen   = 8 + 20 + 36,
++              .iv     = "\xdb\x56\x72\xc9\x7a\xa8\xf0\xb2",
++              .assoc  = "\xdb\x56\x72\xc9\x7a\xa8\xf0\xb2",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\x14\x5a\xd0\x1d\xbf\x82\x4e\xc7"
++                        "\x56\x08\x63\xdc\x71\xe3\xe0\xc0"
++                        "\x3d\x6c\x23\x27\xda\x0e\x7f\x29"
++                        "\xfd\x8d\x3c\x1b\xf7\x7a\x63\xd9"
++                        "\x7e\x0f\xe9\xf6",
++              .clen   = 16 + 20,
++      }, { /* RFC 3686 Case 8 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x24"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33"
++                        "\xf6\xd6\x6d\x6b\xd5\x2d\x59\xbb"
++                        "\x07\x96\x36\x58\x79\xef\xf8\x86"
++                        "\xc6\x6d\xd5\x1a\x5b\x6a\x99\x74"
++                        "\x4b\x50\x59\x0c\x87\xa2\x38\x84"
++                        "\x00\xfa\xac\x24",
++              .klen   = 8 + 20 + 36,
++              .iv     = "\xc1\x58\x5e\xf1\x5a\x43\xd8\x75",
++              .assoc  = "\xc1\x58\x5e\xf1\x5a\x43\xd8\x75",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\xf0\x5e\x23\x1b\x38\x94\x61\x2c"
++                        "\x49\xee\x00\x0b\x80\x4e\xb2\xa9"
++                        "\xb8\x30\x6b\x50\x8f\x83\x9d\x6a"
++                        "\x55\x30\x83\x1d\x93\x44\xaf\x1c"
++                        "\xe7\xee\x22\xa4\xdd\xbf\x5d\x44"
++                        "\x3b\x43\x1c\x69\x55\x11\xd5\xad"
++                        "\x14\x5f\x44\xa6",
++              .clen   = 32 + 20,
++      },
++};
++
+ static const struct aead_testvec hmac_sha1_ecb_cipher_null_tv_temp[] = {
+       { /* Input data from RFC 2410 Case 1 */
+ #ifdef __LITTLE_ENDIAN
diff --git a/target/linux/generic/backport-6.12/916-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch b/target/linux/generic/backport-6.12/916-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch
new file mode 100644 (file)
index 0000000..6999c91
--- /dev/null
@@ -0,0 +1,278 @@
+From 3f4d32aff1d4e06501f909b1e5037ec8caa76d01 Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Thu, 5 Mar 2026 21:08:21 +0100
+Subject: [PATCH 2/5] crypto: testmgr - Add test vectors for
+ authenc(hmac(sha224),rfc3686(ctr(aes)))
+
+Test vectors were generated starting from existing RFC3686(CTR(AES)) test
+vectors and adding HMAC(SHA224) computed with software implementation.
+Then, the results were double-checked on Mediatek MT7986 (safexcel).
+Platform pass self-tests.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+---
+ crypto/testmgr.c |   6 +-
+ crypto/testmgr.h | 235 +++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 240 insertions(+), 1 deletion(-)
+
+--- a/crypto/testmgr.c
++++ b/crypto/testmgr.c
+@@ -4452,8 +4452,12 @@ static const struct alg_test_desc alg_te
+               }
+       }, {
+               .alg = "authenc(hmac(sha224),rfc3686(ctr(aes)))",
+-              .test = alg_test_null,
++              .generic_driver = "authenc(hmac-sha224-lib,rfc3686(ctr(aes-lib)))",
++              .test = alg_test_aead,
+               .fips_allowed = 1,
++              .suite = {
++                      .aead = __VECS(hmac_sha224_aes_ctr_rfc3686_tv_temp)
++              }
+       }, {
+               .alg = "authenc(hmac(sha256),cbc(aes))",
+               .test = alg_test_aead,
+--- a/crypto/testmgr.h
++++ b/crypto/testmgr.h
+@@ -17996,6 +17996,241 @@ static const struct aead_testvec hmac_sh
+       },
+ };
++static const struct aead_testvec hmac_sha224_aes_ctr_rfc3686_tv_temp[] = {
++      { /* RFC 3686 Case 1 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00"
++                        "\xae\x68\x52\xf8\x12\x10\x67\xcc"
++                        "\x4b\xf7\xa5\x76\x55\x77\xf3\x9e"
++                        "\x00\x00\x00\x30",
++              .klen   = 8 + 28 + 20,
++              .iv     = "\x00\x00\x00\x00\x00\x00\x00\x00",
++              .assoc  = "\x00\x00\x00\x00\x00\x00\x00\x00",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\xe4\x09\x5d\x4f\xb7\xa7\xb3\x79"
++                        "\x2d\x61\x75\xa3\x26\x13\x11\xb8"
++                        "\x36\xb4\x3b\x9c\x62\xed\xcf\x77"
++                        "\xdc\x19\x27\x3f\x92\x80\x52\xce"
++                        "\x8f\xad\x01\x0b\x79\xda\x04\x83"
++                        "\xcb\x45\x1a\x52",
++              .clen   = 16 + 28,
++      }, { /* RFC 3686 Case 2 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b"
++                        "\x7e\x24\x06\x78\x17\xfa\xe0\xd7"
++                        "\x43\xd6\xce\x1f\x32\x53\x91\x63"
++                        "\x00\x6c\xb6\xdb",
++              .klen   = 8 + 28 + 20,
++              .iv     = "\xc0\x54\x3b\x59\xda\x48\xd9\x0b",
++              .assoc  = "\xc0\x54\x3b\x59\xda\x48\xd9\x0b",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\x51\x04\xa1\x06\x16\x8a\x72\xd9"
++                        "\x79\x0d\x41\xee\x8e\xda\xd3\x88"
++                        "\xeb\x2e\x1e\xfc\x46\xda\x57\xc8"
++                        "\xfc\xe6\x30\xdf\x91\x41\xbe\x28"
++                        "\x7f\xe4\x8f\xa7\x06\x71\xe9\xe5"
++                        "\x16\x79\xef\xf9\x7e\x5c\x93\x4d"
++                        "\xa0\xf8\x3b\x3a\xaa\x1c\xc0\xd9"
++                        "\x6b\x48\x49\x01",
++              .clen   = 32 + 28,
++      }, { /* RFC 3686 Case 3 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd"
++                        "\x76\x91\xbe\x03\x5e\x50\x20\xa8"
++                        "\xac\x6e\x61\x85\x29\xf9\xa0\xdc"
++                        "\x00\xe0\x01\x7b",
++              .klen   = 8 + 28 + 20,
++              .iv     = "\x27\x77\x7f\x3f\x4a\x17\x86\xf0",
++              .assoc  = "\x27\x77\x7f\x3f\x4a\x17\x86\xf0",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
++                        "\x20\x21\x22\x23",
++              .plen   = 36,
++              .ctext  = "\xc1\xcf\x48\xa8\x9f\x2f\xfd\xd9"
++                        "\xcf\x46\x52\xe9\xef\xdb\x72\xd7"
++                        "\x45\x40\xa4\x2b\xde\x6d\x78\x36"
++                        "\xd5\x9a\x5c\xea\xae\xf3\x10\x53"
++                        "\x25\xb2\x07\x2f"
++                        "\xb0\x19\x45\xee\xa7\x31\xd9\xd0"
++                        "\x74\x6b\xb8\xb1\x67\x61\x2f\x8c"
++                        "\x68\xde\xe3\xc9\x3b\x0c\x72\xda"
++                        "\x48\xba\x1b\x51",
++              .clen   = 36 + 28,
++      }, { /* RFC 3686 Case 4 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x1c"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00"
++                        "\x16\xaf\x5b\x14\x5f\xc9\xf5\x79"
++                        "\xc1\x75\xf9\x3e\x3b\xfb\x0e\xed"
++                        "\x86\x3d\x06\xcc\xfd\xb7\x85\x15"
++                        "\x00\x00\x00\x48",
++              .klen   = 8 + 28 + 28,
++              .iv     = "\x36\x73\x3c\x14\x7d\x6d\x93\xcb",
++              .assoc  = "\x36\x73\x3c\x14\x7d\x6d\x93\xcb",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\x4b\x55\x38\x4f\xe2\x59\xc9\xc8"
++                        "\x4e\x79\x35\xa0\x03\xcb\xe9\x28"
++                        "\xfd\xf5\x35\x26\x50\x3d\xdf\x80"
++                        "\x6e\xbe\xba\x8d\x56\xf3\x03\xb7"
++                        "\x27\xb8\x13\xe8\x72\x8f\xc9\x52"
++                        "\x4a\xb7\xc3\x3a",
++              .clen   = 16 + 28,
++      }, { /* RFC 3686 Case 5 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x1c"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b"
++                        "\x7c\x5c\xb2\x40\x1b\x3d\xc3\x3c"
++                        "\x19\xe7\x34\x08\x19\xe0\xf6\x9c"
++                        "\x67\x8c\x3d\xb8\xe6\xf6\xa9\x1a"
++                        "\x00\x96\xb0\x3b",
++              .klen   = 8 + 28 + 28,
++              .iv     = "\x02\x0c\x6e\xad\xc2\xcb\x50\x0d",
++              .assoc  = "\x02\x0c\x6e\xad\xc2\xcb\x50\x0d",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\x45\x32\x43\xfc\x60\x9b\x23\x32"
++                        "\x7e\xdf\xaa\xfa\x71\x31\xcd\x9f"
++                        "\x84\x90\x70\x1c\x5a\xd4\xa7\x9c"
++                        "\xfc\x1f\xe0\xff\x42\xf4\xfb\x00"
++                        "\x72\x89\xa8\x04\xa5\xac\x8f\x29"
++                        "\xe6\xb8\x58\xe8\xcf\x6a\x91\x89"
++                        "\xd3\x66\x3b\xdc\xce\x43\x23\xb7"
++                        "\x6a\xdd\x9d\xbd",
++              .clen   = 32 + 28,
++      }, { /* RFC 3686 Case 7 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x24"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00"
++                        "\x77\x6b\xef\xf2\x85\x1d\xb0\x6f"
++                        "\x4c\x8a\x05\x42\xc8\x69\x6f\x6c"
++                        "\x6a\x81\xaf\x1e\xec\x96\xb4\xd3"
++                        "\x7f\xc1\xd6\x89\xe6\xc1\xc1\x04"
++                        "\x00\x00\x00\x60",
++              .klen   = 8 + 28 + 36,
++              .iv     = "\xdb\x56\x72\xc9\x7a\xa8\xf0\xb2",
++              .assoc  = "\xdb\x56\x72\xc9\x7a\xa8\xf0\xb2",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\x14\x5a\xd0\x1d\xbf\x82\x4e\xc7"
++                        "\x56\x08\x63\xdc\x71\xe3\xe0\xc0"
++                        "\xfe\xdf\x6f\x62\x8a\x79\xb5\x34"
++                        "\xd0\x6f\x32\xaf\x31\x50\x5b\x1f"
++                        "\xe0\x6d\x0b\xbc\x02\x25\xee\x74"
++                        "\x7a\xdf\x97\x3c",
++              .clen   = 16 + 28,
++      }, { /* RFC 3686 Case 8 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x24"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b"
++                        "\xf6\xd6\x6d\x6b\xd5\x2d\x59\xbb"
++                        "\x07\x96\x36\x58\x79\xef\xf8\x86"
++                        "\xc6\x6d\xd5\x1a\x5b\x6a\x99\x74"
++                        "\x4b\x50\x59\x0c\x87\xa2\x38\x84"
++                        "\x00\xfa\xac\x24",
++              .klen   = 8 + 28 + 36,
++              .iv     = "\xc1\x58\x5e\xf1\x5a\x43\xd8\x75",
++              .assoc  = "\xc1\x58\x5e\xf1\x5a\x43\xd8\x75",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\xf0\x5e\x23\x1b\x38\x94\x61\x2c"
++                        "\x49\xee\x00\x0b\x80\x4e\xb2\xa9"
++                        "\xb8\x30\x6b\x50\x8f\x83\x9d\x6a"
++                        "\x55\x30\x83\x1d\x93\x44\xaf\x1c"
++                        "\x19\x1e\x9c\x2c\x6d\x4e\x21\xda"
++                        "\x6c\x4d\x88\x90\xf8\x5f\xa5\x9d"
++                        "\xb4\xd4\x40\xad\xfa\x67\x3f\x0e"
++                        "\x11\x12\xd6\x10",
++              .clen   = 32 + 28,
++      },
++};
++
+ static const struct aead_testvec hmac_sha256_aes_cbc_tv_temp[] = {
+       { /* RFC 3602 Case 1 */
+ #ifdef __LITTLE_ENDIAN
diff --git a/target/linux/generic/backport-6.12/917-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch b/target/linux/generic/backport-6.12/917-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch
new file mode 100644 (file)
index 0000000..bd09c4c
--- /dev/null
@@ -0,0 +1,278 @@
+From 65af3a0bff7264ab84f9e1f2099c658601ca1b66 Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Thu, 5 Mar 2026 21:08:22 +0100
+Subject: [PATCH 3/5] crypto: testmgr - Add test vectors for
+ authenc(hmac(sha256),rfc3686(ctr(aes)))
+
+Test vectors were generated starting from existing RFC3686(CTR(AES)) test
+vectors and adding HMAC(SHA256) computed with software implementation.
+Then, the results were double-checked on Mediatek MT7986 (safexcel).
+Platform pass self-tests.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+---
+ crypto/testmgr.c |   6 +-
+ crypto/testmgr.h | 235 +++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 240 insertions(+), 1 deletion(-)
+
+--- a/crypto/testmgr.c
++++ b/crypto/testmgr.c
+@@ -4483,8 +4483,12 @@ static const struct alg_test_desc alg_te
+               .fips_allowed = 1,
+       }, {
+               .alg = "authenc(hmac(sha256),rfc3686(ctr(aes)))",
+-              .test = alg_test_null,
++              .generic_driver = "authenc(hmac-sha256-lib,rfc3686(ctr(aes-lib)))",
++              .test = alg_test_aead,
+               .fips_allowed = 1,
++              .suite = {
++                      .aead = __VECS(hmac_sha256_aes_ctr_rfc3686_tv_temp)
++              }
+       }, {
+               .alg = "authenc(hmac(sha384),cbc(aes))",
+               .generic_driver = "authenc(hmac-sha384-lib,cbc(aes-generic))",
+--- a/crypto/testmgr.h
++++ b/crypto/testmgr.h
+@@ -18514,6 +18514,241 @@ static const struct aead_testvec hmac_sh
+       },
+ };
++static const struct aead_testvec hmac_sha256_aes_ctr_rfc3686_tv_temp[] = {
++      { /* RFC 3686 Case 1 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\xae\x68\x52\xf8\x12\x10\x67\xcc"
++                        "\x4b\xf7\xa5\x76\x55\x77\xf3\x9e"
++                        "\x00\x00\x00\x30",
++              .klen   = 8 + 32 + 20,
++              .iv     = "\x00\x00\x00\x00\x00\x00\x00\x00",
++              .assoc  = "\x00\x00\x00\x00\x00\x00\x00\x00",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\xe4\x09\x5d\x4f\xb7\xa7\xb3\x79"
++                        "\x2d\x61\x75\xa3\x26\x13\x11\xb8"
++                        "\x9b\xa2\x34\x62\xe5\xb3\xe8\x2d"
++                        "\x6d\xdb\x93\x64\xa5\x08\x2e\x77"
++                        "\x72\x1f\x21\x94\xc7\xbe\x14\xa6"
++                        "\xcd\xea\x96\xa1\x29\x8f\x30\xc3",
++              .clen   = 16 + 32,
++      }, { /* RFC 3686 Case 2 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
++                        "\x7e\x24\x06\x78\x17\xfa\xe0\xd7"
++                        "\x43\xd6\xce\x1f\x32\x53\x91\x63"
++                        "\x00\x6c\xb6\xdb",
++              .klen   = 8 + 32 + 20,
++              .iv     = "\xc0\x54\x3b\x59\xda\x48\xd9\x0b",
++              .assoc  = "\xc0\x54\x3b\x59\xda\x48\xd9\x0b",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\x51\x04\xa1\x06\x16\x8a\x72\xd9"
++                        "\x79\x0d\x41\xee\x8e\xda\xd3\x88"
++                        "\xeb\x2e\x1e\xfc\x46\xda\x57\xc8"
++                        "\xfc\xe6\x30\xdf\x91\x41\xbe\x28"
++                        "\x22\xf7\x95\xa8\xbb\xcd\x19\xf4"
++                        "\x58\x16\x54\x28\x2b\xf4\x52\xe7"
++                        "\x5c\x6c\xe1\x44\x0b\xd5\x10\x6e"
++                        "\xe1\xf7\x04\xc4\x2c\xab\x93\xdd",
++              .clen   = 32 + 32,
++      }, { /* RFC 3686 Case 3 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd\xee\xff\x11\x22"
++                        "\x76\x91\xbe\x03\x5e\x50\x20\xa8"
++                        "\xac\x6e\x61\x85\x29\xf9\xa0\xdc"
++                        "\x00\xe0\x01\x7b",
++              .klen   = 8 + 32 + 20,
++              .iv     = "\x27\x77\x7f\x3f\x4a\x17\x86\xf0",
++              .assoc  = "\x27\x77\x7f\x3f\x4a\x17\x86\xf0",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
++                        "\x20\x21\x22\x23",
++              .plen   = 36,
++              .ctext  = "\xc1\xcf\x48\xa8\x9f\x2f\xfd\xd9"
++                        "\xcf\x46\x52\xe9\xef\xdb\x72\xd7"
++                        "\x45\x40\xa4\x2b\xde\x6d\x78\x36"
++                        "\xd5\x9a\x5c\xea\xae\xf3\x10\x53"
++                        "\x25\xb2\x07\x2f"
++                        "\x1d\x05\x5f\x77\x3b\x4f\x5c\x21"
++                        "\x29\xea\xf1\xa8\x71\x49\x7b\x0b"
++                        "\x66\x0d\xff\x18\x81\x63\xfc\xc3"
++                        "\x91\xb6\x38\xc8\xcd\x2d\x39\x83",
++              .clen   = 36 + 32,
++      }, { /* RFC 3686 Case 4 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x1c"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x16\xaf\x5b\x14\x5f\xc9\xf5\x79"
++                        "\xc1\x75\xf9\x3e\x3b\xfb\x0e\xed"
++                        "\x86\x3d\x06\xcc\xfd\xb7\x85\x15"
++                        "\x00\x00\x00\x48",
++              .klen   = 8 + 32 + 28,
++              .iv     = "\x36\x73\x3c\x14\x7d\x6d\x93\xcb",
++              .assoc  = "\x36\x73\x3c\x14\x7d\x6d\x93\xcb",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\x4b\x55\x38\x4f\xe2\x59\xc9\xc8"
++                        "\x4e\x79\x35\xa0\x03\xcb\xe9\x28"
++                        "\x8d\x03\x77\xb2\x1c\xc9\xe0\xac"
++                        "\xde\x69\xbe\x8a\xef\x5b\x13\x74"
++                        "\x1d\x39\xbc\xdc\x95\xa4\xbf\xc3"
++                        "\xd5\xc6\xd1\xda\xda\x3b\xca\x78",
++              .clen   = 16 + 32,
++      }, { /* RFC 3686 Case 5 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x1c"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
++                        "\x7c\x5c\xb2\x40\x1b\x3d\xc3\x3c"
++                        "\x19\xe7\x34\x08\x19\xe0\xf6\x9c"
++                        "\x67\x8c\x3d\xb8\xe6\xf6\xa9\x1a"
++                        "\x00\x96\xb0\x3b",
++              .klen   = 8 + 32 + 28,
++              .iv     = "\x02\x0c\x6e\xad\xc2\xcb\x50\x0d",
++              .assoc  = "\x02\x0c\x6e\xad\xc2\xcb\x50\x0d",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\x45\x32\x43\xfc\x60\x9b\x23\x32"
++                        "\x7e\xdf\xaa\xfa\x71\x31\xcd\x9f"
++                        "\x84\x90\x70\x1c\x5a\xd4\xa7\x9c"
++                        "\xfc\x1f\xe0\xff\x42\xf4\xfb\x00"
++                        "\x34\x06\x2b\x3d\xf1\xa8\x3d\xf1"
++                        "\xa6\x5e\x5c\x1a\xdb\x0c\xb5\x1e"
++                        "\x8f\xdb\xf4\xca\x7d\x09\x5e\x81"
++                        "\xdb\x32\x07\x4a\x1d\x1c\x6d\x83",
++              .clen   = 32 + 32,
++      }, { /* RFC 3686 Case 7 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x24"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x77\x6b\xef\xf2\x85\x1d\xb0\x6f"
++                        "\x4c\x8a\x05\x42\xc8\x69\x6f\x6c"
++                        "\x6a\x81\xaf\x1e\xec\x96\xb4\xd3"
++                        "\x7f\xc1\xd6\x89\xe6\xc1\xc1\x04"
++                        "\x00\x00\x00\x60",
++              .klen   = 8 + 32 + 36,
++              .iv     = "\xdb\x56\x72\xc9\x7a\xa8\xf0\xb2",
++              .assoc  = "\xdb\x56\x72\xc9\x7a\xa8\xf0\xb2",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\x14\x5a\xd0\x1d\xbf\x82\x4e\xc7"
++                        "\x56\x08\x63\xdc\x71\xe3\xe0\xc0"
++                        "\xc3\xb4\x5f\xb0\xbf\xf5\x1b\xff"
++                        "\x7c\xf1\x79\x00\x63\x50\xdd\x77"
++                        "\xc0\x4a\xba\xcd\xdc\x47\x05\x2a"
++                        "\x5d\x85\x2d\x83\x44\xca\x79\x2c",
++              .clen   = 16 + 32,
++      }, { /* RFC 3686 Case 8 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x24"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
++                        "\xf6\xd6\x6d\x6b\xd5\x2d\x59\xbb"
++                        "\x07\x96\x36\x58\x79\xef\xf8\x86"
++                        "\xc6\x6d\xd5\x1a\x5b\x6a\x99\x74"
++                        "\x4b\x50\x59\x0c\x87\xa2\x38\x84"
++                        "\x00\xfa\xac\x24",
++              .klen   = 8 + 32 + 36,
++              .iv     = "\xc1\x58\x5e\xf1\x5a\x43\xd8\x75",
++              .assoc  = "\xc1\x58\x5e\xf1\x5a\x43\xd8\x75",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\xf0\x5e\x23\x1b\x38\x94\x61\x2c"
++                        "\x49\xee\x00\x0b\x80\x4e\xb2\xa9"
++                        "\xb8\x30\x6b\x50\x8f\x83\x9d\x6a"
++                        "\x55\x30\x83\x1d\x93\x44\xaf\x1c"
++                        "\xc8\x59\x5d\xe1\xba\xac\x13\x82"
++                        "\xfd\x21\x7c\x8c\x23\x31\x04\x02"
++                        "\x9e\x69\x5b\x57\xa8\x13\xe7\x21"
++                        "\x60\x0c\x24\xc2\x80\x4a\x93\x6e",
++              .clen   = 32 + 32,
++      },
++};
++
+ static const struct aead_testvec hmac_sha384_aes_cbc_tv_temp[] = {
+       { /* RFC 3602 Case 1 */
+ #ifdef __LITTLE_ENDIAN
diff --git a/target/linux/generic/backport-6.12/918-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch b/target/linux/generic/backport-6.12/918-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch
new file mode 100644 (file)
index 0000000..5283fcc
--- /dev/null
@@ -0,0 +1,306 @@
+From 07cbbf6f38e6a91a7fecab3d6f86485425748d3e Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Thu, 5 Mar 2026 21:08:23 +0100
+Subject: [PATCH 4/5] crypto: testmgr - Add test vectors for
+ authenc(hmac(sha384),rfc3686(ctr(aes)))
+
+Test vectors were generated starting from existing RFC3686(CTR(AES)) test
+vectors and adding HMAC(SHA384) computed with software implementation.
+Then, the results were double-checked on Mediatek MT7986 (safexcel).
+Platform pass self-tests.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+---
+ crypto/testmgr.c |   6 +-
+ crypto/testmgr.h | 263 +++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 268 insertions(+), 1 deletion(-)
+
+--- a/crypto/testmgr.c
++++ b/crypto/testmgr.c
+@@ -4514,8 +4514,12 @@ static const struct alg_test_desc alg_te
+               .fips_allowed = 1,
+       }, {
+               .alg = "authenc(hmac(sha384),rfc3686(ctr(aes)))",
+-              .test = alg_test_null,
++              .generic_driver = "authenc(hmac-sha384-lib,rfc3686(ctr(aes-lib)))",
++              .test = alg_test_aead,
+               .fips_allowed = 1,
++              .suite = {
++                      .aead = __VECS(hmac_sha384_aes_ctr_rfc3686_tv_temp)
++              }
+       }, {
+               .alg = "authenc(hmac(sha512),cbc(aes))",
+               .fips_allowed = 1,
+--- a/crypto/testmgr.h
++++ b/crypto/testmgr.h
+@@ -19060,6 +19060,269 @@ static const struct aead_testvec hmac_sh
+       },
+ };
++static const struct aead_testvec hmac_sha384_aes_ctr_rfc3686_tv_temp[] = {
++      { /* RFC 3686 Case 1 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\xae\x68\x52\xf8\x12\x10\x67\xcc"
++                        "\x4b\xf7\xa5\x76\x55\x77\xf3\x9e"
++                        "\x00\x00\x00\x30",
++              .klen   = 8 + 48 + 20,
++              .iv     = "\x00\x00\x00\x00\x00\x00\x00\x00",
++              .assoc  = "\x00\x00\x00\x00\x00\x00\x00\x00",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\xe4\x09\x5d\x4f\xb7\xa7\xb3\x79"
++                        "\x2d\x61\x75\xa3\x26\x13\x11\xb8"
++                        "\x45\x51\x59\x72\x16\xd3\xc6\x15"
++                        "\x25\x1e\xe8\x92\x2e\x47\x52\xcc"
++                        "\x91\x9c\x24\xef\x11\xb2\x53\x00"
++                        "\x10\x20\x43\x06\xe2\x35\x88\x9e"
++                        "\x18\x32\x5a\x79\x7d\x73\x7e\x89"
++                        "\xfe\xa1\xda\xa4\x86\xc4\x2a\x04",
++              .clen   = 16 + 48,
++      }, { /* RFC 3686 Case 2 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
++                        "\x40\x41\x42\x43\x44\x45\x46\x47"
++                        "\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
++                        "\x7e\x24\x06\x78\x17\xfa\xe0\xd7"
++                        "\x43\xd6\xce\x1f\x32\x53\x91\x63"
++                        "\x00\x6c\xb6\xdb",
++              .klen   = 8 + 48 + 20,
++              .iv     = "\xc0\x54\x3b\x59\xda\x48\xd9\x0b",
++              .assoc  = "\xc0\x54\x3b\x59\xda\x48\xd9\x0b",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\x51\x04\xa1\x06\x16\x8a\x72\xd9"
++                        "\x79\x0d\x41\xee\x8e\xda\xd3\x88"
++                        "\xeb\x2e\x1e\xfc\x46\xda\x57\xc8"
++                        "\xfc\xe6\x30\xdf\x91\x41\xbe\x28"
++                        "\x83\x65\x32\x1e\x6b\x60\xe6\x4a"
++                        "\xe2\xab\x52\x2b\xa6\x70\x3a\xfa"
++                        "\xd2\xec\x83\xe4\x31\x0c\x28\x40"
++                        "\x9b\x5e\x18\xa4\xdc\x48\xb8\x56"
++                        "\x33\xab\x7f\x2b\xaf\xe4\x3a\xe3"
++                        "\x8a\x61\xf6\x22\xb4\x6b\xfe\x7d",
++              .clen   = 32 + 48,
++      }, { /* RFC 3686 Case 3 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd\xee\xff\x11\x22"
++                        "\x33\x44\x55\x66\x77\x88\x99\xaa"
++                        "\xbb\xcc\xdd\xee\xff\x11\x22\x33"
++                        "\x76\x91\xbe\x03\x5e\x50\x20\xa8"
++                        "\xac\x6e\x61\x85\x29\xf9\xa0\xdc"
++                        "\x00\xe0\x01\x7b",
++              .klen   = 8 + 48 + 20,
++              .iv     = "\x27\x77\x7f\x3f\x4a\x17\x86\xf0",
++              .assoc  = "\x27\x77\x7f\x3f\x4a\x17\x86\xf0",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
++                        "\x20\x21\x22\x23",
++              .plen   = 36,
++              .ctext  = "\xc1\xcf\x48\xa8\x9f\x2f\xfd\xd9"
++                        "\xcf\x46\x52\xe9\xef\xdb\x72\xd7"
++                        "\x45\x40\xa4\x2b\xde\x6d\x78\x36"
++                        "\xd5\x9a\x5c\xea\xae\xf3\x10\x53"
++                        "\x25\xb2\x07\x2f"
++                        "\x4a\xaa\xad\x3b\x3b\xb6\x9a\xba"
++                        "\xa1\x7b\xc6\xce\x96\xc3\xff\x67"
++                        "\xf3\x0c\x33\x57\xf0\x51\x24\x08"
++                        "\xed\x4f\x6a\x9c\x22\x42\xbd\x18"
++                        "\x97\x74\x68\x36\x00\xf1\x69\x3a"
++                        "\x18\x77\x40\xf0\x56\xba\xba\xe0",
++              .clen   = 36 + 48,
++      }, { /* RFC 3686 Case 4 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x1c"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x16\xaf\x5b\x14\x5f\xc9\xf5\x79"
++                        "\xc1\x75\xf9\x3e\x3b\xfb\x0e\xed"
++                        "\x86\x3d\x06\xcc\xfd\xb7\x85\x15"
++                        "\x00\x00\x00\x48",
++              .klen   = 8 + 48 + 28,
++              .iv     = "\x36\x73\x3c\x14\x7d\x6d\x93\xcb",
++              .assoc  = "\x36\x73\x3c\x14\x7d\x6d\x93\xcb",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\x4b\x55\x38\x4f\xe2\x59\xc9\xc8"
++                        "\x4e\x79\x35\xa0\x03\xcb\xe9\x28"
++                        "\x36\xd6\xc7\x55\xac\xb6\x0b\x14"
++                        "\x95\x71\xf9\x86\x30\xe3\x96\xc3"
++                        "\x76\x85\x6d\xa5\x06\xed\x6f\x34"
++                        "\xcc\x1f\xcc\x2d\x88\x06\xb0\x1d"
++                        "\xbe\xd9\xa2\xd3\x64\xf1\x33\x03"
++                        "\x13\x50\x8f\xae\x61\x2d\x82\xb8",
++              .clen   = 16 + 48,
++      }, { /* RFC 3686 Case 5 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x1c"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
++                        "\x40\x41\x42\x43\x44\x45\x46\x47"
++                        "\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
++                        "\x7c\x5c\xb2\x40\x1b\x3d\xc3\x3c"
++                        "\x19\xe7\x34\x08\x19\xe0\xf6\x9c"
++                        "\x67\x8c\x3d\xb8\xe6\xf6\xa9\x1a"
++                        "\x00\x96\xb0\x3b",
++              .klen   = 8 + 48 + 28,
++              .iv     = "\x02\x0c\x6e\xad\xc2\xcb\x50\x0d",
++              .assoc  = "\x02\x0c\x6e\xad\xc2\xcb\x50\x0d",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\x45\x32\x43\xfc\x60\x9b\x23\x32"
++                        "\x7e\xdf\xaa\xfa\x71\x31\xcd\x9f"
++                        "\x84\x90\x70\x1c\x5a\xd4\xa7\x9c"
++                        "\xfc\x1f\xe0\xff\x42\xf4\xfb\x00"
++                        "\x80\x12\x67\x22\xf2\x4d\x9b\xbf"
++                        "\xdc\x38\xd3\xaa\x12\xc0\x58\x1a"
++                        "\x9a\x62\x6e\x42\x3d\x44\x63\xdd"
++                        "\xee\x7e\xe3\xa3\xdf\x2a\x65\x05"
++                        "\xd0\xc1\xd2\x54\x55\x35\x5c\xc7"
++                        "\xb0\xb5\xb1\x36\xe0\x0b\xaf\x72",
++              .clen   = 32 + 48,
++      }, { /* RFC 3686 Case 7 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x24"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x77\x6b\xef\xf2\x85\x1d\xb0\x6f"
++                        "\x4c\x8a\x05\x42\xc8\x69\x6f\x6c"
++                        "\x6a\x81\xaf\x1e\xec\x96\xb4\xd3"
++                        "\x7f\xc1\xd6\x89\xe6\xc1\xc1\x04"
++                        "\x00\x00\x00\x60",
++              .klen   = 8 + 48 + 36,
++              .iv     = "\xdb\x56\x72\xc9\x7a\xa8\xf0\xb2",
++              .assoc  = "\xdb\x56\x72\xc9\x7a\xa8\xf0\xb2",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\x14\x5a\xd0\x1d\xbf\x82\x4e\xc7"
++                        "\x56\x08\x63\xdc\x71\xe3\xe0\xc0"
++                        "\xb1\x7b\xb1\xec\xca\x94\x55\xc4"
++                        "\x3f\x2b\xb1\x70\x04\x91\xf5\x9d"
++                        "\x1a\xc0\xe1\x2a\x93\x5f\x96\x2a"
++                        "\x12\x85\x38\x36\xe1\xb2\xe9\xf0"
++                        "\xf2\x6e\x5d\x81\xcc\x49\x07\x9c"
++                        "\x5b\x88\xc8\xcc\xc4\x21\x4f\x32",
++              .clen   = 16 + 48,
++      }, { /* RFC 3686 Case 8 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x24"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
++                        "\x40\x41\x42\x43\x44\x45\x46\x47"
++                        "\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
++                        "\xf6\xd6\x6d\x6b\xd5\x2d\x59\xbb"
++                        "\x07\x96\x36\x58\x79\xef\xf8\x86"
++                        "\xc6\x6d\xd5\x1a\x5b\x6a\x99\x74"
++                        "\x4b\x50\x59\x0c\x87\xa2\x38\x84"
++                        "\x00\xfa\xac\x24",
++              .klen   = 8 + 48 + 36,
++              .iv     = "\xc1\x58\x5e\xf1\x5a\x43\xd8\x75",
++              .assoc  = "\xc1\x58\x5e\xf1\x5a\x43\xd8\x75",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\xf0\x5e\x23\x1b\x38\x94\x61\x2c"
++                        "\x49\xee\x00\x0b\x80\x4e\xb2\xa9"
++                        "\xb8\x30\x6b\x50\x8f\x83\x9d\x6a"
++                        "\x55\x30\x83\x1d\x93\x44\xaf\x1c"
++                        "\xd6\x96\xbb\x12\x39\xc4\x4d\xe2"
++                        "\x4c\x02\xe7\x1f\xdc\xb2\xb1\x57"
++                        "\x38\x0d\xdd\x13\xb3\x89\x57\x9e"
++                        "\x1f\xb5\x48\x32\xc4\xd3\x9d\x1f"
++                        "\x68\xab\x8d\xc6\xa8\x05\x3a\xc2"
++                        "\x87\xaf\x23\xb3\xe4\x1b\xde\xb3",
++              .clen   = 32 + 48,
++      },
++};
++
+ static const struct aead_testvec hmac_sha512_aes_cbc_tv_temp[] = {
+       { /* RFC 3602 Case 1 */
+ #ifdef __LITTLE_ENDIAN
diff --git a/target/linux/generic/backport-6.12/919-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch b/target/linux/generic/backport-6.12/919-v7.1-crypto-testmgr-Add-test-vectors-for-authenc-hmac-sha.patch
new file mode 100644 (file)
index 0000000..87949eb
--- /dev/null
@@ -0,0 +1,334 @@
+From 07f1bc80c478c7198065d3896ed3685428314a9c Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Thu, 5 Mar 2026 21:08:24 +0100
+Subject: [PATCH 5/5] crypto: testmgr - Add test vectors for
+ authenc(hmac(sha512),rfc3686(ctr(aes)))
+
+Test vectors were generated starting from existing RFC3686(CTR(AES)) test
+vectors and adding HMAC(SHA512) computed with software implementation.
+Then, the results were double-checked on Mediatek MT7986 (safexcel).
+Platform pass self-tests.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+---
+ crypto/testmgr.c |   6 +-
+ crypto/testmgr.h | 291 +++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 296 insertions(+), 1 deletion(-)
+
+--- a/crypto/testmgr.c
++++ b/crypto/testmgr.c
+@@ -4545,8 +4545,12 @@ static const struct alg_test_desc alg_te
+               .fips_allowed = 1,
+       }, {
+               .alg = "authenc(hmac(sha512),rfc3686(ctr(aes)))",
+-              .test = alg_test_null,
++              .generic_driver = "authenc(hmac-sha512-lib,rfc3686(ctr(aes-lib)))",
++              .test = alg_test_aead,
+               .fips_allowed = 1,
++              .suite = {
++                      .aead = __VECS(hmac_sha512_aes_ctr_rfc3686_tv_temp)
++              }
+       }, {
+               .alg = "blake2b-160",
+               .test = alg_test_hash,
+--- a/crypto/testmgr.h
++++ b/crypto/testmgr.h
+@@ -19719,6 +19719,297 @@ static const struct aead_testvec hmac_md
+       },
+ };
++static const struct aead_testvec hmac_sha512_aes_ctr_rfc3686_tv_temp[] = {
++      { /* RFC 3686 Case 1 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\xae\x68\x52\xf8\x12\x10\x67\xcc"
++                        "\x4b\xf7\xa5\x76\x55\x77\xf3\x9e"
++                        "\x00\x00\x00\x30",
++              .klen   = 8 + 64 + 20,
++              .iv     = "\x00\x00\x00\x00\x00\x00\x00\x00",
++              .assoc  = "\x00\x00\x00\x00\x00\x00\x00\x00",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\xe4\x09\x5d\x4f\xb7\xa7\xb3\x79"
++                        "\x2d\x61\x75\xa3\x26\x13\x11\xb8"
++                        "\xa4\x45\x3a\x44\x9c\xe5\x1c\xd9"
++                        "\x10\x43\x51\x2e\x76\x5e\xf8\x9d"
++                        "\x03\x12\x1a\x31\x00\x33\x10\xb4"
++                        "\x94\x4b\x70\x84\x6c\xda\xb1\x46"
++                        "\x24\xb6\x3b\x2a\xec\xd5\x67\xb8"
++                        "\x65\xa2\xbd\xac\x18\xe2\xf8\x55"
++                        "\xc6\x91\xb0\x92\x84\x2d\x74\x44"
++                        "\xa7\xee\xc3\x44\xa0\x07\x0e\x62",
++              .clen   = 16 + 64,
++      }, { /* RFC 3686 Case 2 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
++                        "\x40\x41\x42\x43\x44\x45\x46\x47"
++                        "\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
++                        "\x50\x51\x52\x53\x54\x55\x56\x57"
++                        "\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
++                        "\x7e\x24\x06\x78\x17\xfa\xe0\xd7"
++                        "\x43\xd6\xce\x1f\x32\x53\x91\x63"
++                        "\x00\x6c\xb6\xdb",
++              .klen   = 8 + 64 + 20,
++              .iv     = "\xc0\x54\x3b\x59\xda\x48\xd9\x0b",
++              .assoc  = "\xc0\x54\x3b\x59\xda\x48\xd9\x0b",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\x51\x04\xa1\x06\x16\x8a\x72\xd9"
++                        "\x79\x0d\x41\xee\x8e\xda\xd3\x88"
++                        "\xeb\x2e\x1e\xfc\x46\xda\x57\xc8"
++                        "\xfc\xe6\x30\xdf\x91\x41\xbe\x28"
++                        "\xec\x67\x0d\xb3\xbd\x98\x13\x01"
++                        "\x2b\x04\x9b\xe6\x06\x67\x3c\x76"
++                        "\xcd\x41\xb7\xcc\x70\x6c\x7f\xc8"
++                        "\x67\xbd\x22\x39\xb2\xaa\xe8\x88"
++                        "\xe0\x4f\x81\x52\xdf\xc9\xc3\xd6"
++                        "\x44\xf4\x66\x33\x87\x64\x61\x02"
++                        "\x02\xa2\x64\x15\x2b\xe9\x0b\x3d"
++                        "\x4c\xea\xa1\xa5\xa7\xc9\xd3\x1b",
++              .clen   = 32 + 64,
++      }, { /* RFC 3686 Case 3 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x14"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x22\x33\x44\x55\x66\x77\x88\x99"
++                        "\xaa\xbb\xcc\xdd\xee\xff\x11\x22"
++                        "\x33\x44\x55\x66\x77\x88\x99\xaa"
++                        "\xbb\xcc\xdd\xee\xff\x11\x22\x33"
++                        "\x44\x55\x66\x77\x88\x99\xaa\xbb"
++                        "\xcc\xdd\xee\xff\x11\x22\x33\x44"
++                        "\x76\x91\xbe\x03\x5e\x50\x20\xa8"
++                        "\xac\x6e\x61\x85\x29\xf9\xa0\xdc"
++                        "\x00\xe0\x01\x7b",
++              .klen   = 8 + 64 + 20,
++              .iv     = "\x27\x77\x7f\x3f\x4a\x17\x86\xf0",
++              .assoc  = "\x27\x77\x7f\x3f\x4a\x17\x86\xf0",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
++                        "\x20\x21\x22\x23",
++              .plen   = 36,
++              .ctext  = "\xc1\xcf\x48\xa8\x9f\x2f\xfd\xd9"
++                        "\xcf\x46\x52\xe9\xef\xdb\x72\xd7"
++                        "\x45\x40\xa4\x2b\xde\x6d\x78\x36"
++                        "\xd5\x9a\x5c\xea\xae\xf3\x10\x53"
++                        "\x25\xb2\x07\x2f"
++                        "\x6f\x90\xb6\xa3\x35\x43\x59\xff"
++                        "\x1e\x32\xd6\xfe\xfa\x33\xf9\xf0"
++                        "\x31\x2f\x03\x2d\x88\x1d\xab\xbf"
++                        "\x0e\x19\x16\xd9\xf3\x98\x3e\xdd"
++                        "\x0c\xec\xfe\xe8\x89\x13\x91\x15"
++                        "\xf6\x61\x65\x5c\x1b\x7d\xde\xc0"
++                        "\xe4\xba\x6d\x27\xe2\x89\x23\x24"
++                        "\x15\x82\x37\x3d\x48\xd3\xc9\x32",
++              .clen   = 36 + 64,
++      }, { /* RFC 3686 Case 4 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x1c"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x16\xaf\x5b\x14\x5f\xc9\xf5\x79"
++                        "\xc1\x75\xf9\x3e\x3b\xfb\x0e\xed"
++                        "\x86\x3d\x06\xcc\xfd\xb7\x85\x15"
++                        "\x00\x00\x00\x48",
++              .klen   = 8 + 64 + 28,
++              .iv     = "\x36\x73\x3c\x14\x7d\x6d\x93\xcb",
++              .assoc  = "\x36\x73\x3c\x14\x7d\x6d\x93\xcb",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\x4b\x55\x38\x4f\xe2\x59\xc9\xc8"
++                        "\x4e\x79\x35\xa0\x03\xcb\xe9\x28"
++                        "\x25\xea\xdc\xad\x52\xb8\x0f\x70"
++                        "\xe7\x39\x83\x80\x10\x3f\x18\xc4"
++                        "\xf8\x59\x14\x25\x5f\xba\x20\x87"
++                        "\x0b\x04\x5e\xf7\xde\x41\x39\xff"
++                        "\xa2\xee\x84\x3f\x9d\x38\xfd\x17"
++                        "\xc0\x66\x5e\x74\x39\xe3\xd3\xd7"
++                        "\x3d\xbc\xe3\x99\x2f\xe7\xef\x37"
++                        "\x61\x03\xf3\x9e\x01\xaf\xba\x9d",
++              .clen   = 16 + 64,
++      }, { /* RFC 3686 Case 5 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x1c"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
++                        "\x40\x41\x42\x43\x44\x45\x46\x47"
++                        "\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
++                        "\x50\x51\x52\x53\x54\x55\x56\x57"
++                        "\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
++                        "\x7c\x5c\xb2\x40\x1b\x3d\xc3\x3c"
++                        "\x19\xe7\x34\x08\x19\xe0\xf6\x9c"
++                        "\x67\x8c\x3d\xb8\xe6\xf6\xa9\x1a"
++                        "\x00\x96\xb0\x3b",
++              .klen   = 8 + 64 + 28,
++              .iv     = "\x02\x0c\x6e\xad\xc2\xcb\x50\x0d",
++              .assoc  = "\x02\x0c\x6e\xad\xc2\xcb\x50\x0d",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\x45\x32\x43\xfc\x60\x9b\x23\x32"
++                        "\x7e\xdf\xaa\xfa\x71\x31\xcd\x9f"
++                        "\x84\x90\x70\x1c\x5a\xd4\xa7\x9c"
++                        "\xfc\x1f\xe0\xff\x42\xf4\xfb\x00"
++                        "\x51\xa3\xe6\x1d\x23\x7d\xd1\x18"
++                        "\x55\x9c\x1c\x92\x2b\xc2\xcd\xfe"
++                        "\x8a\xa8\xa5\x96\x65\x2e\x9d\xdb"
++                        "\x06\xd2\x1c\x57\x2b\x76\xb5\x9c"
++                        "\xd4\x3e\x8b\x61\x54\x2d\x08\xe5"
++                        "\xb2\xf8\x88\x20\x0c\xad\xe8\x85"
++                        "\x61\x8e\x5c\xa4\x96\x2c\xe2\x7d"
++                        "\x4f\xb6\x1d\xb2\x8c\xd7\xe3\x38",
++              .clen   = 32 + 64,
++      }, { /* RFC 3686 Case 7 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x24"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x77\x6b\xef\xf2\x85\x1d\xb0\x6f"
++                        "\x4c\x8a\x05\x42\xc8\x69\x6f\x6c"
++                        "\x6a\x81\xaf\x1e\xec\x96\xb4\xd3"
++                        "\x7f\xc1\xd6\x89\xe6\xc1\xc1\x04"
++                        "\x00\x00\x00\x60",
++              .klen   = 8 + 64 + 36,
++              .iv     = "\xdb\x56\x72\xc9\x7a\xa8\xf0\xb2",
++              .assoc  = "\xdb\x56\x72\xc9\x7a\xa8\xf0\xb2",
++              .alen   = 8,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\x14\x5a\xd0\x1d\xbf\x82\x4e\xc7"
++                        "\x56\x08\x63\xdc\x71\xe3\xe0\xc0"
++                        "\x6b\x68\x0b\x99\x9a\x4d\xc8\xb9"
++                        "\x35\xea\xcd\x56\x3f\x40\xa2\xb6"
++                        "\x68\xda\x59\xd8\xa0\x89\xcd\x52"
++                        "\xb1\x6e\xed\xc1\x42\x10\xa5\x0f"
++                        "\x88\x0b\x80\xce\xc4\x67\xf0\x45"
++                        "\x5d\xb2\x9e\xde\x1c\x79\x52\x0d"
++                        "\xff\x75\x36\xd5\x0f\x52\x8e\xe5"
++                        "\x31\x85\xcf\x1d\x31\xf8\x62\x67",
++              .clen   = 16 + 64,
++      }, { /* RFC 3686 Case 8 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x24"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
++                        "\x40\x41\x42\x43\x44\x45\x46\x47"
++                        "\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
++                        "\x50\x51\x52\x53\x54\x55\x56\x57"
++                        "\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
++                        "\xf6\xd6\x6d\x6b\xd5\x2d\x59\xbb"
++                        "\x07\x96\x36\x58\x79\xef\xf8\x86"
++                        "\xc6\x6d\xd5\x1a\x5b\x6a\x99\x74"
++                        "\x4b\x50\x59\x0c\x87\xa2\x38\x84"
++                        "\x00\xfa\xac\x24",
++              .klen   = 8 + 64 + 36,
++              .iv     = "\xc1\x58\x5e\xf1\x5a\x43\xd8\x75",
++              .assoc  = "\xc1\x58\x5e\xf1\x5a\x43\xd8\x75",
++              .alen   = 8,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\xf0\x5e\x23\x1b\x38\x94\x61\x2c"
++                        "\x49\xee\x00\x0b\x80\x4e\xb2\xa9"
++                        "\xb8\x30\x6b\x50\x8f\x83\x9d\x6a"
++                        "\x55\x30\x83\x1d\x93\x44\xaf\x1c"
++                        "\x9a\xac\x38\xbd\xf3\xcf\xd5\xd0"
++                        "\x09\x07\xa6\xe1\x7f\xd6\x79\x98"
++                        "\x4e\x90\x0e\xc0\x3d\xa0\xf2\x12"
++                        "\x52\x79\x9c\x17\xff\xb9\xb8\xe3"
++                        "\x2f\x31\xcb\xbd\x63\x70\x72\x7b"
++                        "\x4e\x1e\xd1\xde\xb5\x6b\x7d\x54"
++                        "\x68\x56\xdd\xe5\x53\xee\x29\xd2"
++                        "\x85\xa1\x73\x61\x00\xa9\x26\x8f",
++              .clen   = 32 + 64,
++      },
++};
++
+ static const struct aead_testvec hmac_sha1_des_cbc_tv_temp[] = {
+       { /*Generated with cryptopp*/
+ #ifdef __LITTLE_ENDIAN
diff --git a/target/linux/generic/backport-6.12/930-v7.1-crypto-safexcel-Group-authenc-ciphersuites.patch b/target/linux/generic/backport-6.12/930-v7.1-crypto-safexcel-Group-authenc-ciphersuites.patch
new file mode 100644 (file)
index 0000000..b0b2745
--- /dev/null
@@ -0,0 +1,64 @@
+From c75daa3730132c55dea7cc9c0f8818aea491fe0c Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Tue, 3 Feb 2026 19:21:51 +0100
+Subject: [PATCH 1/2] crypto: safexcel - Group authenc ciphersuites
+
+Move authenc(sha1,des) and authenc(sha1,3des) ciphersuites to appropriate
+groups. No functional changes intended.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Acked-by: Antoine Tenart <atenart@kernel.org>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+---
+ drivers/crypto/inside-secure/safexcel.c | 4 ++--
+ drivers/crypto/inside-secure/safexcel.h | 4 ++--
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/crypto/inside-secure/safexcel.c
++++ b/drivers/crypto/inside-secure/safexcel.c
+@@ -1209,7 +1209,6 @@ static struct safexcel_alg_template *saf
+       &safexcel_alg_authenc_hmac_sha256_cbc_aes,
+       &safexcel_alg_authenc_hmac_sha384_cbc_aes,
+       &safexcel_alg_authenc_hmac_sha512_cbc_aes,
+-      &safexcel_alg_authenc_hmac_sha1_cbc_des3_ede,
+       &safexcel_alg_authenc_hmac_sha1_ctr_aes,
+       &safexcel_alg_authenc_hmac_sha224_ctr_aes,
+       &safexcel_alg_authenc_hmac_sha256_ctr_aes,
+@@ -1242,11 +1241,12 @@ static struct safexcel_alg_template *saf
+       &safexcel_alg_hmac_sha3_256,
+       &safexcel_alg_hmac_sha3_384,
+       &safexcel_alg_hmac_sha3_512,
+-      &safexcel_alg_authenc_hmac_sha1_cbc_des,
++      &safexcel_alg_authenc_hmac_sha1_cbc_des3_ede,
+       &safexcel_alg_authenc_hmac_sha256_cbc_des3_ede,
+       &safexcel_alg_authenc_hmac_sha224_cbc_des3_ede,
+       &safexcel_alg_authenc_hmac_sha512_cbc_des3_ede,
+       &safexcel_alg_authenc_hmac_sha384_cbc_des3_ede,
++      &safexcel_alg_authenc_hmac_sha1_cbc_des,
+       &safexcel_alg_authenc_hmac_sha256_cbc_des,
+       &safexcel_alg_authenc_hmac_sha224_cbc_des,
+       &safexcel_alg_authenc_hmac_sha512_cbc_des,
+--- a/drivers/crypto/inside-secure/safexcel.h
++++ b/drivers/crypto/inside-secure/safexcel.h
+@@ -950,7 +950,6 @@ extern struct safexcel_alg_template safe
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha256_cbc_aes;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha384_cbc_aes;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha512_cbc_aes;
+-extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha1_cbc_des3_ede;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha1_ctr_aes;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha224_ctr_aes;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha256_ctr_aes;
+@@ -983,11 +982,12 @@ extern struct safexcel_alg_template safe
+ extern struct safexcel_alg_template safexcel_alg_hmac_sha3_256;
+ extern struct safexcel_alg_template safexcel_alg_hmac_sha3_384;
+ extern struct safexcel_alg_template safexcel_alg_hmac_sha3_512;
+-extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha1_cbc_des;
++extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha1_cbc_des3_ede;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha256_cbc_des3_ede;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha224_cbc_des3_ede;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha512_cbc_des3_ede;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha384_cbc_des3_ede;
++extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha1_cbc_des;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha256_cbc_des;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha224_cbc_des;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha512_cbc_des;
diff --git a/target/linux/generic/backport-6.12/931-v7.1-crypto-safexcel-Add-support-for-authenc-hmac-md5-sui.patch b/target/linux/generic/backport-6.12/931-v7.1-crypto-safexcel-Add-support-for-authenc-hmac-md5-sui.patch
new file mode 100644 (file)
index 0000000..1a1616d
--- /dev/null
@@ -0,0 +1,279 @@
+From f050e4209ab0ba3f13bb6272a07ce87cbea922c9 Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Tue, 3 Feb 2026 19:21:52 +0100
+Subject: [PATCH 2/2] crypto: safexcel - Add support for authenc(hmac(md5),*)
+ suites
+
+This patch adds support for the following AEAD ciphersuites:
+- authenc(hmac(md5),cbc(aes))
+- authenc(hmac(md5),cbc(des)))
+- authenc(hmac(md5),cbc(des3_ede))
+- authenc(hmac(md5),rfc3686(ctr(aes)))
+
+The first three ciphersuites were tested using testmgr and the recently
+sent test vectors. They passed self-tests.
+
+This is enhanced version of the patch found in the mtk-openwrt-feeds repo.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+Reviewed-by: Antoine Tenart <atenart@kernel.org>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+---
+ drivers/crypto/inside-secure/safexcel.c       |   4 +
+ drivers/crypto/inside-secure/safexcel.h       |   4 +
+ .../crypto/inside-secure/safexcel_cipher.c    | 149 ++++++++++++++++++
+ 3 files changed, 157 insertions(+)
+
+--- a/drivers/crypto/inside-secure/safexcel.c
++++ b/drivers/crypto/inside-secure/safexcel.c
+@@ -1204,11 +1204,13 @@ static struct safexcel_alg_template *saf
+       &safexcel_alg_hmac_sha256,
+       &safexcel_alg_hmac_sha384,
+       &safexcel_alg_hmac_sha512,
++      &safexcel_alg_authenc_hmac_md5_cbc_aes,
+       &safexcel_alg_authenc_hmac_sha1_cbc_aes,
+       &safexcel_alg_authenc_hmac_sha224_cbc_aes,
+       &safexcel_alg_authenc_hmac_sha256_cbc_aes,
+       &safexcel_alg_authenc_hmac_sha384_cbc_aes,
+       &safexcel_alg_authenc_hmac_sha512_cbc_aes,
++      &safexcel_alg_authenc_hmac_md5_ctr_aes,
+       &safexcel_alg_authenc_hmac_sha1_ctr_aes,
+       &safexcel_alg_authenc_hmac_sha224_ctr_aes,
+       &safexcel_alg_authenc_hmac_sha256_ctr_aes,
+@@ -1241,11 +1243,13 @@ static struct safexcel_alg_template *saf
+       &safexcel_alg_hmac_sha3_256,
+       &safexcel_alg_hmac_sha3_384,
+       &safexcel_alg_hmac_sha3_512,
++      &safexcel_alg_authenc_hmac_md5_cbc_des3_ede,
+       &safexcel_alg_authenc_hmac_sha1_cbc_des3_ede,
+       &safexcel_alg_authenc_hmac_sha256_cbc_des3_ede,
+       &safexcel_alg_authenc_hmac_sha224_cbc_des3_ede,
+       &safexcel_alg_authenc_hmac_sha512_cbc_des3_ede,
+       &safexcel_alg_authenc_hmac_sha384_cbc_des3_ede,
++      &safexcel_alg_authenc_hmac_md5_cbc_des,
+       &safexcel_alg_authenc_hmac_sha1_cbc_des,
+       &safexcel_alg_authenc_hmac_sha256_cbc_des,
+       &safexcel_alg_authenc_hmac_sha224_cbc_des,
+--- a/drivers/crypto/inside-secure/safexcel.h
++++ b/drivers/crypto/inside-secure/safexcel.h
+@@ -945,11 +945,13 @@ extern struct safexcel_alg_template safe
+ extern struct safexcel_alg_template safexcel_alg_hmac_sha256;
+ extern struct safexcel_alg_template safexcel_alg_hmac_sha384;
+ extern struct safexcel_alg_template safexcel_alg_hmac_sha512;
++extern struct safexcel_alg_template safexcel_alg_authenc_hmac_md5_cbc_aes;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha1_cbc_aes;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha224_cbc_aes;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha256_cbc_aes;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha384_cbc_aes;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha512_cbc_aes;
++extern struct safexcel_alg_template safexcel_alg_authenc_hmac_md5_ctr_aes;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha1_ctr_aes;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha224_ctr_aes;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha256_ctr_aes;
+@@ -982,11 +984,13 @@ extern struct safexcel_alg_template safe
+ extern struct safexcel_alg_template safexcel_alg_hmac_sha3_256;
+ extern struct safexcel_alg_template safexcel_alg_hmac_sha3_384;
+ extern struct safexcel_alg_template safexcel_alg_hmac_sha3_512;
++extern struct safexcel_alg_template safexcel_alg_authenc_hmac_md5_cbc_des3_ede;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha1_cbc_des3_ede;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha256_cbc_des3_ede;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha224_cbc_des3_ede;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha512_cbc_des3_ede;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha384_cbc_des3_ede;
++extern struct safexcel_alg_template safexcel_alg_authenc_hmac_md5_cbc_des;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha1_cbc_des;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha256_cbc_des;
+ extern struct safexcel_alg_template safexcel_alg_authenc_hmac_sha224_cbc_des;
+--- a/drivers/crypto/inside-secure/safexcel_cipher.c
++++ b/drivers/crypto/inside-secure/safexcel_cipher.c
+@@ -17,6 +17,7 @@
+ #include <crypto/internal/des.h>
+ #include <crypto/gcm.h>
+ #include <crypto/ghash.h>
++#include <crypto/md5.h>
+ #include <crypto/poly1305.h>
+ #include <crypto/sha1.h>
+ #include <crypto/sha2.h>
+@@ -462,6 +463,9 @@ static int safexcel_aead_setkey(struct c
+       /* Auth key */
+       switch (ctx->hash_alg) {
++      case CONTEXT_CONTROL_CRYPTO_ALG_MD5:
++              alg = "safexcel-md5";
++              break;
+       case CONTEXT_CONTROL_CRYPTO_ALG_SHA1:
+               alg = "safexcel-sha1";
+               break;
+@@ -1662,6 +1666,42 @@ static int safexcel_aead_cra_init(struct
+       return 0;
+ }
++static int safexcel_aead_md5_cra_init(struct crypto_tfm *tfm)
++{
++      struct safexcel_cipher_ctx *ctx = crypto_tfm_ctx(tfm);
++
++      safexcel_aead_cra_init(tfm);
++      ctx->hash_alg = CONTEXT_CONTROL_CRYPTO_ALG_MD5;
++      ctx->state_sz = MD5_DIGEST_SIZE;
++      return 0;
++}
++
++struct safexcel_alg_template safexcel_alg_authenc_hmac_md5_cbc_aes = {
++      .type = SAFEXCEL_ALG_TYPE_AEAD,
++      .algo_mask = SAFEXCEL_ALG_AES | SAFEXCEL_ALG_MD5,
++      .alg.aead = {
++              .setkey = safexcel_aead_setkey,
++              .encrypt = safexcel_aead_encrypt,
++              .decrypt = safexcel_aead_decrypt,
++              .ivsize = AES_BLOCK_SIZE,
++              .maxauthsize = MD5_DIGEST_SIZE,
++              .base = {
++                      .cra_name = "authenc(hmac(md5),cbc(aes))",
++                      .cra_driver_name = "safexcel-authenc-hmac-md5-cbc-aes",
++                      .cra_priority = SAFEXCEL_CRA_PRIORITY,
++                      .cra_flags = CRYPTO_ALG_ASYNC |
++                                   CRYPTO_ALG_ALLOCATES_MEMORY |
++                                   CRYPTO_ALG_KERN_DRIVER_ONLY,
++                      .cra_blocksize = AES_BLOCK_SIZE,
++                      .cra_ctxsize = sizeof(struct safexcel_cipher_ctx),
++                      .cra_alignmask = 0,
++                      .cra_init = safexcel_aead_md5_cra_init,
++                      .cra_exit = safexcel_aead_cra_exit,
++                      .cra_module = THIS_MODULE,
++              },
++      },
++};
++
+ static int safexcel_aead_sha1_cra_init(struct crypto_tfm *tfm)
+ {
+       struct safexcel_cipher_ctx *ctx = crypto_tfm_ctx(tfm);
+@@ -1842,6 +1882,43 @@ struct safexcel_alg_template safexcel_al
+       },
+ };
++static int safexcel_aead_md5_des3_cra_init(struct crypto_tfm *tfm)
++{
++      struct safexcel_cipher_ctx *ctx = crypto_tfm_ctx(tfm);
++
++      safexcel_aead_md5_cra_init(tfm);
++      ctx->alg = SAFEXCEL_3DES; /* override default */
++      ctx->blocksz = DES3_EDE_BLOCK_SIZE;
++      ctx->ivmask = EIP197_OPTION_2_TOKEN_IV_CMD;
++      return 0;
++}
++
++struct safexcel_alg_template safexcel_alg_authenc_hmac_md5_cbc_des3_ede = {
++      .type = SAFEXCEL_ALG_TYPE_AEAD,
++      .algo_mask = SAFEXCEL_ALG_DES | SAFEXCEL_ALG_MD5,
++      .alg.aead = {
++              .setkey = safexcel_aead_setkey,
++              .encrypt = safexcel_aead_encrypt,
++              .decrypt = safexcel_aead_decrypt,
++              .ivsize = DES3_EDE_BLOCK_SIZE,
++              .maxauthsize = MD5_DIGEST_SIZE,
++              .base = {
++                      .cra_name = "authenc(hmac(md5),cbc(des3_ede))",
++                      .cra_driver_name = "safexcel-authenc-hmac-md5-cbc-des3_ede",
++                      .cra_priority = SAFEXCEL_CRA_PRIORITY,
++                      .cra_flags = CRYPTO_ALG_ASYNC |
++                                   CRYPTO_ALG_ALLOCATES_MEMORY |
++                                   CRYPTO_ALG_KERN_DRIVER_ONLY,
++                      .cra_blocksize = DES3_EDE_BLOCK_SIZE,
++                      .cra_ctxsize = sizeof(struct safexcel_cipher_ctx),
++                      .cra_alignmask = 0,
++                      .cra_init = safexcel_aead_md5_des3_cra_init,
++                      .cra_exit = safexcel_aead_cra_exit,
++                      .cra_module = THIS_MODULE,
++              },
++      },
++};
++
+ static int safexcel_aead_sha1_des3_cra_init(struct crypto_tfm *tfm)
+ {
+       struct safexcel_cipher_ctx *ctx = crypto_tfm_ctx(tfm);
+@@ -2027,6 +2104,43 @@ struct safexcel_alg_template safexcel_al
+       },
+ };
++static int safexcel_aead_md5_des_cra_init(struct crypto_tfm *tfm)
++{
++      struct safexcel_cipher_ctx *ctx = crypto_tfm_ctx(tfm);
++
++      safexcel_aead_md5_cra_init(tfm);
++      ctx->alg = SAFEXCEL_DES; /* override default */
++      ctx->blocksz = DES_BLOCK_SIZE;
++      ctx->ivmask = EIP197_OPTION_2_TOKEN_IV_CMD;
++      return 0;
++}
++
++struct safexcel_alg_template safexcel_alg_authenc_hmac_md5_cbc_des = {
++      .type = SAFEXCEL_ALG_TYPE_AEAD,
++      .algo_mask = SAFEXCEL_ALG_DES | SAFEXCEL_ALG_MD5,
++      .alg.aead = {
++              .setkey = safexcel_aead_setkey,
++              .encrypt = safexcel_aead_encrypt,
++              .decrypt = safexcel_aead_decrypt,
++              .ivsize = DES_BLOCK_SIZE,
++              .maxauthsize = MD5_DIGEST_SIZE,
++              .base = {
++                      .cra_name = "authenc(hmac(md5),cbc(des))",
++                      .cra_driver_name = "safexcel-authenc-hmac-md5-cbc-des",
++                      .cra_priority = SAFEXCEL_CRA_PRIORITY,
++                      .cra_flags = CRYPTO_ALG_ASYNC |
++                                   CRYPTO_ALG_ALLOCATES_MEMORY |
++                                   CRYPTO_ALG_KERN_DRIVER_ONLY,
++                      .cra_blocksize = DES_BLOCK_SIZE,
++                      .cra_ctxsize = sizeof(struct safexcel_cipher_ctx),
++                      .cra_alignmask = 0,
++                      .cra_init = safexcel_aead_md5_des_cra_init,
++                      .cra_exit = safexcel_aead_cra_exit,
++                      .cra_module = THIS_MODULE,
++              },
++      },
++};
++
+ static int safexcel_aead_sha1_des_cra_init(struct crypto_tfm *tfm)
+ {
+       struct safexcel_cipher_ctx *ctx = crypto_tfm_ctx(tfm);
+@@ -2209,6 +2323,41 @@ struct safexcel_alg_template safexcel_al
+                       .cra_exit = safexcel_aead_cra_exit,
+                       .cra_module = THIS_MODULE,
+               },
++      },
++};
++
++static int safexcel_aead_md5_ctr_cra_init(struct crypto_tfm *tfm)
++{
++      struct safexcel_cipher_ctx *ctx = crypto_tfm_ctx(tfm);
++
++      safexcel_aead_md5_cra_init(tfm);
++      ctx->mode = CONTEXT_CONTROL_CRYPTO_MODE_CTR_LOAD; /* override default */
++      return 0;
++}
++
++struct safexcel_alg_template safexcel_alg_authenc_hmac_md5_ctr_aes = {
++      .type = SAFEXCEL_ALG_TYPE_AEAD,
++      .algo_mask = SAFEXCEL_ALG_AES | SAFEXCEL_ALG_MD5,
++      .alg.aead = {
++              .setkey = safexcel_aead_setkey,
++              .encrypt = safexcel_aead_encrypt,
++              .decrypt = safexcel_aead_decrypt,
++              .ivsize = CTR_RFC3686_IV_SIZE,
++              .maxauthsize = MD5_DIGEST_SIZE,
++              .base = {
++                      .cra_name = "authenc(hmac(md5),rfc3686(ctr(aes)))",
++                      .cra_driver_name = "safexcel-authenc-hmac-md5-ctr-aes",
++                      .cra_priority = SAFEXCEL_CRA_PRIORITY,
++                      .cra_flags = CRYPTO_ALG_ASYNC |
++                                   CRYPTO_ALG_ALLOCATES_MEMORY |
++                                   CRYPTO_ALG_KERN_DRIVER_ONLY,
++                      .cra_blocksize = 1,
++                      .cra_ctxsize = sizeof(struct safexcel_cipher_ctx),
++                      .cra_alignmask = 0,
++                      .cra_init = safexcel_aead_md5_ctr_cra_init,
++                      .cra_exit = safexcel_aead_cra_exit,
++                      .cra_module = THIS_MODULE,
++              },
+       },
+ };
diff --git a/target/linux/generic/config-6.12 b/target/linux/generic/config-6.12
new file mode 100644 (file)
index 0000000..37fdcc8
--- /dev/null
@@ -0,0 +1,7833 @@
+# CONFIG_104_QUAD_8 is not set
+CONFIG_32BIT=y
+# CONFIG_6LOWPAN is not set
+# CONFIG_6LOWPAN_DEBUGFS is not set
+# CONFIG_6PACK is not set
+# CONFIG_8139CP is not set
+# CONFIG_8139TOO is not set
+# CONFIG_9P_FS is not set
+# CONFIG_AB8500_CORE is not set
+# CONFIG_ABP060MG is not set
+# CONFIG_ABX500_CORE is not set
+# CONFIG_ACCESSIBILITY is not set
+# CONFIG_ACENIC is not set
+# CONFIG_ACERHDF is not set
+# CONFIG_ACER_WIRELESS is not set
+# CONFIG_ACER_WMI is not set
+# CONFIG_ACORN_PARTITION is not set
+# CONFIG_ACPI_AGDI is not set
+# CONFIG_ACPI_ALS is not set
+# CONFIG_ACPI_APEI is not set
+# CONFIG_ACPI_APEI_PCIEAER is not set
+# CONFIG_ACPI_BUTTON is not set
+# CONFIG_ACPI_CONFIGFS is not set
+# CONFIG_ACPI_EXTLOG is not set
+# CONFIG_ACPI_HED is not set
+# CONFIG_ACPI_NFIT is not set
+# CONFIG_ACPI_REDUCED_HARDWARE_ONLY is not set
+# CONFIG_ACPI_TABLE_UPGRADE is not set
+# CONFIG_ACPI_TOSHIBA is not set
+# CONFIG_ACPI_VIDEO is not set
+# CONFIG_AD2S1200 is not set
+# CONFIG_AD2S1210 is not set
+# CONFIG_AD2S90 is not set
+# CONFIG_AD3552R is not set
+# CONFIG_AD4000 is not set
+# CONFIG_AD4130 is not set
+# CONFIG_AD4695 is not set
+# CONFIG_AD5064 is not set
+# CONFIG_AD5110 is not set
+# CONFIG_AD525X_DPOT is not set
+# CONFIG_AD5272 is not set
+# CONFIG_AD5360 is not set
+# CONFIG_AD5380 is not set
+# CONFIG_AD5421 is not set
+# CONFIG_AD5446 is not set
+# CONFIG_AD5449 is not set
+# CONFIG_AD5504 is not set
+# CONFIG_AD5592R is not set
+# CONFIG_AD5593R is not set
+# CONFIG_AD5624R_SPI is not set
+# CONFIG_AD5686 is not set
+# CONFIG_AD5686_SPI is not set
+# CONFIG_AD5696_I2C is not set
+# CONFIG_AD5755 is not set
+# CONFIG_AD5758 is not set
+# CONFIG_AD5761 is not set
+# CONFIG_AD5764 is not set
+# CONFIG_AD5766 is not set
+# CONFIG_AD5770R is not set
+# CONFIG_AD5791 is not set
+# CONFIG_AD5933 is not set
+# CONFIG_AD7091R5 is not set
+# CONFIG_AD7091R8 is not set
+# CONFIG_AD7124 is not set
+# CONFIG_AD7150 is not set
+# CONFIG_AD7173 is not set
+# CONFIG_AD7192 is not set
+# CONFIG_AD7266 is not set
+# CONFIG_AD7280 is not set
+# CONFIG_AD7291 is not set
+# CONFIG_AD7292 is not set
+# CONFIG_AD7293 is not set
+# CONFIG_AD7298 is not set
+# CONFIG_AD7303 is not set
+# CONFIG_AD7380 is not set
+# CONFIG_AD74115 is not set
+# CONFIG_AD74413R is not set
+# CONFIG_AD7476 is not set
+# CONFIG_AD7606 is not set
+# CONFIG_AD7606_IFACE_PARALLEL is not set
+# CONFIG_AD7606_IFACE_SPI is not set
+# CONFIG_AD7746 is not set
+# CONFIG_AD7766 is not set
+# CONFIG_AD7768_1 is not set
+# CONFIG_AD7780 is not set
+# CONFIG_AD7791 is not set
+# CONFIG_AD7793 is not set
+# CONFIG_AD7816 is not set
+# CONFIG_AD7887 is not set
+# CONFIG_AD7923 is not set
+# CONFIG_AD7944 is not set
+# CONFIG_AD7949 is not set
+# CONFIG_AD799X is not set
+# CONFIG_AD8366 is not set
+# CONFIG_AD8801 is not set
+# CONFIG_AD9467 is not set
+# CONFIG_AD9523 is not set
+# CONFIG_AD9739A is not set
+# CONFIG_AD9832 is not set
+# CONFIG_AD9834 is not set
+# CONFIG_ADA4250 is not set
+# CONFIG_ADAPTEC_STARFIRE is not set
+# CONFIG_ADF4350 is not set
+# CONFIG_ADF4371 is not set
+# CONFIG_ADF4377 is not set
+# CONFIG_ADFS_FS is not set
+# CONFIG_ADIN1100_PHY is not set
+# CONFIG_ADIN1110 is not set
+# CONFIG_ADIN_PHY is not set
+# CONFIG_ADIS16080 is not set
+# CONFIG_ADIS16130 is not set
+# CONFIG_ADIS16136 is not set
+# CONFIG_ADIS16201 is not set
+# CONFIG_ADIS16203 is not set
+# CONFIG_ADIS16209 is not set
+# CONFIG_ADIS16240 is not set
+# CONFIG_ADIS16260 is not set
+# CONFIG_ADIS16400 is not set
+# CONFIG_ADIS16460 is not set
+# CONFIG_ADIS16475 is not set
+# CONFIG_ADIS16480 is not set
+# CONFIG_ADI_AXI_ADC is not set
+# CONFIG_ADI_AXI_DAC is not set
+# CONFIG_ADJD_S311 is not set
+# CONFIG_ADM6996_PHY is not set
+# CONFIG_ADM8211 is not set
+# CONFIG_ADMFM2000 is not set
+# CONFIG_ADMV1013 is not set
+# CONFIG_ADMV1014 is not set
+# CONFIG_ADMV4420 is not set
+# CONFIG_ADMV8818 is not set
+# CONFIG_ADRF6780 is not set
+# CONFIG_ADT7316 is not set
+# CONFIG_ADUX1020 is not set
+CONFIG_ADVISE_SYSCALLS=y
+# CONFIG_ADXL313_I2C is not set
+# CONFIG_ADXL313_SPI is not set
+# CONFIG_ADXL345_I2C is not set
+# CONFIG_ADXL345_SPI is not set
+# CONFIG_ADXL355_I2C is not set
+# CONFIG_ADXL355_SPI is not set
+# CONFIG_ADXL367_I2C is not set
+# CONFIG_ADXL367_SPI is not set
+# CONFIG_ADXL372_I2C is not set
+# CONFIG_ADXL372_SPI is not set
+# CONFIG_ADXL380_I2C is not set
+# CONFIG_ADXL380_SPI is not set
+# CONFIG_ADXRS290 is not set
+# CONFIG_ADXRS450 is not set
+CONFIG_AEABI=y
+# CONFIG_AF8133J is not set
+# CONFIG_AFE4403 is not set
+# CONFIG_AFE4404 is not set
+# CONFIG_AFFS_FS is not set
+# CONFIG_AFS_DEBUG_CURSOR is not set
+# CONFIG_AFS_FS is not set
+# CONFIG_AF_KCM is not set
+# CONFIG_AF_RXRPC is not set
+# CONFIG_AF_RXRPC_INJECT_LOSS is not set
+# CONFIG_AF_RXRPC_INJECT_RX_DELAY is not set
+# CONFIG_AF_RXRPC_IPV6 is not set
+CONFIG_AF_UNIX_OOB=y
+# CONFIG_AGP is not set
+# CONFIG_AHCI_BRCM is not set
+# CONFIG_AHCI_CEVA is not set
+# CONFIG_AHCI_DWC is not set
+# CONFIG_AHCI_IMX is not set
+# CONFIG_AHCI_MVEBU is not set
+# CONFIG_AHCI_QORIQ is not set
+# CONFIG_AHCI_XGENE is not set
+CONFIG_AIO=y
+# CONFIG_AIR_EN8811H_PHY is not set
+# CONFIG_AIX_PARTITION is not set
+# CONFIG_AK09911 is not set
+# CONFIG_AK8974 is not set
+# CONFIG_AK8975 is not set
+# CONFIG_AL3010 is not set
+# CONFIG_AL3320A is not set
+# CONFIG_ALIM7101_WDT is not set
+CONFIG_ALLOW_DEV_COREDUMP=y
+# CONFIG_ALTERA_MBOX is not set
+# CONFIG_ALTERA_MSGDMA is not set
+# CONFIG_ALTERA_STAPL is not set
+# CONFIG_ALTERA_TSE is not set
+# CONFIG_ALX is not set
+# CONFIG_AL_FIC is not set
+# CONFIG_AM2315 is not set
+# CONFIG_AM335X_PHY_USB is not set
+# CONFIG_AMBA_PL08X is not set
+# CONFIG_AMD8111_ETH is not set
+# CONFIG_AMD_MEM_ENCRYPT is not set
+# CONFIG_AMD_PHY is not set
+# CONFIG_AMD_QDMA is not set
+# CONFIG_AMD_XGBE is not set
+# CONFIG_AMD_XGBE_HAVE_ECC is not set
+# CONFIG_AMIGA_PARTITION is not set
+# CONFIG_AMILO_RFKILL is not set
+# CONFIG_AMPERE_ERRATUM_AC03_CPU_38 is not set
+# CONFIG_AMT is not set
+# CONFIG_ANDROID_BINDER_IPC is not set
+# CONFIG_ANON_VMA_NAME is not set
+# CONFIG_AOSONG_AGS02MA is not set
+# CONFIG_APDS9300 is not set
+# CONFIG_APDS9306 is not set
+# CONFIG_APDS9802ALS is not set
+# CONFIG_APDS9960 is not set
+# CONFIG_APM_EMULATION is not set
+# CONFIG_APPLE_GMUX is not set
+# CONFIG_APPLE_MFI_FASTCHARGE is not set
+# CONFIG_APPLE_PROPERTIES is not set
+# CONFIG_APPLICOM is not set
+# CONFIG_AQTION is not set
+# CONFIG_AQUANTIA_PHY is not set
+# CONFIG_AR5523 is not set
+# CONFIG_AR8216_PHY is not set
+# CONFIG_AR8216_PHY_LEDS is not set
+# CONFIG_ARCH_ACTIONS is not set
+# CONFIG_ARCH_AIROHA is not set
+# CONFIG_ARCH_ALPINE is not set
+# CONFIG_ARCH_APPLE is not set
+# CONFIG_ARCH_ARTPEC is not set
+# CONFIG_ARCH_ASPEED is not set
+# CONFIG_ARCH_AT91 is not set
+# CONFIG_ARCH_AXXIA is not set
+# CONFIG_ARCH_BCM is not set
+# CONFIG_ARCH_BCM2835 is not set
+# CONFIG_ARCH_BCMBCA is not set
+# CONFIG_ARCH_BCM_21664 is not set
+# CONFIG_ARCH_BCM_23550 is not set
+# CONFIG_ARCH_BCM_281XX is not set
+# CONFIG_ARCH_BCM_5301X is not set
+# CONFIG_ARCH_BCM_53573 is not set
+# CONFIG_ARCH_BCM_CYGNUS is not set
+# CONFIG_ARCH_BCM_HR2 is not set
+# CONFIG_ARCH_BCM_IPROC is not set
+# CONFIG_ARCH_BCM_NSP is not set
+# CONFIG_ARCH_BERLIN is not set
+CONFIG_ARCH_BINFMT_ELF_STATE=y
+# CONFIG_ARCH_BITMAIN is not set
+# CONFIG_ARCH_BRCMSTB is not set
+# CONFIG_ARCH_CLPS711X is not set
+# CONFIG_ARCH_DAVINCI is not set
+CONFIG_ARCH_DEFAULT_CRASH_DUMP=y
+# CONFIG_ARCH_DIGICOLOR is not set
+# CONFIG_ARCH_DMA_ADDR_T_64BIT is not set
+# CONFIG_ARCH_DOVE is not set
+# CONFIG_ARCH_EP93XX is not set
+# CONFIG_ARCH_EXYNOS is not set
+CONFIG_ARCH_FLATMEM_ENABLE=y
+# CONFIG_ARCH_FOOTBRIDGE is not set
+CONFIG_ARCH_FORCE_MAX_ORDER=11
+# CONFIG_ARCH_GEMINI is not set
+# CONFIG_ARCH_HI3xxx is not set
+# CONFIG_ARCH_HIGHBANK is not set
+# CONFIG_ARCH_HISI is not set
+# CONFIG_ARCH_HPE is not set
+# CONFIG_ARCH_INTEGRATOR is not set
+# CONFIG_ARCH_INTEL_SOCFPGA is not set
+# CONFIG_ARCH_IXP4XX is not set
+# CONFIG_ARCH_K3 is not set
+# CONFIG_ARCH_KEEMBAY is not set
+# CONFIG_ARCH_KEYSTONE is not set
+# CONFIG_ARCH_LAYERSCAPE is not set
+# CONFIG_ARCH_LG1K is not set
+# CONFIG_ARCH_LPC32XX is not set
+# CONFIG_ARCH_MA35 is not set
+# CONFIG_ARCH_MEDIATEK is not set
+# CONFIG_ARCH_MESON is not set
+# CONFIG_ARCH_MILBEAUT is not set
+CONFIG_ARCH_MMAP_RND_BITS=8
+CONFIG_ARCH_MMAP_RND_BITS_MAX=16
+CONFIG_ARCH_MMAP_RND_BITS_MIN=8
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=16
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN=8
+# CONFIG_ARCH_MMP is not set
+# CONFIG_ARCH_MSTARV7 is not set
+# CONFIG_ARCH_MULTIPLATFORM is not set
+# CONFIG_ARCH_MULTI_V6 is not set
+# CONFIG_ARCH_MULTI_V7 is not set
+# CONFIG_ARCH_MV78XX0 is not set
+# CONFIG_ARCH_MVEBU is not set
+# CONFIG_ARCH_MXC is not set
+# CONFIG_ARCH_MXS is not set
+# CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED is not set
+# CONFIG_ARCH_NOMADIK is not set
+# CONFIG_ARCH_NPCM is not set
+# CONFIG_ARCH_NSPIRE is not set
+# CONFIG_ARCH_NXP is not set
+# CONFIG_ARCH_OMAP is not set
+# CONFIG_ARCH_OMAP1 is not set
+# CONFIG_ARCH_OMAP2 is not set
+# CONFIG_ARCH_OMAP2PLUS is not set
+# CONFIG_ARCH_OMAP3 is not set
+# CONFIG_ARCH_OMAP4 is not set
+# CONFIG_ARCH_ORION5X is not set
+# CONFIG_ARCH_PENSANDO is not set
+# CONFIG_ARCH_PXA is not set
+# CONFIG_ARCH_QCOM is not set
+# CONFIG_ARCH_RDA is not set
+# CONFIG_ARCH_REALTEK is not set
+# CONFIG_ARCH_REALVIEW is not set
+# CONFIG_ARCH_RENESAS is not set
+# CONFIG_ARCH_ROCKCHIP is not set
+# CONFIG_ARCH_RPC is not set
+# CONFIG_ARCH_S32 is not set
+# CONFIG_ARCH_S3C64XX is not set
+# CONFIG_ARCH_S5PV210 is not set
+# CONFIG_ARCH_SA1100 is not set
+# CONFIG_ARCH_SEATTLE is not set
+# CONFIG_ARCH_SHMOBILE is not set
+# CONFIG_ARCH_SPARX5 is not set
+# CONFIG_ARCH_SPRD is not set
+# CONFIG_ARCH_STI is not set
+# CONFIG_ARCH_STM32 is not set
+# CONFIG_ARCH_SUNPLUS is not set
+# CONFIG_ARCH_SUNXI is not set
+# CONFIG_ARCH_SYNQUACER is not set
+# CONFIG_ARCH_TEGRA is not set
+# CONFIG_ARCH_THUNDER is not set
+# CONFIG_ARCH_THUNDER2 is not set
+# CONFIG_ARCH_U8500 is not set
+# CONFIG_ARCH_UNIPHIER is not set
+# CONFIG_ARCH_VERSATILE is not set
+# CONFIG_ARCH_VEXPRESS is not set
+# CONFIG_ARCH_VIRT is not set
+# CONFIG_ARCH_VISCONTI is not set
+# CONFIG_ARCH_VT8500 is not set
+# CONFIG_ARCH_WANTS_THP_SWAP is not set
+# CONFIG_ARCH_WM8505 is not set
+# CONFIG_ARCH_WM8750 is not set
+# CONFIG_ARCH_WM8850 is not set
+# CONFIG_ARCH_XGENE is not set
+# CONFIG_ARCH_ZYNQ is not set
+# CONFIG_ARCH_ZYNQMP is not set
+# CONFIG_ARCNET is not set
+# CONFIG_ARC_IRQ_NO_AUTOSAVE is not set
+# CONFIG_ARM64_16K_PAGES is not set
+# CONFIG_ARM64_64K_PAGES is not set
+# CONFIG_ARM64_AMU_EXTN is not set
+# CONFIG_ARM64_BTI is not set
+CONFIG_ARM64_CNP=y
+# CONFIG_ARM64_CONTPTE is not set
+# CONFIG_ARM64_E0PD is not set
+# CONFIG_ARM64_EPAN is not set
+# CONFIG_ARM64_ERRATUM_1024718 is not set
+# CONFIG_ARM64_ERRATUM_1165522 is not set
+# CONFIG_ARM64_ERRATUM_1286807 is not set
+# CONFIG_ARM64_ERRATUM_1319367 is not set
+# CONFIG_ARM64_ERRATUM_1418040 is not set
+# CONFIG_ARM64_ERRATUM_1463225 is not set
+# CONFIG_ARM64_ERRATUM_1508412 is not set
+# CONFIG_ARM64_ERRATUM_1530923 is not set
+# CONFIG_ARM64_ERRATUM_1542419 is not set
+# CONFIG_ARM64_ERRATUM_1742098 is not set
+# CONFIG_ARM64_ERRATUM_2051678 is not set
+# CONFIG_ARM64_ERRATUM_2054223 is not set
+# CONFIG_ARM64_ERRATUM_2067961 is not set
+# CONFIG_ARM64_ERRATUM_2077057 is not set
+# CONFIG_ARM64_ERRATUM_2441007 is not set
+# CONFIG_ARM64_ERRATUM_2441009 is not set
+# CONFIG_ARM64_ERRATUM_2645198 is not set
+# CONFIG_ARM64_ERRATUM_2658417 is not set
+# CONFIG_ARM64_ERRATUM_2966298 is not set
+# CONFIG_ARM64_ERRATUM_3117295 is not set
+# CONFIG_ARM64_ERRATUM_3194386 is not set
+# CONFIG_ARM64_ERRATUM_819472 is not set
+# CONFIG_ARM64_ERRATUM_824069 is not set
+# CONFIG_ARM64_ERRATUM_826319 is not set
+# CONFIG_ARM64_ERRATUM_827319 is not set
+# CONFIG_ARM64_ERRATUM_832075 is not set
+# CONFIG_ARM64_ERRATUM_834220 is not set
+# CONFIG_ARM64_ERRATUM_843419 is not set
+# CONFIG_ARM64_ERRATUM_845719 is not set
+# CONFIG_ARM64_ERRATUM_858921 is not set
+# CONFIG_ARM64_HW_AFDBM is not set
+# CONFIG_ARM64_LSE_ATOMICS is not set
+# CONFIG_ARM64_MTE is not set
+CONFIG_ARM64_PAN=y
+# CONFIG_ARM64_PMEM is not set
+# CONFIG_ARM64_POE is not set
+# CONFIG_ARM64_PSEUDO_NMI is not set
+# CONFIG_ARM64_PTR_AUTH is not set
+# CONFIG_ARM64_RAS_EXTN is not set
+# CONFIG_ARM64_RELOC_TEST is not set
+# CONFIG_ARM64_SME is not set
+# CONFIG_ARM64_SVE is not set
+CONFIG_ARM64_SW_TTBR0_PAN=y
+# CONFIG_ARM64_TLB_RANGE is not set
+# CONFIG_ARM64_USE_LSE_ATOMICS is not set
+# CONFIG_ARM64_VA_BITS_48 is not set
+# CONFIG_ARM64_VA_BITS_52 is not set
+# CONFIG_ARM_APPENDED_DTB is not set
+# CONFIG_ARM_ARCH_TIMER is not set
+# CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEND is not set
+# CONFIG_ARM_CCI is not set
+# CONFIG_ARM_CCI400_PMU is not set
+# CONFIG_ARM_CCI5xx_PMU is not set
+# CONFIG_ARM_CCI_PMU is not set
+# CONFIG_ARM_CCN is not set
+# CONFIG_ARM_CMN is not set
+# CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU is not set
+# CONFIG_ARM_CPUIDLE is not set
+CONFIG_ARM_CPU_TOPOLOGY=y
+# CONFIG_ARM_DEBUG_WX is not set
+CONFIG_ARM_DMA_MEM_BUFFERABLE=y
+# CONFIG_ARM_DSU_PMU is not set
+# CONFIG_ARM_ERRATA_326103 is not set
+# CONFIG_ARM_ERRATA_364296 is not set
+# CONFIG_ARM_ERRATA_411920 is not set
+# CONFIG_ARM_ERRATA_430973 is not set
+# CONFIG_ARM_ERRATA_458693 is not set
+# CONFIG_ARM_ERRATA_460075 is not set
+# CONFIG_ARM_ERRATA_643719 is not set
+# CONFIG_ARM_ERRATA_720789 is not set
+# CONFIG_ARM_ERRATA_742230 is not set
+# CONFIG_ARM_ERRATA_742231 is not set
+# CONFIG_ARM_ERRATA_743622 is not set
+# CONFIG_ARM_ERRATA_751472 is not set
+# CONFIG_ARM_ERRATA_754322 is not set
+# CONFIG_ARM_ERRATA_754327 is not set
+# CONFIG_ARM_ERRATA_764319 is not set
+# CONFIG_ARM_ERRATA_764369 is not set
+# CONFIG_ARM_ERRATA_773022 is not set
+# CONFIG_ARM_ERRATA_775420 is not set
+# CONFIG_ARM_ERRATA_798181 is not set
+# CONFIG_ARM_ERRATA_814220 is not set
+# CONFIG_ARM_ERRATA_818325_852422 is not set
+# CONFIG_ARM_ERRATA_821420 is not set
+# CONFIG_ARM_ERRATA_825619 is not set
+# CONFIG_ARM_ERRATA_852421 is not set
+# CONFIG_ARM_ERRATA_852423 is not set
+# CONFIG_ARM_ERRATA_857271 is not set
+# CONFIG_ARM_ERRATA_857272 is not set
+# CONFIG_ARM_FFA_TRANSPORT is not set
+CONFIG_ARM_GIC_MAX_NR=1
+# CONFIG_ARM_KIRKWOOD_CPUFREQ is not set
+# CONFIG_ARM_KPROBES_TEST is not set
+# CONFIG_ARM_LPAE is not set
+# CONFIG_ARM_MEDIATEK_CPUFREQ_HW is not set
+# CONFIG_ARM_MHU is not set
+CONFIG_ARM_MODULE_PLTS=y
+# CONFIG_ARM_NI is not set
+# CONFIG_ARM_PAN is not set
+# CONFIG_ARM_PATCH_PHYS_VIRT is not set
+# CONFIG_ARM_PSCI is not set
+# CONFIG_ARM_PSCI_CHECKER is not set
+# CONFIG_ARM_PSCI_CPUIDLE is not set
+# CONFIG_ARM_PTDUMP_DEBUGFS is not set
+# CONFIG_ARM_SBSA_WATCHDOG is not set
+# CONFIG_ARM_SCMI_PROTOCOL is not set
+# CONFIG_ARM_SCPI_PROTOCOL is not set
+# CONFIG_ARM_SDE_INTERFACE is not set
+# CONFIG_ARM_SMCCC_SOC_ID is not set
+# CONFIG_ARM_SMC_WATCHDOG is not set
+# CONFIG_ARM_SMMU_V3_PMU is not set
+# CONFIG_ARM_SP805_WATCHDOG is not set
+# CONFIG_ARM_SPE_PMU is not set
+# CONFIG_ARM_THUMBEE is not set
+# CONFIG_ARM_TIMER_SP804 is not set
+# CONFIG_ARM_UNWIND is not set
+# CONFIG_ARM_VIRT_EXT is not set
+# CONFIG_AS21XXX_PHY is not set
+# CONFIG_AS3935 is not set
+# CONFIG_AS73211 is not set
+# CONFIG_ASM9260_TIMER is not set
+# CONFIG_ASN1 is not set
+# CONFIG_ASUS_LAPTOP is not set
+# CONFIG_ASUS_TF103C_DOCK is not set
+# CONFIG_ASUS_WIRELESS is not set
+# CONFIG_ASUS_WMI is not set
+# CONFIG_ASYMMETRIC_KEY_TYPE is not set
+# CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE is not set
+# CONFIG_ASYNC_RAID6_TEST is not set
+# CONFIG_ASYNC_TX_DMA is not set
+# CONFIG_AT76C50X_USB is not set
+# CONFIG_AT803X_PHY is not set
+# CONFIG_AT91_SAMA5D2_ADC is not set
+# CONFIG_ATA is not set
+# CONFIG_ATAGS is not set
+CONFIG_ATAGS_PROC=y
+# CONFIG_ATALK is not set
+# CONFIG_ATARI_PARTITION is not set
+# CONFIG_ATA_ACPI is not set
+CONFIG_ATA_BMDMA=y
+# CONFIG_ATA_FORCE is not set
+# CONFIG_ATA_GENERIC is not set
+# CONFIG_ATA_LEDS is not set
+# CONFIG_ATA_NONSTANDARD is not set
+# CONFIG_ATA_OVER_ETH is not set
+# CONFIG_ATA_PIIX is not set
+CONFIG_ATA_SFF=y
+# CONFIG_ATA_VERBOSE_ERROR is not set
+# CONFIG_ATH10K is not set
+# CONFIG_ATH25 is not set
+# CONFIG_ATH5K is not set
+# CONFIG_ATH6KL is not set
+# CONFIG_ATH79 is not set
+# CONFIG_ATH9K is not set
+# CONFIG_ATH9K_HTC is not set
+# CONFIG_ATH_DEBUG is not set
+# CONFIG_ATL1 is not set
+# CONFIG_ATL1C is not set
+# CONFIG_ATL1E is not set
+# CONFIG_ATL2 is not set
+# CONFIG_ATLAS_EZO_SENSOR is not set
+# CONFIG_ATLAS_PH_SENSOR is not set
+# CONFIG_ATM is not set
+# CONFIG_ATMEL_PIT is not set
+# CONFIG_ATMEL_SSC is not set
+# CONFIG_ATM_BR2684 is not set
+CONFIG_ATM_BR2684_IPFILTER=y
+# CONFIG_ATM_CLIP is not set
+CONFIG_ATM_CLIP_NO_ICMP=y
+# CONFIG_ATM_DRIVERS is not set
+# CONFIG_ATM_DUMMY is not set
+# CONFIG_ATM_ENI is not set
+# CONFIG_ATM_FORE200E is not set
+# CONFIG_ATM_HE is not set
+# CONFIG_ATM_IA is not set
+# CONFIG_ATM_IDT77252 is not set
+# CONFIG_ATM_LANAI is not set
+# CONFIG_ATM_LANE is not set
+# CONFIG_ATM_MPOA is not set
+# CONFIG_ATM_NICSTAR is not set
+# CONFIG_ATM_SOLOS is not set
+# CONFIG_ATM_TCP is not set
+# CONFIG_ATOMIC64_SELFTEST is not set
+# CONFIG_ATP is not set
+# CONFIG_AUDIT is not set
+# CONFIG_AUDIT_ARCH_COMPAT_GENERIC is not set
+# CONFIG_AUTOFS_FS is not set
+# CONFIG_AUTO_ZRELADDR is not set
+# CONFIG_AUXDISPLAY is not set
+# CONFIG_AW96103 is not set
+# CONFIG_AX25 is not set
+# CONFIG_AX25_DAMA_SLAVE is not set
+# CONFIG_AX88796 is not set
+# CONFIG_AX88796B_PHY is not set
+# CONFIG_AXP20X_ADC is not set
+# CONFIG_AXP20X_POWER is not set
+# CONFIG_AXP288_ADC is not set
+# CONFIG_AXP288_FUEL_GAUGE is not set
+# CONFIG_B43 is not set
+# CONFIG_B43LEGACY is not set
+# CONFIG_B44 is not set
+# CONFIG_B53 is not set
+# CONFIG_B53_MDIO_DRIVER is not set
+# CONFIG_B53_MMAP_DRIVER is not set
+# CONFIG_B53_SERDES is not set
+# CONFIG_B53_SPI_DRIVER is not set
+# CONFIG_B53_SRAB_DRIVER is not set
+# CONFIG_BACKLIGHT_ADP8860 is not set
+# CONFIG_BACKLIGHT_ADP8870 is not set
+# CONFIG_BACKLIGHT_APPLE is not set
+# CONFIG_BACKLIGHT_ARCXCNN is not set
+# CONFIG_BACKLIGHT_BD6107 is not set
+# CONFIG_BACKLIGHT_CLASS_DEVICE is not set
+# CONFIG_BACKLIGHT_GPIO is not set
+# CONFIG_BACKLIGHT_KTD253 is not set
+# CONFIG_BACKLIGHT_KTD2801 is not set
+# CONFIG_BACKLIGHT_KTZ8866 is not set
+# CONFIG_BACKLIGHT_LED is not set
+# CONFIG_BACKLIGHT_LM3509 is not set
+# CONFIG_BACKLIGHT_LM3630A is not set
+# CONFIG_BACKLIGHT_LM3639 is not set
+# CONFIG_BACKLIGHT_LP855X is not set
+# CONFIG_BACKLIGHT_LV5207LP is not set
+# CONFIG_BACKLIGHT_MP3309C is not set
+# CONFIG_BACKLIGHT_PANDORA is not set
+# CONFIG_BACKLIGHT_PWM is not set
+# CONFIG_BACKLIGHT_QCOM_WLED is not set
+# CONFIG_BACKLIGHT_SAHARA is not set
+# CONFIG_BACKTRACE_SELF_TEST is not set
+# CONFIG_BACKTRACE_VERBOSE is not set
+# CONFIG_BAREUDP is not set
+# CONFIG_BASE_SMALL is not set
+# CONFIG_BATMAN_ADV is not set
+# CONFIG_BATTERY_BQ27XXX is not set
+# CONFIG_BATTERY_BQ27XXX_HDQ is not set
+# CONFIG_BATTERY_CW2015 is not set
+# CONFIG_BATTERY_DS2760 is not set
+# CONFIG_BATTERY_DS2780 is not set
+# CONFIG_BATTERY_DS2781 is not set
+# CONFIG_BATTERY_DS2782 is not set
+# CONFIG_BATTERY_GAUGE_LTC2941 is not set
+# CONFIG_BATTERY_GOLDFISH is not set
+# CONFIG_BATTERY_LEGO_EV3 is not set
+# CONFIG_BATTERY_MAX17040 is not set
+# CONFIG_BATTERY_MAX17042 is not set
+# CONFIG_BATTERY_MAX1720X is not set
+# CONFIG_BATTERY_MAX1721X is not set
+# CONFIG_BATTERY_PM8916_BMS_VM is not set
+# CONFIG_BATTERY_RT5033 is not set
+# CONFIG_BATTERY_SAMSUNG_SDI is not set
+# CONFIG_BATTERY_SBS is not set
+# CONFIG_BATTERY_UG3105 is not set
+# CONFIG_BAYCOM_EPP is not set
+# CONFIG_BAYCOM_PAR is not set
+# CONFIG_BAYCOM_SER_FDX is not set
+# CONFIG_BAYCOM_SER_HDX is not set
+# CONFIG_BCACHE is not set
+# CONFIG_BCACHEFS_FS is not set
+# CONFIG_BCM47XX is not set
+# CONFIG_BCM54140_PHY is not set
+# CONFIG_BCM63XX is not set
+# CONFIG_BCM63XX_PHY is not set
+# CONFIG_BCM7038_L1_IRQ is not set
+# CONFIG_BCM7038_WDT is not set
+# CONFIG_BCM7120_L2_IRQ is not set
+# CONFIG_BCM7XXX_PHY is not set
+# CONFIG_BCM84881_PHY is not set
+# CONFIG_BCM87XX_PHY is not set
+# CONFIG_BCMA is not set
+# CONFIG_BCMA_DRIVER_GPIO is not set
+CONFIG_BCMA_POSSIBLE=y
+# CONFIG_BCMGENET is not set
+# CONFIG_BCM_IPROC_ADC is not set
+# CONFIG_BCM_KONA_USB2_PHY is not set
+# CONFIG_BCM_SBA_RAID is not set
+# CONFIG_BCM_VK is not set
+# CONFIG_BDI_SWITCH is not set
+# CONFIG_BE2ISCSI is not set
+# CONFIG_BE2NET is not set
+# CONFIG_BEFS_FS is not set
+# CONFIG_BFS_FS is not set
+# CONFIG_BGMAC is not set
+# CONFIG_BH1745 is not set
+# CONFIG_BH1750 is not set
+# CONFIG_BH1780 is not set
+# CONFIG_BIG_KEYS is not set
+# CONFIG_BIG_LITTLE is not set
+CONFIG_BINARY_PRINTF=y
+CONFIG_BINFMT_ELF=y
+# CONFIG_BINFMT_ELF_FDPIC is not set
+# CONFIG_BINFMT_FLAT is not set
+# CONFIG_BINFMT_MISC is not set
+CONFIG_BINFMT_SCRIPT=y
+CONFIG_BITREVERSE=y
+# CONFIG_BLK_CGROUP_IOCOST is not set
+# CONFIG_BLK_CGROUP_IOLATENCY is not set
+# CONFIG_BLK_CGROUP_IOPRIO is not set
+# CONFIG_BLK_DEBUG_FS is not set
+CONFIG_BLK_DEV=y
+# CONFIG_BLK_DEV_3W_XXXX_RAID is not set
+# CONFIG_BLK_DEV_BSG is not set
+# CONFIG_BLK_DEV_BSGLIB is not set
+# CONFIG_BLK_DEV_COW_COMMON is not set
+# CONFIG_BLK_DEV_DM is not set
+# CONFIG_BLK_DEV_DRBD is not set
+# CONFIG_BLK_DEV_FD is not set
+CONFIG_BLK_DEV_INITRD=y
+# CONFIG_BLK_DEV_INTEGRITY is not set
+# CONFIG_BLK_DEV_IO_TRACE is not set
+# CONFIG_BLK_DEV_LOOP is not set
+CONFIG_BLK_DEV_LOOP_MIN_COUNT=8
+# CONFIG_BLK_DEV_MD is not set
+# CONFIG_BLK_DEV_NBD is not set
+# CONFIG_BLK_DEV_NULL_BLK is not set
+# CONFIG_BLK_DEV_NVME is not set
+# CONFIG_BLK_DEV_PCIESSD_MTIP32XX is not set
+# CONFIG_BLK_DEV_PMEM is not set
+# CONFIG_BLK_DEV_RAM is not set
+# CONFIG_BLK_DEV_RBD is not set
+# CONFIG_BLK_DEV_SD is not set
+# CONFIG_BLK_DEV_SR is not set
+# CONFIG_BLK_DEV_THROTTLING is not set
+# CONFIG_BLK_DEV_UBLK is not set
+CONFIG_BLK_DEV_WRITE_MOUNTED=y
+# CONFIG_BLK_DEV_ZONED is not set
+# CONFIG_BLK_INLINE_ENCRYPTION is not set
+# CONFIG_BLK_SED_OPAL is not set
+# CONFIG_BLK_WBT is not set
+CONFIG_BLOCK=y
+# CONFIG_BLOCK_LEGACY_AUTOLOAD is not set
+# CONFIG_BLOCK_NOTIFIERS is not set
+# CONFIG_BMA180 is not set
+# CONFIG_BMA220 is not set
+# CONFIG_BMA400 is not set
+# CONFIG_BMC150_ACCEL is not set
+# CONFIG_BMC150_MAGN is not set
+# CONFIG_BMC150_MAGN_I2C is not set
+# CONFIG_BMC150_MAGN_SPI is not set
+# CONFIG_BME680 is not set
+# CONFIG_BMG160 is not set
+# CONFIG_BMI088_ACCEL is not set
+# CONFIG_BMI160_I2C is not set
+# CONFIG_BMI160_SPI is not set
+# CONFIG_BMI323_I2C is not set
+# CONFIG_BMI323_SPI is not set
+# CONFIG_BMIPS_GENERIC is not set
+# CONFIG_BMP280 is not set
+# CONFIG_BNA is not set
+# CONFIG_BNX2 is not set
+# CONFIG_BNX2X is not set
+# CONFIG_BNX2X_SRIOV is not set
+# CONFIG_BNXT is not set
+# CONFIG_BONDING is not set
+# CONFIG_BOOKE_WDT is not set
+CONFIG_BOOKE_WDT_DEFAULT_TIMEOUT=3
+# CONFIG_BOOTPARAM_HARDLOCKUP_PANIC is not set
+# CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set
+# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set
+# CONFIG_BOOTTIME_TRACING is not set
+# CONFIG_BOOT_CONFIG is not set
+# CONFIG_BOOT_PRINTK_DELAY is not set
+CONFIG_BOOT_RAW=y
+# CONFIG_BOSCH_BNO055_I2C is not set
+# CONFIG_BOSCH_BNO055_SERIAL is not set
+# CONFIG_BOUNCE is not set
+CONFIG_BPF=y
+CONFIG_BPF_JIT=y
+# CONFIG_BPF_JIT_ALWAYS_ON is not set
+CONFIG_BPF_JIT_DEFAULT_ON=y
+# CONFIG_BPF_LSM is not set
+# CONFIG_BPF_PRELOAD is not set
+# CONFIG_BPF_STREAM_PARSER is not set
+CONFIG_BPF_SYSCALL=y
+CONFIG_BPF_UNPRIV_DEFAULT_OFF=y
+# CONFIG_BPQETHER is not set
+CONFIG_BQL=y
+CONFIG_BRANCH_PROFILE_NONE=y
+# CONFIG_BRCMFMAC is not set
+# CONFIG_BRCMSMAC is not set
+# CONFIG_BRCMSTB_GISB_ARB is not set
+# CONFIG_BRCMSTB_L2_IRQ is not set
+CONFIG_BRIDGE=y
+# CONFIG_BRIDGE_CFM is not set
+# CONFIG_BRIDGE_EBT_802_3 is not set
+# CONFIG_BRIDGE_EBT_AMONG is not set
+# CONFIG_BRIDGE_EBT_ARP is not set
+# CONFIG_BRIDGE_EBT_ARPREPLY is not set
+# CONFIG_BRIDGE_EBT_BROUTE is not set
+# CONFIG_BRIDGE_EBT_DNAT is not set
+# CONFIG_BRIDGE_EBT_IP is not set
+# CONFIG_BRIDGE_EBT_IP6 is not set
+# CONFIG_BRIDGE_EBT_LIMIT is not set
+# CONFIG_BRIDGE_EBT_LOG is not set
+# CONFIG_BRIDGE_EBT_MARK is not set
+# CONFIG_BRIDGE_EBT_MARK_T is not set
+# CONFIG_BRIDGE_EBT_NFLOG is not set
+# CONFIG_BRIDGE_EBT_PKTTYPE is not set
+# CONFIG_BRIDGE_EBT_REDIRECT is not set
+# CONFIG_BRIDGE_EBT_SNAT is not set
+# CONFIG_BRIDGE_EBT_STP is not set
+# CONFIG_BRIDGE_EBT_T_FILTER is not set
+# CONFIG_BRIDGE_EBT_T_NAT is not set
+# CONFIG_BRIDGE_EBT_VLAN is not set
+CONFIG_BRIDGE_IGMP_SNOOPING=y
+# CONFIG_BRIDGE_MRP is not set
+# CONFIG_BRIDGE_NETFILTER is not set
+# CONFIG_BRIDGE_NF_EBTABLES is not set
+CONFIG_BRIDGE_VLAN_FILTERING=y
+# CONFIG_BROADCOM_PHY is not set
+CONFIG_BROKEN_ON_SMP=y
+# CONFIG_BSD_DISKLABEL is not set
+# CONFIG_BSD_PROCESS_ACCT is not set
+# CONFIG_BSD_PROCESS_ACCT_V3 is not set
+# CONFIG_BT is not set
+# CONFIG_BTRFS_ASSERT is not set
+# CONFIG_BTRFS_DEBUG is not set
+# CONFIG_BTRFS_FS is not set
+# CONFIG_BTRFS_FS_POSIX_ACL is not set
+# CONFIG_BTRFS_FS_REF_VERIFY is not set
+# CONFIG_BTRFS_FS_RUN_SANITY_TESTS is not set
+# CONFIG_BT_AOSPEXT is not set
+# CONFIG_BT_ATH3K is not set
+# CONFIG_BT_BNEP is not set
+CONFIG_BT_BNEP_MC_FILTER=y
+CONFIG_BT_BNEP_PROTO_FILTER=y
+# CONFIG_BT_BREDR is not set
+# CONFIG_BT_CMTP is not set
+# CONFIG_BT_FEATURE_DEBUG is not set
+# CONFIG_BT_HCIBCM203X is not set
+# CONFIG_BT_HCIBCM4377 is not set
+# CONFIG_BT_HCIBFUSB is not set
+# CONFIG_BT_HCIBLUECARD is not set
+# CONFIG_BT_HCIBPA10X is not set
+# CONFIG_BT_HCIBT3C is not set
+# CONFIG_BT_HCIBTSDIO is not set
+# CONFIG_BT_HCIBTUSB is not set
+# CONFIG_BT_HCIBTUSB_AUTOSUSPEND is not set
+# CONFIG_BT_HCIBTUSB_MTK is not set
+CONFIG_BT_HCIBTUSB_POLL_SYNC=y
+# CONFIG_BT_HCIBTUSB_RTL is not set
+# CONFIG_BT_HCIDTL1 is not set
+# CONFIG_BT_HCIUART is not set
+# CONFIG_BT_HCIUART_3WIRE is not set
+# CONFIG_BT_HCIUART_AG6XX is not set
+# CONFIG_BT_HCIUART_AML is not set
+# CONFIG_BT_HCIUART_ATH3K is not set
+CONFIG_BT_HCIUART_BCSP=y
+CONFIG_BT_HCIUART_H4=y
+# CONFIG_BT_HCIUART_LL is not set
+# CONFIG_BT_HCIUART_MRVL is not set
+# CONFIG_BT_HCIUART_QCA is not set
+# CONFIG_BT_HCIUART_RTL is not set
+# CONFIG_BT_HCIVHCI is not set
+# CONFIG_BT_HIDP is not set
+# CONFIG_BT_INTEL_PCIE is not set
+# CONFIG_BT_LE is not set
+# CONFIG_BT_LEDS is not set
+CONFIG_BT_LE_L2CAP_ECRED=y
+# CONFIG_BT_MRVL is not set
+# CONFIG_BT_MSFTEXT is not set
+# CONFIG_BT_MTKSDIO is not set
+# CONFIG_BT_MTKUART is not set
+# CONFIG_BT_NXPUART is not set
+# CONFIG_BT_RFCOMM is not set
+CONFIG_BT_RFCOMM_TTY=y
+# CONFIG_BT_SELFTEST is not set
+# CONFIG_BT_VIRTIO is not set
+CONFIG_BUG=y
+# CONFIG_BUG_ON_DATA_CORRUPTION is not set
+CONFIG_BUILDTIME_TABLE_SORT=y
+CONFIG_BUILD_SALT=""
+# CONFIG_C2PORT is not set
+# CONFIG_CACHESTAT_SYSCALL is not set
+CONFIG_CACHE_L2X0_PMU=y
+# CONFIG_CADENCE_WATCHDOG is not set
+# CONFIG_CAIF is not set
+# CONFIG_CAN is not set
+# CONFIG_CAN_BCM is not set
+# CONFIG_CAN_CAN327 is not set
+# CONFIG_CAN_CTUCANFD_PCI is not set
+# CONFIG_CAN_CTUCANFD_PLATFORM is not set
+# CONFIG_CAN_DEBUG_DEVICES is not set
+# CONFIG_CAN_DEV is not set
+# CONFIG_CAN_ESD_402_PCI is not set
+# CONFIG_CAN_ESD_USB is not set
+# CONFIG_CAN_ETAS_ES58X is not set
+# CONFIG_CAN_F81604 is not set
+# CONFIG_CAN_GS_USB is not set
+# CONFIG_CAN_GW is not set
+# CONFIG_CAN_HI311X is not set
+# CONFIG_CAN_IFI_CANFD is not set
+# CONFIG_CAN_ISOTP is not set
+# CONFIG_CAN_J1939 is not set
+# CONFIG_CAN_KVASER_PCIEFD is not set
+# CONFIG_CAN_MCBA_USB is not set
+# CONFIG_CAN_MCP251XFD is not set
+# CONFIG_CAN_M_CAN is not set
+# CONFIG_CAN_NETLINK is not set
+# CONFIG_CAN_PEAK_PCIEFD is not set
+# CONFIG_CAN_RAW is not set
+# CONFIG_CAN_RCAR is not set
+# CONFIG_CAN_RCAR_CANFD is not set
+# CONFIG_CAN_ROCKCHIP_CANFD is not set
+# CONFIG_CAN_SLCAN is not set
+# CONFIG_CAN_SUN4I is not set
+# CONFIG_CAN_UCAN is not set
+# CONFIG_CAN_VCAN is not set
+# CONFIG_CAN_VXCAN is not set
+# CONFIG_CAPI_TRACE is not set
+CONFIG_CARDBUS=y
+# CONFIG_CARL9170 is not set
+# CONFIG_CASSINI is not set
+# CONFIG_CAVIUM_CPT is not set
+# CONFIG_CAVIUM_ERRATUM_22375 is not set
+# CONFIG_CAVIUM_ERRATUM_23144 is not set
+# CONFIG_CAVIUM_ERRATUM_23154 is not set
+# CONFIG_CAVIUM_ERRATUM_27456 is not set
+# CONFIG_CAVIUM_ERRATUM_30115 is not set
+# CONFIG_CAVIUM_OCTEON_SOC is not set
+# CONFIG_CAVIUM_PTP is not set
+# CONFIG_CAVIUM_TX2_ERRATUM_219 is not set
+# CONFIG_CB710_CORE is not set
+# CONFIG_CC10001_ADC is not set
+# CONFIG_CCS811 is not set
+CONFIG_CC_CAN_LINK=y
+CONFIG_CC_NO_STRINGOP_OVERFLOW=y
+CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y
+# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
+# CONFIG_CDROM_PKTCDVD is not set
+# CONFIG_CDX_BUS is not set
+# CONFIG_CEPH_FS is not set
+# CONFIG_CEPH_LIB is not set
+# CONFIG_CFG80211 is not set
+# CONFIG_CFG80211_CERTIFICATION_ONUS is not set
+CONFIG_CFG80211_HEADERS=y
+# CONFIG_CGROUPS is not set
+# CONFIG_CGROUP_FAVOR_DYNMODS is not set
+# CONFIG_CGROUP_MISC is not set
+# CONFIG_CHARGER_ADP5061 is not set
+# CONFIG_CHARGER_BD99954 is not set
+# CONFIG_CHARGER_BQ2415X is not set
+# CONFIG_CHARGER_BQ24190 is not set
+# CONFIG_CHARGER_BQ24257 is not set
+# CONFIG_CHARGER_BQ24735 is not set
+# CONFIG_CHARGER_BQ2515X is not set
+# CONFIG_CHARGER_BQ256XX is not set
+# CONFIG_CHARGER_BQ25890 is not set
+# CONFIG_CHARGER_BQ25980 is not set
+# CONFIG_CHARGER_DETECTOR_MAX14656 is not set
+# CONFIG_CHARGER_GPIO is not set
+# CONFIG_CHARGER_ISP1704 is not set
+# CONFIG_CHARGER_LP8727 is not set
+# CONFIG_CHARGER_LT3651 is not set
+# CONFIG_CHARGER_LTC4162L is not set
+# CONFIG_CHARGER_MANAGER is not set
+# CONFIG_CHARGER_MAX77976 is not set
+# CONFIG_CHARGER_MAX8903 is not set
+# CONFIG_CHARGER_PM8916_LBC is not set
+# CONFIG_CHARGER_QCOM_SMB2 is not set
+# CONFIG_CHARGER_QCOM_SMBB is not set
+# CONFIG_CHARGER_RT9455 is not set
+# CONFIG_CHARGER_RT9467 is not set
+# CONFIG_CHARGER_RT9471 is not set
+# CONFIG_CHARGER_SBS is not set
+# CONFIG_CHARGER_SMB347 is not set
+# CONFIG_CHARGER_TWL4030 is not set
+# CONFIG_CHARGER_UCS1002 is not set
+# CONFIG_CHECKPOINT_RESTORE is not set
+# CONFIG_CHELSIO_T1 is not set
+# CONFIG_CHELSIO_T3 is not set
+# CONFIG_CHELSIO_T4 is not set
+# CONFIG_CHELSIO_T4VF is not set
+# CONFIG_CHROME_PLATFORMS is not set
+# CONFIG_CHR_DEV_SCH is not set
+# CONFIG_CHR_DEV_SG is not set
+# CONFIG_CHR_DEV_ST is not set
+# CONFIG_CICADA_PHY is not set
+# CONFIG_CIFS is not set
+CONFIG_CIFS_ALLOW_INSECURE_LEGACY=y
+# CONFIG_CIFS_COMPRESSION is not set
+# CONFIG_CIFS_DEBUG is not set
+# CONFIG_CIFS_DEBUG2 is not set
+# CONFIG_CIFS_FSCACHE is not set
+# CONFIG_CIFS_NFSD_EXPORT is not set
+CONFIG_CIFS_POSIX=y
+# CONFIG_CIFS_STATS2 is not set
+# CONFIG_CIFS_SWN_UPCALL is not set
+CONFIG_CIFS_XATTR=y
+# CONFIG_CIO_DAC is not set
+# CONFIG_CLKSRC_PISTACHIO is not set
+# CONFIG_CLKSRC_VERSATILE is not set
+# CONFIG_CLK_GFM_LPASS_SM8250 is not set
+# CONFIG_CLK_HSDK is not set
+# CONFIG_CLK_ICST is not set
+# CONFIG_CLK_QORIQ is not set
+# CONFIG_CLK_SP810 is not set
+# CONFIG_CLK_TWL is not set
+CONFIG_CLS_U32_MARK=y
+# CONFIG_CLS_U32_PERF is not set
+# CONFIG_CM32181 is not set
+# CONFIG_CM3232 is not set
+# CONFIG_CM3323 is not set
+# CONFIG_CM3605 is not set
+# CONFIG_CM36651 is not set
+# CONFIG_CMA is not set
+CONFIG_CMDLINE=""
+# CONFIG_CMDLINE_BOOL is not set
+# CONFIG_CMDLINE_EXTEND is not set
+# CONFIG_CMDLINE_FORCE is not set
+# CONFIG_CMDLINE_FROM_BOOTLOADER is not set
+# CONFIG_CMDLINE_PARTITION is not set
+# CONFIG_CNIC is not set
+# CONFIG_CODA_FS is not set
+# CONFIG_CODE_PATCHING_SELFTEST is not set
+# CONFIG_COMEDI is not set
+# CONFIG_COMMON_CLK_AXI_CLKGEN is not set
+# CONFIG_COMMON_CLK_BOSTON is not set
+# CONFIG_COMMON_CLK_CDCE706 is not set
+# CONFIG_COMMON_CLK_CDCE925 is not set
+# CONFIG_COMMON_CLK_CS2000_CP is not set
+# CONFIG_COMMON_CLK_FIXED_MMIO is not set
+# CONFIG_COMMON_CLK_IPROC is not set
+# CONFIG_COMMON_CLK_MAX9485 is not set
+# CONFIG_COMMON_CLK_MEDIATEK_FHCTL is not set
+# CONFIG_COMMON_CLK_MT6765 is not set
+# CONFIG_COMMON_CLK_MT8167 is not set
+# CONFIG_COMMON_CLK_MT8167_AUDSYS is not set
+# CONFIG_COMMON_CLK_MT8167_IMGSYS is not set
+# CONFIG_COMMON_CLK_MT8167_MFGCFG is not set
+# CONFIG_COMMON_CLK_MT8167_MMSYS is not set
+# CONFIG_COMMON_CLK_MT8167_VDECSYS is not set
+# CONFIG_COMMON_CLK_MT8188 is not set
+# CONFIG_COMMON_CLK_MT8192 is not set
+# CONFIG_COMMON_CLK_NXP is not set
+# CONFIG_COMMON_CLK_PIC32 is not set
+# CONFIG_COMMON_CLK_PISTACHIO is not set
+# CONFIG_COMMON_CLK_PWM is not set
+# CONFIG_COMMON_CLK_PXA is not set
+# CONFIG_COMMON_CLK_QCOM is not set
+# CONFIG_COMMON_CLK_RS9_PCIE is not set
+# CONFIG_COMMON_CLK_SI514 is not set
+# CONFIG_COMMON_CLK_SI521XX is not set
+# CONFIG_COMMON_CLK_SI5341 is not set
+# CONFIG_COMMON_CLK_SI5351 is not set
+# CONFIG_COMMON_CLK_SI544 is not set
+# CONFIG_COMMON_CLK_SI570 is not set
+# CONFIG_COMMON_CLK_VC3 is not set
+# CONFIG_COMMON_CLK_VC5 is not set
+# CONFIG_COMMON_CLK_VC7 is not set
+# CONFIG_COMMON_CLK_XGENE is not set
+# CONFIG_COMMON_CLK_XLNX_CLKWZRD is not set
+CONFIG_COMPACTION=y
+# CONFIG_COMPAL_LAPTOP is not set
+# CONFIG_COMPAT is not set
+# CONFIG_COMPAT_BRK is not set
+# CONFIG_COMPILE_TEST is not set
+# CONFIG_COMPRESSED_INSTALL is not set
+# CONFIG_CONFIGFS_FS is not set
+# CONFIG_CONNECTOR is not set
+CONFIG_CONSOLE_LOGLEVEL_DEFAULT=7
+CONFIG_CONSOLE_LOGLEVEL_QUIET=4
+CONFIG_CONSTRUCTORS=y
+# CONFIG_CONTEXT_SWITCH_TRACER is not set
+# CONFIG_CORDIC is not set
+# CONFIG_COREDUMP is not set
+# CONFIG_CORESIGHT is not set
+# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
+# CONFIG_CORTINA_PHY is not set
+# CONFIG_COUNTER is not set
+# CONFIG_CPA_DEBUG is not set
+# CONFIG_CPUFREQ_DT is not set
+# CONFIG_CPUFREQ_DT_PLATDEV is not set
+# CONFIG_CPU_BIG_ENDIAN is not set
+# CONFIG_CPU_BPREDICT_DISABLE is not set
+# CONFIG_CPU_DCACHE_DISABLE is not set
+# CONFIG_CPU_FREQ is not set
+# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set
+# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set
+# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set
+# CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL is not set
+# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set
+# CONFIG_CPU_FREQ_GOV_SCHEDUTIL is not set
+# CONFIG_CPU_FREQ_THERMAL is not set
+# CONFIG_CPU_HOTPLUG_STATE_CONTROL is not set
+# CONFIG_CPU_ICACHE_DISABLE is not set
+# CONFIG_CPU_ICACHE_MISMATCH_WORKAROUND is not set
+# CONFIG_CPU_IDLE is not set
+# CONFIG_CPU_IDLE_GOV_LADDER is not set
+# CONFIG_CPU_IDLE_GOV_MENU is not set
+# CONFIG_CPU_IDLE_GOV_TEO is not set
+# CONFIG_CPU_IDLE_MULTIPLE_DRIVERS is not set
+# CONFIG_CPU_ISOLATION is not set
+# CONFIG_CPU_LITTLE_ENDIAN is not set
+# CONFIG_CPU_NO_EFFICIENT_FFS is not set
+CONFIG_CPU_SW_DOMAIN_PAN=y
+# CONFIG_CPU_THERMAL is not set
+# CONFIG_CRAMFS is not set
+CONFIG_CRAMFS_BLOCKDEV=y
+# CONFIG_CRAMFS_MTD is not set
+# CONFIG_CRASH_DUMP is not set
+# CONFIG_CRASH_HOTPLUG is not set
+# CONFIG_CRC16 is not set
+CONFIG_CRC32=y
+# CONFIG_CRC32_BIT is not set
+# CONFIG_CRC32_SARWATE is not set
+# CONFIG_CRC32_SELFTEST is not set
+# CONFIG_CRC32_SLICEBY4 is not set
+CONFIG_CRC32_SLICEBY8=y
+# CONFIG_CRC4 is not set
+# CONFIG_CRC64 is not set
+# CONFIG_CRC64_ROCKSOFT is not set
+# CONFIG_CRC7 is not set
+# CONFIG_CRC8 is not set
+# CONFIG_CRC_CCITT is not set
+# CONFIG_CRC_ITU_T is not set
+# CONFIG_CRC_T10DIF is not set
+# CONFIG_CROSS_MEMORY_ATTACH is not set
+# CONFIG_CROS_HPS_I2C is not set
+CONFIG_CRYPTO=y
+# CONFIG_CRYPTO_842 is not set
+CONFIG_CRYPTO_ACOMP2=y
+# CONFIG_CRYPTO_ADIANTUM is not set
+CONFIG_CRYPTO_AEAD=y
+CONFIG_CRYPTO_AEAD2=y
+# CONFIG_CRYPTO_AEGIS128 is not set
+# CONFIG_CRYPTO_AEGIS128_AESNI_SSE2 is not set
+CONFIG_CRYPTO_AES=y
+# CONFIG_CRYPTO_AES_ARM is not set
+# CONFIG_CRYPTO_AES_ARM64 is not set
+# CONFIG_CRYPTO_AES_ARM64_BS is not set
+# CONFIG_CRYPTO_AES_ARM64_CE is not set
+# CONFIG_CRYPTO_AES_ARM64_CE_BLK is not set
+# CONFIG_CRYPTO_AES_ARM64_CE_CCM is not set
+# CONFIG_CRYPTO_AES_ARM64_NEON_BLK is not set
+# CONFIG_CRYPTO_AES_ARM_BS is not set
+# CONFIG_CRYPTO_AES_ARM_CE is not set
+# CONFIG_CRYPTO_AES_NI_INTEL is not set
+# CONFIG_CRYPTO_AES_RISCV64 is not set
+# CONFIG_CRYPTO_AES_TI is not set
+CONFIG_CRYPTO_AKCIPHER=y
+CONFIG_CRYPTO_AKCIPHER2=y
+CONFIG_CRYPTO_ALGAPI=y
+CONFIG_CRYPTO_ALGAPI2=y
+# CONFIG_CRYPTO_ANSI_CPRNG is not set
+# CONFIG_CRYPTO_ANUBIS is not set
+# CONFIG_CRYPTO_ARC4 is not set
+# CONFIG_CRYPTO_ARIA is not set
+# CONFIG_CRYPTO_AUTHENC is not set
+# CONFIG_CRYPTO_BLAKE2B is not set
+# CONFIG_CRYPTO_BLAKE2B_NEON is not set
+# CONFIG_CRYPTO_BLAKE2S_ARM is not set
+# CONFIG_CRYPTO_BLAKE2S_X86 is not set
+# CONFIG_CRYPTO_BLOWFISH is not set
+# CONFIG_CRYPTO_CAMELLIA is not set
+# CONFIG_CRYPTO_CAST5 is not set
+# CONFIG_CRYPTO_CAST6 is not set
+# CONFIG_CRYPTO_CBC is not set
+CONFIG_CRYPTO_CCM=y
+# CONFIG_CRYPTO_CHACHA20 is not set
+# CONFIG_CRYPTO_CHACHA20POLY1305 is not set
+# CONFIG_CRYPTO_CHACHA20_NEON is not set
+# CONFIG_CRYPTO_CHACHA20_X86_64 is not set
+# CONFIG_CRYPTO_CHACHA_MIPS is not set
+# CONFIG_CRYPTO_CHACHA_RISCV64 is not set
+# CONFIG_CRYPTO_CMAC is not set
+# CONFIG_CRYPTO_CRC32 is not set
+# CONFIG_CRYPTO_CRC32C is not set
+# CONFIG_CRYPTO_CRC32C_INTEL is not set
+# CONFIG_CRYPTO_CRC32_ARM_CE is not set
+# CONFIG_CRYPTO_CRCT10DIF is not set
+# CONFIG_CRYPTO_CRCT10DIF_ARM64_CE is not set
+# CONFIG_CRYPTO_CRCT10DIF_ARM_CE is not set
+# CONFIG_CRYPTO_CRYPTD is not set
+CONFIG_CRYPTO_CTR=y
+# CONFIG_CRYPTO_CTS is not set
+# CONFIG_CRYPTO_CURVE25519 is not set
+# CONFIG_CRYPTO_CURVE25519_NEON is not set
+# CONFIG_CRYPTO_CURVE25519_X86 is not set
+# CONFIG_CRYPTO_DEFLATE is not set
+# CONFIG_CRYPTO_DES is not set
+# CONFIG_CRYPTO_DEV_AMLOGIC_GXL is not set
+# CONFIG_CRYPTO_DEV_ATMEL_AES is not set
+# CONFIG_CRYPTO_DEV_ATMEL_AUTHENC is not set
+# CONFIG_CRYPTO_DEV_ATMEL_ECC is not set
+# CONFIG_CRYPTO_DEV_ATMEL_SHA is not set
+# CONFIG_CRYPTO_DEV_ATMEL_SHA204A is not set
+# CONFIG_CRYPTO_DEV_ATMEL_TDES is not set
+# CONFIG_CRYPTO_DEV_CAVIUM_ZIP is not set
+# CONFIG_CRYPTO_DEV_CCP is not set
+# CONFIG_CRYPTO_DEV_CCP_DEBUGFS is not set
+# CONFIG_CRYPTO_DEV_CCREE is not set
+# CONFIG_CRYPTO_DEV_FSL_CAAM is not set
+# CONFIG_CRYPTO_DEV_FSL_CAAM_CRYPTO_API_DESC is not set
+# CONFIG_CRYPTO_DEV_FSL_CAAM_DEBUG is not set
+# CONFIG_CRYPTO_DEV_FSL_CAAM_INTC is not set
+# CONFIG_CRYPTO_DEV_FSL_CAAM_RNG_TEST is not set
+# CONFIG_CRYPTO_DEV_HIFN_795X is not set
+# CONFIG_CRYPTO_DEV_HISI_SEC is not set
+# CONFIG_CRYPTO_DEV_HISI_ZIP is not set
+# CONFIG_CRYPTO_DEV_IMGTEC_HASH is not set
+# CONFIG_CRYPTO_DEV_MARVELL_CESA is not set
+# CONFIG_CRYPTO_DEV_MXS_DCP is not set
+# CONFIG_CRYPTO_DEV_NITROX_CNN55XX is not set
+# CONFIG_CRYPTO_DEV_OCTEONTX_CPT is not set
+# CONFIG_CRYPTO_DEV_QAT_420XX is not set
+# CONFIG_CRYPTO_DEV_QAT_4XXX is not set
+# CONFIG_CRYPTO_DEV_QAT_C3XXX is not set
+# CONFIG_CRYPTO_DEV_QAT_C3XXXVF is not set
+# CONFIG_CRYPTO_DEV_QAT_C62X is not set
+# CONFIG_CRYPTO_DEV_QAT_C62XVF is not set
+# CONFIG_CRYPTO_DEV_QAT_DH895xCC is not set
+# CONFIG_CRYPTO_DEV_QAT_DH895xCCVF is not set
+# CONFIG_CRYPTO_DEV_QCE is not set
+# CONFIG_CRYPTO_DEV_S5P is not set
+# CONFIG_CRYPTO_DEV_SAFEXCEL is not set
+# CONFIG_CRYPTO_DEV_SAHARA is not set
+# CONFIG_CRYPTO_DEV_SP_PSP is not set
+# CONFIG_CRYPTO_DEV_TALITOS is not set
+# CONFIG_CRYPTO_DEV_VIRTIO is not set
+# CONFIG_CRYPTO_DH is not set
+# CONFIG_CRYPTO_DRBG_CTR is not set
+# CONFIG_CRYPTO_DRBG_HASH is not set
+# CONFIG_CRYPTO_DRBG_MENU is not set
+# CONFIG_CRYPTO_ECB is not set
+# CONFIG_CRYPTO_ECDH is not set
+# CONFIG_CRYPTO_ECDSA is not set
+# CONFIG_CRYPTO_ECHAINIV is not set
+# CONFIG_CRYPTO_ECRDSA is not set
+# CONFIG_CRYPTO_ESSIV is not set
+# CONFIG_CRYPTO_FCRYPT is not set
+# CONFIG_CRYPTO_FIPS is not set
+CONFIG_CRYPTO_GCM=y
+CONFIG_CRYPTO_GHASH=y
+# CONFIG_CRYPTO_GHASH_ARM64_CE is not set
+# CONFIG_CRYPTO_GHASH_ARM_CE is not set
+# CONFIG_CRYPTO_GHASH_CLMUL_NI_INTEL is not set
+# CONFIG_CRYPTO_GHASH_RISCV64 is not set
+CONFIG_CRYPTO_HASH=y
+CONFIG_CRYPTO_HASH2=y
+# CONFIG_CRYPTO_HCTR2 is not set
+# CONFIG_CRYPTO_HMAC is not set
+# CONFIG_CRYPTO_HW is not set
+# CONFIG_CRYPTO_JITTERENTROPY is not set
+# CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set
+# CONFIG_CRYPTO_KEYWRAP is not set
+# CONFIG_CRYPTO_KHAZAD is not set
+CONFIG_CRYPTO_KPP=y
+CONFIG_CRYPTO_KPP2=y
+CONFIG_CRYPTO_LIB_AES=y
+CONFIG_CRYPTO_LIB_ARC4=y
+# CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC is not set
+# CONFIG_CRYPTO_LIB_CHACHA is not set
+# CONFIG_CRYPTO_LIB_CHACHA20POLY1305 is not set
+# CONFIG_CRYPTO_LIB_CURVE25519 is not set
+# CONFIG_CRYPTO_LIB_POLY1305 is not set
+CONFIG_CRYPTO_LIB_POLY1305_RSIZE=9
+# CONFIG_CRYPTO_LRW is not set
+# CONFIG_CRYPTO_LZ4 is not set
+# CONFIG_CRYPTO_LZ4HC is not set
+# CONFIG_CRYPTO_LZO is not set
+CONFIG_CRYPTO_MANAGER=y
+CONFIG_CRYPTO_MANAGER2=y
+CONFIG_CRYPTO_MANAGER_DISABLE_TESTS=y
+# CONFIG_CRYPTO_MD4 is not set
+# CONFIG_CRYPTO_MD5 is not set
+# CONFIG_CRYPTO_MICHAEL_MIC is not set
+# CONFIG_CRYPTO_NHPOLY1305_NEON is not set
+CONFIG_CRYPTO_NULL=y
+CONFIG_CRYPTO_NULL2=y
+# CONFIG_CRYPTO_PCBC is not set
+CONFIG_CRYPTO_PCRYPT=y
+# CONFIG_CRYPTO_POLY1305 is not set
+# CONFIG_CRYPTO_POLY1305_ARM is not set
+# CONFIG_CRYPTO_POLY1305_MIPS is not set
+# CONFIG_CRYPTO_POLY1305_NEON is not set
+# CONFIG_CRYPTO_POLY1305_X86_64 is not set
+# CONFIG_CRYPTO_POLYVAL_ARM64_CE is not set
+# CONFIG_CRYPTO_RMD160 is not set
+# CONFIG_CRYPTO_RNG is not set
+# CONFIG_CRYPTO_RSA is not set
+# CONFIG_CRYPTO_SEED is not set
+# CONFIG_CRYPTO_SEQIV is not set
+# CONFIG_CRYPTO_SERPENT is not set
+# CONFIG_CRYPTO_SHA1 is not set
+# CONFIG_CRYPTO_SHA1_ARM is not set
+# CONFIG_CRYPTO_SHA1_ARM64_CE is not set
+# CONFIG_CRYPTO_SHA1_ARM_CE is not set
+# CONFIG_CRYPTO_SHA1_ARM_NEON is not set
+# CONFIG_CRYPTO_SHA256 is not set
+# CONFIG_CRYPTO_SHA256_ARM is not set
+# CONFIG_CRYPTO_SHA256_ARM64 is not set
+# CONFIG_CRYPTO_SHA256_RISCV64 is not set
+# CONFIG_CRYPTO_SHA2_ARM64_CE is not set
+# CONFIG_CRYPTO_SHA2_ARM_CE is not set
+# CONFIG_CRYPTO_SHA3 is not set
+# CONFIG_CRYPTO_SHA3_ARM64 is not set
+# CONFIG_CRYPTO_SHA512 is not set
+# CONFIG_CRYPTO_SHA512_ARM is not set
+# CONFIG_CRYPTO_SHA512_ARM64 is not set
+# CONFIG_CRYPTO_SHA512_ARM64_CE is not set
+# CONFIG_CRYPTO_SHA512_RISCV64 is not set
+# CONFIG_CRYPTO_SIMD is not set
+CONFIG_CRYPTO_SKCIPHER=y
+CONFIG_CRYPTO_SKCIPHER2=y
+# CONFIG_CRYPTO_SM3 is not set
+# CONFIG_CRYPTO_SM3_ARM64_CE is not set
+# CONFIG_CRYPTO_SM3_GENERIC is not set
+# CONFIG_CRYPTO_SM3_NEON is not set
+# CONFIG_CRYPTO_SM3_RISCV64 is not set
+# CONFIG_CRYPTO_SM4 is not set
+# CONFIG_CRYPTO_SM4_ARM64_CE is not set
+# CONFIG_CRYPTO_SM4_ARM64_CE_BLK is not set
+# CONFIG_CRYPTO_SM4_ARM64_CE_CCM is not set
+# CONFIG_CRYPTO_SM4_ARM64_CE_GCM is not set
+# CONFIG_CRYPTO_SM4_ARM64_NEON_BLK is not set
+# CONFIG_CRYPTO_SM4_GENERIC is not set
+# CONFIG_CRYPTO_SM4_RISCV64 is not set
+# CONFIG_CRYPTO_STREEBOG is not set
+# CONFIG_CRYPTO_TEA is not set
+# CONFIG_CRYPTO_TEST is not set
+# CONFIG_CRYPTO_TWOFISH is not set
+# CONFIG_CRYPTO_TWOFISH_586 is not set
+# CONFIG_CRYPTO_TWOFISH_COMMON is not set
+# CONFIG_CRYPTO_USER is not set
+# CONFIG_CRYPTO_USER_API_AEAD is not set
+# CONFIG_CRYPTO_USER_API_ENABLE_OBSOLETE is not set
+# CONFIG_CRYPTO_USER_API_HASH is not set
+# CONFIG_CRYPTO_USER_API_RNG is not set
+# CONFIG_CRYPTO_USER_API_RNG_CAVP is not set
+# CONFIG_CRYPTO_USER_API_SKCIPHER is not set
+# CONFIG_CRYPTO_VMAC is not set
+# CONFIG_CRYPTO_WP512 is not set
+# CONFIG_CRYPTO_XCBC is not set
+# CONFIG_CRYPTO_XTS is not set
+# CONFIG_CRYPTO_XXHASH is not set
+# CONFIG_CRYPTO_ZSTD is not set
+# CONFIG_CS5535_MFGPT is not set
+# CONFIG_CS89x0 is not set
+# CONFIG_CS89x0_PLATFORM is not set
+# CONFIG_CSD_LOCK_WAIT_DEBUG is not set
+# CONFIG_CUSE is not set
+# CONFIG_CW1200 is not set
+# CONFIG_CXD2880_SPI_DRV is not set
+# CONFIG_CXL_BASE is not set
+# CONFIG_CXL_BUS is not set
+# CONFIG_CYPRESS_FIRMWARE is not set
+# CONFIG_CZNIC_PLATFORMS is not set
+# CONFIG_DA280 is not set
+# CONFIG_DA311 is not set
+# CONFIG_DAMON is not set
+# CONFIG_DAVICOM_PHY is not set
+# CONFIG_DAX is not set
+# CONFIG_DCB is not set
+# CONFIG_DDR is not set
+# CONFIG_DEBUG_ALIGN_RODATA is not set
+# CONFIG_DEBUG_ATOMIC_SLEEP is not set
+# CONFIG_DEBUG_BUGVERBOSE is not set
+# CONFIG_DEBUG_CGROUP_REF is not set
+# CONFIG_DEBUG_DEVRES is not set
+# CONFIG_DEBUG_DRIVER is not set
+# CONFIG_DEBUG_EFI is not set
+# CONFIG_DEBUG_FORCE_FUNCTION_ALIGN_64B is not set
+# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set
+CONFIG_DEBUG_FS=y
+CONFIG_DEBUG_FS_ALLOW_ALL=y
+# CONFIG_DEBUG_FS_ALLOW_NONE is not set
+# CONFIG_DEBUG_FS_DISALLOW_MOUNT is not set
+# CONFIG_DEBUG_GPIO is not set
+# CONFIG_DEBUG_HIGHMEM is not set
+# CONFIG_DEBUG_ICEDCC is not set
+# CONFIG_DEBUG_INFO is not set
+# CONFIG_DEBUG_INFO_BTF is not set
+CONFIG_DEBUG_INFO_COMPRESSED_NONE=y
+# CONFIG_DEBUG_INFO_COMPRESSED_ZLIB is not set
+# CONFIG_DEBUG_INFO_COMPRESSED_ZSTD is not set
+# CONFIG_DEBUG_INFO_DWARF4 is not set
+# CONFIG_DEBUG_INFO_DWARF5 is not set
+CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
+# CONFIG_DEBUG_INFO_NONE is not set
+# CONFIG_DEBUG_INFO_REDUCED is not set
+# CONFIG_DEBUG_INFO_SPLIT is not set
+# CONFIG_DEBUG_IRQFLAGS is not set
+CONFIG_DEBUG_KERNEL=y
+# CONFIG_DEBUG_KMAP_LOCAL is not set
+# CONFIG_DEBUG_KMEMLEAK is not set
+# CONFIG_DEBUG_KOBJECT is not set
+# CONFIG_DEBUG_KOBJECT_RELEASE is not set
+# CONFIG_DEBUG_LIST is not set
+# CONFIG_DEBUG_LL is not set
+# CONFIG_DEBUG_LL_UART_8250 is not set
+# CONFIG_DEBUG_LL_UART_PL01X is not set
+# CONFIG_DEBUG_LOCKDEP is not set
+# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set
+# CONFIG_DEBUG_LOCK_ALLOC is not set
+# CONFIG_DEBUG_MAPLE_TREE is not set
+# CONFIG_DEBUG_MEMORY_INIT is not set
+# CONFIG_DEBUG_MISC is not set
+# CONFIG_DEBUG_MUTEXES is not set
+# CONFIG_DEBUG_NET is not set
+# CONFIG_DEBUG_NOTIFIERS is not set
+# CONFIG_DEBUG_OBJECTS is not set
+# CONFIG_DEBUG_PAGEALLOC is not set
+# CONFIG_DEBUG_PAGE_REF is not set
+# CONFIG_DEBUG_PERF_USE_VMALLOC is not set
+# CONFIG_DEBUG_PER_CPU_MAPS is not set
+# CONFIG_DEBUG_PINCTRL is not set
+# CONFIG_DEBUG_PLIST is not set
+# CONFIG_DEBUG_PREEMPT is not set
+# CONFIG_DEBUG_RODATA_TEST is not set
+# CONFIG_DEBUG_RSEQ is not set
+# CONFIG_DEBUG_RT_MUTEXES is not set
+# CONFIG_DEBUG_RWSEMS is not set
+# CONFIG_DEBUG_SECTION_MISMATCH is not set
+# CONFIG_DEBUG_SEMIHOSTING is not set
+# CONFIG_DEBUG_SG is not set
+# CONFIG_DEBUG_SHIRQ is not set
+# CONFIG_DEBUG_SPINLOCK is not set
+# CONFIG_DEBUG_STACKOVERFLOW is not set
+# CONFIG_DEBUG_STACK_USAGE is not set
+# CONFIG_DEBUG_TEST_DRIVER_REMOVE is not set
+# CONFIG_DEBUG_UART_8250_PALMCHIP is not set
+# CONFIG_DEBUG_UART_8250_WORD is not set
+# CONFIG_DEBUG_UART_FLOW_CONTROL is not set
+# CONFIG_DEBUG_USER is not set
+# CONFIG_DEBUG_VIRTUAL is not set
+# CONFIG_DEBUG_VM is not set
+# CONFIG_DEBUG_VM_MAPLE_TREE is not set
+# CONFIG_DEBUG_VM_PGFLAGS is not set
+# CONFIG_DEBUG_VM_PGTABLE is not set
+# CONFIG_DEBUG_VM_RB is not set
+# CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set
+# CONFIG_DEBUG_WW_MUTEX_SLOWPATH is not set
+# CONFIG_DEBUG_WX is not set
+# CONFIG_DEBUG_ZBOOT is not set
+# CONFIG_DEFAULT_CODEL is not set
+CONFIG_DEFAULT_CUBIC=y
+# CONFIG_DEFAULT_FQ is not set
+CONFIG_DEFAULT_FQ_CODEL=y
+# CONFIG_DEFAULT_FQ_PIE is not set
+CONFIG_DEFAULT_HOSTNAME="(none)"
+CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120
+CONFIG_DEFAULT_INIT=""
+CONFIG_DEFAULT_MMAP_MIN_ADDR=4096
+CONFIG_DEFAULT_NET_SCH="fq_codel"
+# CONFIG_DEFAULT_PFIFO_FAST is not set
+# CONFIG_DEFAULT_RENO is not set
+CONFIG_DEFAULT_SECURITY_DAC=y
+# CONFIG_DEFAULT_SECURITY_SELINUX is not set
+# CONFIG_DEFAULT_SFQ is not set
+CONFIG_DEFAULT_TCP_CONG="cubic"
+# CONFIG_DEFERRED_STRUCT_PAGE_INIT is not set
+# CONFIG_DELL_LAPTOP is not set
+# CONFIG_DELL_RBTN is not set
+# CONFIG_DELL_SMBIOS is not set
+# CONFIG_DELL_SMO8800 is not set
+# CONFIG_DEPRECATED_PARAM_STRUCT is not set
+# CONFIG_DETECT_HUNG_TASK is not set
+# CONFIG_DEVMEM is not set
+CONFIG_DEVPORT=y
+# CONFIG_DEVTMPFS is not set
+# CONFIG_DEVTMPFS_MOUNT is not set
+# CONFIG_DEVTMPFS_SAFE is not set
+# CONFIG_DEV_DAX is not set
+# CONFIG_DHT11 is not set
+CONFIG_DIMLIB=y
+# CONFIG_DL2K is not set
+# CONFIG_DLHL60D is not set
+# CONFIG_DLM is not set
+# CONFIG_DM9000 is not set
+# CONFIG_DM9051 is not set
+# CONFIG_DMABUF_DEBUG is not set
+# CONFIG_DMABUF_HEAPS is not set
+# CONFIG_DMABUF_MOVE_NOTIFY is not set
+# CONFIG_DMABUF_SELFTESTS is not set
+# CONFIG_DMABUF_SYSFS_STATS is not set
+# CONFIG_DMADEVICES is not set
+# CONFIG_DMADEVICES_DEBUG is not set
+# CONFIG_DMAPOOL_TEST is not set
+# CONFIG_DMARD06 is not set
+# CONFIG_DMARD09 is not set
+# CONFIG_DMARD10 is not set
+# CONFIG_DMATEST is not set
+# CONFIG_DMA_API_DEBUG is not set
+CONFIG_DMA_COHERENT_POOL=y
+CONFIG_DMA_DECLARE_COHERENT=y
+# CONFIG_DMA_ENGINE is not set
+# CONFIG_DMA_FENCE_TRACE is not set
+# CONFIG_DMA_JZ4780 is not set
+# CONFIG_DMA_MAP_BENCHMARK is not set
+CONFIG_DMA_NONCOHERENT_MMAP=y
+# CONFIG_DMA_RESTRICTED_POOL is not set
+# CONFIG_DMA_SHARED_BUFFER is not set
+# CONFIG_DM_AUDIT is not set
+# CONFIG_DM_CACHE is not set
+# CONFIG_DM_CLONE is not set
+# CONFIG_DM_DEBUG is not set
+# CONFIG_DM_DELAY is not set
+# CONFIG_DM_DUST is not set
+# CONFIG_DM_EBS is not set
+# CONFIG_DM_ERA is not set
+# CONFIG_DM_FLAKEY is not set
+# CONFIG_DM_INTEGRITY is not set
+# CONFIG_DM_LOG_USERSPACE is not set
+# CONFIG_DM_LOG_WRITES is not set
+# CONFIG_DM_MULTIPATH is not set
+# CONFIG_DM_RAID is not set
+# CONFIG_DM_SWITCH is not set
+# CONFIG_DM_THIN_PROVISIONING is not set
+# CONFIG_DM_UEVENT is not set
+# CONFIG_DM_UNSTRIPED is not set
+# CONFIG_DM_VDO is not set
+# CONFIG_DM_VERITY is not set
+# CONFIG_DM_WRITECACHE is not set
+# CONFIG_DM_ZERO is not set
+# CONFIG_DM_ZONED is not set
+# CONFIG_DNET is not set
+# CONFIG_DNOTIFY is not set
+# CONFIG_DNS_RESOLVER is not set
+# CONFIG_DP83640_PHY is not set
+# CONFIG_DP83822_PHY is not set
+# CONFIG_DP83848_PHY is not set
+# CONFIG_DP83867_PHY is not set
+# CONFIG_DP83869_PHY is not set
+# CONFIG_DP83TC811_PHY is not set
+# CONFIG_DP83TD510_PHY is not set
+# CONFIG_DP83TG720_PHY is not set
+# CONFIG_DPM_WATCHDOG is not set
+# CONFIG_DPOT_DAC is not set
+# CONFIG_DPS310 is not set
+CONFIG_DQL=y
+# CONFIG_DRAGONRISE_FF is not set
+# CONFIG_DRM is not set
+# CONFIG_DRM_ACCEL is not set
+# CONFIG_DRM_ACCEL_HABANALABS is not set
+# CONFIG_DRM_ACCEL_IVPU is not set
+# CONFIG_DRM_ACCEL_QAIC is not set
+# CONFIG_DRM_AMDGPU is not set
+# CONFIG_DRM_AMDGPU_CIK is not set
+# CONFIG_DRM_AMDGPU_SI is not set
+# CONFIG_DRM_AMDGPU_USERPTR is not set
+# CONFIG_DRM_AMDGPU_WERROR is not set
+# CONFIG_DRM_AMD_ACP is not set
+# CONFIG_DRM_AMD_DC_SI is not set
+# CONFIG_DRM_AMD_ISP is not set
+# CONFIG_DRM_AMD_SECURE_DISPLAY is not set
+# CONFIG_DRM_ANALOGIX_ANX6345 is not set
+# CONFIG_DRM_ANALOGIX_ANX7625 is not set
+# CONFIG_DRM_ANALOGIX_ANX78XX is not set
+# CONFIG_DRM_ARCPGU is not set
+# CONFIG_DRM_ARMADA is not set
+# CONFIG_DRM_AST is not set
+# CONFIG_DRM_ATMEL_HLCDC is not set
+# CONFIG_DRM_BOCHS is not set
+# CONFIG_DRM_CDNS_DSI is not set
+# CONFIG_DRM_CDNS_MHDP8546 is not set
+# CONFIG_DRM_CHIPONE_ICN6211 is not set
+# CONFIG_DRM_CHRONTEL_CH7033 is not set
+# CONFIG_DRM_CIRRUS_QEMU is not set
+# CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS is not set
+# CONFIG_DRM_DEBUG_MM is not set
+# CONFIG_DRM_DEBUG_MODESET_LOCK is not set
+# CONFIG_DRM_DISPLAY_CONNECTOR is not set
+# CONFIG_DRM_DISPLAY_DP_AUX_CEC is not set
+# CONFIG_DRM_DISPLAY_DP_AUX_CHARDEV is not set
+# CONFIG_DRM_DW_HDMI_CEC is not set
+# CONFIG_DRM_ETNAVIV is not set
+# CONFIG_DRM_EXYNOS is not set
+# CONFIG_DRM_FBDEV_EMULATION is not set
+# CONFIG_DRM_FBDEV_LEAK_PHYS_SMEM is not set
+# CONFIG_DRM_FSL_DCU is not set
+# CONFIG_DRM_GM12U320 is not set
+# CONFIG_DRM_GMA500 is not set
+# CONFIG_DRM_GUD is not set
+# CONFIG_DRM_HDLCD is not set
+# CONFIG_DRM_HISI_HIBMC is not set
+# CONFIG_DRM_HISI_KIRIN is not set
+# CONFIG_DRM_I2C_ADV7511 is not set
+# CONFIG_DRM_I2C_CH7006 is not set
+# CONFIG_DRM_I2C_NXP_TDA9950 is not set
+# CONFIG_DRM_I2C_NXP_TDA998X is not set
+# CONFIG_DRM_I2C_SIL164 is not set
+# CONFIG_DRM_I915 is not set
+# CONFIG_DRM_I915_DEBUG_WAKEREF is not set
+# CONFIG_DRM_I915_GVT_KVMGT is not set
+# CONFIG_DRM_I915_REPLAY_GPU_HANGS_API is not set
+# CONFIG_DRM_IMX_LCDIF is not set
+# CONFIG_DRM_ITE_IT6505 is not set
+# CONFIG_DRM_ITE_IT66121 is not set
+# CONFIG_DRM_KOMEDA is not set
+# CONFIG_DRM_LIB_RANDOM is not set
+# CONFIG_DRM_LIMA is not set
+# CONFIG_DRM_LOAD_EDID_FIRMWARE is not set
+# CONFIG_DRM_LOGICVC is not set
+# CONFIG_DRM_LONTIUM_LT8912B is not set
+# CONFIG_DRM_LONTIUM_LT9211 is not set
+# CONFIG_DRM_LONTIUM_LT9611 is not set
+# CONFIG_DRM_LONTIUM_LT9611UXC is not set
+# CONFIG_DRM_LOONGSON is not set
+# CONFIG_DRM_LVDS_CODEC is not set
+# CONFIG_DRM_MALI_DISPLAY is not set
+# CONFIG_DRM_MCDE is not set
+# CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW is not set
+# CONFIG_DRM_MGAG200 is not set
+# CONFIG_DRM_MXSFB is not set
+# CONFIG_DRM_NOUVEAU is not set
+# CONFIG_DRM_NWL_MIPI_DSI is not set
+# CONFIG_DRM_NXP_PTN3460 is not set
+# CONFIG_DRM_OFDRM is not set
+# CONFIG_DRM_OMAP is not set
+# CONFIG_DRM_PANEL_ABT_Y030XX067A is not set
+# CONFIG_DRM_PANEL_ARM_VERSATILE is not set
+# CONFIG_DRM_PANEL_ASUS_Z00T_TM5P5_NT35596 is not set
+# CONFIG_DRM_PANEL_AUO_A030JTN01 is not set
+# CONFIG_DRM_PANEL_BOE_BF060Y8M_AJ0 is not set
+# CONFIG_DRM_PANEL_BOE_HIMAX8279D is not set
+# CONFIG_DRM_PANEL_BOE_TH101MB31UIG002_28A is not set
+# CONFIG_DRM_PANEL_BOE_TV101WUM_LL2 is not set
+# CONFIG_DRM_PANEL_BOE_TV101WUM_NL6 is not set
+# CONFIG_DRM_PANEL_DSI_CM is not set
+# CONFIG_DRM_PANEL_EBBG_FT8719 is not set
+# CONFIG_DRM_PANEL_EDP is not set
+# CONFIG_DRM_PANEL_ELIDA_KD35T133 is not set
+# CONFIG_DRM_PANEL_FEIXIN_K101_IM2BA02 is not set
+# CONFIG_DRM_PANEL_FEIYANG_FY07024DI26A30D is not set
+# CONFIG_DRM_PANEL_HIMAX_HX83102 is not set
+# CONFIG_DRM_PANEL_HIMAX_HX83112A is not set
+# CONFIG_DRM_PANEL_HIMAX_HX8394 is not set
+# CONFIG_DRM_PANEL_ILITEK_IL9322 is not set
+# CONFIG_DRM_PANEL_ILITEK_ILI9341 is not set
+# CONFIG_DRM_PANEL_ILITEK_ILI9805 is not set
+# CONFIG_DRM_PANEL_ILITEK_ILI9806E is not set
+# CONFIG_DRM_PANEL_ILITEK_ILI9881C is not set
+# CONFIG_DRM_PANEL_ILITEK_ILI9882T is not set
+# CONFIG_DRM_PANEL_INNOLUX_EJ030NA is not set
+# CONFIG_DRM_PANEL_INNOLUX_P079ZCA is not set
+# CONFIG_DRM_PANEL_JADARD_JD9365DA_H3 is not set
+# CONFIG_DRM_PANEL_JDI_LPM102A188A is not set
+# CONFIG_DRM_PANEL_JDI_LT070ME05000 is not set
+# CONFIG_DRM_PANEL_JDI_R63452 is not set
+# CONFIG_DRM_PANEL_KHADAS_TS050 is not set
+# CONFIG_DRM_PANEL_KINGDISPLAY_KD097D04 is not set
+# CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W is not set
+# CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829 is not set
+# CONFIG_DRM_PANEL_LG_LB035Q02 is not set
+# CONFIG_DRM_PANEL_LG_LG4573 is not set
+# CONFIG_DRM_PANEL_LG_SW43408 is not set
+# CONFIG_DRM_PANEL_LINCOLNTECH_LCD197 is not set
+# CONFIG_DRM_PANEL_LVDS is not set
+# CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966 is not set
+# CONFIG_DRM_PANEL_MANTIX_MLAF057WE51 is not set
+# CONFIG_DRM_PANEL_MIPI_DBI is not set
+# CONFIG_DRM_PANEL_NEC_NL8048HL11 is not set
+# CONFIG_DRM_PANEL_NEWVISION_NV3051D is not set
+# CONFIG_DRM_PANEL_NEWVISION_NV3052C is not set
+# CONFIG_DRM_PANEL_NOVATEK_NT35510 is not set
+# CONFIG_DRM_PANEL_NOVATEK_NT35560 is not set
+# CONFIG_DRM_PANEL_NOVATEK_NT35950 is not set
+# CONFIG_DRM_PANEL_NOVATEK_NT36523 is not set
+# CONFIG_DRM_PANEL_NOVATEK_NT36672A is not set
+# CONFIG_DRM_PANEL_NOVATEK_NT36672E is not set
+# CONFIG_DRM_PANEL_NOVATEK_NT39016 is not set
+# CONFIG_DRM_PANEL_OLIMEX_LCD_OLINUXINO is not set
+# CONFIG_DRM_PANEL_ORISETECH_OTA5601A is not set
+# CONFIG_DRM_PANEL_ORISETECH_OTM8009A is not set
+# CONFIG_DRM_PANEL_OSD_OSD101T2587_53TS is not set
+# CONFIG_DRM_PANEL_PANASONIC_VVX10F034N00 is not set
+# CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN is not set
+# CONFIG_DRM_PANEL_RAYDIUM_RM67191 is not set
+# CONFIG_DRM_PANEL_RAYDIUM_RM68200 is not set
+# CONFIG_DRM_PANEL_RAYDIUM_RM692E5 is not set
+# CONFIG_DRM_PANEL_RAYDIUM_RM69380 is not set
+# CONFIG_DRM_PANEL_RONBO_RB070D30 is not set
+# CONFIG_DRM_PANEL_SAMSUNG_ATNA33XC20 is not set
+# CONFIG_DRM_PANEL_SAMSUNG_DB7430 is not set
+# CONFIG_DRM_PANEL_SAMSUNG_LD9040 is not set
+# CONFIG_DRM_PANEL_SAMSUNG_S6D16D0 is not set
+# CONFIG_DRM_PANEL_SAMSUNG_S6D27A1 is not set
+# CONFIG_DRM_PANEL_SAMSUNG_S6D7AA0 is not set
+# CONFIG_DRM_PANEL_SAMSUNG_S6E3FA7 is not set
+# CONFIG_DRM_PANEL_SAMSUNG_S6E3HA2 is not set
+# CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03 is not set
+# CONFIG_DRM_PANEL_SAMSUNG_S6E63M0 is not set
+# CONFIG_DRM_PANEL_SAMSUNG_S6E88A0_AMS452EF01 is not set
+# CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0 is not set
+# CONFIG_DRM_PANEL_SAMSUNG_SOFEF00 is not set
+# CONFIG_DRM_PANEL_SEIKO_43WVF1G is not set
+# CONFIG_DRM_PANEL_SHARP_LQ101R1SX01 is not set
+# CONFIG_DRM_PANEL_SHARP_LS037V7DW01 is not set
+# CONFIG_DRM_PANEL_SHARP_LS043T1LE01 is not set
+# CONFIG_DRM_PANEL_SHARP_LS060T1SX01 is not set
+# CONFIG_DRM_PANEL_SIMPLE is not set
+# CONFIG_DRM_PANEL_SITRONIX_ST7701 is not set
+# CONFIG_DRM_PANEL_SITRONIX_ST7703 is not set
+# CONFIG_DRM_PANEL_SITRONIX_ST7789V is not set
+# CONFIG_DRM_PANEL_SONY_ACX565AKM is not set
+# CONFIG_DRM_PANEL_SONY_TD4353_JDI is not set
+# CONFIG_DRM_PANEL_SONY_TULIP_TRULY_NT35521 is not set
+# CONFIG_DRM_PANEL_STARTEK_KD070FHFID015 is not set
+# CONFIG_DRM_PANEL_SYNAPTICS_R63353 is not set
+# CONFIG_DRM_PANEL_TDO_TL070WSH30 is not set
+# CONFIG_DRM_PANEL_TPO_TD028TTEC1 is not set
+# CONFIG_DRM_PANEL_TPO_TD043MTEA1 is not set
+# CONFIG_DRM_PANEL_TPO_TPG110 is not set
+# CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA is not set
+# CONFIG_DRM_PANEL_VISIONOX_R66451 is not set
+# CONFIG_DRM_PANEL_VISIONOX_RM69299 is not set
+# CONFIG_DRM_PANEL_VISIONOX_VTDR6130 is not set
+# CONFIG_DRM_PANEL_WIDECHIPS_WS2401 is not set
+# CONFIG_DRM_PANEL_XINPENG_XPP055C272 is not set
+# CONFIG_DRM_PANFROST is not set
+# CONFIG_DRM_PANIC is not set
+# CONFIG_DRM_PANTHOR is not set
+# CONFIG_DRM_PARADE_PS8622 is not set
+# CONFIG_DRM_PARADE_PS8640 is not set
+# CONFIG_DRM_PL111 is not set
+# CONFIG_DRM_POWERVR is not set
+# CONFIG_DRM_QXL is not set
+# CONFIG_DRM_RADEON is not set
+# CONFIG_DRM_RADEON_USERPTR is not set
+# CONFIG_DRM_RCAR_DW_HDMI is not set
+# CONFIG_DRM_RCAR_LVDS is not set
+# CONFIG_DRM_RCAR_USE_LVDS is not set
+# CONFIG_DRM_RCAR_USE_MIPI_DSI is not set
+# CONFIG_DRM_ROCKCHIP is not set
+# CONFIG_DRM_SAMSUNG_DSIM is not set
+# CONFIG_DRM_SII902X is not set
+# CONFIG_DRM_SII9234 is not set
+# CONFIG_DRM_SIL_SII8620 is not set
+# CONFIG_DRM_SIMPLEDRM is not set
+# CONFIG_DRM_SIMPLE_BRIDGE is not set
+# CONFIG_DRM_SSD130X is not set
+# CONFIG_DRM_STI is not set
+# CONFIG_DRM_STM is not set
+# CONFIG_DRM_SUN4I is not set
+# CONFIG_DRM_THINE_THC63LVD1024 is not set
+# CONFIG_DRM_TIDSS is not set
+# CONFIG_DRM_TILCDC is not set
+# CONFIG_DRM_TI_DLPC3433 is not set
+# CONFIG_DRM_TI_SN65DSI83 is not set
+# CONFIG_DRM_TI_SN65DSI86 is not set
+# CONFIG_DRM_TI_TFP410 is not set
+# CONFIG_DRM_TI_TPD12S015 is not set
+# CONFIG_DRM_TOSHIBA_TC358762 is not set
+# CONFIG_DRM_TOSHIBA_TC358764 is not set
+# CONFIG_DRM_TOSHIBA_TC358767 is not set
+# CONFIG_DRM_TOSHIBA_TC358768 is not set
+# CONFIG_DRM_TOSHIBA_TC358775 is not set
+# CONFIG_DRM_TVE200 is not set
+# CONFIG_DRM_UDL is not set
+# CONFIG_DRM_V3D is not set
+# CONFIG_DRM_VBOXVIDEO is not set
+# CONFIG_DRM_VC4_HDMI_CEC is not set
+# CONFIG_DRM_VGEM is not set
+# CONFIG_DRM_VIRTIO_GPU is not set
+# CONFIG_DRM_VKMS is not set
+# CONFIG_DRM_VMWGFX is not set
+# CONFIG_DRM_WERROR is not set
+# CONFIG_DRM_XE is not set
+# CONFIG_DRM_XEN is not set
+# CONFIG_DRM_XEN_FRONTEND is not set
+# CONFIG_DS1682 is not set
+# CONFIG_DS1803 is not set
+# CONFIG_DS4424 is not set
+# CONFIG_DST_CACHE is not set
+# CONFIG_DTLK is not set
+# CONFIG_DUMMY is not set
+CONFIG_DUMMY_CONSOLE_COLUMNS=80
+CONFIG_DUMMY_CONSOLE_ROWS=25
+# CONFIG_DUMMY_IRQ is not set
+# CONFIG_DVB_A8293 is not set
+# CONFIG_DVB_AF9013 is not set
+# CONFIG_DVB_AF9033 is not set
+# CONFIG_DVB_AS102 is not set
+# CONFIG_DVB_ASCOT2E is not set
+# CONFIG_DVB_ATBM8830 is not set
+# CONFIG_DVB_AU8522_DTV is not set
+# CONFIG_DVB_AU8522_V4L is not set
+# CONFIG_DVB_B2C2_FLEXCOP_PCI is not set
+# CONFIG_DVB_B2C2_FLEXCOP_USB is not set
+# CONFIG_DVB_BCM3510 is not set
+# CONFIG_DVB_CORE is not set
+# CONFIG_DVB_CX22700 is not set
+# CONFIG_DVB_CX22702 is not set
+# CONFIG_DVB_CX24110 is not set
+# CONFIG_DVB_CX24116 is not set
+# CONFIG_DVB_CX24117 is not set
+# CONFIG_DVB_CX24120 is not set
+# CONFIG_DVB_CX24123 is not set
+# CONFIG_DVB_CXD2099 is not set
+# CONFIG_DVB_CXD2820R is not set
+# CONFIG_DVB_CXD2841ER is not set
+# CONFIG_DVB_CXD2880 is not set
+# CONFIG_DVB_DDBRIDGE is not set
+# CONFIG_DVB_DEMUX_SECTION_LOSS_LOG is not set
+# CONFIG_DVB_DIB3000MB is not set
+# CONFIG_DVB_DIB3000MC is not set
+# CONFIG_DVB_DIB7000M is not set
+# CONFIG_DVB_DIB7000P is not set
+# CONFIG_DVB_DIB8000 is not set
+# CONFIG_DVB_DIB9000 is not set
+# CONFIG_DVB_DRX39XYJ is not set
+# CONFIG_DVB_DRXD is not set
+# CONFIG_DVB_DRXK is not set
+# CONFIG_DVB_DS3000 is not set
+# CONFIG_DVB_DUMMY_FE is not set
+# CONFIG_DVB_DYNAMIC_MINORS is not set
+# CONFIG_DVB_EC100 is not set
+# CONFIG_DVB_FIREDTV is not set
+# CONFIG_DVB_HELENE is not set
+# CONFIG_DVB_HORUS3A is not set
+# CONFIG_DVB_ISL6405 is not set
+# CONFIG_DVB_ISL6421 is not set
+# CONFIG_DVB_ISL6423 is not set
+# CONFIG_DVB_IX2505V is not set
+# CONFIG_DVB_L64781 is not set
+# CONFIG_DVB_LG2160 is not set
+# CONFIG_DVB_LGDT3305 is not set
+# CONFIG_DVB_LGDT3306A is not set
+# CONFIG_DVB_LGDT330X is not set
+# CONFIG_DVB_LGS8GL5 is not set
+# CONFIG_DVB_LGS8GXX is not set
+# CONFIG_DVB_LNBH25 is not set
+# CONFIG_DVB_LNBH29 is not set
+# CONFIG_DVB_LNBP21 is not set
+# CONFIG_DVB_LNBP22 is not set
+# CONFIG_DVB_M88DS3103 is not set
+# CONFIG_DVB_M88RS2000 is not set
+CONFIG_DVB_MAX_ADAPTERS=16
+# CONFIG_DVB_MB86A16 is not set
+# CONFIG_DVB_MB86A20S is not set
+# CONFIG_DVB_MMAP is not set
+# CONFIG_DVB_MN88443X is not set
+# CONFIG_DVB_MN88472 is not set
+# CONFIG_DVB_MN88473 is not set
+# CONFIG_DVB_MT312 is not set
+# CONFIG_DVB_MT352 is not set
+# CONFIG_DVB_MXL5XX is not set
+# CONFIG_DVB_MXL692 is not set
+# CONFIG_DVB_NET is not set
+# CONFIG_DVB_NETUP_UNIDVB is not set
+# CONFIG_DVB_NGENE is not set
+# CONFIG_DVB_NXT200X is not set
+# CONFIG_DVB_NXT6000 is not set
+# CONFIG_DVB_OR51132 is not set
+# CONFIG_DVB_OR51211 is not set
+# CONFIG_DVB_PLATFORM_DRIVERS is not set
+# CONFIG_DVB_PLL is not set
+# CONFIG_DVB_PLUTO2 is not set
+# CONFIG_DVB_PT1 is not set
+# CONFIG_DVB_PT3 is not set
+# CONFIG_DVB_RTL2830 is not set
+# CONFIG_DVB_RTL2832 is not set
+# CONFIG_DVB_RTL2832_SDR is not set
+# CONFIG_DVB_S5H1409 is not set
+# CONFIG_DVB_S5H1411 is not set
+# CONFIG_DVB_S5H1420 is not set
+# CONFIG_DVB_S5H1432 is not set
+# CONFIG_DVB_S921 is not set
+# CONFIG_DVB_SI2165 is not set
+# CONFIG_DVB_SI2168 is not set
+# CONFIG_DVB_SI21XX is not set
+# CONFIG_DVB_SP2 is not set
+# CONFIG_DVB_SP8870 is not set
+# CONFIG_DVB_SP887X is not set
+# CONFIG_DVB_STB0899 is not set
+# CONFIG_DVB_STB6000 is not set
+# CONFIG_DVB_STB6100 is not set
+# CONFIG_DVB_STV0288 is not set
+# CONFIG_DVB_STV0297 is not set
+# CONFIG_DVB_STV0299 is not set
+# CONFIG_DVB_STV0367 is not set
+# CONFIG_DVB_STV0900 is not set
+# CONFIG_DVB_STV090x is not set
+# CONFIG_DVB_STV0910 is not set
+# CONFIG_DVB_STV6110 is not set
+# CONFIG_DVB_STV6110x is not set
+# CONFIG_DVB_STV6111 is not set
+# CONFIG_DVB_TC90522 is not set
+# CONFIG_DVB_TDA10021 is not set
+# CONFIG_DVB_TDA10023 is not set
+# CONFIG_DVB_TDA10048 is not set
+# CONFIG_DVB_TDA1004X is not set
+# CONFIG_DVB_TDA10071 is not set
+# CONFIG_DVB_TDA10086 is not set
+# CONFIG_DVB_TDA18271C2DD is not set
+# CONFIG_DVB_TDA665x is not set
+# CONFIG_DVB_TDA8083 is not set
+# CONFIG_DVB_TDA8261 is not set
+# CONFIG_DVB_TDA826X is not set
+# CONFIG_DVB_TEST_DRIVERS is not set
+# CONFIG_DVB_TS2020 is not set
+# CONFIG_DVB_TTUSB_BUDGET is not set
+# CONFIG_DVB_TTUSB_DEC is not set
+# CONFIG_DVB_TUA6100 is not set
+# CONFIG_DVB_TUNER_CX24113 is not set
+# CONFIG_DVB_TUNER_DIB0070 is not set
+# CONFIG_DVB_TUNER_DIB0090 is not set
+# CONFIG_DVB_TUNER_ITD1000 is not set
+# CONFIG_DVB_ULE_DEBUG is not set
+# CONFIG_DVB_USB is not set
+# CONFIG_DVB_USB_V2 is not set
+# CONFIG_DVB_VES1820 is not set
+# CONFIG_DVB_VES1X93 is not set
+# CONFIG_DVB_ZD1301_DEMOD is not set
+# CONFIG_DVB_ZL10036 is not set
+# CONFIG_DVB_ZL10039 is not set
+# CONFIG_DVB_ZL10353 is not set
+# CONFIG_DWC_PCIE_PMU is not set
+# CONFIG_DWC_XLGMAC is not set
+# CONFIG_DWMAC_DWC_QOS_ETH is not set
+# CONFIG_DWMAC_INTEL_PLAT is not set
+# CONFIG_DWMAC_IPQ806X is not set
+# CONFIG_DWMAC_LOONGSON is not set
+# CONFIG_DWMAC_LPC18XX is not set
+# CONFIG_DWMAC_MESON is not set
+# CONFIG_DWMAC_ROCKCHIP is not set
+# CONFIG_DWMAC_SOCFPGA is not set
+# CONFIG_DWMAC_STI is not set
+# CONFIG_DW_AXI_DMAC is not set
+# CONFIG_DW_DMAC is not set
+# CONFIG_DW_DMAC_PCI is not set
+# CONFIG_DW_EDMA is not set
+# CONFIG_DW_EDMA_PCIE is not set
+# CONFIG_DW_WATCHDOG is not set
+# CONFIG_DW_XDATA_PCIE is not set
+# CONFIG_DYNAMIC_DEBUG is not set
+# CONFIG_DYNAMIC_DEBUG_CORE is not set
+# CONFIG_E100 is not set
+# CONFIG_E1000 is not set
+# CONFIG_E1000E is not set
+# CONFIG_E1000E_HWTS is not set
+# CONFIG_EARLY_PRINTK_8250 is not set
+# CONFIG_EARLY_PRINTK_USB_XDBC is not set
+# CONFIG_EBC_C384_WDT is not set
+# CONFIG_ECHO is not set
+# CONFIG_ECRYPT_FS is not set
+# CONFIG_EC_LENOVO_YOGA_C630 is not set
+# CONFIG_EDAC is not set
+# CONFIG_EEEPC_LAPTOP is not set
+# CONFIG_EEPROM_93CX6 is not set
+# CONFIG_EEPROM_93XX46 is not set
+# CONFIG_EEPROM_AT24 is not set
+# CONFIG_EEPROM_AT25 is not set
+# CONFIG_EEPROM_DIGSY_MTC_CFG is not set
+# CONFIG_EEPROM_EE1004 is not set
+# CONFIG_EEPROM_IDT_89HPESX is not set
+# CONFIG_EEPROM_MAX6875 is not set
+# CONFIG_EFI is not set
+CONFIG_EFI_PARTITION=y
+# CONFIG_EFI_VARS_PSTORE is not set
+# CONFIG_EFS_FS is not set
+CONFIG_ELFCORE=y
+# CONFIG_ELF_CORE is not set
+# CONFIG_EMAC_ROCKCHIP is not set
+# CONFIG_EM_TIMER_STI is not set
+# CONFIG_ENA_ETHERNET is not set
+# CONFIG_ENC28J60 is not set
+# CONFIG_ENCLOSURE_SERVICES is not set
+# CONFIG_ENCRYPTED_KEYS is not set
+# CONFIG_ENCX24J600 is not set
+# CONFIG_ENERGY_MODEL is not set
+# CONFIG_ENIC is not set
+# CONFIG_ENS160 is not set
+# CONFIG_ENS210 is not set
+# CONFIG_ENVELOPE_DETECTOR is not set
+# CONFIG_EPAPR_PARAVIRT is not set
+# CONFIG_EPIC100 is not set
+CONFIG_EPOLL=y
+# CONFIG_EQUALIZER is not set
+# CONFIG_EROFS_FS is not set
+# CONFIG_EROFS_FS_BACKED_BY_FILE is not set
+# CONFIG_EROFS_FS_DEBUG is not set
+# CONFIG_EROFS_FS_ONDEMAND is not set
+CONFIG_EROFS_FS_PCPU_KTHREAD=y
+CONFIG_EROFS_FS_PCPU_KTHREAD_HIPRI=y
+# CONFIG_EROFS_FS_POSIX_ACL is not set
+# CONFIG_EROFS_FS_SECURITY is not set
+# CONFIG_EROFS_FS_XATTR is not set
+# CONFIG_EROFS_FS_ZIP is not set
+# CONFIG_EROFS_FS_ZIP_DEFLATE is not set
+# CONFIG_EROFS_FS_ZIP_ZSTD is not set
+# CONFIG_ET131X is not set
+CONFIG_ETHERNET=y
+# CONFIG_ETHOC is not set
+CONFIG_ETHTOOL_NETLINK=y
+CONFIG_EVENTFD=y
+# CONFIG_EVM is not set
+CONFIG_EXECMEM=y
+# CONFIG_EXFAT_FS is not set
+CONFIG_EXPERT=y
+CONFIG_EXPORTFS=y
+# CONFIG_EXPORTFS_BLOCK_OPS is not set
+# CONFIG_EXT2_FS is not set
+CONFIG_EXT2_FS_XATTR=y
+# CONFIG_EXT3_FS is not set
+# CONFIG_EXT4_DEBUG is not set
+# CONFIG_EXT4_FS is not set
+# CONFIG_EXT4_FS_POSIX_ACL is not set
+# CONFIG_EXT4_FS_SECURITY is not set
+CONFIG_EXT4_USE_FOR_EXT2=y
+# CONFIG_EXTCON is not set
+# CONFIG_EXTCON_ADC_JACK is not set
+# CONFIG_EXTCON_AXP288 is not set
+# CONFIG_EXTCON_FSA9480 is not set
+# CONFIG_EXTCON_GPIO is not set
+# CONFIG_EXTCON_INTEL_INT3496 is not set
+# CONFIG_EXTCON_LC824206XA is not set
+# CONFIG_EXTCON_MAX3355 is not set
+# CONFIG_EXTCON_PTN5150 is not set
+# CONFIG_EXTCON_QCOM_SPMI_MISC is not set
+# CONFIG_EXTCON_RT8973A is not set
+# CONFIG_EXTCON_SM5502 is not set
+# CONFIG_EXTCON_USBC_TUSB320 is not set
+# CONFIG_EXTCON_USB_GPIO is not set
+CONFIG_EXTRA_FIRMWARE=""
+CONFIG_EXTRA_TARGETS=""
+# CONFIG_EXYNOS_ADC is not set
+# CONFIG_EYEQ is not set
+# CONFIG_EZCHIP_NPS_MANAGEMENT_ENET is not set
+# CONFIG_EZX_PCAP is not set
+# CONFIG_F2FS_CHECK_FS is not set
+# CONFIG_F2FS_FAULT_INJECTION is not set
+# CONFIG_F2FS_FS is not set
+# CONFIG_F2FS_FS_COMPRESSION is not set
+# CONFIG_F2FS_FS_POSIX_ACL is not set
+# CONFIG_F2FS_FS_SECURITY is not set
+CONFIG_F2FS_FS_XATTR=y
+# CONFIG_F2FS_IOSTAT is not set
+CONFIG_F2FS_STAT_FS=y
+# CONFIG_F2FS_UNFAIR_RWSEM is not set
+# CONFIG_FAILOVER is not set
+# CONFIG_FAIR_GROUP_SCHED is not set
+# CONFIG_FANOTIFY is not set
+# CONFIG_FANOTIFY_ACCESS_PERMISSIONS is not set
+CONFIG_FAT_DEFAULT_CODEPAGE=437
+CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1"
+# CONFIG_FAT_DEFAULT_UTF8 is not set
+# CONFIG_FAT_FS is not set
+# CONFIG_FAULT_INJECTION is not set
+# CONFIG_FB is not set
+# CONFIG_FBNIC is not set
+# CONFIG_FB_3DFX is not set
+# CONFIG_FB_ARC is not set
+# CONFIG_FB_ARK is not set
+# CONFIG_FB_ASILIANT is not set
+# CONFIG_FB_ATMEL is not set
+# CONFIG_FB_ATY is not set
+# CONFIG_FB_ATY128 is not set
+# CONFIG_FB_BACKLIGHT is not set
+# CONFIG_FB_BIG_ENDIAN is not set
+# CONFIG_FB_BOTH_ENDIAN is not set
+# CONFIG_FB_BROADSHEET is not set
+# CONFIG_FB_CARMINE is not set
+# CONFIG_FB_CFB_COPYAREA is not set
+# CONFIG_FB_CFB_FILLRECT is not set
+# CONFIG_FB_CFB_IMAGEBLIT is not set
+# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set
+# CONFIG_FB_CIRRUS is not set
+# CONFIG_FB_CYBER2000 is not set
+# CONFIG_FB_DDC is not set
+# CONFIG_FB_DEVICE is not set
+# CONFIG_FB_FOREIGN_ENDIAN is not set
+# CONFIG_FB_FSL_DIU is not set
+# CONFIG_FB_GEODE is not set
+# CONFIG_FB_GOLDFISH is not set
+# CONFIG_FB_HGA is not set
+# CONFIG_FB_I740 is not set
+# CONFIG_FB_IBM_GXT4500 is not set
+# CONFIG_FB_IMSTT is not set
+# CONFIG_FB_IMX is not set
+# CONFIG_FB_KYRO is not set
+# CONFIG_FB_LITTLE_ENDIAN is not set
+# CONFIG_FB_MACMODES is not set
+# CONFIG_FB_MATROX is not set
+# CONFIG_FB_MB862XX is not set
+# CONFIG_FB_METRONOME is not set
+# CONFIG_FB_MODE_HELPERS is not set
+# CONFIG_FB_N411 is not set
+# CONFIG_FB_NEOMAGIC is not set
+CONFIG_FB_NOTIFY=y
+# CONFIG_FB_NVIDIA is not set
+# CONFIG_FB_OF is not set
+# CONFIG_FB_OMAP2 is not set
+# CONFIG_FB_OPENCORES is not set
+# CONFIG_FB_PM2 is not set
+# CONFIG_FB_PM3 is not set
+# CONFIG_FB_PS3 is not set
+# CONFIG_FB_PXA is not set
+# CONFIG_FB_RADEON is not set
+# CONFIG_FB_RIVA is not set
+# CONFIG_FB_S1D13XXX is not set
+# CONFIG_FB_S3 is not set
+# CONFIG_FB_SAVAGE is not set
+# CONFIG_FB_SIMPLE is not set
+# CONFIG_FB_SIS is not set
+# CONFIG_FB_SM712 is not set
+# CONFIG_FB_SM750 is not set
+# CONFIG_FB_SMSCUFX is not set
+# CONFIG_FB_SSD1307 is not set
+# CONFIG_FB_SVGALIB is not set
+# CONFIG_FB_SYS_COPYAREA is not set
+# CONFIG_FB_SYS_FILLRECT is not set
+# CONFIG_FB_SYS_IMAGEBLIT is not set
+# CONFIG_FB_TFT is not set
+# CONFIG_FB_TFT_AGM1264K_FL is not set
+# CONFIG_FB_TFT_BD663474 is not set
+# CONFIG_FB_TFT_HX8340BN is not set
+# CONFIG_FB_TFT_HX8347D is not set
+# CONFIG_FB_TFT_HX8353D is not set
+# CONFIG_FB_TFT_HX8357D is not set
+# CONFIG_FB_TFT_ILI9163 is not set
+# CONFIG_FB_TFT_ILI9320 is not set
+# CONFIG_FB_TFT_ILI9325 is not set
+# CONFIG_FB_TFT_ILI9340 is not set
+# CONFIG_FB_TFT_ILI9341 is not set
+# CONFIG_FB_TFT_ILI9481 is not set
+# CONFIG_FB_TFT_ILI9486 is not set
+# CONFIG_FB_TFT_PCD8544 is not set
+# CONFIG_FB_TFT_RA8875 is not set
+# CONFIG_FB_TFT_S6D02A1 is not set
+# CONFIG_FB_TFT_S6D1121 is not set
+# CONFIG_FB_TFT_SEPS525 is not set
+# CONFIG_FB_TFT_SH1106 is not set
+# CONFIG_FB_TFT_SSD1289 is not set
+# CONFIG_FB_TFT_SSD1305 is not set
+# CONFIG_FB_TFT_SSD1306 is not set
+# CONFIG_FB_TFT_SSD1331 is not set
+# CONFIG_FB_TFT_SSD1351 is not set
+# CONFIG_FB_TFT_ST7735R is not set
+# CONFIG_FB_TFT_ST7789V is not set
+# CONFIG_FB_TFT_TINYLCD is not set
+# CONFIG_FB_TFT_TLS8204 is not set
+# CONFIG_FB_TFT_UC1611 is not set
+# CONFIG_FB_TFT_UC1701 is not set
+# CONFIG_FB_TFT_UPD161704 is not set
+# CONFIG_FB_TILEBLITTING is not set
+# CONFIG_FB_TRIDENT is not set
+# CONFIG_FB_UDL is not set
+# CONFIG_FB_UVESA is not set
+# CONFIG_FB_VGA16 is not set
+# CONFIG_FB_VIA is not set
+# CONFIG_FB_VIRTUAL is not set
+# CONFIG_FB_VOODOO1 is not set
+# CONFIG_FB_VT8623 is not set
+# CONFIG_FCOE is not set
+# CONFIG_FCOE_FNIC is not set
+# CONFIG_FDDI is not set
+# CONFIG_FEALNX is not set
+# CONFIG_FHANDLE is not set
+CONFIG_FIB_RULES=y
+# CONFIG_FIELDBUS_DEV is not set
+CONFIG_FILE_LOCKING=y
+# CONFIG_FIND_BIT_BENCHMARK is not set
+# CONFIG_FIREWIRE is not set
+# CONFIG_FIREWIRE_NOSY is not set
+# CONFIG_FIRMWARE_EDID is not set
+# CONFIG_FIRMWARE_MEMMAP is not set
+# CONFIG_FIXED_PHY is not set
+CONFIG_FLATMEM=y
+CONFIG_FLATMEM_MANUAL=y
+# CONFIG_FM10K is not set
+# CONFIG_FONTS is not set
+# CONFIG_FONT_6x8 is not set
+# CONFIG_FONT_TER16x32 is not set
+# CONFIG_FORCEDETH is not set
+# CONFIG_FORCE_NR_CPUS is not set
+CONFIG_FORTIFY_SOURCE=y
+# CONFIG_FPGA is not set
+# CONFIG_FPROBE is not set
+# CONFIG_FRAMEBUFFER_CONSOLE is not set
+# CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER is not set
+# CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION is not set
+# CONFIG_FRAME_POINTER is not set
+# CONFIG_FREEZER is not set
+# CONFIG_FSCACHE is not set
+# CONFIG_FSI is not set
+# CONFIG_FSL_DPAA2_SWITCH is not set
+# CONFIG_FSL_EDMA is not set
+# CONFIG_FSL_ENETC is not set
+# CONFIG_FSL_ENETC_IERB is not set
+# CONFIG_FSL_ENETC_MDIO is not set
+# CONFIG_FSL_ENETC_VF is not set
+# CONFIG_FSL_ERRATUM_A008585 is not set
+# CONFIG_FSL_MC_BUS is not set
+# CONFIG_FSL_PQ_MDIO is not set
+# CONFIG_FSL_QDMA is not set
+# CONFIG_FSL_RCPM is not set
+# CONFIG_FSL_XGMAC_MDIO is not set
+CONFIG_FSNOTIFY=y
+# CONFIG_FS_DAX is not set
+# CONFIG_FS_ENCRYPTION is not set
+# CONFIG_FS_POSIX_ACL is not set
+CONFIG_FS_STACK=y
+# CONFIG_FS_VERITY is not set
+# CONFIG_FTGMAC100 is not set
+# CONFIG_FTL is not set
+# CONFIG_FTMAC100 is not set
+# CONFIG_FTRACE is not set
+# CONFIG_FTRACE_RECORD_RECURSION is not set
+# CONFIG_FTRACE_SORT_STARTUP_TEST is not set
+# CONFIG_FTRACE_STARTUP_TEST is not set
+# CONFIG_FTRACE_VALIDATE_RCU_IS_WATCHING is not set
+# CONFIG_FTR_FIXUP_SELFTEST is not set
+# CONFIG_FTWDT010_WATCHDOG is not set
+# CONFIG_FUEL_GAUGE_MM8013 is not set
+# CONFIG_FUJITSU_ERRATUM_010001 is not set
+# CONFIG_FUJITSU_ES is not set
+# CONFIG_FUJITSU_LAPTOP is not set
+# CONFIG_FUJITSU_TABLET is not set
+# CONFIG_FUNCTION_ERROR_INJECTION is not set
+# CONFIG_FUNCTION_GRAPH_RETVAL is not set
+# CONFIG_FUNCTION_TRACER is not set
+# CONFIG_FUN_ETH is not set
+# CONFIG_FUSE_FS is not set
+# CONFIG_FUSE_PASSTHROUGH is not set
+# CONFIG_FUSION is not set
+# CONFIG_FUSION_FC is not set
+# CONFIG_FUSION_SAS is not set
+# CONFIG_FUSION_SPI is not set
+CONFIG_FUTEX=y
+CONFIG_FUTEX_PI=y
+# CONFIG_FW_CFG_SYSFS is not set
+# CONFIG_FW_DEVLINK_SYNC_STATE_TIMEOUT is not set
+CONFIG_FW_LOADER=y
+# CONFIG_FW_LOADER_COMPRESS is not set
+# CONFIG_FW_LOADER_DEBUG is not set
+CONFIG_FW_LOADER_USER_HELPER=y
+CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y
+# CONFIG_FW_UPLOAD is not set
+# CONFIG_FXAS21002C is not set
+# CONFIG_FXLS8962AF_I2C is not set
+# CONFIG_FXLS8962AF_SPI is not set
+# CONFIG_FXOS8700_I2C is not set
+# CONFIG_FXOS8700_SPI is not set
+CONFIG_GACT_PROB=y
+# CONFIG_GADGET_UAC1 is not set
+# CONFIG_GAMEPORT is not set
+CONFIG_GCC_NO_STRINGOP_OVERFLOW=y
+# CONFIG_GCC_PLUGINS is not set
+# CONFIG_GCOV is not set
+# CONFIG_GCOV_KERNEL is not set
+# CONFIG_GDB_SCRIPTS is not set
+# CONFIG_GEMINI_ETHERNET is not set
+# CONFIG_GENERIC_ADC_BATTERY is not set
+# CONFIG_GENERIC_ADC_THERMAL is not set
+CONFIG_GENERIC_CALIBRATE_DELAY=y
+# CONFIG_GENERIC_CPU_DEVICES is not set
+CONFIG_GENERIC_HWEIGHT=y
+# CONFIG_GENERIC_IRQ_DEBUGFS is not set
+CONFIG_GENERIC_IRQ_IPI=y
+CONFIG_GENERIC_IRQ_PROBE=y
+CONFIG_GENERIC_NET_UTILS=y
+# CONFIG_GENERIC_PHY is not set
+CONFIG_GENERIC_PTDUMP=y
+CONFIG_GENERIC_VDSO_TIME_NS=y
+# CONFIG_GENEVE is not set
+# CONFIG_GENWQE is not set
+# CONFIG_GFS2_FS is not set
+# CONFIG_GIGABYTE_WMI is not set
+# CONFIG_GLOB_SELFTEST is not set
+# CONFIG_GNSS is not set
+# CONFIG_GOLDFISH is not set
+# CONFIG_GOOGLE_CBMEM is not set
+# CONFIG_GOOGLE_FIRMWARE is not set
+# CONFIG_GOOGLE_FRAMEBUFFER_COREBOOT is not set
+# CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY is not set
+# CONFIG_GOOGLE_SMI is not set
+# CONFIG_GP2AP002 is not set
+# CONFIG_GP2AP020A00F is not set
+# CONFIG_GPD_POCKET_FAN is not set
+CONFIG_GPIOLIB=y
+CONFIG_GPIOLIB_FASTPATH_LIMIT=512
+# CONFIG_GPIO_104_DIO_48E is not set
+# CONFIG_GPIO_104_IDIO_16 is not set
+# CONFIG_GPIO_104_IDI_48 is not set
+# CONFIG_GPIO_74X164 is not set
+# CONFIG_GPIO_74XX_MMIO is not set
+# CONFIG_GPIO_ADNP is not set
+# CONFIG_GPIO_AGGREGATOR is not set
+# CONFIG_GPIO_ALTERA is not set
+# CONFIG_GPIO_AMD8111 is not set
+# CONFIG_GPIO_AMDPT is not set
+# CONFIG_GPIO_AMD_FCH is not set
+# CONFIG_GPIO_BCM_KONA is not set
+# CONFIG_GPIO_BRCMSTB is not set
+# CONFIG_GPIO_BT8XX is not set
+# CONFIG_GPIO_CADENCE is not set
+# CONFIG_GPIO_CDEV is not set
+# CONFIG_GPIO_CDEV_V1 is not set
+# CONFIG_GPIO_CS5535 is not set
+# CONFIG_GPIO_DS4520 is not set
+# CONFIG_GPIO_DWAPB is not set
+# CONFIG_GPIO_EM is not set
+# CONFIG_GPIO_EXAR is not set
+# CONFIG_GPIO_F7188X is not set
+# CONFIG_GPIO_FTGPIO010 is not set
+# CONFIG_GPIO_FXL6408 is not set
+# CONFIG_GPIO_GENERIC_PLATFORM is not set
+# CONFIG_GPIO_GPIO_MM is not set
+# CONFIG_GPIO_GRGPIO is not set
+# CONFIG_GPIO_GW_PLD is not set
+# CONFIG_GPIO_HISI is not set
+# CONFIG_GPIO_HLWD is not set
+# CONFIG_GPIO_ICH is not set
+# CONFIG_GPIO_IT87 is not set
+# CONFIG_GPIO_LATCH is not set
+# CONFIG_GPIO_LOGICVC is not set
+# CONFIG_GPIO_LINE_MUX is not set
+# CONFIG_GPIO_MAX3191X is not set
+# CONFIG_GPIO_MAX7300 is not set
+# CONFIG_GPIO_MAX7301 is not set
+# CONFIG_GPIO_MAX732X is not set
+# CONFIG_GPIO_MB86S7X is not set
+# CONFIG_GPIO_MC33880 is not set
+# CONFIG_GPIO_ML_IOH is not set
+# CONFIG_GPIO_MOCKUP is not set
+# CONFIG_GPIO_MPC8XXX is not set
+# CONFIG_GPIO_PCA953X is not set
+# CONFIG_GPIO_PCA953X_IRQ is not set
+# CONFIG_GPIO_PCA9570 is not set
+# CONFIG_GPIO_PCF857X is not set
+# CONFIG_GPIO_PCH is not set
+# CONFIG_GPIO_PCIE_IDIO_24 is not set
+# CONFIG_GPIO_PCI_IDIO_16 is not set
+# CONFIG_GPIO_PISOSR is not set
+# CONFIG_GPIO_PL061 is not set
+# CONFIG_GPIO_RCAR is not set
+# CONFIG_GPIO_RDC321X is not set
+# CONFIG_GPIO_SAMA5D2_PIOBU is not set
+# CONFIG_GPIO_SCH is not set
+# CONFIG_GPIO_SCH311X is not set
+# CONFIG_GPIO_SIFIVE is not set
+# CONFIG_GPIO_SIM is not set
+# CONFIG_GPIO_SLOPPY_LOGIC_ANALYZER is not set
+# CONFIG_GPIO_SYSCON is not set
+CONFIG_GPIO_SYSFS=y
+# CONFIG_GPIO_TPIC2810 is not set
+# CONFIG_GPIO_TS4900 is not set
+# CONFIG_GPIO_TS5500 is not set
+# CONFIG_GPIO_VIRTIO is not set
+# CONFIG_GPIO_VIRTUSER is not set
+# CONFIG_GPIO_VX855 is not set
+# CONFIG_GPIO_WATCHDOG is not set
+# CONFIG_GPIO_WINBOND is not set
+# CONFIG_GPIO_WS16C48 is not set
+# CONFIG_GPIO_XGENE is not set
+# CONFIG_GPIO_XILINX is not set
+# CONFIG_GPIO_XRA1403 is not set
+# CONFIG_GPIO_ZEVIO is not set
+# CONFIG_GP_PCI1XXXX is not set
+# CONFIG_GREENASIA_FF is not set
+# CONFIG_GREYBUS is not set
+# CONFIG_GTP is not set
+# CONFIG_GUP_TEST is not set
+# CONFIG_GVE is not set
+# CONFIG_HAMACHI is not set
+# CONFIG_HAMRADIO is not set
+# CONFIG_HAPPYMEAL is not set
+CONFIG_HARDENED_USERCOPY=y
+CONFIG_HARDEN_BRANCH_HISTORY=y
+# CONFIG_HARDLOCKUP_DETECTOR is not set
+# CONFIG_HAVE_ARM_ARCH_TIMER is not set
+# CONFIG_HCALL_STATS is not set
+# CONFIG_HDC100X is not set
+# CONFIG_HDC2010 is not set
+# CONFIG_HDC3020 is not set
+# CONFIG_HDLC is not set
+# CONFIG_HDLC_CISCO is not set
+# CONFIG_HDLC_FR is not set
+# CONFIG_HDLC_PPP is not set
+# CONFIG_HDLC_RAW is not set
+# CONFIG_HDLC_RAW_ETH is not set
+# CONFIG_HDMI_LPE_AUDIO is not set
+# CONFIG_HDQ_MASTER_OMAP is not set
+# CONFIG_HEADERS_INSTALL is not set
+# CONFIG_HFSPLUS_FS is not set
+# CONFIG_HFS_FS is not set
+# CONFIG_HI6421V600_IRQ is not set
+# CONFIG_HI8435 is not set
+# CONFIG_HIBERNATION is not set
+# CONFIG_HIBERNATION_COMP_LZ4 is not set
+# CONFIG_HID is not set
+# CONFIG_HIDRAW is not set
+# CONFIG_HID_A4TECH is not set
+# CONFIG_HID_ACCUTOUCH is not set
+# CONFIG_HID_ACRUX is not set
+# CONFIG_HID_ACRUX_FF is not set
+# CONFIG_HID_ALPS is not set
+# CONFIG_HID_APPLE is not set
+# CONFIG_HID_APPLEIR is not set
+# CONFIG_HID_ASUS is not set
+# CONFIG_HID_AUREAL is not set
+# CONFIG_HID_BATTERY_STRENGTH is not set
+# CONFIG_HID_BELKIN is not set
+# CONFIG_HID_BETOP_FF is not set
+# CONFIG_HID_BIGBEN_FF is not set
+# CONFIG_HID_BPF is not set
+# CONFIG_HID_CHERRY is not set
+# CONFIG_HID_CHICONY is not set
+# CONFIG_HID_CMEDIA is not set
+# CONFIG_HID_CORSAIR is not set
+# CONFIG_HID_COUGAR is not set
+# CONFIG_HID_CP2112 is not set
+# CONFIG_HID_CREATIVE_SB0540 is not set
+# CONFIG_HID_CYPRESS is not set
+# CONFIG_HID_DRAGONRISE is not set
+# CONFIG_HID_ELAN is not set
+# CONFIG_HID_ELECOM is not set
+# CONFIG_HID_ELO is not set
+# CONFIG_HID_EMS_FF is not set
+# CONFIG_HID_EVISION is not set
+# CONFIG_HID_EZKEY is not set
+# CONFIG_HID_FT260 is not set
+# CONFIG_HID_GEMBIRD is not set
+# CONFIG_HID_GENERIC is not set
+# CONFIG_HID_GFRM is not set
+# CONFIG_HID_GLORIOUS is not set
+# CONFIG_HID_GOODIX_SPI is not set
+# CONFIG_HID_GOOGLE_HAMMER is not set
+# CONFIG_HID_GOOGLE_STADIA_FF is not set
+# CONFIG_HID_GREENASIA is not set
+# CONFIG_HID_GT683R is not set
+# CONFIG_HID_GYRATION is not set
+# CONFIG_HID_HOLTEK is not set
+# CONFIG_HID_ICADE is not set
+# CONFIG_HID_ITE is not set
+# CONFIG_HID_JABRA is not set
+# CONFIG_HID_KENSINGTON is not set
+# CONFIG_HID_KEYTOUCH is not set
+# CONFIG_HID_KYE is not set
+# CONFIG_HID_LCPOWER is not set
+# CONFIG_HID_LED is not set
+# CONFIG_HID_LENOVO is not set
+# CONFIG_HID_LETSKETCH is not set
+# CONFIG_HID_LOGITECH is not set
+# CONFIG_HID_LOGITECH_DJ is not set
+# CONFIG_HID_LOGITECH_HIDPP is not set
+# CONFIG_HID_MACALLY is not set
+# CONFIG_HID_MAGICMOUSE is not set
+# CONFIG_HID_MALTRON is not set
+# CONFIG_HID_MAYFLASH is not set
+# CONFIG_HID_MCP2200 is not set
+# CONFIG_HID_MCP2221 is not set
+# CONFIG_HID_MEGAWORLD_FF is not set
+# CONFIG_HID_MICROSOFT is not set
+# CONFIG_HID_MONTEREY is not set
+# CONFIG_HID_MULTITOUCH is not set
+# CONFIG_HID_NINTENDO is not set
+# CONFIG_HID_NTI is not set
+# CONFIG_HID_NTRIG is not set
+# CONFIG_HID_NVIDIA_SHIELD is not set
+# CONFIG_HID_ORTEK is not set
+# CONFIG_HID_PANTHERLORD is not set
+# CONFIG_HID_PENMOUNT is not set
+# CONFIG_HID_PETALYNX is not set
+# CONFIG_HID_PICOLCD is not set
+# CONFIG_HID_PID is not set
+# CONFIG_HID_PLANTRONICS is not set
+# CONFIG_HID_PLAYSTATION is not set
+# CONFIG_HID_PRIMAX is not set
+# CONFIG_HID_PRODIKEYS is not set
+# CONFIG_HID_PXRC is not set
+# CONFIG_HID_RAZER is not set
+# CONFIG_HID_REDRAGON is not set
+# CONFIG_HID_RETRODE is not set
+# CONFIG_HID_RMI is not set
+# CONFIG_HID_ROCCAT is not set
+# CONFIG_HID_SAITEK is not set
+# CONFIG_HID_SAMSUNG is not set
+# CONFIG_HID_SEMITEK is not set
+# CONFIG_HID_SENSOR_HUB is not set
+# CONFIG_HID_SIGMAMICRO is not set
+# CONFIG_HID_SMARTJOYPLUS is not set
+# CONFIG_HID_SONY is not set
+# CONFIG_HID_SPEEDLINK is not set
+# CONFIG_HID_STEAM is not set
+# CONFIG_HID_STEELSERIES is not set
+# CONFIG_HID_SUNPLUS is not set
+# CONFIG_HID_SUPPORT is not set
+# CONFIG_HID_THINGM is not set
+# CONFIG_HID_THRUSTMASTER is not set
+# CONFIG_HID_TIVO is not set
+# CONFIG_HID_TOPRE is not set
+# CONFIG_HID_TOPSEED is not set
+# CONFIG_HID_TWINHAN is not set
+# CONFIG_HID_U2FZERO is not set
+# CONFIG_HID_UCLOGIC is not set
+# CONFIG_HID_UDRAW_PS3 is not set
+# CONFIG_HID_VIEWSONIC is not set
+# CONFIG_HID_VIVALDI is not set
+# CONFIG_HID_VRC2 is not set
+# CONFIG_HID_WACOM is not set
+# CONFIG_HID_WALTOP is not set
+# CONFIG_HID_WIIMOTE is not set
+# CONFIG_HID_WINWING is not set
+# CONFIG_HID_XIAOMI is not set
+# CONFIG_HID_XINMO is not set
+# CONFIG_HID_ZEROPLUS is not set
+# CONFIG_HID_ZYDACRON is not set
+# CONFIG_HIGHMEM is not set
+CONFIG_HIGH_RES_TIMERS=y
+# CONFIG_HINIC is not set
+# CONFIG_HIP04_ETH is not set
+# CONFIG_HIPPI is not set
+# CONFIG_HISILICON_ERRATUM_161010101 is not set
+# CONFIG_HISILICON_ERRATUM_161600802 is not set
+# CONFIG_HISILICON_ERRATUM_162100801 is not set
+# CONFIG_HISI_DMA is not set
+# CONFIG_HISI_FEMAC is not set
+# CONFIG_HISI_HIKEY_USB is not set
+# CONFIG_HISI_PCIE_PMU is not set
+# CONFIG_HISI_PTT is not set
+# CONFIG_HIST_TRIGGERS_DEBUG is not set
+# CONFIG_HIX5HD2_GMAC is not set
+# CONFIG_HMC425 is not set
+# CONFIG_HMC6352 is not set
+# CONFIG_HNS is not set
+# CONFIG_HNS3 is not set
+# CONFIG_HNS3_PMU is not set
+# CONFIG_HNS_DSAF is not set
+# CONFIG_HNS_ENET is not set
+# CONFIG_HOTPLUG_CPU is not set
+# CONFIG_HOTPLUG_PCI is not set
+# CONFIG_HP03 is not set
+# CONFIG_HP206C is not set
+CONFIG_HPET_MMAP_DEFAULT=y
+# CONFIG_HPFS_FS is not set
+# CONFIG_HP_ILO is not set
+# CONFIG_HP_WATCHDOG is not set
+# CONFIG_HSA_AMD is not set
+# CONFIG_HSC030PA is not set
+# CONFIG_HSI is not set
+# CONFIG_HSR is not set
+# CONFIG_HTC_EGPIO is not set
+# CONFIG_HTE is not set
+# CONFIG_HTS221 is not set
+# CONFIG_HTU21 is not set
+# CONFIG_HUAWEI_WMI is not set
+# CONFIG_HUGETLBFS is not set
+# CONFIG_HUGETLB_PAGE is not set
+# CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP_DEFAULT_ON is not set
+# CONFIG_HVC_DCC is not set
+# CONFIG_HVC_UDBG is not set
+# CONFIG_HWLAT_TRACER is not set
+# CONFIG_HWMON is not set
+# CONFIG_HWMON_DEBUG_CHIP is not set
+# CONFIG_HWMON_VID is not set
+# CONFIG_HWSPINLOCK is not set
+# CONFIG_HWSPINLOCK_OMAP is not set
+CONFIG_HW_PERF_EVENTS=y
+# CONFIG_HW_RANDOM is not set
+# CONFIG_HW_RANDOM_AMD is not set
+# CONFIG_HW_RANDOM_ARM_SMCCC_TRNG is not set
+# CONFIG_HW_RANDOM_ATMEL is not set
+# CONFIG_HW_RANDOM_BA431 is not set
+# CONFIG_HW_RANDOM_BCM2835 is not set
+# CONFIG_HW_RANDOM_CAVIUM is not set
+# CONFIG_HW_RANDOM_CCTRNG is not set
+# CONFIG_HW_RANDOM_CN10K is not set
+# CONFIG_HW_RANDOM_EXYNOS is not set
+# CONFIG_HW_RANDOM_GEODE is not set
+# CONFIG_HW_RANDOM_INTEL is not set
+# CONFIG_HW_RANDOM_IPROC_RNG200 is not set
+# CONFIG_HW_RANDOM_MTK is not set
+# CONFIG_HW_RANDOM_OMAP is not set
+# CONFIG_HW_RANDOM_OMAP3_ROM is not set
+# CONFIG_HW_RANDOM_PPC4XX is not set
+# CONFIG_HW_RANDOM_TIMERIOMEM is not set
+CONFIG_HW_RANDOM_TPM=y
+# CONFIG_HW_RANDOM_VIA is not set
+# CONFIG_HW_RANDOM_VIRTIO is not set
+# CONFIG_HW_RANDOM_XIPHERA is not set
+# CONFIG_HX711 is not set
+# CONFIG_HX9023S is not set
+# CONFIG_HYPERV is not set
+CONFIG_HZ=100
+CONFIG_HZ_100=y
+# CONFIG_HZ_1000 is not set
+# CONFIG_HZ_1024 is not set
+# CONFIG_HZ_128 is not set
+# CONFIG_HZ_200 is not set
+# CONFIG_HZ_24 is not set
+# CONFIG_HZ_250 is not set
+# CONFIG_HZ_256 is not set
+# CONFIG_HZ_300 is not set
+# CONFIG_HZ_48 is not set
+# CONFIG_HZ_500 is not set
+# CONFIG_HZ_PERIODIC is not set
+# CONFIG_I2C is not set
+# CONFIG_I2C_ALGOBIT is not set
+# CONFIG_I2C_ALGOPCA is not set
+# CONFIG_I2C_ALGOPCF is not set
+# CONFIG_I2C_ALI1535 is not set
+# CONFIG_I2C_ALI1563 is not set
+# CONFIG_I2C_ALI15X3 is not set
+# CONFIG_I2C_AMD756 is not set
+# CONFIG_I2C_AMD8111 is not set
+# CONFIG_I2C_ARB_GPIO_CHALLENGE is not set
+# CONFIG_I2C_AU1550 is not set
+# CONFIG_I2C_BCM2835 is not set
+# CONFIG_I2C_BCM_IPROC is not set
+# CONFIG_I2C_BRCMSTB is not set
+# CONFIG_I2C_CADENCE is not set
+# CONFIG_I2C_CBUS_GPIO is not set
+# CONFIG_I2C_CHARDEV is not set
+# CONFIG_I2C_CP2615 is not set
+# CONFIG_I2C_DEBUG_ALGO is not set
+# CONFIG_I2C_DEBUG_BUS is not set
+# CONFIG_I2C_DEBUG_CORE is not set
+# CONFIG_I2C_DEMUX_PINCTRL is not set
+# CONFIG_I2C_DESIGNWARE_AMDPSP is not set
+# CONFIG_I2C_DESIGNWARE_BAYTRAIL is not set
+# CONFIG_I2C_DESIGNWARE_CORE is not set
+# CONFIG_I2C_DESIGNWARE_PCI is not set
+# CONFIG_I2C_DESIGNWARE_PLATFORM is not set
+# CONFIG_I2C_DESIGNWARE_SLAVE is not set
+# CONFIG_I2C_DIOLAN_U2C is not set
+# CONFIG_I2C_EG20T is not set
+# CONFIG_I2C_ELEKTOR is not set
+# CONFIG_I2C_EMEV2 is not set
+# CONFIG_I2C_GPIO is not set
+# CONFIG_I2C_GPIO_FAULT_INJECTOR is not set
+# CONFIG_I2C_HELPER_AUTO is not set
+# CONFIG_I2C_HID is not set
+# CONFIG_I2C_HID_OF is not set
+# CONFIG_I2C_HID_OF_ELAN is not set
+# CONFIG_I2C_HID_OF_GOODIX is not set
+# CONFIG_I2C_HISI is not set
+# CONFIG_I2C_I801 is not set
+# CONFIG_I2C_IBM_IIC is not set
+# CONFIG_I2C_IMG is not set
+# CONFIG_I2C_ISCH is not set
+# CONFIG_I2C_ISMT is not set
+# CONFIG_I2C_JZ4780 is not set
+# CONFIG_I2C_MLXCPLD is not set
+# CONFIG_I2C_MPC is not set
+# CONFIG_I2C_MT65XX is not set
+# CONFIG_I2C_MUX is not set
+# CONFIG_I2C_MUX_GPIO is not set
+# CONFIG_I2C_MUX_GPMUX is not set
+# CONFIG_I2C_MUX_LTC4306 is not set
+# CONFIG_I2C_MUX_MLXCPLD is not set
+# CONFIG_I2C_MUX_PCA9541 is not set
+# CONFIG_I2C_MUX_PCA954x is not set
+# CONFIG_I2C_MUX_PINCTRL is not set
+# CONFIG_I2C_MUX_REG is not set
+# CONFIG_I2C_MV64XXX is not set
+# CONFIG_I2C_NFORCE2 is not set
+# CONFIG_I2C_NOMADIK is not set
+# CONFIG_I2C_NVIDIA_GPU is not set
+# CONFIG_I2C_OCORES is not set
+# CONFIG_I2C_OCTEON is not set
+# CONFIG_I2C_PARPORT is not set
+# CONFIG_I2C_PCA_ISA is not set
+# CONFIG_I2C_PCA_PLATFORM is not set
+# CONFIG_I2C_PCI1XXXX is not set
+# CONFIG_I2C_PIIX4 is not set
+# CONFIG_I2C_PXA_PCI is not set
+# CONFIG_I2C_PXA_SLAVE is not set
+# CONFIG_I2C_RCAR is not set
+# CONFIG_I2C_RK3X is not set
+# CONFIG_I2C_ROBOTFUZZ_OSIF is not set
+# CONFIG_I2C_S3C2410 is not set
+# CONFIG_I2C_SCMI is not set
+# CONFIG_I2C_SH_MOBILE is not set
+# CONFIG_I2C_SIMTEC is not set
+# CONFIG_I2C_SIS5595 is not set
+# CONFIG_I2C_SIS630 is not set
+# CONFIG_I2C_SIS96X is not set
+# CONFIG_I2C_SLAVE is not set
+# CONFIG_I2C_SLAVE_EEPROM is not set
+# CONFIG_I2C_SMBUS is not set
+# CONFIG_I2C_STUB is not set
+# CONFIG_I2C_TAOS_EVM is not set
+# CONFIG_I2C_THUNDERX is not set
+# CONFIG_I2C_TINY_USB is not set
+# CONFIG_I2C_VERSATILE is not set
+# CONFIG_I2C_VIA is not set
+# CONFIG_I2C_VIAPRO is not set
+# CONFIG_I2C_VIRTIO is not set
+# CONFIG_I2C_XILINX is not set
+# CONFIG_I3C is not set
+# CONFIG_I40E is not set
+# CONFIG_I40EVF is not set
+# CONFIG_I6300ESB_WDT is not set
+# CONFIG_I82092 is not set
+# CONFIG_I82365 is not set
+# CONFIG_IAQCORE is not set
+# CONFIG_IBM_ASM is not set
+# CONFIG_IBM_EMAC_DEBUG is not set
+# CONFIG_IBM_EMAC_EMAC4 is not set
+# CONFIG_IBM_EMAC_MAL_CLR_ICINTSTAT is not set
+# CONFIG_IBM_EMAC_MAL_COMMON_ERR is not set
+# CONFIG_IBM_EMAC_NO_FLOW_CTRL is not set
+# CONFIG_IBM_EMAC_RGMII is not set
+# CONFIG_IBM_EMAC_TAH is not set
+# CONFIG_IBM_EMAC_ZMII is not set
+# CONFIG_ICE is not set
+# CONFIG_ICP10100 is not set
+# CONFIG_ICPLUS_PHY is not set
+# CONFIG_ICS932S401 is not set
+# CONFIG_IDEAPAD_LAPTOP is not set
+# CONFIG_IDLE_PAGE_TRACKING is not set
+# CONFIG_IDPF is not set
+# CONFIG_IEEE802154 is not set
+# CONFIG_IEEE802154_ADF7242 is not set
+# CONFIG_IEEE802154_ATUSB is not set
+# CONFIG_IEEE802154_CA8210 is not set
+# CONFIG_IEEE802154_HWSIM is not set
+# CONFIG_IEEE802154_MCR20A is not set
+# CONFIG_IFB is not set
+# CONFIG_IGB is not set
+# CONFIG_IGBVF is not set
+# CONFIG_IGC is not set
+# CONFIG_IIO is not set
+# CONFIG_IIO_BUFFER is not set
+# CONFIG_IIO_BUFFER_CB is not set
+# CONFIG_IIO_BUFFER_DMA is not set
+# CONFIG_IIO_BUFFER_DMAENGINE is not set
+# CONFIG_IIO_BUFFER_HW_CONSUMER is not set
+# CONFIG_IIO_CONFIGFS is not set
+CONFIG_IIO_CONSUMERS_PER_TRIGGER=2
+# CONFIG_IIO_CROS_EC_ACCEL_LEGACY is not set
+# CONFIG_IIO_INTERRUPT_TRIGGER is not set
+# CONFIG_IIO_KX022A_I2C is not set
+# CONFIG_IIO_KX022A_SPI is not set
+# CONFIG_IIO_MUX is not set
+# CONFIG_IIO_RESCALE is not set
+# CONFIG_IIO_SIMPLE_DUMMY is not set
+# CONFIG_IIO_SSP_SENSORHUB is not set
+# CONFIG_IIO_ST_ACCEL_3AXIS is not set
+# CONFIG_IIO_ST_GYRO_3AXIS is not set
+# CONFIG_IIO_ST_LSM6DSX is not set
+# CONFIG_IIO_ST_LSM9DS0 is not set
+# CONFIG_IIO_ST_MAGN_3AXIS is not set
+# CONFIG_IIO_ST_PRESS is not set
+# CONFIG_IIO_SW_DEVICE is not set
+# CONFIG_IIO_SW_TRIGGER is not set
+# CONFIG_IIO_SYSFS_TRIGGER is not set
+# CONFIG_IIO_TRIGGER is not set
+# CONFIG_IIO_TRIGGERED_EVENT is not set
+# CONFIG_IKCONFIG is not set
+# CONFIG_IKCONFIG_PROC is not set
+# CONFIG_IKHEADERS is not set
+# CONFIG_IMA is not set
+# CONFIG_IMGPDC_WDT is not set
+# CONFIG_IMG_MDC_DMA is not set
+# CONFIG_IMX7D_ADC is not set
+# CONFIG_IMX8QXP_ADC is not set
+# CONFIG_IMX93_ADC is not set
+# CONFIG_IMX_IPUV3_CORE is not set
+# CONFIG_IMX_SCMI_BBM_EXT is not set
+# CONFIG_IMX_SCMI_MISC_DRV is not set
+# CONFIG_IMX_THERMAL is not set
+# CONFIG_INA2XX_ADC is not set
+# CONFIG_INDIRECT_PIO is not set
+CONFIG_INET=y
+# CONFIG_INET6_AH is not set
+# CONFIG_INET6_ESP is not set
+# CONFIG_INET6_ESPINTCP is not set
+# CONFIG_INET6_IPCOMP is not set
+# CONFIG_INET6_TUNNEL is not set
+# CONFIG_INET6_XFRM_TUNNEL is not set
+# CONFIG_INET_AH is not set
+# CONFIG_INET_DIAG is not set
+# CONFIG_INET_ESP is not set
+# CONFIG_INET_ESPINTCP is not set
+# CONFIG_INET_IPCOMP is not set
+CONFIG_INET_TABLE_PERTURB_ORDER=16
+# CONFIG_INET_TCP_DIAG is not set
+# CONFIG_INET_TUNNEL is not set
+# CONFIG_INET_UDP_DIAG is not set
+# CONFIG_INET_XFRM_TUNNEL is not set
+# CONFIG_INFINIBAND is not set
+# CONFIG_INFTL is not set
+# CONFIG_INGENIC_ADC is not set
+# CONFIG_INGENIC_CGU_JZ4725B is not set
+# CONFIG_INGENIC_CGU_JZ4740 is not set
+# CONFIG_INGENIC_CGU_JZ4755 is not set
+# CONFIG_INGENIC_CGU_JZ4760 is not set
+# CONFIG_INGENIC_CGU_JZ4770 is not set
+# CONFIG_INGENIC_CGU_JZ4780 is not set
+# CONFIG_INGENIC_CGU_X1000 is not set
+# CONFIG_INGENIC_CGU_X1830 is not set
+# CONFIG_INGENIC_OST is not set
+# CONFIG_INGENIC_SYSOST is not set
+# CONFIG_INGENIC_TCU_CLK is not set
+# CONFIG_INGENIC_TCU_IRQ is not set
+# CONFIG_INGENIC_TIMER is not set
+# CONFIG_INITRAMFS_PRESERVE_MTIME is not set
+CONFIG_INIT_ENV_ARG_LIMIT=32
+# CONFIG_INIT_ON_ALLOC_DEFAULT_ON is not set
+# CONFIG_INIT_ON_FREE_DEFAULT_ON is not set
+# CONFIG_INIT_STACK_ALL_PATTERN is not set
+# CONFIG_INIT_STACK_ALL_ZERO is not set
+CONFIG_INIT_STACK_NONE=y
+CONFIG_INOTIFY_USER=y
+# CONFIG_INPUT is not set
+# CONFIG_INPUT_AD714X is not set
+# CONFIG_INPUT_ADXL34X is not set
+# CONFIG_INPUT_APANEL is not set
+# CONFIG_INPUT_ATI_REMOTE2 is not set
+# CONFIG_INPUT_ATLAS_BTNS is not set
+# CONFIG_INPUT_ATMEL_CAPTOUCH is not set
+# CONFIG_INPUT_AXP20X_PEK is not set
+# CONFIG_INPUT_BMA150 is not set
+# CONFIG_INPUT_CM109 is not set
+# CONFIG_INPUT_CMA3000 is not set
+# CONFIG_INPUT_DA7280_HAPTICS is not set
+# CONFIG_INPUT_DRV260X_HAPTICS is not set
+# CONFIG_INPUT_DRV2665_HAPTICS is not set
+# CONFIG_INPUT_DRV2667_HAPTICS is not set
+# CONFIG_INPUT_E3X0_BUTTON is not set
+# CONFIG_INPUT_EVBUG is not set
+# CONFIG_INPUT_EVDEV is not set
+# CONFIG_INPUT_FF_MEMLESS is not set
+# CONFIG_INPUT_GPIO_BEEPER is not set
+# CONFIG_INPUT_GPIO_DECODER is not set
+# CONFIG_INPUT_GPIO_ROTARY_ENCODER is not set
+# CONFIG_INPUT_GPIO_VIBRA is not set
+# CONFIG_INPUT_IBM_PANEL is not set
+# CONFIG_INPUT_IDEAPAD_SLIDEBAR is not set
+# CONFIG_INPUT_IMS_PCU is not set
+# CONFIG_INPUT_IQS269A is not set
+# CONFIG_INPUT_IQS626A is not set
+# CONFIG_INPUT_IQS7222 is not set
+# CONFIG_INPUT_JOYDEV is not set
+# CONFIG_INPUT_JOYSTICK is not set
+# CONFIG_INPUT_KEYBOARD is not set
+# CONFIG_INPUT_KEYSPAN_REMOTE is not set
+# CONFIG_INPUT_KXTJ9 is not set
+# CONFIG_INPUT_LEDS is not set
+# CONFIG_INPUT_MATRIXKMAP is not set
+# CONFIG_INPUT_MAX8997_HAPTIC is not set
+CONFIG_INPUT_MISC=y
+# CONFIG_INPUT_MMA8450 is not set
+# CONFIG_INPUT_MOUSE is not set
+# CONFIG_INPUT_MOUSEDEV is not set
+# CONFIG_INPUT_PALMAS_PWRBUTTON is not set
+# CONFIG_INPUT_PCF8574 is not set
+# CONFIG_INPUT_PCSPKR is not set
+# CONFIG_INPUT_PM8941_PWRKEY is not set
+# CONFIG_INPUT_PM8XXX_VIBRATOR is not set
+# CONFIG_INPUT_POWERMATE is not set
+# CONFIG_INPUT_PWM_BEEPER is not set
+# CONFIG_INPUT_PWM_VIBRA is not set
+# CONFIG_INPUT_REGULATOR_HAPTIC is not set
+# CONFIG_INPUT_SOC_BUTTON_ARRAY is not set
+# CONFIG_INPUT_SPARSEKMAP is not set
+# CONFIG_INPUT_TABLET is not set
+# CONFIG_INPUT_TOUCHSCREEN is not set
+# CONFIG_INPUT_TPS65218_PWRBUTTON is not set
+# CONFIG_INPUT_TWL4030_PWRBUTTON is not set
+# CONFIG_INPUT_TWL4030_VIBRA is not set
+# CONFIG_INPUT_TWL6040_VIBRA is not set
+# CONFIG_INPUT_UINPUT is not set
+# CONFIG_INPUT_WISTRON_BTNS is not set
+# CONFIG_INPUT_YEALINK is not set
+# CONFIG_INSPUR_PLATFORM_PROFILE is not set
+# CONFIG_INT340X_THERMAL is not set
+# CONFIG_INTEGRITY is not set
+# CONFIG_INTEGRITY_AUDIT is not set
+# CONFIG_INTEGRITY_SIGNATURE is not set
+# CONFIG_INTEL_ATOMISP2_LED is not set
+# CONFIG_INTEL_ATOMISP2_PM is not set
+# CONFIG_INTEL_HID_EVENT is not set
+# CONFIG_INTEL_IDLE is not set
+# CONFIG_INTEL_IDMA64 is not set
+# CONFIG_INTEL_INT0002_VGPIO is not set
+# CONFIG_INTEL_IOATDMA is not set
+# CONFIG_INTEL_ISH_HID is not set
+# CONFIG_INTEL_MEI is not set
+# CONFIG_INTEL_MEI_GSC_PROXY is not set
+# CONFIG_INTEL_MEI_HDCP is not set
+# CONFIG_INTEL_MEI_ME is not set
+# CONFIG_INTEL_MEI_PXP is not set
+# CONFIG_INTEL_MEI_TXE is not set
+# CONFIG_INTEL_OAKTRAIL is not set
+# CONFIG_INTEL_PMC_CORE is not set
+# CONFIG_INTEL_PUNIT_IPC is not set
+# CONFIG_INTEL_RST is not set
+# CONFIG_INTEL_SKL_INT3472 is not set
+# CONFIG_INTEL_SMARTCONNECT is not set
+# CONFIG_INTEL_SOC_PMIC is not set
+# CONFIG_INTEL_SOC_PMIC_CHTDC_TI is not set
+# CONFIG_INTEL_SOC_PMIC_CHTWC is not set
+# CONFIG_INTEL_TH is not set
+# CONFIG_INTEL_VBTN is not set
+# CONFIG_INTEL_WMI_SBL_FW_UPDATE is not set
+# CONFIG_INTEL_WMI_THUNDERBOLT is not set
+# CONFIG_INTEL_XWAY_PHY is not set
+# CONFIG_INTERCONNECT is not set
+# CONFIG_INTERVAL_TREE_TEST is not set
+# CONFIG_INV_ICM42600_I2C is not set
+# CONFIG_INV_ICM42600_SPI is not set
+# CONFIG_INV_MPU6050_I2C is not set
+# CONFIG_INV_MPU6050_IIO is not set
+# CONFIG_INV_MPU6050_SPI is not set
+# CONFIG_IOMMU_SUPPORT is not set
+# CONFIG_IONIC is not set
+# CONFIG_IOSCHED_BFQ is not set
+# CONFIG_IOSM is not set
+CONFIG_IO_STRICT_DEVMEM=y
+# CONFIG_IO_URING is not set
+CONFIG_IO_WQ=y
+# CONFIG_IP17XX_PHY is not set
+# CONFIG_IP5XXX_POWER is not set
+# CONFIG_IP6_NF_FILTER is not set
+# CONFIG_IP6_NF_IPTABLES is not set
+# CONFIG_IP6_NF_MANGLE is not set
+# CONFIG_IP6_NF_MATCH_AH is not set
+# CONFIG_IP6_NF_MATCH_EUI64 is not set
+# CONFIG_IP6_NF_MATCH_FRAG is not set
+# CONFIG_IP6_NF_MATCH_HL is not set
+# CONFIG_IP6_NF_MATCH_IPV6HEADER is not set
+# CONFIG_IP6_NF_MATCH_MH is not set
+# CONFIG_IP6_NF_MATCH_OPTS is not set
+# CONFIG_IP6_NF_MATCH_RPFILTER is not set
+# CONFIG_IP6_NF_MATCH_RT is not set
+# CONFIG_IP6_NF_MATCH_SRH is not set
+# CONFIG_IP6_NF_NAT is not set
+# CONFIG_IP6_NF_RAW is not set
+# CONFIG_IP6_NF_SECURITY is not set
+# CONFIG_IP6_NF_TARGET_HL is not set
+# CONFIG_IP6_NF_TARGET_MASQUERADE is not set
+# CONFIG_IP6_NF_TARGET_REJECT is not set
+# CONFIG_IP6_NF_TARGET_SYNPROXY is not set
+# CONFIG_IPACK_BUS is not set
+# CONFIG_IPC_NS is not set
+# CONFIG_IPMB_DEVICE_INTERFACE is not set
+# CONFIG_IPMI_HANDLER is not set
+# CONFIG_IPU_BRIDGE is not set
+# CONFIG_IPV6 is not set
+# CONFIG_IPV6_FOU is not set
+# CONFIG_IPV6_FOU_TUNNEL is not set
+# CONFIG_IPV6_ILA is not set
+# CONFIG_IPV6_IOAM6_LWTUNNEL is not set
+# CONFIG_IPV6_MIP6 is not set
+# CONFIG_IPV6_MROUTE is not set
+# CONFIG_IPV6_MROUTE_MULTIPLE_TABLES is not set
+# CONFIG_IPV6_MULTIPLE_TABLES is not set
+CONFIG_IPV6_NDISC_NODETYPE=y
+# CONFIG_IPV6_OPTIMISTIC_DAD is not set
+# CONFIG_IPV6_ROUTER_PREF is not set
+# CONFIG_IPV6_ROUTE_INFO is not set
+# CONFIG_IPV6_RPL_LWTUNNEL is not set
+# CONFIG_IPV6_SEG6_HMAC is not set
+# CONFIG_IPV6_SEG6_LWTUNNEL is not set
+# CONFIG_IPV6_SIT is not set
+# CONFIG_IPV6_SIT_6RD is not set
+# CONFIG_IPV6_TUNNEL is not set
+# CONFIG_IPV6_VTI is not set
+# CONFIG_IPVLAN is not set
+# CONFIG_IPVTAP is not set
+# CONFIG_IPW2100 is not set
+# CONFIG_IPW2100_DEBUG is not set
+CONFIG_IPW2100_MONITOR=y
+# CONFIG_IPW2200 is not set
+# CONFIG_IPW2200_DEBUG is not set
+CONFIG_IPW2200_MONITOR=y
+# CONFIG_IPW2200_PROMISCUOUS is not set
+# CONFIG_IPW2200_QOS is not set
+# CONFIG_IPW2200_RADIOTAP is not set
+# CONFIG_IPWIRELESS is not set
+CONFIG_IP_ADVANCED_ROUTER=y
+# CONFIG_IP_DCCP is not set
+# CONFIG_IP_FIB_TRIE_STATS is not set
+# CONFIG_IP_MROUTE is not set
+CONFIG_IP_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IP_MULTICAST=y
+CONFIG_IP_MULTIPLE_TABLES=y
+# CONFIG_IP_NF_ARPFILTER is not set
+# CONFIG_IP_NF_ARPTABLES is not set
+# CONFIG_IP_NF_ARP_MANGLE is not set
+# CONFIG_IP_NF_FILTER is not set
+# CONFIG_IP_NF_IPTABLES is not set
+# CONFIG_IP_NF_MANGLE is not set
+# CONFIG_IP_NF_MATCH_AH is not set
+# CONFIG_IP_NF_MATCH_ECN is not set
+# CONFIG_IP_NF_MATCH_RPFILTER is not set
+# CONFIG_IP_NF_MATCH_TTL is not set
+# CONFIG_IP_NF_RAW is not set
+# CONFIG_IP_NF_SECURITY is not set
+# CONFIG_IP_NF_TARGET_ECN is not set
+# CONFIG_IP_NF_TARGET_MASQUERADE is not set
+# CONFIG_IP_NF_TARGET_NETMAP is not set
+# CONFIG_IP_NF_TARGET_REDIRECT is not set
+# CONFIG_IP_NF_TARGET_REJECT is not set
+# CONFIG_IP_NF_TARGET_SYNPROXY is not set
+# CONFIG_IP_NF_TARGET_TTL is not set
+# CONFIG_IP_PIMSM_V1 is not set
+# CONFIG_IP_PIMSM_V2 is not set
+# CONFIG_IP_PNP is not set
+CONFIG_IP_ROUTE_MULTIPATH=y
+CONFIG_IP_ROUTE_VERBOSE=y
+# CONFIG_IP_SCTP is not set
+# CONFIG_IP_SET is not set
+# CONFIG_IP_SET_HASH_IPMAC is not set
+# CONFIG_IP_VS is not set
+# CONFIG_IP_VS_MH is not set
+CONFIG_IP_VS_MH_TAB_INDEX=10
+# CONFIG_IP_VS_TWOS is not set
+# CONFIG_IRQSOFF_TRACER is not set
+# CONFIG_IRQ_ALL_CPUS is not set
+# CONFIG_IRQ_POLL is not set
+# CONFIG_IRQ_TIME_ACCOUNTING is not set
+# CONFIG_IRSD200 is not set
+# CONFIG_IR_GPIO_CIR is not set
+# CONFIG_IR_HIX5HD2 is not set
+# CONFIG_IR_IGORPLUGUSB is not set
+# CONFIG_IR_IGUANA is not set
+# CONFIG_IR_IMG is not set
+# CONFIG_IR_IMON is not set
+# CONFIG_IR_IMON_RAW is not set
+# CONFIG_IR_JVC_DECODER is not set
+# CONFIG_IR_MCEUSB is not set
+# CONFIG_IR_NEC_DECODER is not set
+# CONFIG_IR_RC5_DECODER is not set
+# CONFIG_IR_RC6_DECODER is not set
+# CONFIG_IR_REDRAT3 is not set
+# CONFIG_IR_SERIAL is not set
+# CONFIG_IR_SONY_DECODER is not set
+# CONFIG_IR_STREAMZAP is not set
+# CONFIG_IR_TOY is not set
+# CONFIG_IR_TTUSBIR is not set
+# CONFIG_ISA_BUS is not set
+# CONFIG_ISA_BUS_API is not set
+# CONFIG_ISCSI_BOOT_SYSFS is not set
+# CONFIG_ISCSI_TCP is not set
+CONFIG_ISDN=y
+# CONFIG_ISDN_CAPI is not set
+# CONFIG_ISL29003 is not set
+# CONFIG_ISL29020 is not set
+# CONFIG_ISL29125 is not set
+# CONFIG_ISL29501 is not set
+# CONFIG_ISL76682 is not set
+# CONFIG_ISO9660_FS is not set
+# CONFIG_ISS4xx is not set
+# CONFIG_ITG3200 is not set
+# CONFIG_IWL3945 is not set
+# CONFIG_IWLWIFI is not set
+# CONFIG_IXGBE is not set
+# CONFIG_IXGBEVF is not set
+# CONFIG_JAILHOUSE_GUEST is not set
+# CONFIG_JBD2_DEBUG is not set
+# CONFIG_JFFS2_CMODE_FAVOURLZO is not set
+# CONFIG_JFFS2_CMODE_NONE is not set
+CONFIG_JFFS2_CMODE_PRIORITY=y
+# CONFIG_JFFS2_CMODE_SIZE is not set
+CONFIG_JFFS2_COMPRESSION_OPTIONS=y
+CONFIG_JFFS2_FS=y
+CONFIG_JFFS2_FS_DEBUG=0
+# CONFIG_JFFS2_FS_POSIX_ACL is not set
+# CONFIG_JFFS2_FS_SECURITY is not set
+# CONFIG_JFFS2_FS_WBUF_VERIFY is not set
+CONFIG_JFFS2_FS_WRITEBUFFER=y
+CONFIG_JFFS2_FS_XATTR=y
+CONFIG_JFFS2_LZMA=y
+# CONFIG_JFFS2_LZO is not set
+CONFIG_JFFS2_RTIME=y
+# CONFIG_JFFS2_RUBIN is not set
+CONFIG_JFFS2_SUMMARY=y
+# CONFIG_JFFS2_ZLIB is not set
+# CONFIG_JFS_DEBUG is not set
+# CONFIG_JFS_FS is not set
+# CONFIG_JFS_POSIX_ACL is not set
+# CONFIG_JFS_SECURITY is not set
+# CONFIG_JFS_STATISTICS is not set
+# CONFIG_JME is not set
+CONFIG_JOLIET=y
+# CONFIG_JSA1212 is not set
+# CONFIG_JUMP_LABEL is not set
+# CONFIG_JZ4740_WDT is not set
+# CONFIG_KALLSYMS is not set
+# CONFIG_KALLSYMS_ABSOLUTE_PERCPU is not set
+# CONFIG_KALLSYMS_ALL is not set
+# CONFIG_KALLSYMS_SELFTEST is not set
+# CONFIG_KALLSYMS_UNCOMPRESSED is not set
+# CONFIG_KARMA_PARTITION is not set
+# CONFIG_KASAN is not set
+# CONFIG_KASAN_MODULE_TEST is not set
+CONFIG_KASAN_STACK=y
+# CONFIG_KCMP is not set
+# CONFIG_KCOV is not set
+CONFIG_KCOV_IRQ_AREA_SIZE=0x40000
+# CONFIG_KCSAN is not set
+# CONFIG_KEBA_CP500 is not set
+# CONFIG_KERNEL_BZIP2 is not set
+# CONFIG_KERNEL_GZIP is not set
+# CONFIG_KERNEL_LZ4 is not set
+# CONFIG_KERNEL_LZMA is not set
+# CONFIG_KERNEL_LZO is not set
+CONFIG_KERNEL_MODE_NEON=y
+CONFIG_KERNEL_XZ=y
+# CONFIG_KERNEL_ZSTD is not set
+CONFIG_KERNFS=y
+# CONFIG_KEXEC is not set
+# CONFIG_KEXEC_FILE is not set
+# CONFIG_KEXEC_SIG is not set
+# CONFIG_KEYBOARD_ADC is not set
+# CONFIG_KEYBOARD_ADP5588 is not set
+# CONFIG_KEYBOARD_ADP5589 is not set
+# CONFIG_KEYBOARD_APPLESPI is not set
+# CONFIG_KEYBOARD_ATKBD is not set
+# CONFIG_KEYBOARD_BCM is not set
+# CONFIG_KEYBOARD_CAP11XX is not set
+# CONFIG_KEYBOARD_CYPRESS_SF is not set
+# CONFIG_KEYBOARD_DLINK_DIR685 is not set
+# CONFIG_KEYBOARD_GPIO is not set
+# CONFIG_KEYBOARD_GPIO_POLLED is not set
+# CONFIG_KEYBOARD_LKKBD is not set
+# CONFIG_KEYBOARD_LM8323 is not set
+# CONFIG_KEYBOARD_LM8333 is not set
+# CONFIG_KEYBOARD_MATRIX is not set
+# CONFIG_KEYBOARD_MAX7359 is not set
+# CONFIG_KEYBOARD_MPR121 is not set
+# CONFIG_KEYBOARD_MT6779 is not set
+# CONFIG_KEYBOARD_NEWTON is not set
+# CONFIG_KEYBOARD_OMAP4 is not set
+# CONFIG_KEYBOARD_OPENCORES is not set
+# CONFIG_KEYBOARD_PINEPHONE is not set
+# CONFIG_KEYBOARD_PXA27x is not set
+# CONFIG_KEYBOARD_QT1050 is not set
+# CONFIG_KEYBOARD_QT1070 is not set
+# CONFIG_KEYBOARD_QT2160 is not set
+# CONFIG_KEYBOARD_SAMSUNG is not set
+# CONFIG_KEYBOARD_SH_KEYSC is not set
+# CONFIG_KEYBOARD_SNVS_PWRKEY is not set
+# CONFIG_KEYBOARD_STMPE is not set
+# CONFIG_KEYBOARD_STOWAWAY is not set
+# CONFIG_KEYBOARD_SUNKBD is not set
+# CONFIG_KEYBOARD_TCA6416 is not set
+# CONFIG_KEYBOARD_TCA8418 is not set
+# CONFIG_KEYBOARD_TEGRA is not set
+# CONFIG_KEYBOARD_TM2_TOUCHKEY is not set
+# CONFIG_KEYBOARD_TWL4030 is not set
+# CONFIG_KEYBOARD_XTKBD is not set
+# CONFIG_KEYS is not set
+# CONFIG_KEYS_REQUEST_CACHE is not set
+# CONFIG_KEY_DH_OPERATIONS is not set
+# CONFIG_KFENCE is not set
+# CONFIG_KGDB is not set
+# CONFIG_KMX61 is not set
+# CONFIG_KPROBES is not set
+# CONFIG_KPROBES_SANITY_TEST is not set
+# CONFIG_KPROBE_EVENTS_ON_NOTRACE is not set
+# CONFIG_KPROBE_EVENT_GEN_TEST is not set
+# CONFIG_KS8842 is not set
+# CONFIG_KS8851 is not set
+# CONFIG_KS8851_MLL is not set
+# CONFIG_KSM is not set
+# CONFIG_KSZ884X_PCI is not set
+# CONFIG_KUNIT is not set
+CONFIG_KUSER_HELPERS=y
+# CONFIG_KVM_AMD is not set
+# CONFIG_KVM_AMD_SEV is not set
+# CONFIG_KVM_GUEST is not set
+# CONFIG_KVM_HYPERV is not set
+# CONFIG_KVM_INTEL is not set
+# CONFIG_KVM_INTEL_PROVE_VE is not set
+CONFIG_KVM_MAX_NR_VCPUS=1024
+# CONFIG_KVM_PROVE_MMU is not set
+# CONFIG_KVM_SW_PROTECTED_VM is not set
+# CONFIG_KVM_WERROR is not set
+# CONFIG_KVM_XEN is not set
+# CONFIG_KXCJK1013 is not set
+# CONFIG_KXSD9 is not set
+# CONFIG_L2TP is not set
+# CONFIG_L2TP_ETH is not set
+# CONFIG_L2TP_IP is not set
+# CONFIG_L2TP_V3 is not set
+# CONFIG_LAN743X is not set
+# CONFIG_LAN865X is not set
+# CONFIG_LAN966X_OIC is not set
+# CONFIG_LAN966X_SWITCH is not set
+# CONFIG_LANTIQ is not set
+# CONFIG_LAPB is not set
+# CONFIG_LATENCYTOP is not set
+# CONFIG_LATTICE_ECP3_CONFIG is not set
+# CONFIG_LCD_AMS369FG06 is not set
+# CONFIG_LCD_CLASS_DEVICE is not set
+# CONFIG_LCD_HX8357 is not set
+# CONFIG_LCD_ILI922X is not set
+# CONFIG_LCD_ILI9320 is not set
+# CONFIG_LCD_L4F00242T03 is not set
+# CONFIG_LCD_LMS283GF05 is not set
+# CONFIG_LCD_LMS501KF03 is not set
+# CONFIG_LCD_LTV350QV is not set
+# CONFIG_LCD_OTM3225A is not set
+# CONFIG_LCD_TDO24M is not set
+# CONFIG_LCD_VGG2432A4 is not set
+CONFIG_LDISC_AUTOLOAD=y
+# CONFIG_LDM_PARTITION is not set
+CONFIG_LD_DEAD_CODE_DATA_ELIMINATION=y
+# CONFIG_LD_HEAD_STUB_CATCH is not set
+# CONFIG_LEDS_AN30259A is not set
+# CONFIG_LEDS_APU is not set
+# CONFIG_LEDS_AW200XX is not set
+# CONFIG_LEDS_AW2013 is not set
+# CONFIG_LEDS_BCM6328 is not set
+# CONFIG_LEDS_BCM6358 is not set
+# CONFIG_LEDS_BD2606MVV is not set
+# CONFIG_LEDS_BD2802 is not set
+# CONFIG_LEDS_BLINKM is not set
+CONFIG_LEDS_BRIGHTNESS_HW_CHANGED=y
+CONFIG_LEDS_CLASS=y
+# CONFIG_LEDS_CLASS_FLASH is not set
+CONFIG_LEDS_CLASS_MULTICOLOR=y
+# CONFIG_LEDS_CR0014114 is not set
+# CONFIG_LEDS_DAC124S085 is not set
+# CONFIG_LEDS_EL15203000 is not set
+# CONFIG_LEDS_GPIO is not set
+# CONFIG_LEDS_GROUP_MULTICOLOR is not set
+# CONFIG_LEDS_INTEL_SS4200 is not set
+# CONFIG_LEDS_IS31FL319X is not set
+# CONFIG_LEDS_IS31FL32XX is not set
+# CONFIG_LEDS_KTD202X is not set
+# CONFIG_LEDS_LM3530 is not set
+# CONFIG_LEDS_LM3532 is not set
+# CONFIG_LEDS_LM355x is not set
+# CONFIG_LEDS_LM3642 is not set
+# CONFIG_LEDS_LM3692X is not set
+# CONFIG_LEDS_LM3697 is not set
+# CONFIG_LEDS_LP3944 is not set
+# CONFIG_LEDS_LP3952 is not set
+# CONFIG_LEDS_LP50XX is not set
+# CONFIG_LEDS_LP5521 is not set
+# CONFIG_LEDS_LP5523 is not set
+# CONFIG_LEDS_LP5562 is not set
+# CONFIG_LEDS_LP5569 is not set
+# CONFIG_LEDS_LP55XX_COMMON is not set
+# CONFIG_LEDS_LP8501 is not set
+# CONFIG_LEDS_LP8860 is not set
+# CONFIG_LEDS_LT3593 is not set
+# CONFIG_LEDS_MLXCPLD is not set
+# CONFIG_LEDS_MLXREG is not set
+# CONFIG_LEDS_NCP5623 is not set
+# CONFIG_LEDS_NIC78BX is not set
+# CONFIG_LEDS_NS2 is not set
+# CONFIG_LEDS_OT200 is not set
+# CONFIG_LEDS_PCA9532 is not set
+# CONFIG_LEDS_PCA955X is not set
+# CONFIG_LEDS_PCA963X is not set
+# CONFIG_LEDS_PCA995X is not set
+# CONFIG_LEDS_PWM is not set
+# CONFIG_LEDS_PWM_MULTICOLOR is not set
+# CONFIG_LEDS_REGULATOR is not set
+# CONFIG_LEDS_SPI_BYTE is not set
+# CONFIG_LEDS_ST1202 is not set
+# CONFIG_LEDS_SUN50I_A100 is not set
+# CONFIG_LEDS_SYSCON is not set
+# CONFIG_LEDS_TCA6507 is not set
+# CONFIG_LEDS_TI_LMU_COMMON is not set
+# CONFIG_LEDS_TLC591XX is not set
+CONFIG_LEDS_TRIGGERS=y
+# CONFIG_LEDS_TRIGGER_ACTIVITY is not set
+# CONFIG_LEDS_TRIGGER_BACKLIGHT is not set
+# CONFIG_LEDS_TRIGGER_CAMERA is not set
+# CONFIG_LEDS_TRIGGER_CPU is not set
+CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
+# CONFIG_LEDS_TRIGGER_DISK is not set
+# CONFIG_LEDS_TRIGGER_GPIO is not set
+CONFIG_LEDS_TRIGGER_HEARTBEAT=y
+# CONFIG_LEDS_TRIGGER_INPUT_EVENTS is not set
+# CONFIG_LEDS_TRIGGER_MTD is not set
+CONFIG_LEDS_TRIGGER_NETDEV=y
+# CONFIG_LEDS_TRIGGER_ONESHOT is not set
+# CONFIG_LEDS_TRIGGER_PANIC is not set
+# CONFIG_LEDS_TRIGGER_PATTERN is not set
+CONFIG_LEDS_TRIGGER_TIMER=y
+# CONFIG_LEDS_TRIGGER_TRANSIENT is not set
+# CONFIG_LEDS_TRIGGER_TTY is not set
+# CONFIG_LEDS_TURRIS_OMNIA is not set
+# CONFIG_LEDS_USER is not set
+# CONFIG_LED_TRIGGER_PHY is not set
+# CONFIG_LEGACY_PTYS is not set
+# CONFIG_LEGACY_TIOCSTI is not set
+# CONFIG_LENOVO_WMI_CAMERA is not set
+# CONFIG_LENOVO_YMC is not set
+# CONFIG_LG_LAPTOP is not set
+# CONFIG_LIB80211 is not set
+# CONFIG_LIB80211_CRYPT_CCMP is not set
+# CONFIG_LIB80211_CRYPT_TKIP is not set
+# CONFIG_LIB80211_CRYPT_WEP is not set
+# CONFIG_LIB80211_DEBUG is not set
+# CONFIG_LIBCRC32C is not set
+# CONFIG_LIBERTAS is not set
+# CONFIG_LIBERTAS_THINFIRM is not set
+# CONFIG_LIBERTAS_USB is not set
+# CONFIG_LIBFC is not set
+# CONFIG_LIBFCOE is not set
+# CONFIG_LIBIPW_DEBUG is not set
+# CONFIG_LIBNVDIMM is not set
+# CONFIG_LIDAR_LITE_V2 is not set
+CONFIG_LINEAR_RANGES=y
+# CONFIG_LIQUIDIO is not set
+# CONFIG_LIQUIDIO_VF is not set
+# CONFIG_LIRC is not set
+CONFIG_LIST_HARDENED=y
+# CONFIG_LITEX_LITEETH is not set
+# CONFIG_LITEX_SOC_CONTROLLER is not set
+# CONFIG_LIVEPATCH is not set
+# CONFIG_LKDTM is not set
+CONFIG_LLC=y
+# CONFIG_LLC2 is not set
+# CONFIG_LMK04832 is not set
+# CONFIG_LMP91000 is not set
+CONFIG_LOCALVERSION=""
+# CONFIG_LOCALVERSION_AUTO is not set
+# CONFIG_LOCKD is not set
+CONFIG_LOCKDEP_BITS=15
+CONFIG_LOCKDEP_CHAINS_BITS=16
+CONFIG_LOCKDEP_CIRCULAR_QUEUE_BITS=12
+CONFIG_LOCKDEP_STACK_TRACE_BITS=19
+CONFIG_LOCKDEP_STACK_TRACE_HASH_BITS=14
+CONFIG_LOCKDEP_SUPPORT=y
+CONFIG_LOCKD_V4=y
+# CONFIG_LOCKUP_DETECTOR is not set
+# CONFIG_LOCK_EVENT_COUNTS is not set
+CONFIG_LOCK_MM_AND_FIND_VMA=y
+# CONFIG_LOCK_STAT is not set
+# CONFIG_LOCK_TORTURE_TEST is not set
+# CONFIG_LOGIG940_FF is not set
+# CONFIG_LOGIRUMBLEPAD2_FF is not set
+# CONFIG_LOGITECH_FF is not set
+# CONFIG_LOGIWHEELS_FF is not set
+# CONFIG_LOGO is not set
+CONFIG_LOG_BUF_SHIFT=17
+CONFIG_LOG_CPU_MAX_BUF_SHIFT=12
+# CONFIG_LOONGSON_MC146818 is not set
+# CONFIG_LPC_ICH is not set
+# CONFIG_LPC_SCH is not set
+# CONFIG_LP_CONSOLE is not set
+CONFIG_LRU_GEN=y
+CONFIG_LRU_GEN_ENABLED=y
+# CONFIG_LRU_GEN_STATS is not set
+# CONFIG_LSI_ET1011C_PHY is not set
+CONFIG_LSM="lockdown,yama,loadpin,safesetid,integrity"
+CONFIG_LSM_MMAP_MIN_ADDR=65536
+# CONFIG_LTC1660 is not set
+# CONFIG_LTC2309 is not set
+# CONFIG_LTC2471 is not set
+# CONFIG_LTC2485 is not set
+# CONFIG_LTC2496 is not set
+# CONFIG_LTC2497 is not set
+# CONFIG_LTC2632 is not set
+# CONFIG_LTC2664 is not set
+# CONFIG_LTC2688 is not set
+# CONFIG_LTC2983 is not set
+# CONFIG_LTE_GDM724X is not set
+CONFIG_LTO_NONE=y
+# CONFIG_LTR390 is not set
+# CONFIG_LTR501 is not set
+# CONFIG_LTRF216A is not set
+# CONFIG_LV0104CS is not set
+# CONFIG_LWQ_TEST is not set
+# CONFIG_LWTUNNEL is not set
+# CONFIG_LXT_PHY is not set
+# CONFIG_LZ4HC_COMPRESS is not set
+# CONFIG_LZ4_COMPRESS is not set
+# CONFIG_LZ4_DECOMPRESS is not set
+CONFIG_LZMA_COMPRESS=y
+CONFIG_LZMA_DECOMPRESS=y
+# CONFIG_LZO_COMPRESS is not set
+# CONFIG_LZO_DECOMPRESS is not set
+# CONFIG_M62332 is not set
+# CONFIG_MAC80211 is not set
+# CONFIG_MAC80211_MESSAGE_TRACING is not set
+CONFIG_MAC80211_STA_HASH_MAX_SIZE=0
+# CONFIG_MACB is not set
+# CONFIG_MACH_ASM9260 is not set
+# CONFIG_MACH_DECSTATION is not set
+# CONFIG_MACH_INGENIC is not set
+# CONFIG_MACH_INGENIC_SOC is not set
+# CONFIG_MACH_JAZZ is not set
+# CONFIG_MACH_JZ4740 is not set
+# CONFIG_MACH_LOONGSON2EF is not set
+# CONFIG_MACH_LOONGSON32 is not set
+# CONFIG_MACH_LOONGSON64 is not set
+# CONFIG_MACH_NINTENDO64 is not set
+# CONFIG_MACH_PIC32 is not set
+# CONFIG_MACH_REALTEK_RTL is not set
+# CONFIG_MACH_TX49XX is not set
+# CONFIG_MACINTOSH_DRIVERS is not set
+# CONFIG_MACSEC is not set
+# CONFIG_MACVLAN is not set
+# CONFIG_MACVTAP is not set
+# CONFIG_MAC_EMUMOUSEBTN is not set
+# CONFIG_MAC_PARTITION is not set
+# CONFIG_MAG3110 is not set
+# CONFIG_MAGIC_SYSRQ is not set
+CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1
+# CONFIG_MAGIC_SYSRQ_SERIAL is not set
+CONFIG_MAGIC_SYSRQ_SERIAL_SEQUENCE=""
+# CONFIG_MAILBOX is not set
+# CONFIG_MANAGER_SBS is not set
+# CONFIG_MANGLE_BOOTARGS is not set
+# CONFIG_MARVELL_10G_PHY is not set
+# CONFIG_MARVELL_88Q2XXX_PHY is not set
+# CONFIG_MARVELL_88X2222_PHY is not set
+# CONFIG_MARVELL_PHY is not set
+# CONFIG_MAX1027 is not set
+# CONFIG_MAX11100 is not set
+# CONFIG_MAX1118 is not set
+# CONFIG_MAX11205 is not set
+# CONFIG_MAX11410 is not set
+# CONFIG_MAX1241 is not set
+# CONFIG_MAX1363 is not set
+# CONFIG_MAX30100 is not set
+# CONFIG_MAX30102 is not set
+# CONFIG_MAX30208 is not set
+# CONFIG_MAX31827 is not set
+# CONFIG_MAX31856 is not set
+# CONFIG_MAX31865 is not set
+# CONFIG_MAX34408 is not set
+# CONFIG_MAX44000 is not set
+# CONFIG_MAX44009 is not set
+# CONFIG_MAX517 is not set
+# CONFIG_MAX5432 is not set
+# CONFIG_MAX5481 is not set
+# CONFIG_MAX5487 is not set
+# CONFIG_MAX5522 is not set
+# CONFIG_MAX5821 is not set
+# CONFIG_MAX63XX_WATCHDOG is not set
+# CONFIG_MAX9611 is not set
+# CONFIG_MAXIM_THERMOCOUPLE is not set
+# CONFIG_MAXLINEAR_GPHY is not set
+CONFIG_MAX_SKB_FRAGS=17
+# CONFIG_MB1232 is not set
+# CONFIG_MC3230 is not set
+# CONFIG_MCB is not set
+# CONFIG_MCP320X is not set
+# CONFIG_MCP3422 is not set
+# CONFIG_MCP3564 is not set
+# CONFIG_MCP3911 is not set
+# CONFIG_MCP4018 is not set
+# CONFIG_MCP41010 is not set
+# CONFIG_MCP4131 is not set
+# CONFIG_MCP4531 is not set
+# CONFIG_MCP4725 is not set
+# CONFIG_MCP4728 is not set
+# CONFIG_MCP4821 is not set
+# CONFIG_MCP4922 is not set
+# CONFIG_MCP9600 is not set
+# CONFIG_MCPM is not set
+# CONFIG_MCTP is not set
+# CONFIG_MD is not set
+# CONFIG_MDIO_BCM_UNIMAC is not set
+# CONFIG_MDIO_BITBANG is not set
+# CONFIG_MDIO_BUS_MUX_GPIO is not set
+# CONFIG_MDIO_BUS_MUX_MMIOREG is not set
+# CONFIG_MDIO_BUS_MUX_MULTIPLEXER is not set
+# CONFIG_MDIO_DEVICE is not set
+# CONFIG_MDIO_DEVRES is not set
+# CONFIG_MDIO_HISI_FEMAC is not set
+# CONFIG_MDIO_IPQ4019 is not set
+# CONFIG_MDIO_IPQ8064 is not set
+# CONFIG_MDIO_MSCC_MIIM is not set
+# CONFIG_MDIO_MVUSB is not set
+# CONFIG_MDIO_OCTEON is not set
+# CONFIG_MDIO_THUNDER is not set
+# CONFIG_MDM_GCC_9607 is not set
+# CONFIG_MD_BITMAP_FILE is not set
+# CONFIG_MEDIATEK_GE_PHY is not set
+# CONFIG_MEDIATEK_MT6577_AUXADC is not set
+# CONFIG_MEDIA_ANALOG_TV_SUPPORT is not set
+# CONFIG_MEDIA_ATTACH is not set
+# CONFIG_MEDIA_CAMERA_SUPPORT is not set
+# CONFIG_MEDIA_CEC_SUPPORT is not set
+# CONFIG_MEDIA_CONTROLLER is not set
+# CONFIG_MEDIA_CONTROLLER_DVB is not set
+# CONFIG_MEDIA_DIGITAL_TV_SUPPORT is not set
+# CONFIG_MEDIA_PCI_SUPPORT is not set
+# CONFIG_MEDIA_PLATFORM_DRIVERS is not set
+# CONFIG_MEDIA_PLATFORM_SUPPORT is not set
+# CONFIG_MEDIA_RADIO_SUPPORT is not set
+# CONFIG_MEDIA_SDR_SUPPORT is not set
+# CONFIG_MEDIA_SUBDRV_AUTOSELECT is not set
+# CONFIG_MEDIA_SUPPORT is not set
+# CONFIG_MEDIA_SUPPORT_FILTER is not set
+# CONFIG_MEDIA_TEST_SUPPORT is not set
+# CONFIG_MEDIA_TUNER_E4000 is not set
+# CONFIG_MEDIA_TUNER_FC0011 is not set
+# CONFIG_MEDIA_TUNER_FC0012 is not set
+# CONFIG_MEDIA_TUNER_FC0013 is not set
+# CONFIG_MEDIA_TUNER_FC2580 is not set
+# CONFIG_MEDIA_TUNER_IT913X is not set
+# CONFIG_MEDIA_TUNER_M88RS6000T is not set
+# CONFIG_MEDIA_TUNER_MAX2165 is not set
+# CONFIG_MEDIA_TUNER_MC44S803 is not set
+# CONFIG_MEDIA_TUNER_MSI001 is not set
+# CONFIG_MEDIA_TUNER_MT2060 is not set
+# CONFIG_MEDIA_TUNER_MT2063 is not set
+# CONFIG_MEDIA_TUNER_MT20XX is not set
+# CONFIG_MEDIA_TUNER_MT2131 is not set
+# CONFIG_MEDIA_TUNER_MT2266 is not set
+# CONFIG_MEDIA_TUNER_MXL301RF is not set
+# CONFIG_MEDIA_TUNER_MXL5005S is not set
+# CONFIG_MEDIA_TUNER_MXL5007T is not set
+# CONFIG_MEDIA_TUNER_QM1D1B0004 is not set
+# CONFIG_MEDIA_TUNER_QM1D1C0042 is not set
+# CONFIG_MEDIA_TUNER_QT1010 is not set
+# CONFIG_MEDIA_TUNER_R820T is not set
+# CONFIG_MEDIA_TUNER_SI2157 is not set
+# CONFIG_MEDIA_TUNER_SIMPLE is not set
+# CONFIG_MEDIA_TUNER_TDA18212 is not set
+# CONFIG_MEDIA_TUNER_TDA18218 is not set
+# CONFIG_MEDIA_TUNER_TDA18250 is not set
+# CONFIG_MEDIA_TUNER_TDA18271 is not set
+# CONFIG_MEDIA_TUNER_TDA827X is not set
+# CONFIG_MEDIA_TUNER_TDA8290 is not set
+# CONFIG_MEDIA_TUNER_TDA9887 is not set
+# CONFIG_MEDIA_TUNER_TEA5761 is not set
+# CONFIG_MEDIA_TUNER_TEA5767 is not set
+# CONFIG_MEDIA_TUNER_TUA9001 is not set
+# CONFIG_MEDIA_TUNER_XC2028 is not set
+# CONFIG_MEDIA_TUNER_XC4000 is not set
+# CONFIG_MEDIA_TUNER_XC5000 is not set
+# CONFIG_MEDIA_USB_SUPPORT is not set
+# CONFIG_MEGARAID_LEGACY is not set
+# CONFIG_MEGARAID_NEWGEN is not set
+# CONFIG_MEGARAID_SAS is not set
+# CONFIG_MELLANOX_PLATFORM is not set
+CONFIG_MEMBARRIER=y
+CONFIG_MEMFD_CREATE=y
+# CONFIG_MEMORY is not set
+# CONFIG_MEMORY_FAILURE is not set
+# CONFIG_MEMORY_HOTPLUG is not set
+# CONFIG_MEMSTICK is not set
+# CONFIG_MEMTEST is not set
+# CONFIG_MEM_ALLOC_PROFILING is not set
+# CONFIG_MEN_A21_WDT is not set
+# CONFIG_MESON_SM is not set
+CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4
+# CONFIG_MFD_88PM800 is not set
+# CONFIG_MFD_88PM805 is not set
+# CONFIG_MFD_88PM860X is not set
+# CONFIG_MFD_88PM886_PMIC is not set
+# CONFIG_MFD_AAT2870_CORE is not set
+# CONFIG_MFD_AC100 is not set
+# CONFIG_MFD_ACT8945A is not set
+# CONFIG_MFD_ADP5585 is not set
+# CONFIG_MFD_ARIZONA_I2C is not set
+# CONFIG_MFD_ARIZONA_SPI is not set
+# CONFIG_MFD_AS3711 is not set
+# CONFIG_MFD_AS3722 is not set
+# CONFIG_MFD_ATC260X_I2C is not set
+# CONFIG_MFD_ATMEL_FLEXCOM is not set
+# CONFIG_MFD_ATMEL_HLCDC is not set
+# CONFIG_MFD_AXP20X is not set
+# CONFIG_MFD_AXP20X_I2C is not set
+# CONFIG_MFD_BCM590XX is not set
+# CONFIG_MFD_BD9571MWV is not set
+# CONFIG_MFD_CORE is not set
+# CONFIG_MFD_CPCAP is not set
+# CONFIG_MFD_CS40L50_I2C is not set
+# CONFIG_MFD_CS40L50_SPI is not set
+# CONFIG_MFD_CS42L43_I2C is not set
+# CONFIG_MFD_CS5535 is not set
+# CONFIG_MFD_DA9052_I2C is not set
+# CONFIG_MFD_DA9052_SPI is not set
+# CONFIG_MFD_DA9055 is not set
+# CONFIG_MFD_DA9062 is not set
+# CONFIG_MFD_DA9063 is not set
+# CONFIG_MFD_DA9150 is not set
+# CONFIG_MFD_DLN2 is not set
+# CONFIG_MFD_EXYNOS_LPASS is not set
+# CONFIG_MFD_GATEWORKS_GSC is not set
+# CONFIG_MFD_HI6421_PMIC is not set
+# CONFIG_MFD_INTEL_M10_BMC_SPI is not set
+# CONFIG_MFD_INTEL_QUARK_I2C_GPIO is not set
+# CONFIG_MFD_IQS62X is not set
+# CONFIG_MFD_JANZ_CMODIO is not set
+# CONFIG_MFD_KEMPLD is not set
+# CONFIG_MFD_LM3533 is not set
+# CONFIG_MFD_LOCHNAGAR is not set
+# CONFIG_MFD_LP3943 is not set
+# CONFIG_MFD_LP8788 is not set
+# CONFIG_MFD_MADERA is not set
+# CONFIG_MFD_MAX14577 is not set
+# CONFIG_MFD_MAX5970 is not set
+# CONFIG_MFD_MAX77541 is not set
+# CONFIG_MFD_MAX77620 is not set
+# CONFIG_MFD_MAX77650 is not set
+# CONFIG_MFD_MAX77686 is not set
+# CONFIG_MFD_MAX77693 is not set
+# CONFIG_MFD_MAX77714 is not set
+# CONFIG_MFD_MAX77843 is not set
+# CONFIG_MFD_MAX8907 is not set
+# CONFIG_MFD_MAX8925 is not set
+# CONFIG_MFD_MAX8997 is not set
+# CONFIG_MFD_MAX8998 is not set
+# CONFIG_MFD_MC13XXX is not set
+# CONFIG_MFD_MC13XXX_I2C is not set
+# CONFIG_MFD_MC13XXX_SPI is not set
+# CONFIG_MFD_MENF21BMC is not set
+# CONFIG_MFD_MP2629 is not set
+# CONFIG_MFD_MT6360 is not set
+# CONFIG_MFD_MT6370 is not set
+# CONFIG_MFD_MT6397 is not set
+# CONFIG_MFD_NTXEC is not set
+# CONFIG_MFD_OCELOT is not set
+# CONFIG_MFD_OMAP_USB_HOST is not set
+# CONFIG_MFD_PALMAS is not set
+# CONFIG_MFD_PCF50633 is not set
+# CONFIG_MFD_PM8XXX is not set
+# CONFIG_MFD_QCOM_PM8008 is not set
+# CONFIG_MFD_RC5T583 is not set
+# CONFIG_MFD_RDC321X is not set
+# CONFIG_MFD_RETU is not set
+# CONFIG_MFD_RK8XX_I2C is not set
+# CONFIG_MFD_RK8XX_SPI is not set
+# CONFIG_MFD_RN5T618 is not set
+# CONFIG_MFD_ROHM_BD71828 is not set
+# CONFIG_MFD_ROHM_BD718XX is not set
+# CONFIG_MFD_ROHM_BD957XMUF is not set
+# CONFIG_MFD_ROHM_BD96801 is not set
+# CONFIG_MFD_RSMU_I2C is not set
+# CONFIG_MFD_RSMU_SPI is not set
+# CONFIG_MFD_RT4831 is not set
+# CONFIG_MFD_RT5033 is not set
+# CONFIG_MFD_RT5120 is not set
+# CONFIG_MFD_SEC_CORE is not set
+# CONFIG_MFD_SI476X_CORE is not set
+# CONFIG_MFD_SKY81452 is not set
+# CONFIG_MFD_SL28CPLD is not set
+# CONFIG_MFD_SM501 is not set
+# CONFIG_MFD_SMPRO is not set
+# CONFIG_MFD_SPACEMIT_P1 is not set
+# CONFIG_MFD_STMFX is not set
+# CONFIG_MFD_STMPE is not set
+# CONFIG_MFD_STPMIC1 is not set
+# CONFIG_MFD_SY7636A is not set
+# CONFIG_MFD_SYSCON is not set
+# CONFIG_MFD_TC3589X is not set
+# CONFIG_MFD_TIMBERDALE is not set
+# CONFIG_MFD_TI_AM335X_TSCADC is not set
+# CONFIG_MFD_TI_LMU is not set
+# CONFIG_MFD_TI_LP873X is not set
+# CONFIG_MFD_TI_LP87565 is not set
+# CONFIG_MFD_TN48M_CPLD is not set
+# CONFIG_MFD_TPS65086 is not set
+# CONFIG_MFD_TPS65090 is not set
+# CONFIG_MFD_TPS65217 is not set
+# CONFIG_MFD_TPS65218 is not set
+# CONFIG_MFD_TPS65219 is not set
+# CONFIG_MFD_TPS6586X is not set
+# CONFIG_MFD_TPS65910 is not set
+# CONFIG_MFD_TPS65912 is not set
+# CONFIG_MFD_TPS65912_I2C is not set
+# CONFIG_MFD_TPS65912_SPI is not set
+# CONFIG_MFD_TPS6594_I2C is not set
+# CONFIG_MFD_TPS6594_SPI is not set
+# CONFIG_MFD_TQMX86 is not set
+# CONFIG_MFD_VIPERBOARD is not set
+# CONFIG_MFD_VX855 is not set
+# CONFIG_MFD_WL1273_CORE is not set
+# CONFIG_MFD_WM831X is not set
+# CONFIG_MFD_WM831X_I2C is not set
+# CONFIG_MFD_WM831X_SPI is not set
+# CONFIG_MFD_WM8350_I2C is not set
+# CONFIG_MFD_WM8400 is not set
+# CONFIG_MFD_WM8994 is not set
+# CONFIG_MHI_BUS is not set
+# CONFIG_MHI_BUS_DEBUG is not set
+# CONFIG_MHI_BUS_EP is not set
+# CONFIG_MHI_BUS_PCI_GENERIC is not set
+# CONFIG_MHI_NET is not set
+# CONFIG_MHI_WWAN_CTRL is not set
+# CONFIG_MHI_WWAN_MBIM is not set
+# CONFIG_MICREL_KS8995MA is not set
+# CONFIG_MICREL_PHY is not set
+# CONFIG_MICROCHIP_PHY is not set
+# CONFIG_MICROCHIP_PIT64B is not set
+# CONFIG_MICROCHIP_T1S_PHY is not set
+# CONFIG_MICROCHIP_T1_PHY is not set
+# CONFIG_MICROSEMI_PHY is not set
+# CONFIG_MIGRATION is not set
+CONFIG_MII=y
+# CONFIG_MIKROTIK is not set
+# CONFIG_MIKROTIK_RB532 is not set
+# CONFIG_MINIX_FS is not set
+# CONFIG_MINIX_FS_NATIVE_ENDIAN is not set
+# CONFIG_MINIX_SUBPARTITION is not set
+# CONFIG_MIPS32_N32 is not set
+# CONFIG_MIPS32_O32 is not set
+# CONFIG_MIPS_ALCHEMY is not set
+# CONFIG_MIPS_CDMM is not set
+# CONFIG_MIPS_CMDLINE_DTB_EXTEND is not set
+# CONFIG_MIPS_CMDLINE_FROM_DTB is not set
+# CONFIG_MIPS_COBALT is not set
+# CONFIG_MIPS_CPS is not set
+# CONFIG_MIPS_ELF_APPENDED_DTB is not set
+# CONFIG_MIPS_FP_SUPPORT is not set
+# CONFIG_MIPS_GENERIC is not set
+# CONFIG_MIPS_GENERIC_KERNEL is not set
+# CONFIG_MIPS_MALTA is not set
+# CONFIG_MIPS_O32_FP64_SUPPORT is not set
+# CONFIG_MIPS_PLATFORM_DEVICES is not set
+# CONFIG_MIPS_RAW_APPENDED_DTB is not set
+# CONFIG_MIPS_VA_BITS_48 is not set
+# CONFIG_MIPS_VPE_LOADER is not set
+# CONFIG_MISC_ALCOR_PCI is not set
+CONFIG_MISC_FILESYSTEMS=y
+# CONFIG_MISC_RTSX_PCI is not set
+# CONFIG_MISC_RTSX_USB is not set
+# CONFIG_MISDN is not set
+# CONFIG_MISDN_AVMFRITZ is not set
+# CONFIG_MISDN_HFCPCI is not set
+# CONFIG_MISDN_HFCUSB is not set
+# CONFIG_MISDN_INFINEON is not set
+# CONFIG_MISDN_NETJET is not set
+# CONFIG_MISDN_SPEEDFAX is not set
+# CONFIG_MISDN_W6692 is not set
+CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY=y
+# CONFIG_MKISS is not set
+# CONFIG_MLX4_CORE is not set
+# CONFIG_MLX4_EN is not set
+# CONFIG_MLX5_CORE is not set
+# CONFIG_MLX5_DPLL is not set
+# CONFIG_MLX5_MACSEC is not set
+# CONFIG_MLX5_SF is not set
+# CONFIG_MLX5_VFIO_PCI is not set
+# CONFIG_MLX90614 is not set
+# CONFIG_MLX90632 is not set
+# CONFIG_MLX90635 is not set
+# CONFIG_MLXFW is not set
+# CONFIG_MLXSW_CORE is not set
+# CONFIG_MLX_PLATFORM is not set
+# CONFIG_MMA7455_I2C is not set
+# CONFIG_MMA7455_SPI is not set
+# CONFIG_MMA7660 is not set
+# CONFIG_MMA8452 is not set
+# CONFIG_MMA9551 is not set
+# CONFIG_MMA9553 is not set
+# CONFIG_MMC is not set
+# CONFIG_MMC35240 is not set
+# CONFIG_MMC_ARMMMCI is not set
+# CONFIG_MMC_AU1X is not set
+# CONFIG_MMC_BLOCK is not set
+CONFIG_MMC_BLOCK_MINORS=8
+# CONFIG_MMC_CAVIUM_THUNDERX is not set
+# CONFIG_MMC_CB710 is not set
+# CONFIG_MMC_CQHCI is not set
+# CONFIG_MMC_DEBUG is not set
+# CONFIG_MMC_DW is not set
+# CONFIG_MMC_HSQ is not set
+# CONFIG_MMC_JZ4740 is not set
+# CONFIG_MMC_MTK is not set
+# CONFIG_MMC_MVSDIO is not set
+# CONFIG_MMC_SDHCI is not set
+# CONFIG_MMC_SDHCI_ACPI is not set
+# CONFIG_MMC_SDHCI_AM654 is not set
+# CONFIG_MMC_SDHCI_BCM_KONA is not set
+# CONFIG_MMC_SDHCI_BRCMSTB is not set
+# CONFIG_MMC_SDHCI_CADENCE is not set
+# CONFIG_MMC_SDHCI_F_SDH30 is not set
+# CONFIG_MMC_SDHCI_IPROC is not set
+# CONFIG_MMC_SDHCI_MILBEAUT is not set
+# CONFIG_MMC_SDHCI_MSM is not set
+# CONFIG_MMC_SDHCI_OF_ARASAN is not set
+# CONFIG_MMC_SDHCI_OF_ASPEED is not set
+# CONFIG_MMC_SDHCI_OF_AT91 is not set
+# CONFIG_MMC_SDHCI_OF_DWCMSHC is not set
+# CONFIG_MMC_SDHCI_OF_ESDHC is not set
+# CONFIG_MMC_SDHCI_OF_HLWD is not set
+# CONFIG_MMC_SDHCI_OMAP is not set
+# CONFIG_MMC_SDHCI_PCI is not set
+# CONFIG_MMC_SDHCI_PXAV2 is not set
+# CONFIG_MMC_SDHCI_PXAV3 is not set
+# CONFIG_MMC_SDHCI_S3C is not set
+# CONFIG_MMC_SDHCI_XENON is not set
+# CONFIG_MMC_SDRICOH_CS is not set
+# CONFIG_MMC_SPI is not set
+# CONFIG_MMC_STM32_SDMMC is not set
+# CONFIG_MMC_TEST is not set
+# CONFIG_MMC_TIFM_SD is not set
+# CONFIG_MMC_TOSHIBA_PCI is not set
+# CONFIG_MMC_USDHI6ROL0 is not set
+# CONFIG_MMC_USHC is not set
+# CONFIG_MMC_VIA_SDMMC is not set
+# CONFIG_MMC_VUB300 is not set
+# CONFIG_MMIOTRACE is not set
+CONFIG_MMU=y
+CONFIG_MMU_GATHER_RCU_TABLE_FREE=y
+CONFIG_MMU_GATHER_TABLE_FREE=y
+CONFIG_MODPROBE_PATH="/sbin/modprobe"
+CONFIG_MODULES=y
+# CONFIG_MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS is not set
+# CONFIG_MODULE_COMPRESS is not set
+# CONFIG_MODULE_COMPRESS_GZIP is not set
+# CONFIG_MODULE_COMPRESS_XZ is not set
+# CONFIG_MODULE_COMPRESS_ZSTD is not set
+# CONFIG_MODULE_DEBUG is not set
+# CONFIG_MODULE_FORCE_LOAD is not set
+# CONFIG_MODULE_FORCE_UNLOAD is not set
+# CONFIG_MODULE_SIG is not set
+# CONFIG_MODULE_SRCVERSION_ALL is not set
+CONFIG_MODULE_STRIPPED=y
+CONFIG_MODULE_UNLOAD=y
+# CONFIG_MODULE_UNLOAD_TAINT_TRACKING is not set
+# CONFIG_MODVERSIONS is not set
+# CONFIG_MOST is not set
+# CONFIG_MOTORCOMM_PHY is not set
+# CONFIG_MOUSE_APPLETOUCH is not set
+# CONFIG_MOUSE_BCM5974 is not set
+# CONFIG_MOUSE_CYAPA is not set
+# CONFIG_MOUSE_ELAN_I2C is not set
+# CONFIG_MOUSE_GPIO is not set
+# CONFIG_MOUSE_INPORT is not set
+# CONFIG_MOUSE_LOGIBM is not set
+# CONFIG_MOUSE_PC110PAD is not set
+# CONFIG_MOUSE_PS2_FOCALTECH is not set
+# CONFIG_MOUSE_PS2_SENTELIC is not set
+# CONFIG_MOUSE_SERIAL is not set
+# CONFIG_MOUSE_SYNAPTICS_I2C is not set
+# CONFIG_MOUSE_SYNAPTICS_USB is not set
+# CONFIG_MOUSE_VSXXXAA is not set
+# CONFIG_MOXTET is not set
+# CONFIG_MPL115 is not set
+# CONFIG_MPL115_I2C is not set
+# CONFIG_MPL115_SPI is not set
+# CONFIG_MPL3115 is not set
+# CONFIG_MPLS is not set
+# CONFIG_MPLS_IPTUNNEL is not set
+# CONFIG_MPLS_ROUTING is not set
+# CONFIG_MPRLS0025PA is not set
+# CONFIG_MPTCP is not set
+# CONFIG_MPU3050_I2C is not set
+# CONFIG_MQ_IOSCHED_DEADLINE is not set
+# CONFIG_MQ_IOSCHED_KYBER is not set
+# CONFIG_MS5611 is not set
+# CONFIG_MS5637 is not set
+# CONFIG_MSA311 is not set
+# CONFIG_MSCC_OCELOT_SWITCH is not set
+# CONFIG_MSDOS_FS is not set
+CONFIG_MSDOS_PARTITION=y
+# CONFIG_MSE102X is not set
+# CONFIG_MSI_BITMAP_SELFTEST is not set
+# CONFIG_MSI_LAPTOP is not set
+# CONFIG_MSI_WMI is not set
+# CONFIG_MSI_WMI_PLATFORM is not set
+# CONFIG_MSM_GCC_8953 is not set
+# CONFIG_MSM_MMCC_8994 is not set
+# CONFIG_MST_IRQ is not set
+CONFIG_MTD=y
+# CONFIG_MTD_ABSENT is not set
+# CONFIG_MTD_AFS_PARTS is not set
+CONFIG_MTD_BLKDEVS=y
+CONFIG_MTD_BLOCK=y
+# CONFIG_MTD_BLOCK2MTD is not set
+CONFIG_MTD_CFI=y
+# CONFIG_MTD_CFI_ADV_OPTIONS is not set
+CONFIG_MTD_CFI_AMDSTD=y
+# CONFIG_MTD_CFI_BE_BYTE_SWAP is not set
+CONFIG_MTD_CFI_I1=y
+CONFIG_MTD_CFI_I2=y
+# CONFIG_MTD_CFI_I4 is not set
+# CONFIG_MTD_CFI_I8 is not set
+CONFIG_MTD_CFI_INTELEXT=y
+# CONFIG_MTD_CFI_LE_BYTE_SWAP is not set
+CONFIG_MTD_CFI_NOSWAP=y
+# CONFIG_MTD_CFI_STAA is not set
+CONFIG_MTD_CFI_UTIL=y
+# CONFIG_MTD_CMDLINE_PARTS is not set
+CONFIG_MTD_COMPLEX_MAPPINGS=y
+# CONFIG_MTD_DATAFLASH is not set
+# CONFIG_MTD_DOCG3 is not set
+CONFIG_MTD_GEN_PROBE=y
+# CONFIG_MTD_HYPERBUS is not set
+# CONFIG_MTD_IMPA7 is not set
+# CONFIG_MTD_JEDECPROBE is not set
+# CONFIG_MTD_LPDDR is not set
+# CONFIG_MTD_LPDDR2_NVM is not set
+CONFIG_MTD_MAP_BANK_WIDTH_1=y
+# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set
+CONFIG_MTD_MAP_BANK_WIDTH_2=y
+# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set
+CONFIG_MTD_MAP_BANK_WIDTH_4=y
+# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set
+# CONFIG_MTD_MCHP23K256 is not set
+# CONFIG_MTD_MCHP48L640 is not set
+# CONFIG_MTD_MTDRAM is not set
+# CONFIG_MTD_MYLOADER_PARTS is not set
+# CONFIG_MTD_NAND_AMS_DELTA is not set
+# CONFIG_MTD_NAND_ARASAN is not set
+# CONFIG_MTD_NAND_ATMEL is not set
+# CONFIG_MTD_NAND_AU1550 is not set
+# CONFIG_MTD_NAND_BRCMNAND is not set
+# CONFIG_MTD_NAND_BRCMNAND_BCM63XX is not set
+# CONFIG_MTD_NAND_BRCMNAND_BCMBCA is not set
+# CONFIG_MTD_NAND_BRCMNAND_BRCMSTB is not set
+# CONFIG_MTD_NAND_BRCMNAND_IPROC is not set
+# CONFIG_MTD_NAND_CADENCE is not set
+# CONFIG_MTD_NAND_CAFE is not set
+# CONFIG_MTD_NAND_CS553X is not set
+# CONFIG_MTD_NAND_DAVINCI is not set
+# CONFIG_MTD_NAND_DENALI is not set
+# CONFIG_MTD_NAND_DENALI_DT is not set
+# CONFIG_MTD_NAND_DENALI_PCI is not set
+# CONFIG_MTD_NAND_DISKONCHIP is not set
+# CONFIG_MTD_NAND_ECC is not set
+# CONFIG_MTD_NAND_ECC_MXIC is not set
+# CONFIG_MTD_NAND_ECC_SW_BCH is not set
+# CONFIG_MTD_NAND_ECC_SW_HAMMING is not set
+# CONFIG_MTD_NAND_ECC_SW_HAMMING_SMC is not set
+# CONFIG_MTD_NAND_FSL_ELBC is not set
+# CONFIG_MTD_NAND_FSL_IFC is not set
+# CONFIG_MTD_NAND_FSL_UPM is not set
+# CONFIG_MTD_NAND_FSMC is not set
+# CONFIG_MTD_NAND_GPIO is not set
+# CONFIG_MTD_NAND_GPMI_NAND is not set
+# CONFIG_MTD_NAND_HISI504 is not set
+# CONFIG_MTD_NAND_INTEL_LGM is not set
+# CONFIG_MTD_NAND_MPC5121_NFC is not set
+# CONFIG_MTD_NAND_MTK is not set
+# CONFIG_MTD_NAND_MTK_BMT is not set
+# CONFIG_MTD_NAND_MXC is not set
+# CONFIG_MTD_NAND_MXIC is not set
+# CONFIG_MTD_NAND_NANDSIM is not set
+# CONFIG_MTD_NAND_NDFC is not set
+# CONFIG_MTD_NAND_OMAP2 is not set
+# CONFIG_MTD_NAND_OMAP_BCH_BUILD is not set
+# CONFIG_MTD_NAND_ORION is not set
+# CONFIG_MTD_NAND_PASEMI is not set
+# CONFIG_MTD_NAND_PLATFORM is not set
+# CONFIG_MTD_NAND_RICOH is not set
+# CONFIG_MTD_NAND_S3C2410 is not set
+# CONFIG_MTD_NAND_SHARPSL is not set
+# CONFIG_MTD_NAND_SH_FLCTL is not set
+# CONFIG_MTD_NAND_SOCRATES is not set
+# CONFIG_MTD_NAND_TXX9NDFMC is not set
+CONFIG_MTD_OF_PARTS=y
+# CONFIG_MTD_ONENAND is not set
+# CONFIG_MTD_OOPS is not set
+# CONFIG_MTD_OTP is not set
+# CONFIG_MTD_PARSER_TRX is not set
+# CONFIG_MTD_PARTITIONED_MASTER is not set
+# CONFIG_MTD_PCI is not set
+# CONFIG_MTD_PCMCIA is not set
+# CONFIG_MTD_PHRAM is not set
+# CONFIG_MTD_PHYSMAP is not set
+# CONFIG_MTD_PHYSMAP_COMPAT is not set
+# CONFIG_MTD_PHYSMAP_GEMINI is not set
+# CONFIG_MTD_PHYSMAP_GPIO_ADDR is not set
+# CONFIG_MTD_PHYSMAP_IXP4XX is not set
+CONFIG_MTD_PHYSMAP_OF=y
+# CONFIG_MTD_PHYSMAP_VERSATILE is not set
+# CONFIG_MTD_PLATRAM is not set
+# CONFIG_MTD_PMC551 is not set
+# CONFIG_MTD_RAM is not set
+# CONFIG_MTD_RAW_NAND is not set
+CONFIG_MTD_REDBOOT_DIRECTORY_BLOCK=-1
+# CONFIG_MTD_REDBOOT_PARTS is not set
+# CONFIG_MTD_REDBOOT_PARTS_READONLY is not set
+# CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED is not set
+# CONFIG_MTD_ROM is not set
+CONFIG_MTD_ROOTFS_ROOT_DEV=y
+# CONFIG_MTD_ROUTERBOOT_PARTS is not set
+# CONFIG_MTD_SERCOMM_PARTS is not set
+# CONFIG_MTD_SLRAM is not set
+# CONFIG_MTD_SM_COMMON is not set
+# CONFIG_MTD_SPI_NAND is not set
+# CONFIG_MTD_SPI_NOR is not set
+# CONFIG_MTD_SPI_NOR_SWP_DISABLE is not set
+CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE=y
+# CONFIG_MTD_SPI_NOR_SWP_KEEP is not set
+# CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is not set
+# CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE is not set
+CONFIG_MTD_SPLIT=y
+# CONFIG_MTD_SPLIT_BCM63XX_FW is not set
+# CONFIG_MTD_SPLIT_BCM_WFI_FW is not set
+# CONFIG_MTD_SPLIT_BRNIMAGE_FW is not set
+# CONFIG_MTD_SPLIT_ELF_FW is not set
+# CONFIG_MTD_SPLIT_EVA_FW is not set
+# CONFIG_MTD_SPLIT_FIRMWARE is not set
+CONFIG_MTD_SPLIT_FIRMWARE_NAME="firmware"
+# CONFIG_MTD_SPLIT_FIT_FW is not set
+# CONFIG_MTD_SPLIT_H3C_VFS is not set
+# CONFIG_MTD_SPLIT_JIMAGE_FW is not set
+# CONFIG_MTD_SPLIT_LZMA_FW is not set
+# CONFIG_MTD_SPLIT_MINOR_FW is not set
+# CONFIG_MTD_SPLIT_MSTC_BOOT is not set
+# CONFIG_MTD_SPLIT_OPENWRT_PROLOG is not set
+# CONFIG_MTD_SPLIT_SEAMA_FW is not set
+# CONFIG_MTD_SPLIT_SEIL_FW is not set
+CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y
+CONFIG_MTD_SPLIT_SUPPORT=y
+# CONFIG_MTD_SPLIT_TPLINK_FW is not set
+# CONFIG_MTD_SPLIT_TRX_FW is not set
+# CONFIG_MTD_SPLIT_UIMAGE_FW is not set
+# CONFIG_MTD_SPLIT_WRGG_FW is not set
+# CONFIG_MTD_SST25L is not set
+# CONFIG_MTD_SWAP is not set
+# CONFIG_MTD_TESTS is not set
+# CONFIG_MTD_UBI is not set
+# CONFIG_MTD_UBI_FASTMAP is not set
+# CONFIG_MTD_UBI_GLUEBI is not set
+# CONFIG_MTD_UBI_NVMEM is not set
+# CONFIG_MTD_VIRT_CONCAT is not set
+# CONFIG_MTK_DEVAPC is not set
+# CONFIG_MTK_MMSYS is not set
+# CONFIG_MTK_T7XX is not set
+# CONFIG_MTK_THERMAL is not set
+# CONFIG_MULTIPLEXER is not set
+CONFIG_MULTIUSER=y
+# CONFIG_MUTEX_SPIN_ON_OWNER is not set
+# CONFIG_MUX_ADG792A is not set
+# CONFIG_MUX_ADGS1408 is not set
+# CONFIG_MUX_GPIO is not set
+# CONFIG_MUX_MMIO is not set
+# CONFIG_MV643XX_ETH is not set
+# CONFIG_MVMDIO is not set
+# CONFIG_MVNETA_BM is not set
+# CONFIG_MV_XOR_V2 is not set
+# CONFIG_MWAVE is not set
+# CONFIG_MWL8K is not set
+# CONFIG_MXC4005 is not set
+# CONFIG_MXC6255 is not set
+# CONFIG_MXM_WMI is not set
+# CONFIG_MYRI10GE is not set
+# CONFIG_NAMESPACES is not set
+# CONFIG_NATIONAL_PHY is not set
+# CONFIG_NATSEMI is not set
+# CONFIG_NAU7802 is not set
+# CONFIG_NBPFAXI_DMA is not set
+# CONFIG_NCN26000_PHY is not set
+# CONFIG_NE2000 is not set
+# CONFIG_NE2K_PCI is not set
+CONFIG_NEED_TASKS_RCU=y
+CONFIG_NET=y
+# CONFIG_NETCONSOLE is not set
+# CONFIG_NETCONSOLE_EXTENDED_LOG is not set
+CONFIG_NETDEVICES=y
+# CONFIG_NETDEVSIM is not set
+# CONFIG_NETFILTER is not set
+# CONFIG_NETFILTER_ADVANCED is not set
+# CONFIG_NETFILTER_EGRESS is not set
+# CONFIG_NETFILTER_INGRESS is not set
+# CONFIG_NETFILTER_NETLINK is not set
+# CONFIG_NETFILTER_NETLINK_ACCT is not set
+# CONFIG_NETFILTER_NETLINK_GLUE_CT is not set
+# CONFIG_NETFILTER_NETLINK_HOOK is not set
+# CONFIG_NETFILTER_NETLINK_LOG is not set
+# CONFIG_NETFILTER_NETLINK_OSF is not set
+# CONFIG_NETFILTER_NETLINK_QUEUE is not set
+# CONFIG_NETFILTER_XTABLES is not set
+# CONFIG_NETFILTER_XTABLES_COMPAT is not set
+# CONFIG_NETFILTER_XT_CONNMARK is not set
+# CONFIG_NETFILTER_XT_MARK is not set
+# CONFIG_NETFILTER_XT_MATCH_ADDRTYPE is not set
+# CONFIG_NETFILTER_XT_MATCH_BPF is not set
+# CONFIG_NETFILTER_XT_MATCH_CGROUP is not set
+# CONFIG_NETFILTER_XT_MATCH_CLUSTER is not set
+# CONFIG_NETFILTER_XT_MATCH_COMMENT is not set
+# CONFIG_NETFILTER_XT_MATCH_CONNBYTES is not set
+# CONFIG_NETFILTER_XT_MATCH_CONNLABEL is not set
+# CONFIG_NETFILTER_XT_MATCH_CONNLIMIT is not set
+# CONFIG_NETFILTER_XT_MATCH_CONNMARK is not set
+# CONFIG_NETFILTER_XT_MATCH_CONNTRACK is not set
+# CONFIG_NETFILTER_XT_MATCH_CPU is not set
+# CONFIG_NETFILTER_XT_MATCH_DCCP is not set
+# CONFIG_NETFILTER_XT_MATCH_DEVGROUP is not set
+# CONFIG_NETFILTER_XT_MATCH_DSCP is not set
+# CONFIG_NETFILTER_XT_MATCH_ECN is not set
+# CONFIG_NETFILTER_XT_MATCH_ESP is not set
+# CONFIG_NETFILTER_XT_MATCH_HASHLIMIT is not set
+# CONFIG_NETFILTER_XT_MATCH_HELPER is not set
+# CONFIG_NETFILTER_XT_MATCH_HL is not set
+# CONFIG_NETFILTER_XT_MATCH_IPCOMP is not set
+# CONFIG_NETFILTER_XT_MATCH_IPRANGE is not set
+# CONFIG_NETFILTER_XT_MATCH_L2TP is not set
+# CONFIG_NETFILTER_XT_MATCH_LENGTH is not set
+# CONFIG_NETFILTER_XT_MATCH_LIMIT is not set
+# CONFIG_NETFILTER_XT_MATCH_MAC is not set
+# CONFIG_NETFILTER_XT_MATCH_MARK is not set
+# CONFIG_NETFILTER_XT_MATCH_MULTIPORT is not set
+# CONFIG_NETFILTER_XT_MATCH_NFACCT is not set
+# CONFIG_NETFILTER_XT_MATCH_OSF is not set
+# CONFIG_NETFILTER_XT_MATCH_OWNER is not set
+# CONFIG_NETFILTER_XT_MATCH_PHYSDEV is not set
+# CONFIG_NETFILTER_XT_MATCH_PKTTYPE is not set
+# CONFIG_NETFILTER_XT_MATCH_POLICY is not set
+# CONFIG_NETFILTER_XT_MATCH_QUOTA is not set
+# CONFIG_NETFILTER_XT_MATCH_RATEEST is not set
+# CONFIG_NETFILTER_XT_MATCH_REALM is not set
+# CONFIG_NETFILTER_XT_MATCH_RECENT is not set
+# CONFIG_NETFILTER_XT_MATCH_SCTP is not set
+# CONFIG_NETFILTER_XT_MATCH_SOCKET is not set
+# CONFIG_NETFILTER_XT_MATCH_STATE is not set
+# CONFIG_NETFILTER_XT_MATCH_STATISTIC is not set
+# CONFIG_NETFILTER_XT_MATCH_STRING is not set
+# CONFIG_NETFILTER_XT_MATCH_TCPMSS is not set
+# CONFIG_NETFILTER_XT_MATCH_TIME is not set
+# CONFIG_NETFILTER_XT_MATCH_U32 is not set
+# CONFIG_NETFILTER_XT_TARGET_AUDIT is not set
+# CONFIG_NETFILTER_XT_TARGET_CHECKSUM is not set
+# CONFIG_NETFILTER_XT_TARGET_CLASSIFY is not set
+# CONFIG_NETFILTER_XT_TARGET_CONNMARK is not set
+# CONFIG_NETFILTER_XT_TARGET_CT is not set
+# CONFIG_NETFILTER_XT_TARGET_DSCP is not set
+# CONFIG_NETFILTER_XT_TARGET_HL is not set
+# CONFIG_NETFILTER_XT_TARGET_HMARK is not set
+# CONFIG_NETFILTER_XT_TARGET_IDLETIMER is not set
+# CONFIG_NETFILTER_XT_TARGET_LED is not set
+# CONFIG_NETFILTER_XT_TARGET_LOG is not set
+# CONFIG_NETFILTER_XT_TARGET_MARK is not set
+# CONFIG_NETFILTER_XT_TARGET_NETMAP is not set
+# CONFIG_NETFILTER_XT_TARGET_NFLOG is not set
+# CONFIG_NETFILTER_XT_TARGET_NFQUEUE is not set
+# CONFIG_NETFILTER_XT_TARGET_NOTRACK is not set
+# CONFIG_NETFILTER_XT_TARGET_RATEEST is not set
+# CONFIG_NETFILTER_XT_TARGET_REDIRECT is not set
+# CONFIG_NETFILTER_XT_TARGET_SECMARK is not set
+# CONFIG_NETFILTER_XT_TARGET_TCPMSS is not set
+# CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP is not set
+# CONFIG_NETFILTER_XT_TARGET_TEE is not set
+# CONFIG_NETFILTER_XT_TARGET_TPROXY is not set
+# CONFIG_NETFILTER_XT_TARGET_TRACE is not set
+# CONFIG_NETFS_DEBUG is not set
+# CONFIG_NETFS_STATS is not set
+# CONFIG_NETKIT is not set
+# CONFIG_NETLABEL is not set
+# CONFIG_NETLINK_DIAG is not set
+# CONFIG_NETPOLL is not set
+# CONFIG_NETROM is not set
+CONFIG_NETWORK_FILESYSTEMS=y
+# CONFIG_NETWORK_PHY_TIMESTAMPING is not set
+# CONFIG_NETWORK_SECMARK is not set
+# CONFIG_NETXEN_NIC is not set
+# CONFIG_NET_9P is not set
+# CONFIG_NET_9P_USBG is not set
+# CONFIG_NET_9P_XEN is not set
+# CONFIG_NET_ACT_BPF is not set
+# CONFIG_NET_ACT_CSUM is not set
+# CONFIG_NET_ACT_CT is not set
+# CONFIG_NET_ACT_GACT is not set
+# CONFIG_NET_ACT_GATE is not set
+# CONFIG_NET_ACT_IFE is not set
+# CONFIG_NET_ACT_MIRRED is not set
+# CONFIG_NET_ACT_MPLS is not set
+# CONFIG_NET_ACT_NAT is not set
+# CONFIG_NET_ACT_PEDIT is not set
+# CONFIG_NET_ACT_POLICE is not set
+# CONFIG_NET_ACT_SAMPLE is not set
+# CONFIG_NET_ACT_SIMP is not set
+# CONFIG_NET_ACT_SKBEDIT is not set
+# CONFIG_NET_ACT_SKBMOD is not set
+# CONFIG_NET_ACT_TUNNEL_KEY is not set
+# CONFIG_NET_ACT_VLAN is not set
+# CONFIG_NET_CALXEDA_XGMAC is not set
+CONFIG_NET_CLS=y
+# CONFIG_NET_CLS_ACT is not set
+# CONFIG_NET_CLS_BASIC is not set
+# CONFIG_NET_CLS_BPF is not set
+# CONFIG_NET_CLS_FLOW is not set
+# CONFIG_NET_CLS_FLOWER is not set
+# CONFIG_NET_CLS_FW is not set
+# CONFIG_NET_CLS_MATCHALL is not set
+# CONFIG_NET_CLS_ROUTE4 is not set
+# CONFIG_NET_CLS_U32 is not set
+CONFIG_NET_CORE=y
+# CONFIG_NET_DEVLINK is not set
+# CONFIG_NET_DEV_REFCNT_TRACKER is not set
+# CONFIG_NET_DROP_MONITOR is not set
+# CONFIG_NET_DSA is not set
+# CONFIG_NET_DSA_AR9331 is not set
+# CONFIG_NET_DSA_BCM_SF2 is not set
+# CONFIG_NET_DSA_KS8995 is not set
+# CONFIG_NET_DSA_LANTIQ_GSWIP is not set
+# CONFIG_NET_DSA_LOOP is not set
+# CONFIG_NET_DSA_MICROCHIP_KSZ_COMMON is not set
+# CONFIG_NET_DSA_MSCC_FELIX is not set
+# CONFIG_NET_DSA_MSCC_OCELOT_EXT is not set
+# CONFIG_NET_DSA_MSCC_SEVILLE is not set
+# CONFIG_NET_DSA_MT7530 is not set
+# CONFIG_NET_DSA_MV88E6060 is not set
+# CONFIG_NET_DSA_MV88E6XXX is not set
+# CONFIG_NET_DSA_MV88E6XXX_LEDS is not set
+# CONFIG_NET_DSA_MV88E6XXX_PTP is not set
+# CONFIG_NET_DSA_QCA8K is not set
+# CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT is not set
+# CONFIG_NET_DSA_REALTEK is not set
+# CONFIG_NET_DSA_REALTEK_SMI is not set
+# CONFIG_NET_DSA_SJA1105 is not set
+# CONFIG_NET_DSA_SMSC_LAN9303_I2C is not set
+# CONFIG_NET_DSA_SMSC_LAN9303_MDIO is not set
+# CONFIG_NET_DSA_TAG_AR9331 is not set
+# CONFIG_NET_DSA_TAG_BRCM is not set
+# CONFIG_NET_DSA_TAG_BRCM_LEGACY is not set
+# CONFIG_NET_DSA_TAG_BRCM_LEGACY_FCS is not set
+# CONFIG_NET_DSA_TAG_BRCM_PREPEND is not set
+# CONFIG_NET_DSA_TAG_DSA is not set
+# CONFIG_NET_DSA_TAG_EDSA is not set
+# CONFIG_NET_DSA_TAG_GSWIP is not set
+# CONFIG_NET_DSA_TAG_HELLCREEK is not set
+# CONFIG_NET_DSA_TAG_KSZ is not set
+# CONFIG_NET_DSA_TAG_LAN9303 is not set
+# CONFIG_NET_DSA_TAG_MTK is not set
+# CONFIG_NET_DSA_TAG_NONE is not set
+# CONFIG_NET_DSA_TAG_OCELOT is not set
+# CONFIG_NET_DSA_TAG_OCELOT_8021Q is not set
+# CONFIG_NET_DSA_TAG_QCA is not set
+# CONFIG_NET_DSA_TAG_RTL4_A is not set
+# CONFIG_NET_DSA_TAG_RTL8_4 is not set
+# CONFIG_NET_DSA_TAG_RZN1_A5PSW is not set
+# CONFIG_NET_DSA_TAG_SJA1105 is not set
+# CONFIG_NET_DSA_TAG_TRAILER is not set
+# CONFIG_NET_DSA_TAG_VSC73XX_8021Q is not set
+# CONFIG_NET_DSA_TAG_XRS700X is not set
+# CONFIG_NET_DSA_VITESSE_VSC73XX is not set
+# CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM is not set
+# CONFIG_NET_DSA_VITESSE_VSC73XX_SPI is not set
+# CONFIG_NET_DSA_XRS700X_I2C is not set
+# CONFIG_NET_DSA_XRS700X_MDIO is not set
+# CONFIG_NET_EMATCH is not set
+# CONFIG_NET_EMATCH_CANID is not set
+# CONFIG_NET_EMATCH_CMP is not set
+# CONFIG_NET_EMATCH_IPT is not set
+# CONFIG_NET_EMATCH_META is not set
+# CONFIG_NET_EMATCH_NBYTE is not set
+CONFIG_NET_EMATCH_STACK=32
+# CONFIG_NET_EMATCH_TEXT is not set
+# CONFIG_NET_EMATCH_U32 is not set
+# CONFIG_NET_FAILOVER is not set
+# CONFIG_NET_FC is not set
+# CONFIG_NET_FOU is not set
+# CONFIG_NET_FOU_IP_TUNNELS is not set
+# CONFIG_NET_IFE is not set
+# CONFIG_NET_IPGRE is not set
+CONFIG_NET_IPGRE_BROADCAST=y
+# CONFIG_NET_IPGRE_DEMUX is not set
+# CONFIG_NET_IPIP is not set
+# CONFIG_NET_IPVTI is not set
+# CONFIG_NET_IP_TUNNEL is not set
+# CONFIG_NET_KEY is not set
+# CONFIG_NET_KEY_MIGRATE is not set
+# CONFIG_NET_L3_MASTER_DEV is not set
+# CONFIG_NET_MEDIATEK_STAR_EMAC is not set
+# CONFIG_NET_MPLS_GSO is not set
+# CONFIG_NET_NCSI is not set
+# CONFIG_NET_NSH is not set
+# CONFIG_NET_NS_REFCNT_TRACKER is not set
+# CONFIG_NET_PKTGEN is not set
+# CONFIG_NET_POLL_CONTROLLER is not set
+# CONFIG_NET_PTP_CLASSIFY is not set
+CONFIG_NET_RX_BUSY_POLL=y
+# CONFIG_NET_SB1000 is not set
+CONFIG_NET_SCHED=y
+# CONFIG_NET_SCH_CAKE is not set
+# CONFIG_NET_SCH_CBS is not set
+# CONFIG_NET_SCH_CHOKE is not set
+# CONFIG_NET_SCH_CODEL is not set
+CONFIG_NET_SCH_DEFAULT=y
+# CONFIG_NET_SCH_DRR is not set
+# CONFIG_NET_SCH_ETF is not set
+# CONFIG_NET_SCH_ETS is not set
+CONFIG_NET_SCH_FIFO=y
+# CONFIG_NET_SCH_FQ is not set
+CONFIG_NET_SCH_FQ_CODEL=y
+# CONFIG_NET_SCH_FQ_PIE is not set
+# CONFIG_NET_SCH_GRED is not set
+# CONFIG_NET_SCH_HFSC is not set
+# CONFIG_NET_SCH_HHF is not set
+# CONFIG_NET_SCH_HTB is not set
+# CONFIG_NET_SCH_INGRESS is not set
+# CONFIG_NET_SCH_MQPRIO is not set
+# CONFIG_NET_SCH_MULTIQ is not set
+# CONFIG_NET_SCH_NETEM is not set
+# CONFIG_NET_SCH_PIE is not set
+# CONFIG_NET_SCH_PLUG is not set
+# CONFIG_NET_SCH_PRIO is not set
+# CONFIG_NET_SCH_QFQ is not set
+# CONFIG_NET_SCH_RED is not set
+# CONFIG_NET_SCH_SFB is not set
+# CONFIG_NET_SCH_SFQ is not set
+# CONFIG_NET_SCH_SKBPRIO is not set
+# CONFIG_NET_SCH_TAPRIO is not set
+# CONFIG_NET_SCH_TBF is not set
+# CONFIG_NET_SCH_TEQL is not set
+# CONFIG_NET_SELFTESTS is not set
+CONFIG_NET_SOCK_MSG=y
+CONFIG_NET_SWITCHDEV=y
+# CONFIG_NET_TC_SKB_EXT is not set
+# CONFIG_NET_TEAM is not set
+# CONFIG_NET_TEAM_MODE_ACTIVEBACKUP is not set
+# CONFIG_NET_TEAM_MODE_BROADCAST is not set
+# CONFIG_NET_TEAM_MODE_LOADBALANCE is not set
+# CONFIG_NET_TEAM_MODE_RANDOM is not set
+# CONFIG_NET_TEAM_MODE_ROUNDROBIN is not set
+# CONFIG_NET_TULIP is not set
+# CONFIG_NET_UDP_TUNNEL is not set
+CONFIG_NET_VENDOR_3COM=y
+CONFIG_NET_VENDOR_8390=y
+CONFIG_NET_VENDOR_ADAPTEC=y
+CONFIG_NET_VENDOR_ADI=y
+CONFIG_NET_VENDOR_AGERE=y
+CONFIG_NET_VENDOR_ALACRITECH=y
+CONFIG_NET_VENDOR_ALTEON=y
+CONFIG_NET_VENDOR_AMAZON=y
+CONFIG_NET_VENDOR_AMD=y
+CONFIG_NET_VENDOR_AQUANTIA=y
+CONFIG_NET_VENDOR_ARC=y
+# CONFIG_NET_VENDOR_ASIX is not set
+CONFIG_NET_VENDOR_ATHEROS=y
+CONFIG_NET_VENDOR_BROADCOM=y
+CONFIG_NET_VENDOR_BROCADE=y
+CONFIG_NET_VENDOR_CADENCE=y
+CONFIG_NET_VENDOR_CAVIUM=y
+CONFIG_NET_VENDOR_CHELSIO=y
+CONFIG_NET_VENDOR_CIRRUS=y
+CONFIG_NET_VENDOR_CISCO=y
+CONFIG_NET_VENDOR_CORTINA=y
+CONFIG_NET_VENDOR_DAVICOM=y
+CONFIG_NET_VENDOR_DEC=y
+CONFIG_NET_VENDOR_DLINK=y
+CONFIG_NET_VENDOR_EMULEX=y
+# CONFIG_NET_VENDOR_ENGLEDER is not set
+CONFIG_NET_VENDOR_EZCHIP=y
+CONFIG_NET_VENDOR_FARADAY=y
+CONFIG_NET_VENDOR_FREESCALE=y
+CONFIG_NET_VENDOR_FUJITSU=y
+# CONFIG_NET_VENDOR_FUNGIBLE is not set
+CONFIG_NET_VENDOR_GOOGLE=y
+CONFIG_NET_VENDOR_HISILICON=y
+CONFIG_NET_VENDOR_HUAWEI=y
+CONFIG_NET_VENDOR_I825XX=y
+CONFIG_NET_VENDOR_IBM=y
+CONFIG_NET_VENDOR_INTEL=y
+# CONFIG_NET_VENDOR_LITEX is not set
+CONFIG_NET_VENDOR_MARVELL=y
+CONFIG_NET_VENDOR_MELLANOX=y
+CONFIG_NET_VENDOR_META=y
+CONFIG_NET_VENDOR_MICREL=y
+CONFIG_NET_VENDOR_MICROCHIP=y
+CONFIG_NET_VENDOR_MICROSEMI=y
+# CONFIG_NET_VENDOR_MICROSOFT is not set
+CONFIG_NET_VENDOR_MYRI=y
+CONFIG_NET_VENDOR_NATSEMI=y
+CONFIG_NET_VENDOR_NETERION=y
+CONFIG_NET_VENDOR_NETRONOME=y
+CONFIG_NET_VENDOR_NI=y
+CONFIG_NET_VENDOR_NVIDIA=y
+CONFIG_NET_VENDOR_OKI=y
+CONFIG_NET_VENDOR_PACKET_ENGINES=y
+CONFIG_NET_VENDOR_PENSANDO=y
+CONFIG_NET_VENDOR_QLOGIC=y
+CONFIG_NET_VENDOR_QUALCOMM=y
+CONFIG_NET_VENDOR_RDC=y
+CONFIG_NET_VENDOR_REALTEK=y
+CONFIG_NET_VENDOR_RENESAS=y
+CONFIG_NET_VENDOR_ROCKER=y
+CONFIG_NET_VENDOR_SAMSUNG=y
+CONFIG_NET_VENDOR_SEEQ=y
+CONFIG_NET_VENDOR_SILAN=y
+CONFIG_NET_VENDOR_SIS=y
+CONFIG_NET_VENDOR_SMSC=y
+CONFIG_NET_VENDOR_SOCIONEXT=y
+CONFIG_NET_VENDOR_SOLARFLARE=y
+CONFIG_NET_VENDOR_STMICRO=y
+CONFIG_NET_VENDOR_SUN=y
+CONFIG_NET_VENDOR_SYNOPSYS=y
+CONFIG_NET_VENDOR_TEHUTI=y
+CONFIG_NET_VENDOR_TI=y
+CONFIG_NET_VENDOR_TOSHIBA=y
+# CONFIG_NET_VENDOR_VERTEXCOM is not set
+CONFIG_NET_VENDOR_VIA=y
+# CONFIG_NET_VENDOR_WANGXUN is not set
+CONFIG_NET_VENDOR_WIZNET=y
+CONFIG_NET_VENDOR_XILINX=y
+CONFIG_NET_VENDOR_XIRCOM=y
+# CONFIG_NET_VRF is not set
+# CONFIG_NET_XGENE is not set
+CONFIG_NEW_LEDS=y
+# CONFIG_NFC is not set
+# CONFIG_NFP is not set
+# CONFIG_NFSD is not set
+# CONFIG_NFSD_LEGACY_CLIENT_TRACKING is not set
+# CONFIG_NFSD_V2 is not set
+# CONFIG_NFSD_V2_ACL is not set
+# CONFIG_NFSD_V3_ACL is not set
+# CONFIG_NFSD_V4 is not set
+# CONFIG_NFS_ACL_SUPPORT is not set
+CONFIG_NFS_COMMON=y
+# CONFIG_NFS_DISABLE_UDP_SUPPORT is not set
+# CONFIG_NFS_FS is not set
+# CONFIG_NFS_FSCACHE is not set
+# CONFIG_NFS_LOCALIO is not set
+# CONFIG_NFS_SWAP is not set
+# CONFIG_NFS_V2 is not set
+CONFIG_NFS_V3=y
+# CONFIG_NFS_V3_ACL is not set
+# CONFIG_NFS_V4 is not set
+# CONFIG_NFS_V4_1 is not set
+# CONFIG_NFTL is not set
+# CONFIG_NFT_BRIDGE_META is not set
+# CONFIG_NFT_BRIDGE_REJECT is not set
+# CONFIG_NFT_CONNLIMIT is not set
+# CONFIG_NFT_DUP_IPV4 is not set
+# CONFIG_NFT_DUP_IPV6 is not set
+# CONFIG_NFT_FIB_IPV4 is not set
+# CONFIG_NFT_FIB_IPV6 is not set
+# CONFIG_NFT_FIB_NETDEV is not set
+# CONFIG_NFT_FLOW_OFFLOAD is not set
+# CONFIG_NFT_OSF is not set
+# CONFIG_NFT_REJECT_NETDEV is not set
+# CONFIG_NFT_SOCKET is not set
+# CONFIG_NFT_SYNPROXY is not set
+# CONFIG_NFT_TPROXY is not set
+# CONFIG_NFT_TUNNEL is not set
+# CONFIG_NFT_XFRM is not set
+# CONFIG_NF_CONNTRACK is not set
+# CONFIG_NF_CONNTRACK_AMANDA is not set
+# CONFIG_NF_CONNTRACK_BRIDGE is not set
+# CONFIG_NF_CONNTRACK_EVENTS is not set
+# CONFIG_NF_CONNTRACK_FTP is not set
+# CONFIG_NF_CONNTRACK_H323 is not set
+# CONFIG_NF_CONNTRACK_IRC is not set
+# CONFIG_NF_CONNTRACK_LABELS is not set
+# CONFIG_NF_CONNTRACK_MARK is not set
+# CONFIG_NF_CONNTRACK_NETBIOS_NS is not set
+# CONFIG_NF_CONNTRACK_PPTP is not set
+CONFIG_NF_CONNTRACK_PROCFS=y
+# CONFIG_NF_CONNTRACK_SANE is not set
+# CONFIG_NF_CONNTRACK_SECMARK is not set
+# CONFIG_NF_CONNTRACK_SIP is not set
+# CONFIG_NF_CONNTRACK_SNMP is not set
+# CONFIG_NF_CONNTRACK_TFTP is not set
+# CONFIG_NF_CONNTRACK_TIMEOUT is not set
+# CONFIG_NF_CONNTRACK_TIMESTAMP is not set
+# CONFIG_NF_CONNTRACK_ZONES is not set
+# CONFIG_NF_CT_NETLINK is not set
+# CONFIG_NF_CT_NETLINK_HELPER is not set
+# CONFIG_NF_CT_NETLINK_TIMEOUT is not set
+# CONFIG_NF_CT_PROTO_DCCP is not set
+# CONFIG_NF_CT_PROTO_GRE is not set
+# CONFIG_NF_CT_PROTO_SCTP is not set
+# CONFIG_NF_CT_PROTO_UDPLITE is not set
+# CONFIG_NF_DEFRAG_IPV4 is not set
+# CONFIG_NF_DUP_IPV4 is not set
+# CONFIG_NF_DUP_IPV6 is not set
+# CONFIG_NF_FLOW_TABLE is not set
+# CONFIG_NF_FLOW_TABLE_PROCFS is not set
+# CONFIG_NF_LOG_ARP is not set
+# CONFIG_NF_LOG_IPV4 is not set
+# CONFIG_NF_LOG_SYSLOG is not set
+# CONFIG_NF_NAT is not set
+# CONFIG_NF_NAT_AMANDA is not set
+# CONFIG_NF_NAT_FTP is not set
+# CONFIG_NF_NAT_H323 is not set
+# CONFIG_NF_NAT_IRC is not set
+# CONFIG_NF_NAT_PPTP is not set
+# CONFIG_NF_NAT_SIP is not set
+# CONFIG_NF_NAT_SNMP_BASIC is not set
+# CONFIG_NF_NAT_TFTP is not set
+# CONFIG_NF_REJECT_IPV4 is not set
+# CONFIG_NF_REJECT_IPV6 is not set
+# CONFIG_NF_SOCKET_IPV4 is not set
+# CONFIG_NF_SOCKET_IPV6 is not set
+# CONFIG_NF_TABLES is not set
+CONFIG_NF_TABLES_ARP=y
+CONFIG_NF_TABLES_BRIDGE=y
+CONFIG_NF_TABLES_INET=y
+CONFIG_NF_TABLES_IPV4=y
+CONFIG_NF_TABLES_IPV6=y
+CONFIG_NF_TABLES_NETDEV=y
+# CONFIG_NF_TPROXY_IPV4 is not set
+# CONFIG_NF_TPROXY_IPV6 is not set
+# CONFIG_NGBE is not set
+# CONFIG_NI903X_WDT is not set
+# CONFIG_NIC7018_WDT is not set
+# CONFIG_NILFS2_FS is not set
+# CONFIG_NIU is not set
+# CONFIG_NI_XGE_MANAGEMENT_ENET is not set
+CONFIG_NLATTR=y
+# CONFIG_NLMON is not set
+# CONFIG_NLS is not set
+# CONFIG_NLS_ASCII is not set
+# CONFIG_NLS_CODEPAGE_1250 is not set
+# CONFIG_NLS_CODEPAGE_1251 is not set
+# CONFIG_NLS_CODEPAGE_437 is not set
+# CONFIG_NLS_CODEPAGE_737 is not set
+# CONFIG_NLS_CODEPAGE_775 is not set
+# CONFIG_NLS_CODEPAGE_850 is not set
+# CONFIG_NLS_CODEPAGE_852 is not set
+# CONFIG_NLS_CODEPAGE_855 is not set
+# CONFIG_NLS_CODEPAGE_857 is not set
+# CONFIG_NLS_CODEPAGE_860 is not set
+# CONFIG_NLS_CODEPAGE_861 is not set
+# CONFIG_NLS_CODEPAGE_862 is not set
+# CONFIG_NLS_CODEPAGE_863 is not set
+# CONFIG_NLS_CODEPAGE_864 is not set
+# CONFIG_NLS_CODEPAGE_865 is not set
+# CONFIG_NLS_CODEPAGE_866 is not set
+# CONFIG_NLS_CODEPAGE_869 is not set
+# CONFIG_NLS_CODEPAGE_874 is not set
+# CONFIG_NLS_CODEPAGE_932 is not set
+# CONFIG_NLS_CODEPAGE_936 is not set
+# CONFIG_NLS_CODEPAGE_949 is not set
+# CONFIG_NLS_CODEPAGE_950 is not set
+CONFIG_NLS_DEFAULT="iso8859-1"
+# CONFIG_NLS_ISO8859_1 is not set
+# CONFIG_NLS_ISO8859_13 is not set
+# CONFIG_NLS_ISO8859_14 is not set
+# CONFIG_NLS_ISO8859_15 is not set
+# CONFIG_NLS_ISO8859_2 is not set
+# CONFIG_NLS_ISO8859_3 is not set
+# CONFIG_NLS_ISO8859_4 is not set
+# CONFIG_NLS_ISO8859_5 is not set
+# CONFIG_NLS_ISO8859_6 is not set
+# CONFIG_NLS_ISO8859_7 is not set
+# CONFIG_NLS_ISO8859_8 is not set
+# CONFIG_NLS_ISO8859_9 is not set
+# CONFIG_NLS_KOI8_R is not set
+# CONFIG_NLS_KOI8_U is not set
+# CONFIG_NLS_MAC_CELTIC is not set
+# CONFIG_NLS_MAC_CENTEURO is not set
+# CONFIG_NLS_MAC_CROATIAN is not set
+# CONFIG_NLS_MAC_CYRILLIC is not set
+# CONFIG_NLS_MAC_GAELIC is not set
+# CONFIG_NLS_MAC_GREEK is not set
+# CONFIG_NLS_MAC_ICELAND is not set
+# CONFIG_NLS_MAC_INUIT is not set
+# CONFIG_NLS_MAC_ROMAN is not set
+# CONFIG_NLS_MAC_ROMANIAN is not set
+# CONFIG_NLS_MAC_TURKISH is not set
+# CONFIG_NLS_UCS2_UTILS is not set
+# CONFIG_NLS_UTF8 is not set
+# CONFIG_NOA1305 is not set
+# CONFIG_NOP_USB_XCEIV is not set
+# CONFIG_NOTIFIER_ERROR_INJECTION is not set
+# CONFIG_NOZOMI is not set
+# CONFIG_NO_HZ is not set
+# CONFIG_NO_HZ_FULL is not set
+# CONFIG_NO_HZ_IDLE is not set
+# CONFIG_NS83820 is not set
+# CONFIG_NTB is not set
+# CONFIG_NTFS3_64BIT_CLUSTER is not set
+# CONFIG_NTFS3_FS is not set
+# CONFIG_NTFS3_FS_POSIX_ACL is not set
+# CONFIG_NTFS3_LZX_XPRESS is not set
+# CONFIG_NTFS_FS is not set
+# CONFIG_NTP_PPS is not set
+# CONFIG_NULL_TTY is not set
+# CONFIG_NUMA is not set
+# CONFIG_NVGRACE_GPU_VFIO_PCI is not set
+# CONFIG_NVIDIA_CARMEL_CNP_ERRATUM is not set
+# CONFIG_NVIDIA_WMI_EC_BACKLIGHT is not set
+# CONFIG_NVMEM is not set
+# CONFIG_NVMEM_BCM_OCOTP is not set
+# CONFIG_NVMEM_BLOCK is not set
+# CONFIG_NVMEM_IMX_OCOTP is not set
+# CONFIG_NVMEM_LAYOUT_ASCII_ENV is not set
+# CONFIG_NVMEM_LAYOUT_MIKROTIK is not set
+# CONFIG_NVMEM_LAYOUT_ONIE_TLV is not set
+# CONFIG_NVMEM_LAYOUT_SL28_VPD is not set
+# CONFIG_NVMEM_LAYOUT_U_BOOT_ENV is not set
+# CONFIG_NVMEM_REBOOT_MODE is not set
+# CONFIG_NVMEM_RMEM is not set
+# CONFIG_NVMEM_SYSFS is not set
+# CONFIG_NVMEM_U_BOOT_ENV is not set
+# CONFIG_NVME_AUTH is not set
+# CONFIG_NVME_FC is not set
+# CONFIG_NVME_HOST_AUTH is not set
+# CONFIG_NVME_TARGET is not set
+# CONFIG_NVME_TCP is not set
+# CONFIG_NVME_VERBOSE_ERRORS is not set
+# CONFIG_NVRAM is not set
+# CONFIG_NV_TCO is not set
+# CONFIG_NXP_C45_TJA11XX_PHY is not set
+# CONFIG_NXP_CBTX_PHY is not set
+# CONFIG_NXP_TJA11XX_PHY is not set
+# CONFIG_N_GSM is not set
+# CONFIG_OABI_COMPAT is not set
+# CONFIG_OA_TC6 is not set
+# CONFIG_OCFS2_FS is not set
+# CONFIG_OCTEONTX2_AF is not set
+# CONFIG_OCTEONTX2_PF is not set
+# CONFIG_OCTEON_EP is not set
+# CONFIG_OCTEON_EP_VF is not set
+# CONFIG_OF_OVERLAY is not set
+CONFIG_OF_PARTITION=y
+CONFIG_OF_RESERVED_MEM=y
+# CONFIG_OF_UNITTEST is not set
+# CONFIG_OID_REGISTRY is not set
+# CONFIG_OMAP2_DSS_DEBUG is not set
+# CONFIG_OMAP2_DSS_DEBUGFS is not set
+# CONFIG_OMAP2_DSS_SDI is not set
+# CONFIG_OMAP_OCP2SCP is not set
+# CONFIG_OMAP_USB2 is not set
+# CONFIG_OMFS_FS is not set
+# CONFIG_OPENVSWITCH is not set
+# CONFIG_OPEN_DICE is not set
+# CONFIG_OPT3001 is not set
+# CONFIG_OPT4001 is not set
+# CONFIG_ORANGEFS_FS is not set
+# CONFIG_ORION_WATCHDOG is not set
+# CONFIG_OSF_PARTITION is not set
+# CONFIG_OSNOISE_TRACER is not set
+CONFIG_OVERLAY_FS=y
+# CONFIG_OVERLAY_FS_DEBUG is not set
+# CONFIG_OVERLAY_FS_INDEX is not set
+# CONFIG_OVERLAY_FS_METACOPY is not set
+CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW=y
+# CONFIG_OVERLAY_FS_REDIRECT_DIR is not set
+CONFIG_OVERLAY_FS_XINO_AUTO=y
+# CONFIG_P54_COMMON is not set
+# CONFIG_PA12203001 is not set
+# CONFIG_PAC1921 is not set
+# CONFIG_PAC1934 is not set
+CONFIG_PACKET=y
+# CONFIG_PACKET_DIAG is not set
+# CONFIG_PACKING is not set
+# CONFIG_PAGE_EXTENSION is not set
+# CONFIG_PAGE_OWNER is not set
+# CONFIG_PAGE_POISONING is not set
+# CONFIG_PAGE_POOL is not set
+# CONFIG_PAGE_POOL_STATS is not set
+# CONFIG_PAGE_REPORTING is not set
+CONFIG_PAGE_SHIFT=12
+# CONFIG_PAGE_SIZE_16KB is not set
+# CONFIG_PAGE_SIZE_32KB is not set
+CONFIG_PAGE_SIZE_4KB=y
+# CONFIG_PAGE_SIZE_64KB is not set
+# CONFIG_PAGE_SIZE_8KB is not set
+# CONFIG_PAGE_TABLE_CHECK is not set
+# CONFIG_PALMAS_GPADC is not set
+# CONFIG_PANASONIC_LAPTOP is not set
+# CONFIG_PANEL is not set
+CONFIG_PANIC_ON_OOPS=y
+CONFIG_PANIC_ON_OOPS_VALUE=1
+CONFIG_PANIC_TIMEOUT=1
+# CONFIG_PANTHERLORD_FF is not set
+# CONFIG_PARAVIRT is not set
+# CONFIG_PARAVIRT_TIME_ACCOUNTING is not set
+# CONFIG_PARPORT is not set
+# CONFIG_PARPORT_1284 is not set
+# CONFIG_PARPORT_GSC is not set
+# CONFIG_PARPORT_PC is not set
+CONFIG_PARTITION_ADVANCED=y
+# CONFIG_PATA_ACPI is not set
+# CONFIG_PATA_ALI is not set
+# CONFIG_PATA_AMD is not set
+# CONFIG_PATA_ARASAN_CF is not set
+# CONFIG_PATA_ARTOP is not set
+# CONFIG_PATA_ATIIXP is not set
+# CONFIG_PATA_ATP867X is not set
+# CONFIG_PATA_CMD640_PCI is not set
+# CONFIG_PATA_CMD64X is not set
+# CONFIG_PATA_CS5520 is not set
+# CONFIG_PATA_CS5530 is not set
+# CONFIG_PATA_CS5535 is not set
+# CONFIG_PATA_CS5536 is not set
+# CONFIG_PATA_CYPRESS is not set
+# CONFIG_PATA_EFAR is not set
+# CONFIG_PATA_HPT366 is not set
+# CONFIG_PATA_HPT37X is not set
+# CONFIG_PATA_HPT3X2N is not set
+# CONFIG_PATA_HPT3X3 is not set
+# CONFIG_PATA_IMX is not set
+# CONFIG_PATA_ISAPNP is not set
+# CONFIG_PATA_IT8213 is not set
+# CONFIG_PATA_IT821X is not set
+# CONFIG_PATA_JMICRON is not set
+# CONFIG_PATA_LEGACY is not set
+# CONFIG_PATA_MARVELL is not set
+# CONFIG_PATA_MPIIX is not set
+# CONFIG_PATA_NETCELL is not set
+# CONFIG_PATA_NINJA32 is not set
+# CONFIG_PATA_NS87410 is not set
+# CONFIG_PATA_NS87415 is not set
+# CONFIG_PATA_OCTEON_CF is not set
+# CONFIG_PATA_OF_PLATFORM is not set
+# CONFIG_PATA_OLDPIIX is not set
+# CONFIG_PATA_OPTI is not set
+# CONFIG_PATA_OPTIDMA is not set
+# CONFIG_PATA_PARPORT is not set
+# CONFIG_PATA_PCMCIA is not set
+# CONFIG_PATA_PDC2027X is not set
+# CONFIG_PATA_PDC_OLD is not set
+# CONFIG_PATA_PLATFORM is not set
+# CONFIG_PATA_QDI is not set
+# CONFIG_PATA_RADISYS is not set
+# CONFIG_PATA_RDC is not set
+# CONFIG_PATA_RZ1000 is not set
+# CONFIG_PATA_SC1200 is not set
+# CONFIG_PATA_SCH is not set
+# CONFIG_PATA_SERVERWORKS is not set
+# CONFIG_PATA_SIL680 is not set
+# CONFIG_PATA_SIS is not set
+# CONFIG_PATA_TOSHIBA is not set
+# CONFIG_PATA_TRIFLEX is not set
+# CONFIG_PATA_VIA is not set
+# CONFIG_PATA_WINBOND is not set
+# CONFIG_PATA_WINBOND_VLB is not set
+# CONFIG_PC104 is not set
+# CONFIG_PC300TOO is not set
+# CONFIG_PCCARD is not set
+# CONFIG_PCH_DMA is not set
+# CONFIG_PCH_GBE is not set
+# CONFIG_PCH_PHUB is not set
+# CONFIG_PCI is not set
+# CONFIG_PCI200SYN is not set
+# CONFIG_PCIEAER is not set
+# CONFIG_PCIEAER_INJECT is not set
+# CONFIG_PCIEASPM is not set
+# CONFIG_PCIEPORTBUS is not set
+# CONFIG_PCIE_AL is not set
+# CONFIG_PCIE_ALTERA is not set
+# CONFIG_PCIE_ARMADA_8K is not set
+CONFIG_PCIE_BUS_DEFAULT=y
+# CONFIG_PCIE_BUS_PEER2PEER is not set
+# CONFIG_PCIE_BUS_PERFORMANCE is not set
+# CONFIG_PCIE_BUS_SAFE is not set
+# CONFIG_PCIE_BUS_TUNE_OFF is not set
+# CONFIG_PCIE_CADENCE_HOST is not set
+# CONFIG_PCIE_CADENCE_PLAT_HOST is not set
+# CONFIG_PCIE_DPC is not set
+# CONFIG_PCIE_DW_PLAT is not set
+# CONFIG_PCIE_DW_PLAT_HOST is not set
+# CONFIG_PCIE_ECRC is not set
+# CONFIG_PCIE_IPROC is not set
+# CONFIG_PCIE_KIRIN is not set
+# CONFIG_PCIE_LAYERSCAPE_GEN4 is not set
+# CONFIG_PCIE_MEDIATEK_GEN3 is not set
+# CONFIG_PCIE_MICROCHIP_HOST is not set
+# CONFIG_PCIE_PTM is not set
+# CONFIG_PCIE_XILINX is not set
+# CONFIG_PCIPCWATCHDOG is not set
+# CONFIG_PCI_CNB20LE_QUIRK is not set
+# CONFIG_PCI_DEBUG is not set
+# CONFIG_PCI_DISABLE_COMMON_QUIRKS is not set
+# CONFIG_PCI_DYNAMIC_OF_NODES is not set
+# CONFIG_PCI_ENDPOINT is not set
+# CONFIG_PCI_ENDPOINT_TEST is not set
+# CONFIG_PCI_FTPCI100 is not set
+# CONFIG_PCI_HISI is not set
+# CONFIG_PCI_HOST_GENERIC is not set
+# CONFIG_PCI_HOST_THUNDER_ECAM is not set
+# CONFIG_PCI_HOST_THUNDER_PEM is not set
+# CONFIG_PCI_IOV is not set
+# CONFIG_PCI_J721E_HOST is not set
+# CONFIG_PCI_LAYERSCAPE is not set
+# CONFIG_PCI_MESON is not set
+# CONFIG_PCI_MSI is not set
+# CONFIG_PCI_NPEM is not set
+# CONFIG_PCI_PASID is not set
+# CONFIG_PCI_PF_STUB is not set
+# CONFIG_PCI_PRI is not set
+CONFIG_PCI_QUIRKS=y
+# CONFIG_PCI_REALLOC_ENABLE_AUTO is not set
+# CONFIG_PCI_STUB is not set
+# CONFIG_PCI_SW_SWITCHTEC is not set
+CONFIG_PCI_SYSCALL=y
+# CONFIG_PCI_V3_SEMI is not set
+# CONFIG_PCI_XGENE is not set
+# CONFIG_PCMCIA is not set
+# CONFIG_PCMCIA_3C574 is not set
+# CONFIG_PCMCIA_3C589 is not set
+# CONFIG_PCMCIA_AHA152X is not set
+# CONFIG_PCMCIA_AXNET is not set
+# CONFIG_PCMCIA_DEBUG is not set
+# CONFIG_PCMCIA_FDOMAIN is not set
+# CONFIG_PCMCIA_FMVJ18X is not set
+# CONFIG_PCMCIA_LOAD_CIS is not set
+# CONFIG_PCMCIA_NINJA_SCSI is not set
+# CONFIG_PCMCIA_NMCLAN is not set
+# CONFIG_PCMCIA_PCNET is not set
+# CONFIG_PCMCIA_QLOGIC is not set
+# CONFIG_PCMCIA_SMC91C92 is not set
+# CONFIG_PCMCIA_SYM53C500 is not set
+# CONFIG_PCMCIA_XIRC2PS is not set
+# CONFIG_PCMCIA_XIRCOM is not set
+# CONFIG_PCNET32 is not set
+CONFIG_PCPU_DEV_REFCNT=y
+CONFIG_PCP_BATCH_SCALE_MAX=5
+# CONFIG_PCSPKR_PLATFORM is not set
+# CONFIG_PCS_MTK_USXGMII is not set
+# CONFIG_PCS_XPCS is not set
+# CONFIG_PD6729 is not set
+# CONFIG_PDC_ADMA is not set
+# CONFIG_PDS_CORE is not set
+# CONFIG_PECI is not set
+# CONFIG_PERCPU_STATS is not set
+# CONFIG_PERCPU_TEST is not set
+# CONFIG_PERF_EVENTS is not set
+# CONFIG_PERF_EVENTS_AMD_POWER is not set
+# CONFIG_PERSISTENT_KEYRINGS is not set
+# CONFIG_PER_VMA_LOCK_STATS is not set
+# CONFIG_PFCP is not set
+# CONFIG_PHANTOM is not set
+# CONFIG_PHONET is not set
+# CONFIG_PHYLIB is not set
+# CONFIG_PHYLIB_LEDS is not set
+# CONFIG_PHYS_ADDR_T_64BIT is not set
+# CONFIG_PHY_BRCM_USB is not set
+# CONFIG_PHY_CADENCE_DPHY is not set
+# CONFIG_PHY_CADENCE_DPHY_RX is not set
+# CONFIG_PHY_CADENCE_SALVO is not set
+# CONFIG_PHY_CADENCE_SIERRA is not set
+# CONFIG_PHY_CADENCE_TORRENT is not set
+# CONFIG_PHY_CAN_TRANSCEIVER is not set
+# CONFIG_PHY_CPCAP_USB is not set
+# CONFIG_PHY_EXYNOS_DP_VIDEO is not set
+# CONFIG_PHY_EXYNOS_MIPI_VIDEO is not set
+# CONFIG_PHY_FSL_IMX8MQ_USB is not set
+# CONFIG_PHY_INGENIC_USB is not set
+# CONFIG_PHY_INTEL_KEEMBAY_EMMC is not set
+# CONFIG_PHY_LAN966X_SERDES is not set
+# CONFIG_PHY_MAPPHONE_MDM6600 is not set
+# CONFIG_PHY_MIXEL_MIPI_DPHY is not set
+# CONFIG_PHY_MTK_HDMI is not set
+# CONFIG_PHY_MTK_MIPI_DSI is not set
+# CONFIG_PHY_MTK_XFI_TPHY is not set
+# CONFIG_PHY_MVEBU_CP110_UTMI is not set
+# CONFIG_PHY_OCELOT_SERDES is not set
+# CONFIG_PHY_PISTACHIO_USB is not set
+# CONFIG_PHY_PXA_28NM_HSIC is not set
+# CONFIG_PHY_PXA_28NM_USB2 is not set
+# CONFIG_PHY_QCOM_USB_HS is not set
+# CONFIG_PHY_QCOM_USB_HSIC is not set
+# CONFIG_PHY_SAMSUNG_USB2 is not set
+# CONFIG_PHY_TUSB1210 is not set
+# CONFIG_PHY_XGENE is not set
+# CONFIG_PID_IN_CONTEXTIDR is not set
+# CONFIG_PID_NS is not set
+CONFIG_PINCONF=y
+# CONFIG_PINCTRL is not set
+# CONFIG_PINCTRL_AMD is not set
+# CONFIG_PINCTRL_AW9523 is not set
+# CONFIG_PINCTRL_AXP209 is not set
+# CONFIG_PINCTRL_CEDARFORK is not set
+# CONFIG_PINCTRL_CY8C95X0 is not set
+# CONFIG_PINCTRL_EXYNOS is not set
+# CONFIG_PINCTRL_ICELAKE is not set
+# CONFIG_PINCTRL_IMX_SCMI is not set
+# CONFIG_PINCTRL_INGENIC is not set
+# CONFIG_PINCTRL_LPASS_LPI is not set
+# CONFIG_PINCTRL_MCP23S08 is not set
+# CONFIG_PINCTRL_MDM9607 is not set
+# CONFIG_PINCTRL_MICROCHIP_SGPIO is not set
+# CONFIG_PINCTRL_MSM8953 is not set
+# CONFIG_PINCTRL_MSM8X74 is not set
+# CONFIG_PINCTRL_MT6779 is not set
+# CONFIG_PINCTRL_MT8167 is not set
+# CONFIG_PINCTRL_MT8192 is not set
+# CONFIG_PINCTRL_MT8195 is not set
+# CONFIG_PINCTRL_MT8365 is not set
+# CONFIG_PINCTRL_MTK_V2 is not set
+# CONFIG_PINCTRL_OCELOT is not set
+# CONFIG_PINCTRL_PISTACHIO is not set
+# CONFIG_PINCTRL_SC7280 is not set
+# CONFIG_PINCTRL_SC8180X is not set
+# CONFIG_PINCTRL_SDX55 is not set
+CONFIG_PINCTRL_SINGLE=y
+# CONFIG_PINCTRL_SM6115 is not set
+# CONFIG_PINCTRL_SM6125 is not set
+# CONFIG_PINCTRL_SM8350 is not set
+# CONFIG_PINCTRL_STMFX is not set
+# CONFIG_PINCTRL_SX150X is not set
+# CONFIG_PING is not set
+CONFIG_PINMUX=y
+# CONFIG_PKCS7_MESSAGE_PARSER is not set
+# CONFIG_PL310_ERRATA_588369 is not set
+# CONFIG_PL310_ERRATA_727915 is not set
+# CONFIG_PL310_ERRATA_753970 is not set
+# CONFIG_PL310_ERRATA_769419 is not set
+# CONFIG_PL320_MBOX is not set
+# CONFIG_PL330_DMA is not set
+# CONFIG_PLATFORM_MHU is not set
+# CONFIG_PLAT_SPEAR is not set
+# CONFIG_PLIP is not set
+# CONFIG_PLX_DMA is not set
+# CONFIG_PM is not set
+# CONFIG_PMBUS is not set
+# CONFIG_PMIC_ADP5520 is not set
+# CONFIG_PMIC_DA903X is not set
+# CONFIG_PMS7003 is not set
+# CONFIG_PM_AUTOSLEEP is not set
+# CONFIG_PM_DEBUG is not set
+# CONFIG_PM_DEVFREQ is not set
+# CONFIG_PM_USERSPACE_AUTOSLEEP is not set
+# CONFIG_PM_WAKELOCKS is not set
+# CONFIG_POSIX_MQUEUE is not set
+CONFIG_POSIX_TIMERS=y
+# CONFIG_POWERCAP is not set
+# CONFIG_POWER_RESET is not set
+# CONFIG_POWER_RESET_BRCMKONA is not set
+# CONFIG_POWER_RESET_BRCMSTB is not set
+# CONFIG_POWER_RESET_GPIO is not set
+# CONFIG_POWER_RESET_GPIO_RESTART is not set
+# CONFIG_POWER_RESET_LINKSTATION is not set
+# CONFIG_POWER_RESET_LTC2952 is not set
+# CONFIG_POWER_RESET_PIIX4_POWEROFF is not set
+# CONFIG_POWER_RESET_QNAP is not set
+# CONFIG_POWER_RESET_REGULATOR is not set
+# CONFIG_POWER_RESET_RESTART is not set
+# CONFIG_POWER_RESET_SYSCON is not set
+# CONFIG_POWER_RESET_SYSCON_POWEROFF is not set
+# CONFIG_POWER_RESET_VERSATILE is not set
+# CONFIG_POWER_RESET_XGENE is not set
+# CONFIG_POWER_SEQUENCING is not set
+# CONFIG_POWER_SUPPLY is not set
+# CONFIG_POWER_SUPPLY_DEBUG is not set
+# CONFIG_POWER_SUPPLY_HWMON is not set
+# CONFIG_PPC4xx_GPIO is not set
+# CONFIG_PPC_16K_PAGES is not set
+# CONFIG_PPC_256K_PAGES is not set
+CONFIG_PPC_4K_PAGES=y
+# CONFIG_PPC_64K_PAGES is not set
+# CONFIG_PPC_DISABLE_WERROR is not set
+# CONFIG_PPC_EMULATED_STATS is not set
+# CONFIG_PPC_EPAPR_HV_BYTECHAN is not set
+# CONFIG_PPC_QUEUED_SPINLOCKS is not set
+# CONFIG_PPP is not set
+# CONFIG_PPPOATM is not set
+# CONFIG_PPPOE is not set
+# CONFIG_PPPOE_HASH_BITS_1 is not set
+# CONFIG_PPPOE_HASH_BITS_2 is not set
+CONFIG_PPPOE_HASH_BITS_4=y
+# CONFIG_PPPOE_HASH_BITS_8 is not set
+# CONFIG_PPPOL2TP is not set
+# CONFIG_PPP_ASYNC is not set
+# CONFIG_PPP_BSDCOMP is not set
+# CONFIG_PPP_DEFLATE is not set
+CONFIG_PPP_FILTER=y
+# CONFIG_PPP_MPPE is not set
+CONFIG_PPP_MULTILINK=y
+# CONFIG_PPP_SYNC_TTY is not set
+# CONFIG_PPS is not set
+# CONFIG_PPS_CLIENT_GPIO is not set
+# CONFIG_PPS_CLIENT_KTIMER is not set
+# CONFIG_PPS_CLIENT_LDISC is not set
+# CONFIG_PPS_CLIENT_PARPORT is not set
+# CONFIG_PPS_DEBUG is not set
+# CONFIG_PPTP is not set
+# CONFIG_PREEMPT is not set
+# CONFIG_PREEMPTIRQ_DELAY_TEST is not set
+# CONFIG_PREEMPT_DYNAMIC is not set
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_NONE_BUILD=y
+# CONFIG_PREEMPT_RT is not set
+# CONFIG_PREEMPT_TRACER is not set
+# CONFIG_PREEMPT_VOLUNTARY is not set
+# CONFIG_PRESTERA is not set
+CONFIG_PREVENT_FIRMWARE_BUILD=y
+# CONFIG_PRIME_NUMBERS is not set
+CONFIG_PRINTK=y
+# CONFIG_PRINTK_CALLER is not set
+# CONFIG_PRINTK_INDEX is not set
+# CONFIG_PRINTK_TIME is not set
+CONFIG_PRINT_STACK_DEPTH=64
+# CONFIG_PROC_CHILDREN is not set
+CONFIG_PROC_FS=y
+# CONFIG_PROC_KCORE is not set
+CONFIG_PROC_MEM_ALWAYS_FORCE=y
+# CONFIG_PROC_MEM_FORCE_PTRACE is not set
+# CONFIG_PROC_MEM_NO_FORCE is not set
+# CONFIG_PROC_PAGE_MONITOR is not set
+# CONFIG_PROC_STRIPPED is not set
+CONFIG_PROC_SYSCTL=y
+# CONFIG_PROC_VMCORE_DEVICE_DUMP is not set
+# CONFIG_PROFILE_ALL_BRANCHES is not set
+# CONFIG_PROFILE_ANNOTATED_BRANCHES is not set
+# CONFIG_PROFILING is not set
+# CONFIG_PROVE_LOCKING is not set
+# CONFIG_PROVE_RAW_LOCK_NESTING is not set
+# CONFIG_PROVE_RCU is not set
+# CONFIG_PROVE_RCU_LIST is not set
+# CONFIG_PSAMPLE is not set
+# CONFIG_PSB6970_PHY is not set
+# CONFIG_PSE_CONTROLLER is not set
+# CONFIG_PSE_PD692X0 is not set
+# CONFIG_PSE_REGULATOR is not set
+# CONFIG_PSE_SI3474 is not set
+# CONFIG_PSE_TPS23881 is not set
+# CONFIG_PSI is not set
+# CONFIG_PSI_DEFAULT_DISABLED is not set
+# CONFIG_PSTORE is not set
+# CONFIG_PSTORE_BLK is not set
+# CONFIG_PSTORE_COMPRESS is not set
+# CONFIG_PSTORE_CONSOLE is not set
+CONFIG_PSTORE_DEFAULT_KMSG_BYTES=10240
+# CONFIG_PSTORE_FTRACE is not set
+# CONFIG_PSTORE_PMSG is not set
+# CONFIG_PSTORE_RAM is not set
+# CONFIG_PTDUMP_DEBUGFS is not set
+# CONFIG_PTP_1588_CLOCK is not set
+# CONFIG_PTP_1588_CLOCK_FC3W is not set
+# CONFIG_PTP_1588_CLOCK_IDT82P33 is not set
+# CONFIG_PTP_1588_CLOCK_IDTCM is not set
+# CONFIG_PTP_1588_CLOCK_IXP46X is not set
+# CONFIG_PTP_1588_CLOCK_KVM is not set
+# CONFIG_PTP_1588_CLOCK_MOCK is not set
+# CONFIG_PTP_1588_CLOCK_OCP is not set
+# CONFIG_PTP_1588_CLOCK_PCH is not set
+# CONFIG_PTP_1588_CLOCK_VMW is not set
+# CONFIG_PVPANIC is not set
+# CONFIG_PWM is not set
+# CONFIG_PWM_ATMEL_TCB is not set
+# CONFIG_PWM_CLK is not set
+# CONFIG_PWM_DEBUG is not set
+# CONFIG_PWM_DWC is not set
+# CONFIG_PWM_FSL_FTM is not set
+# CONFIG_PWM_GPIO is not set
+# CONFIG_PWM_IMG is not set
+# CONFIG_PWM_JZ4740 is not set
+# CONFIG_PWM_MEDIATEK is not set
+# CONFIG_PWM_PCA9685 is not set
+# CONFIG_PWM_RASPBERRYPI_POE is not set
+# CONFIG_PWM_XILINX is not set
+CONFIG_PWRSEQ_EMMC=y
+# CONFIG_PWRSEQ_SD8787 is not set
+CONFIG_PWRSEQ_SIMPLE=y
+# CONFIG_QCA7000 is not set
+# CONFIG_QCA7000_SPI is not set
+# CONFIG_QCA7000_UART is not set
+# CONFIG_QCA807X_PHY is not set
+# CONFIG_QCA808X_PHY is not set
+# CONFIG_QCA83XX_PHY is not set
+# CONFIG_QCOM_A7PLL is not set
+# CONFIG_QCOM_BAM_DMUX is not set
+# CONFIG_QCOM_EMAC is not set
+# CONFIG_QCOM_FALKOR_ERRATUM_1003 is not set
+# CONFIG_QCOM_FALKOR_ERRATUM_1009 is not set
+# CONFIG_QCOM_FALKOR_ERRATUM_E1041 is not set
+# CONFIG_QCOM_GPI_DMA is not set
+# CONFIG_QCOM_HIDMA is not set
+# CONFIG_QCOM_HIDMA_MGMT is not set
+# CONFIG_QCOM_LMH is not set
+# CONFIG_QCOM_PBS is not set
+# CONFIG_QCOM_PD_MAPPER is not set
+# CONFIG_QCOM_PMIC_PDCHARGER_ULOG is not set
+# CONFIG_QCOM_QDF2400_ERRATUM_0065 is not set
+# CONFIG_QCOM_SPMI_ADC5 is not set
+# CONFIG_QCOM_SPMI_ADC_TM5 is not set
+# CONFIG_QCOM_SPMI_IADC is not set
+# CONFIG_QCOM_SPMI_TEMP_ALARM is not set
+# CONFIG_QCOM_SPMI_VADC is not set
+# CONFIG_QCOM_SSC_BLOCK_BUS is not set
+# CONFIG_QED is not set
+# CONFIG_QFMT_V1 is not set
+# CONFIG_QLA3XXX is not set
+# CONFIG_QLCNIC is not set
+# CONFIG_QNX4FS_FS is not set
+# CONFIG_QNX6FS_FS is not set
+# CONFIG_QORIQ_CPUFREQ is not set
+# CONFIG_QORIQ_THERMAL is not set
+# CONFIG_QRTR is not set
+# CONFIG_QRTR_MHI is not set
+# CONFIG_QRTR_TUN is not set
+# CONFIG_QSEMI_PHY is not set
+# CONFIG_QUICC_ENGINE is not set
+# CONFIG_QUOTA is not set
+# CONFIG_QUOTACTL is not set
+# CONFIG_QUOTA_DEBUG is not set
+# CONFIG_QUOTA_NETLINK_INTERFACE is not set
+# CONFIG_R6040 is not set
+# CONFIG_R8169 is not set
+# CONFIG_R8169_LEDS is not set
+# CONFIG_R8712U is not set
+# CONFIG_RADIO_ADAPTERS is not set
+# CONFIG_RADIO_AZTECH is not set
+# CONFIG_RADIO_CADET is not set
+# CONFIG_RADIO_GEMTEK is not set
+# CONFIG_RADIO_MAXIRADIO is not set
+# CONFIG_RADIO_RTRACK is not set
+# CONFIG_RADIO_RTRACK2 is not set
+# CONFIG_RADIO_SF16FMI is not set
+# CONFIG_RADIO_SF16FMR2 is not set
+# CONFIG_RADIO_TERRATEC is not set
+# CONFIG_RADIO_TRUST is not set
+# CONFIG_RADIO_TYPHOON is not set
+# CONFIG_RADIO_ZOLTRIX is not set
+# CONFIG_RAID6_PQ_BENCHMARK is not set
+# CONFIG_RAID_ATTRS is not set
+# CONFIG_RALINK is not set
+# CONFIG_RANDOM32_SELFTEST is not set
+# CONFIG_RANDOMIZE_BASE is not set
+CONFIG_RANDOMIZE_KSTACK_OFFSET=y
+# CONFIG_RANDOMIZE_KSTACK_OFFSET_DEFAULT is not set
+# CONFIG_RANDOM_KMALLOC_CACHES is not set
+# CONFIG_RANDSTRUCT_NONE is not set
+# CONFIG_RAPIDIO is not set
+# CONFIG_RAS is not set
+# CONFIG_RBTREE_TEST is not set
+# CONFIG_RCU_BOOST is not set
+# CONFIG_RCU_CPU_STALL_CPUTIME is not set
+CONFIG_RCU_CPU_STALL_TIMEOUT=60
+# CONFIG_RCU_EQS_DEBUG is not set
+# CONFIG_RCU_EXPERT is not set
+CONFIG_RCU_EXP_CPU_STALL_TIMEOUT=0
+CONFIG_RCU_NEED_SEGCBLIST=y
+# CONFIG_RCU_REF_SCALE_TEST is not set
+# CONFIG_RCU_SCALE_TEST is not set
+CONFIG_RCU_STALL_COMMON=y
+# CONFIG_RCU_STRICT_GRACE_PERIOD is not set
+# CONFIG_RCU_TORTURE_TEST is not set
+# CONFIG_RCU_TRACE is not set
+# CONFIG_RC_ATI_REMOTE is not set
+# CONFIG_RC_CORE is not set
+# CONFIG_RC_DECODERS is not set
+# CONFIG_RC_LOOPBACK is not set
+# CONFIG_RC_MAP is not set
+# CONFIG_RC_XBOX_DVD is not set
+# CONFIG_RDS is not set
+# CONFIG_RD_BZIP2 is not set
+# CONFIG_RD_GZIP is not set
+# CONFIG_RD_LZ4 is not set
+# CONFIG_RD_LZMA is not set
+# CONFIG_RD_LZO is not set
+# CONFIG_RD_XZ is not set
+# CONFIG_RD_ZSTD is not set
+# CONFIG_READABLE_ASM is not set
+# CONFIG_READ_ONLY_THP_FOR_FS is not set
+# CONFIG_REALTEK_PHY is not set
+# CONFIG_REDWOOD is not set
+# CONFIG_REED_SOLOMON is not set
+# CONFIG_REED_SOLOMON_DEC8 is not set
+# CONFIG_REED_SOLOMON_ENC8 is not set
+# CONFIG_REED_SOLOMON_TEST is not set
+# CONFIG_REGMAP is not set
+# CONFIG_REGMAP_I2C is not set
+# CONFIG_REGMAP_MMIO is not set
+# CONFIG_REGMAP_SPI is not set
+# CONFIG_REGULATOR is not set
+# CONFIG_REGULATOR_88PG86X is not set
+# CONFIG_REGULATOR_ACT8865 is not set
+# CONFIG_REGULATOR_AD5398 is not set
+# CONFIG_REGULATOR_ANATOP is not set
+# CONFIG_REGULATOR_AW37503 is not set
+# CONFIG_REGULATOR_DA9121 is not set
+# CONFIG_REGULATOR_DA9210 is not set
+# CONFIG_REGULATOR_DA9211 is not set
+# CONFIG_REGULATOR_DEBUG is not set
+# CONFIG_REGULATOR_FAN53555 is not set
+# CONFIG_REGULATOR_FAN53880 is not set
+# CONFIG_REGULATOR_FIXED_VOLTAGE is not set
+# CONFIG_REGULATOR_GPIO is not set
+# CONFIG_REGULATOR_ISL6271A is not set
+# CONFIG_REGULATOR_ISL9305 is not set
+# CONFIG_REGULATOR_LP3971 is not set
+# CONFIG_REGULATOR_LP3972 is not set
+# CONFIG_REGULATOR_LP872X is not set
+# CONFIG_REGULATOR_LP8755 is not set
+# CONFIG_REGULATOR_LTC3589 is not set
+# CONFIG_REGULATOR_LTC3676 is not set
+# CONFIG_REGULATOR_MAX1586 is not set
+# CONFIG_REGULATOR_MAX20086 is not set
+# CONFIG_REGULATOR_MAX20411 is not set
+# CONFIG_REGULATOR_MAX77503 is not set
+# CONFIG_REGULATOR_MAX77620 is not set
+# CONFIG_REGULATOR_MAX77826 is not set
+# CONFIG_REGULATOR_MAX77857 is not set
+# CONFIG_REGULATOR_MAX8649 is not set
+# CONFIG_REGULATOR_MAX8660 is not set
+# CONFIG_REGULATOR_MAX8893 is not set
+# CONFIG_REGULATOR_MAX8952 is not set
+# CONFIG_REGULATOR_MAX8973 is not set
+# CONFIG_REGULATOR_MCP16502 is not set
+# CONFIG_REGULATOR_MP5416 is not set
+# CONFIG_REGULATOR_MP8859 is not set
+# CONFIG_REGULATOR_MP886X is not set
+# CONFIG_REGULATOR_MPQ7920 is not set
+# CONFIG_REGULATOR_MT6311 is not set
+# CONFIG_REGULATOR_MT6315 is not set
+# CONFIG_REGULATOR_MT6359 is not set
+# CONFIG_REGULATOR_NETLINK_EVENTS is not set
+# CONFIG_REGULATOR_PCA9450 is not set
+# CONFIG_REGULATOR_PF8X00 is not set
+# CONFIG_REGULATOR_PFUZE100 is not set
+# CONFIG_REGULATOR_PV88060 is not set
+# CONFIG_REGULATOR_PV88080 is not set
+# CONFIG_REGULATOR_PV88090 is not set
+# CONFIG_REGULATOR_PWM is not set
+# CONFIG_REGULATOR_QCOM_LABIBB is not set
+# CONFIG_REGULATOR_QCOM_REFGEN is not set
+# CONFIG_REGULATOR_QCOM_SPMI is not set
+# CONFIG_REGULATOR_QCOM_USB_VBUS is not set
+# CONFIG_REGULATOR_RAA215300 is not set
+# CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY is not set
+# CONFIG_REGULATOR_RT4801 is not set
+# CONFIG_REGULATOR_RT4803 is not set
+# CONFIG_REGULATOR_RT5190A is not set
+# CONFIG_REGULATOR_RT5739 is not set
+# CONFIG_REGULATOR_RT5759 is not set
+# CONFIG_REGULATOR_RT6160 is not set
+# CONFIG_REGULATOR_RT6190 is not set
+# CONFIG_REGULATOR_RT6245 is not set
+# CONFIG_REGULATOR_RTMV20 is not set
+# CONFIG_REGULATOR_RTQ2134 is not set
+# CONFIG_REGULATOR_RTQ2208 is not set
+# CONFIG_REGULATOR_RTQ6752 is not set
+# CONFIG_REGULATOR_SLG51000 is not set
+# CONFIG_REGULATOR_SY8106A is not set
+# CONFIG_REGULATOR_SY8824X is not set
+# CONFIG_REGULATOR_SY8827N is not set
+# CONFIG_REGULATOR_TI_ABB is not set
+# CONFIG_REGULATOR_TPS51632 is not set
+# CONFIG_REGULATOR_TPS62360 is not set
+# CONFIG_REGULATOR_TPS6286X is not set
+# CONFIG_REGULATOR_TPS6287X is not set
+# CONFIG_REGULATOR_TPS65023 is not set
+# CONFIG_REGULATOR_TPS6507X is not set
+# CONFIG_REGULATOR_TPS65132 is not set
+# CONFIG_REGULATOR_TPS6524X is not set
+# CONFIG_REGULATOR_USERSPACE_CONSUMER is not set
+# CONFIG_REGULATOR_VCTRL is not set
+# CONFIG_REGULATOR_VEXPRESS is not set
+# CONFIG_REGULATOR_VIRTUAL_CONSUMER is not set
+# CONFIG_REISERFS_CHECK is not set
+# CONFIG_REISERFS_FS is not set
+# CONFIG_REISERFS_FS_POSIX_ACL is not set
+# CONFIG_REISERFS_FS_SECURITY is not set
+CONFIG_REISERFS_FS_XATTR=y
+# CONFIG_REISERFS_PROC_INFO is not set
+# CONFIG_RELAY is not set
+# CONFIG_RELOCATABLE is not set
+CONFIG_RELR=y
+# CONFIG_REMOTEPROC is not set
+# CONFIG_RENESAS_PHY is not set
+# CONFIG_RESET_ATH79 is not set
+# CONFIG_RESET_BERLIN is not set
+# CONFIG_RESET_BRCMSTB is not set
+# CONFIG_RESET_BRCMSTB_RESCAL is not set
+# CONFIG_RESET_CONTROLLER is not set
+# CONFIG_RESET_GPIO is not set
+# CONFIG_RESET_IMX7 is not set
+# CONFIG_RESET_INTEL_GW is not set
+# CONFIG_RESET_LANTIQ is not set
+# CONFIG_RESET_LPC18XX is not set
+# CONFIG_RESET_MESON is not set
+# CONFIG_RESET_PISTACHIO is not set
+# CONFIG_RESET_SIMPLE is not set
+# CONFIG_RESET_SOCFPGA is not set
+# CONFIG_RESET_SUNXI is not set
+# CONFIG_RESET_TEGRA_BPMP is not set
+# CONFIG_RESET_TI_SYSCON is not set
+# CONFIG_RESET_TI_TPS380X is not set
+# CONFIG_RESET_ZYNQ is not set
+# CONFIG_RFD77402 is not set
+# CONFIG_RFD_FTL is not set
+CONFIG_RFKILL=y
+# CONFIG_RFKILL_FULL is not set
+# CONFIG_RFKILL_GPIO is not set
+# CONFIG_RFKILL_INPUT is not set
+# CONFIG_RFKILL_LEDS is not set
+# CONFIG_RICHTEK_RTQ6056 is not set
+# CONFIG_RING_BUFFER_BENCHMARK is not set
+# CONFIG_RING_BUFFER_STARTUP_TEST is not set
+# CONFIG_RING_BUFFER_VALIDATE_TIME_DELTAS is not set
+# CONFIG_RISCV_PMU is not set
+# CONFIG_RISCV_PMU_LEGACY is not set
+# CONFIG_RISCV_PMU_SBI is not set
+# CONFIG_RMI4_CORE is not set
+# CONFIG_RMNET is not set
+# CONFIG_ROCKCHIP_ERRATUM_3588001 is not set
+# CONFIG_ROCKCHIP_PHY is not set
+# CONFIG_ROCKER is not set
+# CONFIG_ROHM_BM1390 is not set
+# CONFIG_ROHM_BU27008 is not set
+# CONFIG_ROHM_BU27034 is not set
+# CONFIG_ROMFS_FS is not set
+# CONFIG_ROSE is not set
+# CONFIG_RPCSEC_GSS_KRB5 is not set
+# CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA1 is not set
+# CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA2 is not set
+# CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_CAMELLIA is not set
+# CONFIG_RPMB is not set
+# CONFIG_RPMSG_QCOM_GLINK_RPM is not set
+# CONFIG_RPMSG_VIRTIO is not set
+# CONFIG_RPMSG_WWAN_CTRL is not set
+# CONFIG_RPR0521 is not set
+# CONFIG_RSEQ is not set
+# CONFIG_RT2X00 is not set
+# CONFIG_RTASE is not set
+# CONFIG_RTC_CLASS is not set
+# CONFIG_RTC_DEBUG is not set
+# CONFIG_RTC_DRV_ABB5ZES3 is not set
+# CONFIG_RTC_DRV_ABEOZ9 is not set
+# CONFIG_RTC_DRV_ABX80X is not set
+# CONFIG_RTC_DRV_ARMADA38X is not set
+# CONFIG_RTC_DRV_AU1XXX is not set
+# CONFIG_RTC_DRV_BQ32K is not set
+# CONFIG_RTC_DRV_BQ4802 is not set
+# CONFIG_RTC_DRV_CADENCE is not set
+CONFIG_RTC_DRV_CMOS=y
+# CONFIG_RTC_DRV_DS1286 is not set
+# CONFIG_RTC_DRV_DS1302 is not set
+# CONFIG_RTC_DRV_DS1305 is not set
+# CONFIG_RTC_DRV_DS1307 is not set
+# CONFIG_RTC_DRV_DS1307_CENTURY is not set
+# CONFIG_RTC_DRV_DS1343 is not set
+# CONFIG_RTC_DRV_DS1347 is not set
+# CONFIG_RTC_DRV_DS1374 is not set
+# CONFIG_RTC_DRV_DS1390 is not set
+# CONFIG_RTC_DRV_DS1511 is not set
+# CONFIG_RTC_DRV_DS1553 is not set
+# CONFIG_RTC_DRV_DS1672 is not set
+# CONFIG_RTC_DRV_DS1685_FAMILY is not set
+# CONFIG_RTC_DRV_DS1742 is not set
+# CONFIG_RTC_DRV_DS2404 is not set
+# CONFIG_RTC_DRV_DS3232 is not set
+# CONFIG_RTC_DRV_EFI is not set
+# CONFIG_RTC_DRV_EM3027 is not set
+# CONFIG_RTC_DRV_EP93XX is not set
+# CONFIG_RTC_DRV_FM3130 is not set
+# CONFIG_RTC_DRV_FTRTC010 is not set
+# CONFIG_RTC_DRV_GENERIC is not set
+# CONFIG_RTC_DRV_GOLDFISH is not set
+# CONFIG_RTC_DRV_HID_SENSOR_TIME is not set
+# CONFIG_RTC_DRV_HYM8563 is not set
+# CONFIG_RTC_DRV_ISL12022 is not set
+# CONFIG_RTC_DRV_ISL12026 is not set
+# CONFIG_RTC_DRV_ISL1208 is not set
+# CONFIG_RTC_DRV_JZ4740 is not set
+# CONFIG_RTC_DRV_M41T80 is not set
+# CONFIG_RTC_DRV_M41T93 is not set
+# CONFIG_RTC_DRV_M41T94 is not set
+# CONFIG_RTC_DRV_M48T35 is not set
+# CONFIG_RTC_DRV_M48T59 is not set
+# CONFIG_RTC_DRV_M48T86 is not set
+# CONFIG_RTC_DRV_MAX31335 is not set
+# CONFIG_RTC_DRV_MAX6900 is not set
+# CONFIG_RTC_DRV_MAX6902 is not set
+# CONFIG_RTC_DRV_MAX6916 is not set
+# CONFIG_RTC_DRV_MAX77686 is not set
+# CONFIG_RTC_DRV_MCP795 is not set
+# CONFIG_RTC_DRV_MOXART is not set
+# CONFIG_RTC_DRV_MPC5121 is not set
+# CONFIG_RTC_DRV_MSM6242 is not set
+# CONFIG_RTC_DRV_MT2712 is not set
+# CONFIG_RTC_DRV_NCT3018Y is not set
+# CONFIG_RTC_DRV_OMAP is not set
+# CONFIG_RTC_DRV_PCF2123 is not set
+# CONFIG_RTC_DRV_PCF2127 is not set
+# CONFIG_RTC_DRV_PCF85063 is not set
+# CONFIG_RTC_DRV_PCF8523 is not set
+# CONFIG_RTC_DRV_PCF85363 is not set
+# CONFIG_RTC_DRV_PCF8563 is not set
+# CONFIG_RTC_DRV_PCF8583 is not set
+# CONFIG_RTC_DRV_PL030 is not set
+# CONFIG_RTC_DRV_PL031 is not set
+# CONFIG_RTC_DRV_PS3 is not set
+# CONFIG_RTC_DRV_R7301 is not set
+# CONFIG_RTC_DRV_R9701 is not set
+# CONFIG_RTC_DRV_RP5C01 is not set
+# CONFIG_RTC_DRV_RS5C348 is not set
+# CONFIG_RTC_DRV_RS5C372 is not set
+# CONFIG_RTC_DRV_RV3028 is not set
+# CONFIG_RTC_DRV_RV3029C2 is not set
+# CONFIG_RTC_DRV_RV3032 is not set
+# CONFIG_RTC_DRV_RV8803 is not set
+# CONFIG_RTC_DRV_RX4581 is not set
+# CONFIG_RTC_DRV_RX6110 is not set
+# CONFIG_RTC_DRV_RX8010 is not set
+# CONFIG_RTC_DRV_RX8025 is not set
+# CONFIG_RTC_DRV_RX8111 is not set
+# CONFIG_RTC_DRV_RX8581 is not set
+# CONFIG_RTC_DRV_S35390A is not set
+# CONFIG_RTC_DRV_SD2405AL is not set
+# CONFIG_RTC_DRV_SD3078 is not set
+# CONFIG_RTC_DRV_SNVS is not set
+# CONFIG_RTC_DRV_STK17TA8 is not set
+# CONFIG_RTC_DRV_SUN6I is not set
+# CONFIG_RTC_DRV_TEGRA is not set
+# CONFIG_RTC_DRV_TEST is not set
+# CONFIG_RTC_DRV_X1205 is not set
+# CONFIG_RTC_DRV_XGENE is not set
+# CONFIG_RTC_DRV_ZYNQMP is not set
+CONFIG_RTC_HCTOSYS=y
+CONFIG_RTC_HCTOSYS_DEVICE="rtc0"
+CONFIG_RTC_INTF_DEV=y
+# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set
+CONFIG_RTC_INTF_PROC=y
+CONFIG_RTC_INTF_SYSFS=y
+CONFIG_RTC_LIB=y
+# CONFIG_RTC_NVMEM is not set
+CONFIG_RTC_SYSTOHC=y
+CONFIG_RTC_SYSTOHC_DEVICE="rtc0"
+# CONFIG_RTL8180 is not set
+# CONFIG_RTL8187 is not set
+# CONFIG_RTL8192E is not set
+# CONFIG_RTL8261N_PHY is not set
+# CONFIG_RTL8306_PHY is not set
+# CONFIG_RTL8366RB_PHY is not set
+# CONFIG_RTL8366S_PHY is not set
+# CONFIG_RTL8366_SMI is not set
+# CONFIG_RTL8366_SMI_DEBUG_FS is not set
+# CONFIG_RTL8367B_PHY is not set
+# CONFIG_RTL8367_PHY is not set
+# CONFIG_RTLLIB is not set
+# CONFIG_RTL_CARDS is not set
+# CONFIG_RTS5208 is not set
+CONFIG_RT_MUTEXES=y
+CONFIG_RUNTIME_TESTING_MENU=y
+# CONFIG_RUST is not set
+# CONFIG_RV is not set
+CONFIG_RXKAD=y
+# CONFIG_RXPERF is not set
+# CONFIG_S2IO is not set
+# CONFIG_SAMPLES is not set
+# CONFIG_SAMSUNG_LAPTOP is not set
+# CONFIG_SATA_ACARD_AHCI is not set
+# CONFIG_SATA_AHCI is not set
+# CONFIG_SATA_AHCI_PLATFORM is not set
+# CONFIG_SATA_DWC is not set
+# CONFIG_SATA_DWC_OLD_DMA is not set
+# CONFIG_SATA_FSL is not set
+# CONFIG_SATA_HIGHBANK is not set
+# CONFIG_SATA_HOST is not set
+# CONFIG_SATA_INIC162X is not set
+CONFIG_SATA_MOBILE_LPM_POLICY=0
+# CONFIG_SATA_MV is not set
+# CONFIG_SATA_NV is not set
+# CONFIG_SATA_PMP is not set
+# CONFIG_SATA_PROMISE is not set
+# CONFIG_SATA_QSTOR is not set
+# CONFIG_SATA_RCAR is not set
+# CONFIG_SATA_SIL is not set
+# CONFIG_SATA_SIL24 is not set
+# CONFIG_SATA_SIS is not set
+# CONFIG_SATA_SVW is not set
+# CONFIG_SATA_SX4 is not set
+# CONFIG_SATA_ULI is not set
+# CONFIG_SATA_VIA is not set
+# CONFIG_SATA_VITESSE is not set
+# CONFIG_SBC_FITPC2_WATCHDOG is not set
+CONFIG_SBITMAP=y
+# CONFIG_SC92031 is not set
+# CONFIG_SCA3000 is not set
+# CONFIG_SCA3300 is not set
+# CONFIG_SCACHE_DEBUGFS is not set
+# CONFIG_SCC is not set
+# CONFIG_SCD30_CORE is not set
+# CONFIG_SCD4X is not set
+# CONFIG_SCF_TORTURE_TEST is not set
+# CONFIG_SCHEDSTATS is not set
+# CONFIG_SCHED_AUTOGROUP is not set
+# CONFIG_SCHED_CLASS_EXT is not set
+# CONFIG_SCHED_CLUSTER is not set
+# CONFIG_SCHED_DEBUG is not set
+CONFIG_SCHED_HRTICK=y
+# CONFIG_SCHED_MC is not set
+CONFIG_SCHED_OMIT_FRAME_POINTER=y
+# CONFIG_SCHED_SMT is not set
+CONFIG_SCHED_STACK_END_CHECK=y
+# CONFIG_SCHED_TRACER is not set
+# CONFIG_SCSI is not set
+# CONFIG_SCSI_3W_9XXX is not set
+# CONFIG_SCSI_3W_SAS is not set
+# CONFIG_SCSI_AACRAID is not set
+# CONFIG_SCSI_ACARD is not set
+# CONFIG_SCSI_ADVANSYS is not set
+# CONFIG_SCSI_AHA152X is not set
+# CONFIG_SCSI_AHA1542 is not set
+# CONFIG_SCSI_AIC79XX is not set
+# CONFIG_SCSI_AIC7XXX is not set
+# CONFIG_SCSI_AIC94XX is not set
+# CONFIG_SCSI_AM53C974 is not set
+# CONFIG_SCSI_ARCMSR is not set
+# CONFIG_SCSI_BFA_FC is not set
+# CONFIG_SCSI_BNX2X_FCOE is not set
+# CONFIG_SCSI_BNX2_ISCSI is not set
+# CONFIG_SCSI_BUSLOGIC is not set
+# CONFIG_SCSI_CHELSIO_FCOE is not set
+# CONFIG_SCSI_CONSTANTS is not set
+# CONFIG_SCSI_CXGB3_ISCSI is not set
+# CONFIG_SCSI_CXGB4_ISCSI is not set
+# CONFIG_SCSI_DC395x is not set
+# CONFIG_SCSI_DEBUG is not set
+# CONFIG_SCSI_DH is not set
+CONFIG_SCSI_DMA=y
+# CONFIG_SCSI_DMX3191D is not set
+# CONFIG_SCSI_ESAS2R is not set
+# CONFIG_SCSI_FC_ATTRS is not set
+# CONFIG_SCSI_FDOMAIN_PCI is not set
+# CONFIG_SCSI_GENERIC_NCR5380 is not set
+# CONFIG_SCSI_HISI_SAS is not set
+# CONFIG_SCSI_HPSA is not set
+# CONFIG_SCSI_HPTIOP is not set
+# CONFIG_SCSI_INIA100 is not set
+# CONFIG_SCSI_INITIO is not set
+# CONFIG_SCSI_IPR is not set
+# CONFIG_SCSI_IPS is not set
+# CONFIG_SCSI_ISCI is not set
+# CONFIG_SCSI_ISCSI_ATTRS is not set
+# CONFIG_SCSI_LOGGING is not set
+CONFIG_SCSI_LOWLEVEL=y
+# CONFIG_SCSI_LOWLEVEL_PCMCIA is not set
+# CONFIG_SCSI_LPFC is not set
+CONFIG_SCSI_MOD=y
+# CONFIG_SCSI_MPI3MR is not set
+# CONFIG_SCSI_MPT2SAS is not set
+# CONFIG_SCSI_MPT3SAS is not set
+# CONFIG_SCSI_MVSAS is not set
+# CONFIG_SCSI_MVSAS_DEBUG is not set
+# CONFIG_SCSI_MVUMI is not set
+# CONFIG_SCSI_MYRB is not set
+# CONFIG_SCSI_MYRS is not set
+# CONFIG_SCSI_NETLINK is not set
+# CONFIG_SCSI_NSP32 is not set
+# CONFIG_SCSI_PM8001 is not set
+# CONFIG_SCSI_PMCRAID is not set
+CONFIG_SCSI_PROC_FS=y
+# CONFIG_SCSI_QLA_FC is not set
+# CONFIG_SCSI_QLA_ISCSI is not set
+# CONFIG_SCSI_QLOGIC_1280 is not set
+# CONFIG_SCSI_QLOGIC_FAS is not set
+# CONFIG_SCSI_SAS_ATTRS is not set
+# CONFIG_SCSI_SAS_LIBSAS is not set
+# CONFIG_SCSI_SCAN_ASYNC is not set
+# CONFIG_SCSI_SMARTPQI is not set
+# CONFIG_SCSI_SNIC is not set
+# CONFIG_SCSI_SPI_ATTRS is not set
+# CONFIG_SCSI_SRP_ATTRS is not set
+# CONFIG_SCSI_STEX is not set
+# CONFIG_SCSI_SYM53C8XX_2 is not set
+# CONFIG_SCSI_UFSHCD is not set
+# CONFIG_SCSI_UFSHCD_PCI is not set
+# CONFIG_SCSI_UFSHCD_PLATFORM is not set
+# CONFIG_SCSI_UFS_BSG is not set
+# CONFIG_SCSI_UFS_CDNS_PLATFORM is not set
+# CONFIG_SCSI_UFS_CRYPTO is not set
+# CONFIG_SCSI_UFS_DWC_TC_PLATFORM is not set
+# CONFIG_SCSI_UFS_EXYNOS is not set
+# CONFIG_SCSI_UFS_FAULT_INJECTION is not set
+# CONFIG_SCSI_UFS_HISI is not set
+# CONFIG_SCSI_UFS_HWMON is not set
+# CONFIG_SCSI_UFS_MEDIATEK is not set
+# CONFIG_SCSI_UFS_QCOM is not set
+# CONFIG_SCSI_UFS_RENESAS is not set
+# CONFIG_SCSI_UFS_ROCKCHIP is not set
+# CONFIG_SCSI_UFS_SPRD is not set
+# CONFIG_SCSI_UFS_TI_J721E is not set
+# CONFIG_SCSI_VIRTIO is not set
+# CONFIG_SCSI_WD719X is not set
+# CONFIG_SC_CAMCC_7180 is not set
+# CONFIG_SC_DISPCC_7280 is not set
+# CONFIG_SC_GCC_7280 is not set
+# CONFIG_SC_GCC_8180X is not set
+# CONFIG_SC_GPUCC_7280 is not set
+# CONFIG_SC_GPUCC_8280XP is not set
+# CONFIG_SC_VIDEOCC_7280 is not set
+# CONFIG_SCx200_ACB is not set
+# CONFIG_SDIO_UART is not set
+# CONFIG_SDM_GPUCC_660 is not set
+# CONFIG_SDM_MMCC_660 is not set
+# CONFIG_SDP500 is not set
+# CONFIG_SDR_MAX2175 is not set
+# CONFIG_SDR_PLATFORM_DRIVERS is not set
+# CONFIG_SDX_GCC_55 is not set
+# CONFIG_SD_ADC_MODULATOR is not set
+# CONFIG_SECCOMP is not set
+# CONFIG_SECCOMP_CACHE_DEBUG is not set
+# CONFIG_SECRETMEM is not set
+CONFIG_SECTION_MISMATCH_WARN_ONLY=y
+# CONFIG_SECURITY is not set
+# CONFIG_SECURITYFS is not set
+# CONFIG_SECURITY_APPARMOR is not set
+CONFIG_SECURITY_DMESG_RESTRICT=y
+# CONFIG_SECURITY_LANDLOCK is not set
+# CONFIG_SECURITY_LOADPIN is not set
+# CONFIG_SECURITY_LOCKDOWN_LSM is not set
+# CONFIG_SECURITY_NETWORK_XFRM is not set
+# CONFIG_SECURITY_PATH is not set
+# CONFIG_SECURITY_SAFESETID is not set
+# CONFIG_SECURITY_SELINUX_AVC_STATS is not set
+# CONFIG_SECURITY_SELINUX_BOOTPARAM is not set
+# CONFIG_SECURITY_SELINUX_DEBUG is not set
+# CONFIG_SECURITY_SELINUX_DEVELOP is not set
+# CONFIG_SECURITY_SMACK is not set
+# CONFIG_SECURITY_TOMOYO is not set
+# CONFIG_SECURITY_YAMA is not set
+CONFIG_SELECT_MEMORY_MODEL=y
+# CONFIG_SENSEAIR_SUNRISE_CO2 is not set
+# CONFIG_SENSIRION_SGP30 is not set
+# CONFIG_SENSIRION_SGP40 is not set
+# CONFIG_SENSORS_ABITUGURU is not set
+# CONFIG_SENSORS_ABITUGURU3 is not set
+# CONFIG_SENSORS_ACBEL_FSG032 is not set
+# CONFIG_SENSORS_ACPI_POWER is not set
+# CONFIG_SENSORS_AD7314 is not set
+# CONFIG_SENSORS_AD7414 is not set
+# CONFIG_SENSORS_AD7418 is not set
+# CONFIG_SENSORS_ADC128D818 is not set
+# CONFIG_SENSORS_ADCXX is not set
+# CONFIG_SENSORS_ADM1025 is not set
+# CONFIG_SENSORS_ADM1026 is not set
+# CONFIG_SENSORS_ADM1029 is not set
+# CONFIG_SENSORS_ADM1031 is not set
+# CONFIG_SENSORS_ADM1177 is not set
+# CONFIG_SENSORS_ADM1266 is not set
+# CONFIG_SENSORS_ADM1275 is not set
+# CONFIG_SENSORS_ADM9240 is not set
+# CONFIG_SENSORS_ADP1050 is not set
+# CONFIG_SENSORS_ADS7828 is not set
+# CONFIG_SENSORS_ADS7871 is not set
+# CONFIG_SENSORS_ADT7310 is not set
+# CONFIG_SENSORS_ADT7410 is not set
+# CONFIG_SENSORS_ADT7411 is not set
+# CONFIG_SENSORS_ADT7462 is not set
+# CONFIG_SENSORS_ADT7470 is not set
+# CONFIG_SENSORS_ADT7475 is not set
+# CONFIG_SENSORS_AHT10 is not set
+# CONFIG_SENSORS_AMC6821 is not set
+# CONFIG_SENSORS_APDS990X is not set
+# CONFIG_SENSORS_APPLESMC is not set
+# CONFIG_SENSORS_AQUACOMPUTER_D5NEXT is not set
+# CONFIG_SENSORS_AS370 is not set
+# CONFIG_SENSORS_ASB100 is not set
+# CONFIG_SENSORS_ASC7621 is not set
+# CONFIG_SENSORS_ASPEED is not set
+# CONFIG_SENSORS_ASUS_ROG_RYUJIN is not set
+# CONFIG_SENSORS_ASUS_WMI is not set
+# CONFIG_SENSORS_ATK0110 is not set
+# CONFIG_SENSORS_ATXP1 is not set
+# CONFIG_SENSORS_AXI_FAN_CONTROL is not set
+# CONFIG_SENSORS_BEL_PFE is not set
+# CONFIG_SENSORS_BH1770 is not set
+# CONFIG_SENSORS_BPA_RS600 is not set
+# CONFIG_SENSORS_CHIPCAP2 is not set
+# CONFIG_SENSORS_CORETEMP is not set
+# CONFIG_SENSORS_CORSAIR_CPRO is not set
+# CONFIG_SENSORS_CORSAIR_PSU is not set
+# CONFIG_SENSORS_DELL_SMM is not set
+# CONFIG_SENSORS_DELTA_AHE50DC_FAN is not set
+# CONFIG_SENSORS_DME1737 is not set
+# CONFIG_SENSORS_DPS920AB is not set
+# CONFIG_SENSORS_DRIVETEMP is not set
+# CONFIG_SENSORS_DS1621 is not set
+# CONFIG_SENSORS_DS620 is not set
+# CONFIG_SENSORS_EMC1403 is not set
+# CONFIG_SENSORS_EMC2103 is not set
+# CONFIG_SENSORS_EMC2305 is not set
+# CONFIG_SENSORS_EMC6W201 is not set
+# CONFIG_SENSORS_F71805F is not set
+# CONFIG_SENSORS_F71882FG is not set
+# CONFIG_SENSORS_F75375S is not set
+# CONFIG_SENSORS_FAM15H_POWER is not set
+# CONFIG_SENSORS_FSCHMD is not set
+# CONFIG_SENSORS_FSP_3Y is not set
+# CONFIG_SENSORS_FTSTEUTATES is not set
+# CONFIG_SENSORS_G760A is not set
+# CONFIG_SENSORS_G762 is not set
+# CONFIG_SENSORS_GIGABYTE_WATERFORCE is not set
+# CONFIG_SENSORS_GL518SM is not set
+# CONFIG_SENSORS_GL520SM is not set
+# CONFIG_SENSORS_GPIO_FAN is not set
+# CONFIG_SENSORS_GSC is not set
+# CONFIG_SENSORS_HDAPS is not set
+# CONFIG_SENSORS_HIH6130 is not set
+# CONFIG_SENSORS_HMC5843 is not set
+# CONFIG_SENSORS_HMC5843_I2C is not set
+# CONFIG_SENSORS_HMC5843_SPI is not set
+# CONFIG_SENSORS_HP_WMI is not set
+# CONFIG_SENSORS_HS3001 is not set
+# CONFIG_SENSORS_I5500 is not set
+# CONFIG_SENSORS_I5K_AMB is not set
+# CONFIG_SENSORS_IBM_CFFPS is not set
+# CONFIG_SENSORS_IIO_HWMON is not set
+# CONFIG_SENSORS_INA209 is not set
+# CONFIG_SENSORS_INA238 is not set
+# CONFIG_SENSORS_INA2XX is not set
+# CONFIG_SENSORS_INA3221 is not set
+# CONFIG_SENSORS_INSPUR_IPSPS is not set
+# CONFIG_SENSORS_IR35221 is not set
+# CONFIG_SENSORS_IR36021 is not set
+# CONFIG_SENSORS_IR38064 is not set
+# CONFIG_SENSORS_IRPS5401 is not set
+# CONFIG_SENSORS_ISL29018 is not set
+# CONFIG_SENSORS_ISL29028 is not set
+# CONFIG_SENSORS_ISL68137 is not set
+# CONFIG_SENSORS_IT87 is not set
+# CONFIG_SENSORS_JC42 is not set
+# CONFIG_SENSORS_K10TEMP is not set
+# CONFIG_SENSORS_K8TEMP is not set
+# CONFIG_SENSORS_LINEAGE is not set
+# CONFIG_SENSORS_LIS3LV02D is not set
+# CONFIG_SENSORS_LIS3_I2C is not set
+# CONFIG_SENSORS_LIS3_SPI is not set
+# CONFIG_SENSORS_LM25066 is not set
+# CONFIG_SENSORS_LM63 is not set
+# CONFIG_SENSORS_LM70 is not set
+# CONFIG_SENSORS_LM73 is not set
+# CONFIG_SENSORS_LM75 is not set
+# CONFIG_SENSORS_LM77 is not set
+# CONFIG_SENSORS_LM78 is not set
+# CONFIG_SENSORS_LM80 is not set
+# CONFIG_SENSORS_LM83 is not set
+# CONFIG_SENSORS_LM85 is not set
+# CONFIG_SENSORS_LM87 is not set
+# CONFIG_SENSORS_LM90 is not set
+# CONFIG_SENSORS_LM92 is not set
+# CONFIG_SENSORS_LM93 is not set
+# CONFIG_SENSORS_LM95234 is not set
+# CONFIG_SENSORS_LM95241 is not set
+# CONFIG_SENSORS_LM95245 is not set
+# CONFIG_SENSORS_LT7182S is not set
+# CONFIG_SENSORS_LTC2945 is not set
+# CONFIG_SENSORS_LTC2947_I2C is not set
+# CONFIG_SENSORS_LTC2947_SPI is not set
+# CONFIG_SENSORS_LTC2978 is not set
+# CONFIG_SENSORS_LTC2990 is not set
+# CONFIG_SENSORS_LTC2991 is not set
+# CONFIG_SENSORS_LTC2992 is not set
+# CONFIG_SENSORS_LTC3815 is not set
+# CONFIG_SENSORS_LTC4151 is not set
+# CONFIG_SENSORS_LTC4215 is not set
+# CONFIG_SENSORS_LTC4222 is not set
+# CONFIG_SENSORS_LTC4245 is not set
+# CONFIG_SENSORS_LTC4260 is not set
+# CONFIG_SENSORS_LTC4261 is not set
+# CONFIG_SENSORS_LTC4282 is not set
+# CONFIG_SENSORS_LTC4286 is not set
+# CONFIG_SENSORS_LTQ_CPUTEMP is not set
+# CONFIG_SENSORS_MAX1111 is not set
+# CONFIG_SENSORS_MAX127 is not set
+# CONFIG_SENSORS_MAX15301 is not set
+# CONFIG_SENSORS_MAX16064 is not set
+# CONFIG_SENSORS_MAX16065 is not set
+# CONFIG_SENSORS_MAX1619 is not set
+# CONFIG_SENSORS_MAX16601 is not set
+# CONFIG_SENSORS_MAX1668 is not set
+# CONFIG_SENSORS_MAX197 is not set
+# CONFIG_SENSORS_MAX20730 is not set
+# CONFIG_SENSORS_MAX20751 is not set
+# CONFIG_SENSORS_MAX31722 is not set
+# CONFIG_SENSORS_MAX31730 is not set
+# CONFIG_SENSORS_MAX31760 is not set
+# CONFIG_SENSORS_MAX31785 is not set
+# CONFIG_SENSORS_MAX31790 is not set
+# CONFIG_SENSORS_MAX34440 is not set
+# CONFIG_SENSORS_MAX6620 is not set
+# CONFIG_SENSORS_MAX6621 is not set
+# CONFIG_SENSORS_MAX6639 is not set
+# CONFIG_SENSORS_MAX6650 is not set
+# CONFIG_SENSORS_MAX6697 is not set
+# CONFIG_SENSORS_MAX8688 is not set
+# CONFIG_SENSORS_MC34VR500 is not set
+# CONFIG_SENSORS_MCP3021 is not set
+# CONFIG_SENSORS_MP2856 is not set
+# CONFIG_SENSORS_MP2888 is not set
+# CONFIG_SENSORS_MP2891 is not set
+# CONFIG_SENSORS_MP2975 is not set
+# CONFIG_SENSORS_MP2993 is not set
+# CONFIG_SENSORS_MP5023 is not set
+# CONFIG_SENSORS_MP5920 is not set
+# CONFIG_SENSORS_MP5990 is not set
+# CONFIG_SENSORS_MP9941 is not set
+# CONFIG_SENSORS_MPQ7932 is not set
+# CONFIG_SENSORS_MPQ8785 is not set
+# CONFIG_SENSORS_MR75203 is not set
+# CONFIG_SENSORS_NCT6683 is not set
+# CONFIG_SENSORS_NCT6775 is not set
+# CONFIG_SENSORS_NCT6775_I2C is not set
+# CONFIG_SENSORS_NCT7802 is not set
+# CONFIG_SENSORS_NCT7904 is not set
+# CONFIG_SENSORS_NPCM7XX is not set
+# CONFIG_SENSORS_NSA320 is not set
+# CONFIG_SENSORS_NTC_THERMISTOR is not set
+# CONFIG_SENSORS_NZXT_KRAKEN2 is not set
+# CONFIG_SENSORS_NZXT_KRAKEN3 is not set
+# CONFIG_SENSORS_NZXT_SMART2 is not set
+# CONFIG_SENSORS_OCC_P8_I2C is not set
+# CONFIG_SENSORS_PC87360 is not set
+# CONFIG_SENSORS_PC87427 is not set
+# CONFIG_SENSORS_PCF8591 is not set
+# CONFIG_SENSORS_PIM4328 is not set
+# CONFIG_SENSORS_PLI1209BC is not set
+# CONFIG_SENSORS_PM6764TR is not set
+# CONFIG_SENSORS_PMBUS is not set
+# CONFIG_SENSORS_POWERZ is not set
+# CONFIG_SENSORS_POWR1220 is not set
+# CONFIG_SENSORS_PT5161L is not set
+# CONFIG_SENSORS_PWM_FAN is not set
+# CONFIG_SENSORS_PXE1610 is not set
+# CONFIG_SENSORS_Q54SJ108A2 is not set
+# CONFIG_SENSORS_RM3100_I2C is not set
+# CONFIG_SENSORS_RM3100_SPI is not set
+# CONFIG_SENSORS_SBRMI is not set
+# CONFIG_SENSORS_SBTSI is not set
+# CONFIG_SENSORS_SCH5627 is not set
+# CONFIG_SENSORS_SCH5636 is not set
+# CONFIG_SENSORS_SCH56XX_COMMON is not set
+# CONFIG_SENSORS_SHT15 is not set
+# CONFIG_SENSORS_SHT21 is not set
+# CONFIG_SENSORS_SHT3x is not set
+# CONFIG_SENSORS_SHT4x is not set
+# CONFIG_SENSORS_SHTC1 is not set
+# CONFIG_SENSORS_SIS5595 is not set
+# CONFIG_SENSORS_SMSC47B397 is not set
+# CONFIG_SENSORS_SMSC47M1 is not set
+# CONFIG_SENSORS_SMSC47M192 is not set
+# CONFIG_SENSORS_SPD5118 is not set
+# CONFIG_SENSORS_STPDDC60 is not set
+# CONFIG_SENSORS_STTS751 is not set
+# CONFIG_SENSORS_TC654 is not set
+# CONFIG_SENSORS_TC74 is not set
+# CONFIG_SENSORS_TDA38640 is not set
+# CONFIG_SENSORS_THMC50 is not set
+# CONFIG_SENSORS_TMP102 is not set
+# CONFIG_SENSORS_TMP103 is not set
+# CONFIG_SENSORS_TMP108 is not set
+# CONFIG_SENSORS_TMP401 is not set
+# CONFIG_SENSORS_TMP421 is not set
+# CONFIG_SENSORS_TMP464 is not set
+# CONFIG_SENSORS_TMP513 is not set
+# CONFIG_SENSORS_TPS23861 is not set
+# CONFIG_SENSORS_TPS40422 is not set
+# CONFIG_SENSORS_TPS53679 is not set
+# CONFIG_SENSORS_TPS546D24 is not set
+# CONFIG_SENSORS_TSL2550 is not set
+# CONFIG_SENSORS_TSL2563 is not set
+# CONFIG_SENSORS_UCD9000 is not set
+# CONFIG_SENSORS_UCD9200 is not set
+# CONFIG_SENSORS_VEXPRESS is not set
+# CONFIG_SENSORS_VIA686A is not set
+# CONFIG_SENSORS_VIA_CPUTEMP is not set
+# CONFIG_SENSORS_VT1211 is not set
+# CONFIG_SENSORS_VT8231 is not set
+# CONFIG_SENSORS_W83627EHF is not set
+# CONFIG_SENSORS_W83627HF is not set
+# CONFIG_SENSORS_W83773G is not set
+# CONFIG_SENSORS_W83781D is not set
+# CONFIG_SENSORS_W83791D is not set
+# CONFIG_SENSORS_W83792D is not set
+# CONFIG_SENSORS_W83793 is not set
+# CONFIG_SENSORS_W83795 is not set
+# CONFIG_SENSORS_W83L785TS is not set
+# CONFIG_SENSORS_W83L786NG is not set
+# CONFIG_SENSORS_XDP710 is not set
+# CONFIG_SENSORS_XDPE122 is not set
+# CONFIG_SENSORS_XDPE152 is not set
+# CONFIG_SENSORS_XGENE is not set
+# CONFIG_SENSORS_ZL6100 is not set
+CONFIG_SERIAL_8250=y
+CONFIG_SERIAL_8250_16550A_VARIANTS=y
+# CONFIG_SERIAL_8250_ACCENT is not set
+# CONFIG_SERIAL_8250_ASPEED_VUART is not set
+# CONFIG_SERIAL_8250_BOCA is not set
+CONFIG_SERIAL_8250_CONSOLE=y
+# CONFIG_SERIAL_8250_CS is not set
+# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
+# CONFIG_SERIAL_8250_DETECT_IRQ is not set
+CONFIG_SERIAL_8250_DMA=y
+# CONFIG_SERIAL_8250_DW is not set
+# CONFIG_SERIAL_8250_EM is not set
+# CONFIG_SERIAL_8250_EXAR is not set
+# CONFIG_SERIAL_8250_EXAR_ST16C554 is not set
+# CONFIG_SERIAL_8250_EXTENDED is not set
+# CONFIG_SERIAL_8250_FINTEK is not set
+# CONFIG_SERIAL_8250_FOURPORT is not set
+# CONFIG_SERIAL_8250_HUB6 is not set
+# CONFIG_SERIAL_8250_INGENIC is not set
+# CONFIG_SERIAL_8250_LPSS is not set
+# CONFIG_SERIAL_8250_MANY_PORTS is not set
+# CONFIG_SERIAL_8250_MID is not set
+CONFIG_SERIAL_8250_NR_UARTS=2
+# CONFIG_SERIAL_8250_PCI is not set
+# CONFIG_SERIAL_8250_PCI1XXXX is not set
+# CONFIG_SERIAL_8250_PERICOM is not set
+# CONFIG_SERIAL_8250_RSA is not set
+# CONFIG_SERIAL_8250_RT288X is not set
+CONFIG_SERIAL_8250_RUNTIME_UARTS=2
+# CONFIG_SERIAL_ALTERA_JTAGUART is not set
+# CONFIG_SERIAL_ALTERA_UART is not set
+# CONFIG_SERIAL_AMBA_PL010 is not set
+# CONFIG_SERIAL_AMBA_PL011 is not set
+# CONFIG_SERIAL_ARC is not set
+# CONFIG_SERIAL_BCM63XX is not set
+# CONFIG_SERIAL_CONEXANT_DIGICOLOR is not set
+CONFIG_SERIAL_CORE=y
+CONFIG_SERIAL_CORE_CONSOLE=y
+# CONFIG_SERIAL_DEV_BUS is not set
+CONFIG_SERIAL_EARLYCON=y
+# CONFIG_SERIAL_EARLYCON_SEMIHOST is not set
+# CONFIG_SERIAL_FSL_LINFLEXUART is not set
+# CONFIG_SERIAL_FSL_LPUART is not set
+# CONFIG_SERIAL_GRLIB_GAISLER_APBUART is not set
+# CONFIG_SERIAL_JSM is not set
+# CONFIG_SERIAL_MAX3100 is not set
+# CONFIG_SERIAL_MAX310X is not set
+# CONFIG_SERIAL_MULTI_INSTANTIATE is not set
+# CONFIG_SERIAL_NONSTANDARD is not set
+# CONFIG_SERIAL_OF_PLATFORM is not set
+# CONFIG_SERIAL_PCH_UART is not set
+# CONFIG_SERIAL_RP2 is not set
+# CONFIG_SERIAL_SC16IS7XX is not set
+# CONFIG_SERIAL_SCCNXP is not set
+# CONFIG_SERIAL_SH_SCI is not set
+# CONFIG_SERIAL_SIFIVE is not set
+# CONFIG_SERIAL_SPRD is not set
+# CONFIG_SERIAL_STM32 is not set
+# CONFIG_SERIAL_ST_ASC is not set
+# CONFIG_SERIAL_TIMBERDALE is not set
+# CONFIG_SERIAL_UARTLITE is not set
+# CONFIG_SERIAL_XILINX_PS_UART is not set
+# CONFIG_SERIO is not set
+# CONFIG_SERIO_ALTERA_PS2 is not set
+# CONFIG_SERIO_AMBAKMI is not set
+# CONFIG_SERIO_APBPS2 is not set
+# CONFIG_SERIO_ARC_PS2 is not set
+# CONFIG_SERIO_CT82C710 is not set
+# CONFIG_SERIO_GPIO_PS2 is not set
+# CONFIG_SERIO_I8042 is not set
+# CONFIG_SERIO_LIBPS2 is not set
+# CONFIG_SERIO_PARKBD is not set
+# CONFIG_SERIO_PCIPS2 is not set
+# CONFIG_SERIO_PS2MULT is not set
+# CONFIG_SERIO_RAW is not set
+# CONFIG_SERIO_SERPORT is not set
+# CONFIG_SERIO_SUN4I_PS2 is not set
+# CONFIG_SERIO_XILINX_XPS_PS2 is not set
+# CONFIG_SFC is not set
+# CONFIG_SFC_FALCON is not set
+# CONFIG_SFC_SIENA is not set
+# CONFIG_SFP is not set
+# CONFIG_SF_PDMA is not set
+# CONFIG_SGETMASK_SYSCALL is not set
+# CONFIG_SGI_IP22 is not set
+# CONFIG_SGI_IP27 is not set
+# CONFIG_SGI_IP28 is not set
+# CONFIG_SGI_IP30 is not set
+# CONFIG_SGI_IP32 is not set
+# CONFIG_SGI_MFD_IOC3 is not set
+# CONFIG_SGI_PARTITION is not set
+# CONFIG_SG_POOL is not set
+# CONFIG_SG_SPLIT is not set
+# CONFIG_SHADOW_CALL_STACK is not set
+CONFIG_SHMEM=y
+# CONFIG_SHRINKER_DEBUG is not set
+# CONFIG_SHUFFLE_PAGE_ALLOCATOR is not set
+# CONFIG_SH_ETH is not set
+# CONFIG_SH_TIMER_CMT is not set
+# CONFIG_SH_TIMER_MTU2 is not set
+# CONFIG_SH_TIMER_TMU is not set
+# CONFIG_SI1133 is not set
+# CONFIG_SI1145 is not set
+# CONFIG_SI7005 is not set
+# CONFIG_SI7020 is not set
+# CONFIG_SIBYTE_BIGSUR is not set
+# CONFIG_SIBYTE_CRHONE is not set
+# CONFIG_SIBYTE_LITTLESUR is not set
+# CONFIG_SIBYTE_RHONE is not set
+# CONFIG_SIBYTE_SENTOSA is not set
+# CONFIG_SIBYTE_SWARM is not set
+CONFIG_SIGNALFD=y
+# CONFIG_SIGNED_PE_FILE_VERIFICATION is not set
+# CONFIG_SIOX is not set
+# CONFIG_SIS190 is not set
+# CONFIG_SIS900 is not set
+# CONFIG_SKGE is not set
+# CONFIG_SKY2 is not set
+# CONFIG_SKY2_DEBUG is not set
+CONFIG_SLAB_BUCKETS=y
+CONFIG_SLAB_FREELIST_HARDENED=y
+CONFIG_SLAB_FREELIST_RANDOM=y
+CONFIG_SLAB_MERGE_DEFAULT=y
+# CONFIG_SLHC is not set
+# CONFIG_SLICOSS is not set
+# CONFIG_SLIMBUS is not set
+# CONFIG_SLIP is not set
+CONFIG_SLUB=y
+CONFIG_SLUB_CPU_PARTIAL=y
+# CONFIG_SLUB_DEBUG is not set
+# CONFIG_SLUB_DEBUG_ON is not set
+# CONFIG_SLUB_STATS is not set
+# CONFIG_SLUB_TINY is not set
+# CONFIG_SMARTJOYPLUS_FF is not set
+# CONFIG_SMB_SERVER is not set
+# CONFIG_SMC9194 is not set
+# CONFIG_SMC91X is not set
+# CONFIG_SMP is not set
+# CONFIG_SMSC911X is not set
+# CONFIG_SMSC9420 is not set
+# CONFIG_SMSC_PHY is not set
+# CONFIG_SMS_SDIO_DRV is not set
+# CONFIG_SMS_USB_DRV is not set
+# CONFIG_SM_CAMCC_8250 is not set
+# CONFIG_SM_FTL is not set
+# CONFIG_SM_GCC_6115 is not set
+# CONFIG_SM_GCC_6125 is not set
+# CONFIG_SM_GCC_6350 is not set
+# CONFIG_SM_GCC_6375 is not set
+# CONFIG_SM_GCC_8350 is not set
+# CONFIG_SND is not set
+# CONFIG_SND_AC97_POWER_SAVE is not set
+# CONFIG_SND_AD1816A is not set
+# CONFIG_SND_AD1848 is not set
+# CONFIG_SND_AD1889 is not set
+# CONFIG_SND_ADLIB is not set
+# CONFIG_SND_ALI5451 is not set
+# CONFIG_SND_ALOOP is not set
+# CONFIG_SND_ALS100 is not set
+# CONFIG_SND_ALS300 is not set
+# CONFIG_SND_ALS4000 is not set
+# CONFIG_SND_AMD_ACP_CONFIG is not set
+# CONFIG_SND_ARM is not set
+# CONFIG_SND_ASIHPI is not set
+# CONFIG_SND_ATIIXP is not set
+# CONFIG_SND_ATIIXP_MODEM is not set
+# CONFIG_SND_ATMEL_AC97C is not set
+# CONFIG_SND_ATMEL_SOC is not set
+# CONFIG_SND_AU8810 is not set
+# CONFIG_SND_AU8820 is not set
+# CONFIG_SND_AU8830 is not set
+# CONFIG_SND_AUDIO_GRAPH_CARD is not set
+# CONFIG_SND_AUDIO_GRAPH_CARD2 is not set
+# CONFIG_SND_AW2 is not set
+# CONFIG_SND_AZT2320 is not set
+# CONFIG_SND_AZT3328 is not set
+# CONFIG_SND_BCD2000 is not set
+# CONFIG_SND_BCM2835 is not set
+# CONFIG_SND_BCM63XX_I2S_WHISTLER is not set
+# CONFIG_SND_BT87X is not set
+# CONFIG_SND_CA0106 is not set
+# CONFIG_SND_CMI8330 is not set
+# CONFIG_SND_CMIPCI is not set
+# CONFIG_SND_CS4231 is not set
+# CONFIG_SND_CS4236 is not set
+# CONFIG_SND_CS4281 is not set
+# CONFIG_SND_CS46XX is not set
+# CONFIG_SND_CS5530 is not set
+# CONFIG_SND_CS5535AUDIO is not set
+# CONFIG_SND_CTL_FAST_LOOKUP is not set
+# CONFIG_SND_CTL_INPUT_VALIDATION is not set
+# CONFIG_SND_CTXFI is not set
+# CONFIG_SND_DARLA20 is not set
+# CONFIG_SND_DARLA24 is not set
+# CONFIG_SND_DEBUG is not set
+# CONFIG_SND_DESIGNWARE_I2S is not set
+CONFIG_SND_DRIVERS=y
+# CONFIG_SND_DUMMY is not set
+# CONFIG_SND_DYNAMIC_MINORS is not set
+# CONFIG_SND_ECHO3G is not set
+# CONFIG_SND_EMU10K1 is not set
+# CONFIG_SND_EMU10K1X is not set
+# CONFIG_SND_EMU10K1_SEQ is not set
+# CONFIG_SND_ENS1370 is not set
+# CONFIG_SND_ENS1371 is not set
+# CONFIG_SND_ES1688 is not set
+# CONFIG_SND_ES18XX is not set
+# CONFIG_SND_ES1938 is not set
+# CONFIG_SND_ES1968 is not set
+# CONFIG_SND_FIREWIRE is not set
+# CONFIG_SND_FM801 is not set
+# CONFIG_SND_GINA20 is not set
+# CONFIG_SND_GINA24 is not set
+# CONFIG_SND_GUSCLASSIC is not set
+# CONFIG_SND_GUSEXTREME is not set
+# CONFIG_SND_GUSMAX is not set
+# CONFIG_SND_HDA_CODEC_CS8409 is not set
+# CONFIG_SND_HDA_CODEC_SENARYTECH is not set
+# CONFIG_SND_HDA_CTL_DEV_ID is not set
+# CONFIG_SND_HDA_INTEL is not set
+# CONFIG_SND_HDA_INTEL_HDMI_SILENT_STREAM is not set
+CONFIG_SND_HDA_POWER_SAVE_DEFAULT=0
+CONFIG_SND_HDA_PREALLOC_SIZE=64
+# CONFIG_SND_HDA_SCODEC_CS35L41_I2C is not set
+# CONFIG_SND_HDA_SCODEC_CS35L41_SPI is not set
+# CONFIG_SND_HDA_SCODEC_CS35L56_I2C is not set
+# CONFIG_SND_HDA_SCODEC_CS35L56_SPI is not set
+# CONFIG_SND_HDA_SCODEC_TAS2781_I2C is not set
+# CONFIG_SND_HDSP is not set
+# CONFIG_SND_HDSPM is not set
+# CONFIG_SND_HRTIMER is not set
+# CONFIG_SND_HWDEP is not set
+# CONFIG_SND_I2S_HI6210_I2S is not set
+# CONFIG_SND_ICE1712 is not set
+# CONFIG_SND_ICE1724 is not set
+# CONFIG_SND_INDIGO is not set
+# CONFIG_SND_INDIGODJ is not set
+# CONFIG_SND_INDIGODJX is not set
+# CONFIG_SND_INDIGOIO is not set
+# CONFIG_SND_INDIGOIOX is not set
+# CONFIG_SND_INTEL8X0 is not set
+# CONFIG_SND_INTEL8X0M is not set
+# CONFIG_SND_INTERWAVE is not set
+# CONFIG_SND_INTERWAVE_STB is not set
+# CONFIG_SND_ISA is not set
+# CONFIG_SND_JZ4740_SOC_I2S is not set
+# CONFIG_SND_KIRKWOOD_SOC is not set
+# CONFIG_SND_KORG1212 is not set
+# CONFIG_SND_LAYLA20 is not set
+# CONFIG_SND_LAYLA24 is not set
+# CONFIG_SND_LOLA is not set
+# CONFIG_SND_LX6464ES is not set
+# CONFIG_SND_MAESTRO3 is not set
+CONFIG_SND_MAX_CARDS=16
+# CONFIG_SND_MIA is not set
+# CONFIG_SND_MIPS is not set
+# CONFIG_SND_MIRO is not set
+# CONFIG_SND_MIXART is not set
+# CONFIG_SND_MIXER_OSS is not set
+# CONFIG_SND_MONA is not set
+# CONFIG_SND_MPC52xx_SOC_EFIKA is not set
+# CONFIG_SND_MPU401 is not set
+# CONFIG_SND_MTPAV is not set
+# CONFIG_SND_MTS64 is not set
+# CONFIG_SND_MXS_SOC is not set
+# CONFIG_SND_NM256 is not set
+# CONFIG_SND_OPL3SA2 is not set
+# CONFIG_SND_OPL3_LIB_SEQ is not set
+# CONFIG_SND_OPL4_LIB_SEQ is not set
+# CONFIG_SND_OPTI92X_AD1848 is not set
+# CONFIG_SND_OPTI92X_CS4231 is not set
+# CONFIG_SND_OPTI93X is not set
+CONFIG_SND_OSSEMUL=y
+# CONFIG_SND_OXYGEN is not set
+CONFIG_SND_PCI=y
+# CONFIG_SND_PCM is not set
+# CONFIG_SND_PCMCIA is not set
+# CONFIG_SND_PCMTEST is not set
+# CONFIG_SND_PCM_OSS is not set
+CONFIG_SND_PCM_OSS_PLUGINS=y
+# CONFIG_SND_PCM_TIMER is not set
+# CONFIG_SND_PCM_XRUN_DEBUG is not set
+# CONFIG_SND_PCXHR is not set
+# CONFIG_SND_PDAUDIOCF is not set
+# CONFIG_SND_PORTMAN2X4 is not set
+# CONFIG_SND_POWERPC_SOC is not set
+# CONFIG_SND_PPC is not set
+CONFIG_SND_PROC_FS=y
+# CONFIG_SND_RAWMIDI is not set
+# CONFIG_SND_RIPTIDE is not set
+# CONFIG_SND_RME32 is not set
+# CONFIG_SND_RME96 is not set
+# CONFIG_SND_RME9652 is not set
+# CONFIG_SND_SB16 is not set
+# CONFIG_SND_SB8 is not set
+# CONFIG_SND_SBAWE is not set
+# CONFIG_SND_SBAWE_SEQ is not set
+# CONFIG_SND_SE6X is not set
+# CONFIG_SND_SEQUENCER is not set
+# CONFIG_SND_SEQ_UMP is not set
+# CONFIG_SND_SERIAL_GENERIC is not set
+# CONFIG_SND_SERIAL_U16550 is not set
+# CONFIG_SND_SIMPLE_CARD is not set
+# CONFIG_SND_SIS7019 is not set
+# CONFIG_SND_SOC is not set
+# CONFIG_SND_SOC_AC97_CODEC is not set
+# CONFIG_SND_SOC_AD193X_I2C is not set
+# CONFIG_SND_SOC_AD193X_SPI is not set
+# CONFIG_SND_SOC_ADAU1372_I2C is not set
+# CONFIG_SND_SOC_ADAU1372_SPI is not set
+# CONFIG_SND_SOC_ADAU1701 is not set
+# CONFIG_SND_SOC_ADAU1761_I2C is not set
+# CONFIG_SND_SOC_ADAU1761_SPI is not set
+# CONFIG_SND_SOC_ADAU7002 is not set
+# CONFIG_SND_SOC_ADAU7118_HW is not set
+# CONFIG_SND_SOC_ADAU7118_I2C is not set
+# CONFIG_SND_SOC_ADI is not set
+# CONFIG_SND_SOC_AK4104 is not set
+# CONFIG_SND_SOC_AK4118 is not set
+# CONFIG_SND_SOC_AK4375 is not set
+# CONFIG_SND_SOC_AK4458 is not set
+# CONFIG_SND_SOC_AK4554 is not set
+# CONFIG_SND_SOC_AK4613 is not set
+# CONFIG_SND_SOC_AK4619 is not set
+# CONFIG_SND_SOC_AK4642 is not set
+# CONFIG_SND_SOC_AK5386 is not set
+# CONFIG_SND_SOC_AK5558 is not set
+# CONFIG_SND_SOC_ALC5623 is not set
+# CONFIG_SND_SOC_AMD_ACP is not set
+# CONFIG_SND_SOC_AMD_ACP3x is not set
+# CONFIG_SND_SOC_AMD_ACP5x is not set
+# CONFIG_SND_SOC_AMD_ACP6x is not set
+# CONFIG_SND_SOC_AMD_ACP_COMMON is not set
+# CONFIG_SND_SOC_AMD_PS is not set
+# CONFIG_SND_SOC_AMD_RENOIR is not set
+# CONFIG_SND_SOC_AMD_RPL_ACP6x is not set
+# CONFIG_SND_SOC_AU1XAUDIO is not set
+# CONFIG_SND_SOC_AU1XPSC is not set
+# CONFIG_SND_SOC_AUDIO_IIO_AUX is not set
+# CONFIG_SND_SOC_AW8738 is not set
+# CONFIG_SND_SOC_AW87390 is not set
+# CONFIG_SND_SOC_AW88261 is not set
+# CONFIG_SND_SOC_AW88395 is not set
+# CONFIG_SND_SOC_AW88399 is not set
+# CONFIG_SND_SOC_BD28623 is not set
+# CONFIG_SND_SOC_BT_SCO is not set
+# CONFIG_SND_SOC_CHV3_CODEC is not set
+# CONFIG_SND_SOC_CHV3_I2S is not set
+# CONFIG_SND_SOC_CS35L32 is not set
+# CONFIG_SND_SOC_CS35L33 is not set
+# CONFIG_SND_SOC_CS35L34 is not set
+# CONFIG_SND_SOC_CS35L35 is not set
+# CONFIG_SND_SOC_CS35L36 is not set
+# CONFIG_SND_SOC_CS35L41_I2C is not set
+# CONFIG_SND_SOC_CS35L41_SPI is not set
+# CONFIG_SND_SOC_CS35L45_I2C is not set
+# CONFIG_SND_SOC_CS35L45_SPI is not set
+# CONFIG_SND_SOC_CS35L56_I2C is not set
+# CONFIG_SND_SOC_CS35L56_SPI is not set
+# CONFIG_SND_SOC_CS4234 is not set
+# CONFIG_SND_SOC_CS4265 is not set
+# CONFIG_SND_SOC_CS4270 is not set
+# CONFIG_SND_SOC_CS4271 is not set
+# CONFIG_SND_SOC_CS4271_I2C is not set
+# CONFIG_SND_SOC_CS4271_SPI is not set
+# CONFIG_SND_SOC_CS42L42 is not set
+# CONFIG_SND_SOC_CS42L51_I2C is not set
+# CONFIG_SND_SOC_CS42L52 is not set
+# CONFIG_SND_SOC_CS42L56 is not set
+# CONFIG_SND_SOC_CS42L73 is not set
+# CONFIG_SND_SOC_CS42L83 is not set
+# CONFIG_SND_SOC_CS42XX8_I2C is not set
+# CONFIG_SND_SOC_CS43130 is not set
+# CONFIG_SND_SOC_CS4341 is not set
+# CONFIG_SND_SOC_CS4349 is not set
+# CONFIG_SND_SOC_CS530X_I2C is not set
+# CONFIG_SND_SOC_CS53L30 is not set
+# CONFIG_SND_SOC_CX2072X is not set
+# CONFIG_SND_SOC_DA7213 is not set
+# CONFIG_SND_SOC_DMIC is not set
+# CONFIG_SND_SOC_ES7134 is not set
+# CONFIG_SND_SOC_ES7241 is not set
+# CONFIG_SND_SOC_ES8311 is not set
+# CONFIG_SND_SOC_ES8316 is not set
+# CONFIG_SND_SOC_ES8326 is not set
+# CONFIG_SND_SOC_ES8328 is not set
+# CONFIG_SND_SOC_ES8328_I2C is not set
+# CONFIG_SND_SOC_ES8328_SPI is not set
+# CONFIG_SND_SOC_EUKREA_TLV320 is not set
+# CONFIG_SND_SOC_FSL_ASOC_CARD is not set
+# CONFIG_SND_SOC_FSL_ASRC is not set
+# CONFIG_SND_SOC_FSL_AUD2HTX is not set
+# CONFIG_SND_SOC_FSL_AUDMIX is not set
+# CONFIG_SND_SOC_FSL_ESAI is not set
+# CONFIG_SND_SOC_FSL_MICFIL is not set
+# CONFIG_SND_SOC_FSL_RPMSG is not set
+# CONFIG_SND_SOC_FSL_SAI is not set
+# CONFIG_SND_SOC_FSL_SPDIF is not set
+# CONFIG_SND_SOC_FSL_SSI is not set
+# CONFIG_SND_SOC_FSL_XCVR is not set
+# CONFIG_SND_SOC_GTM601 is not set
+# CONFIG_SND_SOC_HDA is not set
+# CONFIG_SND_SOC_ICS43432 is not set
+# CONFIG_SND_SOC_IDT821034 is not set
+# CONFIG_SND_SOC_IMG is not set
+# CONFIG_SND_SOC_IMX_AUDMIX is not set
+# CONFIG_SND_SOC_IMX_AUDMUX is not set
+# CONFIG_SND_SOC_IMX_CARD is not set
+# CONFIG_SND_SOC_IMX_ES8328 is not set
+# CONFIG_SND_SOC_IMX_HDMI is not set
+# CONFIG_SND_SOC_IMX_RPMSG is not set
+# CONFIG_SND_SOC_INNO_RK3036 is not set
+# CONFIG_SND_SOC_INTEL_AVS is not set
+# CONFIG_SND_SOC_INTEL_BDW_RT5677_MACH is not set
+# CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH is not set
+# CONFIG_SND_SOC_INTEL_BYTCR_RT5651_MACH is not set
+# CONFIG_SND_SOC_INTEL_BYT_CHT_DA7213_MACH is not set
+# CONFIG_SND_SOC_INTEL_BYT_CHT_ES8316_MACH is not set
+# CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH is not set
+# CONFIG_SND_SOC_INTEL_CATPT is not set
+# CONFIG_SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH is not set
+# CONFIG_SND_SOC_INTEL_CHT_BSW_NAU8824_MACH is not set
+# CONFIG_SND_SOC_INTEL_CHT_BSW_RT5645_MACH is not set
+# CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH is not set
+# CONFIG_SND_SOC_INTEL_HASWELL is not set
+# CONFIG_SND_SOC_INTEL_KEEMBAY is not set
+# CONFIG_SND_SOC_INTEL_SST is not set
+CONFIG_SND_SOC_INTEL_SST_TOPLEVEL=y
+# CONFIG_SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES is not set
+# CONFIG_SND_SOC_JZ4725B_CODEC is not set
+# CONFIG_SND_SOC_JZ4740_CODEC is not set
+# CONFIG_SND_SOC_JZ4770_CODEC is not set
+# CONFIG_SND_SOC_LOONGSON_CARD is not set
+# CONFIG_SND_SOC_LOONGSON_I2S_PCI is not set
+# CONFIG_SND_SOC_LPASS_RX_MACRO is not set
+# CONFIG_SND_SOC_LPASS_TX_MACRO is not set
+# CONFIG_SND_SOC_LPASS_VA_MACRO is not set
+# CONFIG_SND_SOC_LPASS_WSA_MACRO is not set
+# CONFIG_SND_SOC_MAX9759 is not set
+# CONFIG_SND_SOC_MAX98088 is not set
+# CONFIG_SND_SOC_MAX98090 is not set
+# CONFIG_SND_SOC_MAX98357A is not set
+# CONFIG_SND_SOC_MAX98373 is not set
+# CONFIG_SND_SOC_MAX98373_I2C is not set
+# CONFIG_SND_SOC_MAX98388 is not set
+# CONFIG_SND_SOC_MAX98390 is not set
+# CONFIG_SND_SOC_MAX98396 is not set
+# CONFIG_SND_SOC_MAX98504 is not set
+# CONFIG_SND_SOC_MAX98520 is not set
+# CONFIG_SND_SOC_MAX9860 is not set
+# CONFIG_SND_SOC_MAX9867 is not set
+# CONFIG_SND_SOC_MAX98927 is not set
+# CONFIG_SND_SOC_MEDIATEK is not set
+# CONFIG_SND_SOC_MPC5200_AC97 is not set
+# CONFIG_SND_SOC_MPC5200_I2S is not set
+# CONFIG_SND_SOC_MSM8916_WCD_ANALOG is not set
+# CONFIG_SND_SOC_MSM8916_WCD_DIGITAL is not set
+# CONFIG_SND_SOC_MT2701 is not set
+# CONFIG_SND_SOC_MT6351 is not set
+# CONFIG_SND_SOC_MT6357 is not set
+# CONFIG_SND_SOC_MT6358 is not set
+# CONFIG_SND_SOC_MT6359 is not set
+# CONFIG_SND_SOC_MT6359_ACCDET is not set
+# CONFIG_SND_SOC_MT6660 is not set
+# CONFIG_SND_SOC_MT6797 is not set
+# CONFIG_SND_SOC_MT8173 is not set
+# CONFIG_SND_SOC_MT8183 is not set
+# CONFIG_SND_SOC_MT8186 is not set
+# CONFIG_SND_SOC_MT8188 is not set
+# CONFIG_SND_SOC_MT8192 is not set
+# CONFIG_SND_SOC_MT8195 is not set
+# CONFIG_SND_SOC_MT8365 is not set
+# CONFIG_SND_SOC_MTK_BTCVSD is not set
+# CONFIG_SND_SOC_NAU8315 is not set
+# CONFIG_SND_SOC_NAU8325 is not set
+# CONFIG_SND_SOC_NAU8540 is not set
+# CONFIG_SND_SOC_NAU8810 is not set
+# CONFIG_SND_SOC_NAU8821 is not set
+# CONFIG_SND_SOC_NAU8822 is not set
+# CONFIG_SND_SOC_NAU8824 is not set
+# CONFIG_SND_SOC_PCM1681 is not set
+# CONFIG_SND_SOC_PCM1789_I2C is not set
+# CONFIG_SND_SOC_PCM179X_I2C is not set
+# CONFIG_SND_SOC_PCM179X_SPI is not set
+# CONFIG_SND_SOC_PCM186X_I2C is not set
+# CONFIG_SND_SOC_PCM186X_SPI is not set
+# CONFIG_SND_SOC_PCM3060_I2C is not set
+# CONFIG_SND_SOC_PCM3060_SPI is not set
+# CONFIG_SND_SOC_PCM3168A_I2C is not set
+# CONFIG_SND_SOC_PCM3168A_SPI is not set
+# CONFIG_SND_SOC_PCM5102A is not set
+# CONFIG_SND_SOC_PCM512x_I2C is not set
+# CONFIG_SND_SOC_PCM512x_SPI is not set
+# CONFIG_SND_SOC_PCM6240 is not set
+# CONFIG_SND_SOC_PEB2466 is not set
+# CONFIG_SND_SOC_QCOM is not set
+# CONFIG_SND_SOC_RK3308 is not set
+# CONFIG_SND_SOC_RK3328 is not set
+# CONFIG_SND_SOC_RK817 is not set
+# CONFIG_SND_SOC_ROCKCHIP is not set
+# CONFIG_SND_SOC_RT5616 is not set
+# CONFIG_SND_SOC_RT5631 is not set
+# CONFIG_SND_SOC_RT5640 is not set
+# CONFIG_SND_SOC_RT5659 is not set
+# CONFIG_SND_SOC_RT5677_SPI is not set
+# CONFIG_SND_SOC_RT9120 is not set
+# CONFIG_SND_SOC_RTQ9128 is not set
+# CONFIG_SND_SOC_SGTL5000 is not set
+# CONFIG_SND_SOC_SIMPLE_AMPLIFIER is not set
+# CONFIG_SND_SOC_SIMPLE_MUX is not set
+# CONFIG_SND_SOC_SMA1303 is not set
+# CONFIG_SND_SOC_SOF_TOPLEVEL is not set
+# CONFIG_SND_SOC_SPDIF is not set
+# CONFIG_SND_SOC_SRC4XXX_I2C is not set
+# CONFIG_SND_SOC_SSM2305 is not set
+# CONFIG_SND_SOC_SSM2518 is not set
+# CONFIG_SND_SOC_SSM2602_I2C is not set
+# CONFIG_SND_SOC_SSM2602_SPI is not set
+# CONFIG_SND_SOC_SSM3515 is not set
+# CONFIG_SND_SOC_SSM4567 is not set
+# CONFIG_SND_SOC_STA32X is not set
+# CONFIG_SND_SOC_STA350 is not set
+# CONFIG_SND_SOC_STI_SAS is not set
+# CONFIG_SND_SOC_TAS2552 is not set
+# CONFIG_SND_SOC_TAS2562 is not set
+# CONFIG_SND_SOC_TAS2764 is not set
+# CONFIG_SND_SOC_TAS2770 is not set
+# CONFIG_SND_SOC_TAS2780 is not set
+# CONFIG_SND_SOC_TAS2781_I2C is not set
+# CONFIG_SND_SOC_TAS5086 is not set
+# CONFIG_SND_SOC_TAS571X is not set
+# CONFIG_SND_SOC_TAS5720 is not set
+# CONFIG_SND_SOC_TAS5805M is not set
+# CONFIG_SND_SOC_TAS6424 is not set
+# CONFIG_SND_SOC_TDA7419 is not set
+# CONFIG_SND_SOC_TFA9879 is not set
+# CONFIG_SND_SOC_TFA989X is not set
+# CONFIG_SND_SOC_TLV320ADC3XXX is not set
+# CONFIG_SND_SOC_TLV320ADCX140 is not set
+# CONFIG_SND_SOC_TLV320AIC23_I2C is not set
+# CONFIG_SND_SOC_TLV320AIC23_SPI is not set
+# CONFIG_SND_SOC_TLV320AIC31XX is not set
+# CONFIG_SND_SOC_TLV320AIC32X4_I2C is not set
+# CONFIG_SND_SOC_TLV320AIC32X4_SPI is not set
+# CONFIG_SND_SOC_TLV320AIC3X is not set
+# CONFIG_SND_SOC_TLV320AIC3X_I2C is not set
+# CONFIG_SND_SOC_TLV320AIC3X_SPI is not set
+# CONFIG_SND_SOC_TPA6130A2 is not set
+# CONFIG_SND_SOC_TS3A227E is not set
+# CONFIG_SND_SOC_TSCS42XX is not set
+# CONFIG_SND_SOC_TSCS454 is not set
+# CONFIG_SND_SOC_UDA1334 is not set
+# CONFIG_SND_SOC_WM8510 is not set
+# CONFIG_SND_SOC_WM8523 is not set
+# CONFIG_SND_SOC_WM8524 is not set
+# CONFIG_SND_SOC_WM8580 is not set
+# CONFIG_SND_SOC_WM8711 is not set
+# CONFIG_SND_SOC_WM8728 is not set
+# CONFIG_SND_SOC_WM8731 is not set
+# CONFIG_SND_SOC_WM8731_I2C is not set
+# CONFIG_SND_SOC_WM8731_SPI is not set
+# CONFIG_SND_SOC_WM8737 is not set
+# CONFIG_SND_SOC_WM8741 is not set
+# CONFIG_SND_SOC_WM8750 is not set
+# CONFIG_SND_SOC_WM8753 is not set
+# CONFIG_SND_SOC_WM8770 is not set
+# CONFIG_SND_SOC_WM8776 is not set
+# CONFIG_SND_SOC_WM8782 is not set
+# CONFIG_SND_SOC_WM8804_I2C is not set
+# CONFIG_SND_SOC_WM8804_SPI is not set
+# CONFIG_SND_SOC_WM8903 is not set
+# CONFIG_SND_SOC_WM8904 is not set
+# CONFIG_SND_SOC_WM8940 is not set
+# CONFIG_SND_SOC_WM8960 is not set
+# CONFIG_SND_SOC_WM8961 is not set
+# CONFIG_SND_SOC_WM8962 is not set
+# CONFIG_SND_SOC_WM8974 is not set
+# CONFIG_SND_SOC_WM8978 is not set
+# CONFIG_SND_SOC_WM8985 is not set
+# CONFIG_SND_SOC_XILINX_AUDIO_FORMATTER is not set
+# CONFIG_SND_SOC_XILINX_I2S is not set
+# CONFIG_SND_SOC_XILINX_SPDIF is not set
+# CONFIG_SND_SOC_XTFPGA_I2S is not set
+# CONFIG_SND_SOC_ZL38060 is not set
+# CONFIG_SND_SONICVIBES is not set
+# CONFIG_SND_SPI is not set
+# CONFIG_SND_SSCAPE is not set
+# CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_ACPI is not set
+# CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_PCI is not set
+# CONFIG_SND_SUN4I_CODEC is not set
+# CONFIG_SND_SUPPORT_OLD_API is not set
+# CONFIG_SND_TEST_COMPONENT is not set
+# CONFIG_SND_TIMER is not set
+# CONFIG_SND_TRIDENT is not set
+CONFIG_SND_USB=y
+# CONFIG_SND_USB_6FIRE is not set
+# CONFIG_SND_USB_AUDIO is not set
+# CONFIG_SND_USB_AUDIO_MIDI_V2 is not set
+# CONFIG_SND_USB_CAIAQ is not set
+# CONFIG_SND_USB_HIFACE is not set
+# CONFIG_SND_USB_POD is not set
+# CONFIG_SND_USB_PODHD is not set
+# CONFIG_SND_USB_TONEPORT is not set
+# CONFIG_SND_USB_UA101 is not set
+# CONFIG_SND_USB_US122L is not set
+# CONFIG_SND_USB_USX2Y is not set
+# CONFIG_SND_USB_VARIAX is not set
+# CONFIG_SND_UTIMER is not set
+CONFIG_SND_VERBOSE_PROCFS=y
+# CONFIG_SND_VIA82XX is not set
+# CONFIG_SND_VIA82XX_MODEM is not set
+# CONFIG_SND_VIRTIO is not set
+# CONFIG_SND_VIRTUOSO is not set
+# CONFIG_SND_VX222 is not set
+# CONFIG_SND_VXPOCKET is not set
+# CONFIG_SND_WAVEFRONT is not set
+CONFIG_SND_X86=y
+# CONFIG_SND_XEN_FRONTEND is not set
+# CONFIG_SND_YMFPCI is not set
+# CONFIG_SNI_RM is not set
+# CONFIG_SOCIONEXT_SYNQUACER_PREITS is not set
+# CONFIG_SOCK_CGROUP_DATA is not set
+# CONFIG_SOC_AM33XX is not set
+# CONFIG_SOC_AM43XX is not set
+# CONFIG_SOC_BRCMSTB is not set
+# CONFIG_SOC_DRA7XX is not set
+# CONFIG_SOC_HAS_OMAP2_SDRC is not set
+# CONFIG_SOC_OMAP5 is not set
+# CONFIG_SOC_TI is not set
+# CONFIG_SOFTLOCKUP_DETECTOR is not set
+# CONFIG_SOFTLOCKUP_DETECTOR_INTR_STORM is not set
+# CONFIG_SOFT_WATCHDOG is not set
+# CONFIG_SOLARIS_X86_PARTITION is not set
+# CONFIG_SONYPI is not set
+# CONFIG_SONY_LAPTOP is not set
+# CONFIG_SOUND is not set
+# CONFIG_SOUNDWIRE is not set
+# CONFIG_SOUND_OSS_CORE is not set
+# CONFIG_SOUND_OSS_CORE_PRECLAIM is not set
+# CONFIG_SP5100_TCO is not set
+# CONFIG_SPARSEMEM_MANUAL is not set
+# CONFIG_SPARSEMEM_STATIC is not set
+# CONFIG_SPARSEMEM_VMEMMAP_ENABLE is not set
+# CONFIG_SPARSE_IRQ is not set
+# CONFIG_SPEAKUP is not set
+# CONFIG_SPI is not set
+# CONFIG_SPINLOCK_TEST is not set
+# CONFIG_SPI_ALTERA is not set
+# CONFIG_SPI_AMD is not set
+# CONFIG_SPI_AU1550 is not set
+# CONFIG_SPI_AX88796C is not set
+# CONFIG_SPI_AXI_SPI_ENGINE is not set
+# CONFIG_SPI_BCM2835 is not set
+# CONFIG_SPI_BCM63XX_HSSPI is not set
+# CONFIG_SPI_BCMBCA_HSSPI is not set
+# CONFIG_SPI_BCM_QSPI is not set
+# CONFIG_SPI_BITBANG is not set
+# CONFIG_SPI_BUTTERFLY is not set
+# CONFIG_SPI_CADENCE is not set
+# CONFIG_SPI_CADENCE_QUADSPI is not set
+# CONFIG_SPI_CADENCE_XSPI is not set
+# CONFIG_SPI_CH341 is not set
+# CONFIG_SPI_DEBUG is not set
+# CONFIG_SPI_DESIGNWARE is not set
+# CONFIG_SPI_DW_BT1 is not set
+# CONFIG_SPI_DW_BT1_DIRMAP is not set
+# CONFIG_SPI_DW_DMA is not set
+# CONFIG_SPI_DW_MMIO is not set
+# CONFIG_SPI_DW_PCI is not set
+# CONFIG_SPI_FSL_DSPI is not set
+# CONFIG_SPI_FSL_ESPI is not set
+# CONFIG_SPI_FSL_SPI is not set
+# CONFIG_SPI_GPIO is not set
+# CONFIG_SPI_IMG_SPFI is not set
+# CONFIG_SPI_LANTIQ_SSC is not set
+# CONFIG_SPI_LM70_LLP is not set
+# CONFIG_SPI_LOOPBACK_TEST is not set
+# CONFIG_SPI_MASTER is not set
+# CONFIG_SPI_MEM is not set
+# CONFIG_SPI_MICROCHIP_CORE is not set
+# CONFIG_SPI_MICROCHIP_CORE_QSPI is not set
+# CONFIG_SPI_MPC52xx is not set
+# CONFIG_SPI_MPC52xx_PSC is not set
+# CONFIG_SPI_MUX is not set
+# CONFIG_SPI_MXIC is not set
+# CONFIG_SPI_NXP_FLEXSPI is not set
+# CONFIG_SPI_OCTEON is not set
+# CONFIG_SPI_OC_TINY is not set
+# CONFIG_SPI_ORION is not set
+# CONFIG_SPI_PCI1XXXX is not set
+# CONFIG_SPI_PL022 is not set
+# CONFIG_SPI_PPC4xx is not set
+# CONFIG_SPI_PXA2XX is not set
+# CONFIG_SPI_PXA2XX_PCI is not set
+# CONFIG_SPI_QCOM_QSPI is not set
+# CONFIG_SPI_ROCKCHIP is not set
+# CONFIG_SPI_S3C64XX is not set
+# CONFIG_SPI_SC18IS602 is not set
+# CONFIG_SPI_SIFIVE is not set
+# CONFIG_SPI_SLAVE is not set
+# CONFIG_SPI_SN_F_OSPI is not set
+# CONFIG_SPI_SPIDEV is not set
+# CONFIG_SPI_THUNDERX is not set
+# CONFIG_SPI_TI_QSPI is not set
+# CONFIG_SPI_TLE62X0 is not set
+# CONFIG_SPI_TOPCLIFF_PCH is not set
+# CONFIG_SPI_XCOMM is not set
+# CONFIG_SPI_XILINX is not set
+# CONFIG_SPI_ZYNQMP_GQSPI is not set
+# CONFIG_SPMI is not set
+# CONFIG_SPS30 is not set
+# CONFIG_SPS30_I2C is not set
+# CONFIG_SPS30_SERIAL is not set
+CONFIG_SQUASHFS=y
+# CONFIG_SQUASHFS_4K_DEVBLK_SIZE is not set
+# CONFIG_SQUASHFS_CHOICE_DECOMP_BY_MOUNT is not set
+# CONFIG_SQUASHFS_COMPILE_DECOMP_MULTI is not set
+CONFIG_SQUASHFS_COMPILE_DECOMP_MULTI_PERCPU=y
+# CONFIG_SQUASHFS_COMPILE_DECOMP_SINGLE is not set
+CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU=y
+CONFIG_SQUASHFS_EMBEDDED=y
+# CONFIG_SQUASHFS_FILE_CACHE is not set
+CONFIG_SQUASHFS_FILE_DIRECT=y
+CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE=3
+# CONFIG_SQUASHFS_LZ4 is not set
+# CONFIG_SQUASHFS_LZO is not set
+# CONFIG_SQUASHFS_XATTR is not set
+CONFIG_SQUASHFS_XZ=y
+# CONFIG_SQUASHFS_ZLIB is not set
+# CONFIG_SQUASHFS_ZSTD is not set
+# CONFIG_SRAM is not set
+# CONFIG_SRF04 is not set
+# CONFIG_SRF08 is not set
+# CONFIG_SSB is not set
+# CONFIG_SSB_DRIVER_GPIO is not set
+# CONFIG_SSB_HOST_SOC is not set
+# CONFIG_SSB_PCMCIAHOST is not set
+CONFIG_SSB_POSSIBLE=y
+# CONFIG_SSB_SDIOHOST is not set
+# CONFIG_SSFDC is not set
+# CONFIG_SSIF_IPMI_BMC is not set
+CONFIG_STACKDEPOT_MAX_FRAMES=64
+# CONFIG_STACKPROTECTOR is not set
+# CONFIG_STACKPROTECTOR_PER_TASK is not set
+# CONFIG_STACKPROTECTOR_STRONG is not set
+# CONFIG_STACKTRACE is not set
+# CONFIG_STACKTRACE_BUILD_ID is not set
+CONFIG_STACKTRACE_SUPPORT=y
+# CONFIG_STACK_TRACER is not set
+# CONFIG_STACK_VALIDATION is not set
+CONFIG_STAGING=y
+# CONFIG_STAGING_MEDIA is not set
+CONFIG_STANDALONE=y
+# CONFIG_STATIC_KEYS_SELFTEST is not set
+# CONFIG_STATIC_USERMODEHELPER is not set
+# CONFIG_STE10XP is not set
+# CONFIG_STK3310 is not set
+# CONFIG_STK8312 is not set
+# CONFIG_STK8BA50 is not set
+# CONFIG_STM is not set
+# CONFIG_STMMAC_ETH is not set
+# CONFIG_STMMAC_PCI is not set
+# CONFIG_STMMAC_PLATFORM is not set
+# CONFIG_STMMAC_SELFTESTS is not set
+# CONFIG_STMPE_ADC is not set
+# CONFIG_STM_DUMMY is not set
+# CONFIG_STM_SOURCE_CONSOLE is not set
+CONFIG_STP=y
+# CONFIG_STREAM_PARSER is not set
+# CONFIG_STRICT_DEVMEM is not set
+CONFIG_STRICT_KERNEL_RWX=y
+CONFIG_STRICT_MODULE_RWX=y
+CONFIG_STRIP_ASM_SYMS=y
+# CONFIG_STX104 is not set
+# CONFIG_ST_UVIS25 is not set
+# CONFIG_SUN4I_GPADC is not set
+# CONFIG_SUN50I_DE2_BUS is not set
+# CONFIG_SUN50I_ERRATUM_UNKNOWN1 is not set
+# CONFIG_SUNDANCE is not set
+# CONFIG_SUNGEM is not set
+# CONFIG_SUNRPC is not set
+# CONFIG_SUNRPC_DEBUG is not set
+# CONFIG_SUNRPC_GSS is not set
+# CONFIG_SUNXI_SRAM is not set
+# CONFIG_SUN_PARTITION is not set
+# CONFIG_SURFACE_PLATFORMS is not set
+# CONFIG_SUSPEND is not set
+# CONFIG_SUSPEND_SKIP_SYNC is not set
+CONFIG_SWAP=y
+# CONFIG_SWCONFIG is not set
+# CONFIG_SWCONFIG_B53 is not set
+# CONFIG_SWCONFIG_B53_MMAP_DRIVER is not set
+# CONFIG_SWCONFIG_B53_SPI_DRIVER is not set
+# CONFIG_SWCONFIG_B53_SRAB_DRIVER is not set
+# CONFIG_SWCONFIG_LEDS is not set
+# CONFIG_SWIOTLB is not set
+# CONFIG_SWIOTLB_DYNAMIC is not set
+# CONFIG_SW_SYNC is not set
+# CONFIG_SX9310 is not set
+# CONFIG_SX9324 is not set
+# CONFIG_SX9360 is not set
+# CONFIG_SX9500 is not set
+# CONFIG_SXGBE_ETH is not set
+CONFIG_SYMBOLIC_ERRNAME=y
+# CONFIG_SYNC_FILE is not set
+# CONFIG_SYNTH_EVENTS is not set
+# CONFIG_SYNTH_EVENT_GEN_TEST is not set
+CONFIG_SYN_COOKIES=y
+# CONFIG_SYSCON_REBOOT_MODE is not set
+CONFIG_SYSCTL=y
+CONFIG_SYSFS=y
+# CONFIG_SYSFS_SYSCALL is not set
+# CONFIG_SYSTEMPORT is not set
+# CONFIG_SYSTEM_BLACKLIST_KEYRING is not set
+# CONFIG_SYSTEM_DATA_VERIFICATION is not set
+# CONFIG_SYSTEM_TRUSTED_KEYRING is not set
+CONFIG_SYSTEM_TRUSTED_KEYS=""
+# CONFIG_SYSV68_PARTITION is not set
+CONFIG_SYSVIPC=y
+CONFIG_SYSVIPC_SYSCTL=y
+# CONFIG_SYSV_FS is not set
+# CONFIG_SYS_HYPERVISOR is not set
+# CONFIG_T5403 is not set
+# CONFIG_TARGET_CORE is not set
+# CONFIG_TASKSTATS is not set
+# CONFIG_TASKS_RCU is not set
+# CONFIG_TASK_XACCT is not set
+# CONFIG_TC35815 is not set
+# CONFIG_TCG_ATMEL is not set
+# CONFIG_TCG_CRB is not set
+# CONFIG_TCG_FTPM_TEE is not set
+# CONFIG_TCG_INFINEON is not set
+# CONFIG_TCG_NSC is not set
+# CONFIG_TCG_TIS is not set
+# CONFIG_TCG_TIS_I2C is not set
+# CONFIG_TCG_TIS_I2C_ATMEL is not set
+# CONFIG_TCG_TIS_I2C_CR50 is not set
+# CONFIG_TCG_TIS_I2C_INFINEON is not set
+# CONFIG_TCG_TIS_I2C_NUVOTON is not set
+# CONFIG_TCG_TIS_SPI is not set
+# CONFIG_TCG_TIS_ST33ZP24_I2C is not set
+# CONFIG_TCG_TIS_ST33ZP24_SPI is not set
+# CONFIG_TCG_TPM is not set
+# CONFIG_TCG_TPM2_HMAC is not set
+# CONFIG_TCG_VTPM_PROXY is not set
+# CONFIG_TCG_XEN is not set
+# CONFIG_TCIC is not set
+# CONFIG_TCP_AO is not set
+CONFIG_TCP_CONG_ADVANCED=y
+# CONFIG_TCP_CONG_BBR is not set
+# CONFIG_TCP_CONG_BIC is not set
+# CONFIG_TCP_CONG_CDG is not set
+CONFIG_TCP_CONG_CUBIC=y
+# CONFIG_TCP_CONG_DCTCP is not set
+# CONFIG_TCP_CONG_HSTCP is not set
+# CONFIG_TCP_CONG_HTCP is not set
+# CONFIG_TCP_CONG_HYBLA is not set
+# CONFIG_TCP_CONG_ILLINOIS is not set
+# CONFIG_TCP_CONG_LP is not set
+# CONFIG_TCP_CONG_NV is not set
+# CONFIG_TCP_CONG_SCALABLE is not set
+# CONFIG_TCP_CONG_VEGAS is not set
+# CONFIG_TCP_CONG_VENO is not set
+# CONFIG_TCP_CONG_WESTWOOD is not set
+# CONFIG_TCP_CONG_YEAH is not set
+# CONFIG_TCP_MD5SIG is not set
+# CONFIG_TCS3414 is not set
+# CONFIG_TCS3472 is not set
+# CONFIG_TEE is not set
+# CONFIG_TEGRA_AHB is not set
+# CONFIG_TEGRA_HOST1X is not set
+# CONFIG_TEHUTI is not set
+# CONFIG_TEHUTI_TN40 is not set
+# CONFIG_TERANETICS_PHY is not set
+# CONFIG_TEST_ASYNC_DRIVER_PROBE is not set
+# CONFIG_TEST_BITMAP is not set
+# CONFIG_TEST_BITOPS is not set
+# CONFIG_TEST_BLACKHOLE_DEV is not set
+# CONFIG_TEST_BPF is not set
+# CONFIG_TEST_CLOCKSOURCE_WATCHDOG is not set
+# CONFIG_TEST_DEBUG_VIRTUAL is not set
+# CONFIG_TEST_DHRY is not set
+# CONFIG_TEST_DIV64 is not set
+# CONFIG_TEST_DYNAMIC_DEBUG is not set
+# CONFIG_TEST_FIRMWARE is not set
+# CONFIG_TEST_FPU is not set
+# CONFIG_TEST_FREE_PAGES is not set
+# CONFIG_TEST_HEXDUMP is not set
+# CONFIG_TEST_IDA is not set
+# CONFIG_TEST_KMOD is not set
+# CONFIG_TEST_KSTRTOX is not set
+# CONFIG_TEST_LIST_SORT is not set
+# CONFIG_TEST_LKM is not set
+# CONFIG_TEST_LOCKUP is not set
+# CONFIG_TEST_MAPLE_TREE is not set
+# CONFIG_TEST_MEMCAT_P is not set
+# CONFIG_TEST_MEMINIT is not set
+# CONFIG_TEST_MIN_HEAP is not set
+# CONFIG_TEST_MULDIV64 is not set
+# CONFIG_TEST_OBJPOOL is not set
+# CONFIG_TEST_POWER is not set
+# CONFIG_TEST_PRINTF is not set
+# CONFIG_TEST_REF_TRACKER is not set
+# CONFIG_TEST_RHASHTABLE is not set
+# CONFIG_TEST_SCANF is not set
+# CONFIG_TEST_SORT is not set
+# CONFIG_TEST_STATIC_KEYS is not set
+# CONFIG_TEST_SYSCTL is not set
+# CONFIG_TEST_UBSAN is not set
+# CONFIG_TEST_UDELAY is not set
+# CONFIG_TEST_UUID is not set
+# CONFIG_TEST_VMALLOC is not set
+# CONFIG_TEST_XARRAY is not set
+CONFIG_TEXTSEARCH=y
+# CONFIG_TEXTSEARCH_BM is not set
+# CONFIG_TEXTSEARCH_FSM is not set
+# CONFIG_TEXTSEARCH_KMP is not set
+# CONFIG_THERMAL is not set
+# CONFIG_THERMAL_CORE_TESTING is not set
+# CONFIG_THERMAL_DEBUGFS is not set
+# CONFIG_THERMAL_DEFAULT_GOV_BANG_BANG is not set
+# CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE is not set
+# CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR is not set
+# CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE is not set
+# CONFIG_THERMAL_EMULATION is not set
+# CONFIG_THERMAL_GOV_BANG_BANG is not set
+# CONFIG_THERMAL_GOV_FAIR_SHARE is not set
+# CONFIG_THERMAL_GOV_POWER_ALLOCATOR is not set
+# CONFIG_THERMAL_GOV_USER_SPACE is not set
+# CONFIG_THERMAL_HWMON is not set
+# CONFIG_THERMAL_MMIO is not set
+# CONFIG_THERMAL_NETLINK is not set
+# CONFIG_THERMAL_STATISTICS is not set
+# CONFIG_THINKPAD_ACPI is not set
+# CONFIG_THINKPAD_LMI is not set
+# CONFIG_THRUSTMASTER_FF is not set
+# CONFIG_THUMB2_KERNEL is not set
+# CONFIG_THUNDER_NIC_BGX is not set
+# CONFIG_THUNDER_NIC_PF is not set
+# CONFIG_THUNDER_NIC_RGX is not set
+# CONFIG_THUNDER_NIC_VF is not set
+# CONFIG_TICK_CPU_ACCOUNTING is not set
+CONFIG_TICK_ONESHOT=y
+# CONFIG_TIFM_CORE is not set
+# CONFIG_TIGON3 is not set
+# CONFIG_TIMB_DMA is not set
+CONFIG_TIMERFD=y
+# CONFIG_TIMERLAT_TRACER is not set
+# CONFIG_TIME_NS is not set
+# CONFIG_TINYDRM_HX8357D is not set
+# CONFIG_TINYDRM_ILI9163 is not set
+# CONFIG_TINYDRM_ILI9225 is not set
+# CONFIG_TINYDRM_ILI9341 is not set
+# CONFIG_TINYDRM_ILI9486 is not set
+# CONFIG_TINYDRM_MI0283QT is not set
+# CONFIG_TINYDRM_REPAPER is not set
+# CONFIG_TINYDRM_ST7586 is not set
+# CONFIG_TINYDRM_ST7735R is not set
+CONFIG_TINY_RCU=y
+# CONFIG_TIPC is not set
+# CONFIG_TI_ADC081C is not set
+# CONFIG_TI_ADC0832 is not set
+# CONFIG_TI_ADC084S021 is not set
+# CONFIG_TI_ADC108S102 is not set
+# CONFIG_TI_ADC12138 is not set
+# CONFIG_TI_ADC128S052 is not set
+# CONFIG_TI_ADC161S626 is not set
+# CONFIG_TI_ADS1015 is not set
+# CONFIG_TI_ADS1100 is not set
+# CONFIG_TI_ADS1119 is not set
+# CONFIG_TI_ADS124S08 is not set
+# CONFIG_TI_ADS1298 is not set
+# CONFIG_TI_ADS131E08 is not set
+# CONFIG_TI_ADS7924 is not set
+# CONFIG_TI_ADS7950 is not set
+# CONFIG_TI_ADS8344 is not set
+# CONFIG_TI_ADS8688 is not set
+# CONFIG_TI_AM335X_ADC is not set
+# CONFIG_TI_CPSW is not set
+# CONFIG_TI_CPSW_PHY_SEL is not set
+# CONFIG_TI_CPTS is not set
+# CONFIG_TI_DAC082S085 is not set
+# CONFIG_TI_DAC5571 is not set
+# CONFIG_TI_DAC7311 is not set
+# CONFIG_TI_DAC7612 is not set
+# CONFIG_TI_DAVINCI_MDIO is not set
+# CONFIG_TI_LMP92064 is not set
+# CONFIG_TI_ST is not set
+# CONFIG_TI_TLC4541 is not set
+# CONFIG_TI_TMAG5273 is not set
+# CONFIG_TI_TSC2046 is not set
+# CONFIG_TLAN is not set
+# CONFIG_TLS is not set
+# CONFIG_TLS_DEVICE is not set
+# CONFIG_TLS_TOE is not set
+# CONFIG_TMP006 is not set
+# CONFIG_TMP007 is not set
+# CONFIG_TMP117 is not set
+CONFIG_TMPFS=y
+# CONFIG_TMPFS_INODE64 is not set
+# CONFIG_TMPFS_POSIX_ACL is not set
+# CONFIG_TMPFS_QUOTA is not set
+CONFIG_TMPFS_XATTR=y
+# CONFIG_TOPSTAR_LAPTOP is not set
+# CONFIG_TORTURE_TEST is not set
+# CONFIG_TOSHIBA_HAPS is not set
+# CONFIG_TOSHIBA_WMI is not set
+# CONFIG_TOUCHSCREEN_88PM860X is not set
+# CONFIG_TOUCHSCREEN_AD7877 is not set
+# CONFIG_TOUCHSCREEN_AD7879 is not set
+# CONFIG_TOUCHSCREEN_AD7879_I2C is not set
+# CONFIG_TOUCHSCREEN_AD7879_SPI is not set
+# CONFIG_TOUCHSCREEN_ADC is not set
+# CONFIG_TOUCHSCREEN_ADS7846 is not set
+# CONFIG_TOUCHSCREEN_AR1021_I2C is not set
+# CONFIG_TOUCHSCREEN_ATMEL_MXT is not set
+# CONFIG_TOUCHSCREEN_ATMEL_MXT_T37 is not set
+# CONFIG_TOUCHSCREEN_AUO_PIXCIR is not set
+# CONFIG_TOUCHSCREEN_BU21013 is not set
+# CONFIG_TOUCHSCREEN_BU21029 is not set
+# CONFIG_TOUCHSCREEN_CHIPONE_ICN8318 is not set
+# CONFIG_TOUCHSCREEN_CHIPONE_ICN8505 is not set
+# CONFIG_TOUCHSCREEN_COLIBRI_VF50 is not set
+# CONFIG_TOUCHSCREEN_CY8CTMA140 is not set
+# CONFIG_TOUCHSCREEN_CY8CTMG110 is not set
+# CONFIG_TOUCHSCREEN_CYTTSP5 is not set
+# CONFIG_TOUCHSCREEN_CYTTSP_CORE is not set
+# CONFIG_TOUCHSCREEN_CYTTSP_I2C is not set
+# CONFIG_TOUCHSCREEN_CYTTSP_SPI is not set
+# CONFIG_TOUCHSCREEN_DA9034 is not set
+# CONFIG_TOUCHSCREEN_DA9052 is not set
+# CONFIG_TOUCHSCREEN_DYNAPRO is not set
+# CONFIG_TOUCHSCREEN_EDT_FT5X06 is not set
+# CONFIG_TOUCHSCREEN_EETI is not set
+# CONFIG_TOUCHSCREEN_EGALAX is not set
+# CONFIG_TOUCHSCREEN_EGALAX_SERIAL is not set
+# CONFIG_TOUCHSCREEN_EKTF2127 is not set
+# CONFIG_TOUCHSCREEN_ELAN is not set
+# CONFIG_TOUCHSCREEN_ELO is not set
+# CONFIG_TOUCHSCREEN_EXC3000 is not set
+# CONFIG_TOUCHSCREEN_FUJITSU is not set
+# CONFIG_TOUCHSCREEN_GOODIX is not set
+# CONFIG_TOUCHSCREEN_GOODIX_BERLIN_I2C is not set
+# CONFIG_TOUCHSCREEN_GOODIX_BERLIN_SPI is not set
+# CONFIG_TOUCHSCREEN_GUNZE is not set
+# CONFIG_TOUCHSCREEN_HAMPSHIRE is not set
+# CONFIG_TOUCHSCREEN_HIDEEP is not set
+# CONFIG_TOUCHSCREEN_HIMAX_HX83112B is not set
+# CONFIG_TOUCHSCREEN_HP600 is not set
+# CONFIG_TOUCHSCREEN_HP7XX is not set
+# CONFIG_TOUCHSCREEN_HTCPEN is not set
+# CONFIG_TOUCHSCREEN_HYCON_HY46XX is not set
+# CONFIG_TOUCHSCREEN_HYNITRON_CSTXXX is not set
+# CONFIG_TOUCHSCREEN_ILI210X is not set
+# CONFIG_TOUCHSCREEN_ILITEK is not set
+# CONFIG_TOUCHSCREEN_IMAGIS is not set
+# CONFIG_TOUCHSCREEN_IMX6UL_TSC is not set
+# CONFIG_TOUCHSCREEN_INEXIO is not set
+# CONFIG_TOUCHSCREEN_IPAQ_MICRO is not set
+# CONFIG_TOUCHSCREEN_IPROC is not set
+# CONFIG_TOUCHSCREEN_IQS5XX is not set
+# CONFIG_TOUCHSCREEN_IQS7211 is not set
+# CONFIG_TOUCHSCREEN_LPC32XX is not set
+# CONFIG_TOUCHSCREEN_MAX11801 is not set
+# CONFIG_TOUCHSCREEN_MC13783 is not set
+# CONFIG_TOUCHSCREEN_MELFAS_MIP4 is not set
+# CONFIG_TOUCHSCREEN_MIGOR is not set
+# CONFIG_TOUCHSCREEN_MK712 is not set
+# CONFIG_TOUCHSCREEN_MMS114 is not set
+# CONFIG_TOUCHSCREEN_MSG2638 is not set
+# CONFIG_TOUCHSCREEN_MTOUCH is not set
+# CONFIG_TOUCHSCREEN_MX25 is not set
+# CONFIG_TOUCHSCREEN_MXS_LRADC is not set
+# CONFIG_TOUCHSCREEN_NOVATEK_NVT_TS is not set
+# CONFIG_TOUCHSCREEN_PCAP is not set
+# CONFIG_TOUCHSCREEN_PENMOUNT is not set
+# CONFIG_TOUCHSCREEN_PIXCIR is not set
+# CONFIG_TOUCHSCREEN_RASPBERRYPI_FW is not set
+# CONFIG_TOUCHSCREEN_RM_TS is not set
+# CONFIG_TOUCHSCREEN_ROHM_BU21023 is not set
+# CONFIG_TOUCHSCREEN_S6SY761 is not set
+# CONFIG_TOUCHSCREEN_SILEAD is not set
+# CONFIG_TOUCHSCREEN_SIS_I2C is not set
+# CONFIG_TOUCHSCREEN_ST1232 is not set
+# CONFIG_TOUCHSCREEN_STMFTS is not set
+# CONFIG_TOUCHSCREEN_STMPE is not set
+# CONFIG_TOUCHSCREEN_SUN4I is not set
+# CONFIG_TOUCHSCREEN_SUR40 is not set
+# CONFIG_TOUCHSCREEN_SURFACE3_SPI is not set
+# CONFIG_TOUCHSCREEN_SX8654 is not set
+# CONFIG_TOUCHSCREEN_TI_AM335X_TSC is not set
+# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set
+# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set
+# CONFIG_TOUCHSCREEN_TOUCHWIN is not set
+# CONFIG_TOUCHSCREEN_TPS6507X is not set
+# CONFIG_TOUCHSCREEN_TS4800 is not set
+# CONFIG_TOUCHSCREEN_TSC2004 is not set
+# CONFIG_TOUCHSCREEN_TSC2005 is not set
+# CONFIG_TOUCHSCREEN_TSC2007 is not set
+# CONFIG_TOUCHSCREEN_TSC2007_IIO is not set
+# CONFIG_TOUCHSCREEN_TSC200X_CORE is not set
+# CONFIG_TOUCHSCREEN_TSC_SERIO is not set
+# CONFIG_TOUCHSCREEN_USB_3M is not set
+# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set
+# CONFIG_TOUCHSCREEN_USB_DMC_TSC10 is not set
+# CONFIG_TOUCHSCREEN_USB_E2I is not set
+# CONFIG_TOUCHSCREEN_USB_EASYTOUCH is not set
+# CONFIG_TOUCHSCREEN_USB_EGALAX is not set
+# CONFIG_TOUCHSCREEN_USB_ELO is not set
+# CONFIG_TOUCHSCREEN_USB_ETT_TC45USB is not set
+# CONFIG_TOUCHSCREEN_USB_ETURBO is not set
+# CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH is not set
+# CONFIG_TOUCHSCREEN_USB_GOTOP is not set
+# CONFIG_TOUCHSCREEN_USB_GUNZE is not set
+# CONFIG_TOUCHSCREEN_USB_IDEALTEK is not set
+# CONFIG_TOUCHSCREEN_USB_IRTOUCH is not set
+# CONFIG_TOUCHSCREEN_USB_ITM is not set
+# CONFIG_TOUCHSCREEN_USB_JASTEC is not set
+# CONFIG_TOUCHSCREEN_USB_NEXIO is not set
+# CONFIG_TOUCHSCREEN_USB_PANJIT is not set
+# CONFIG_TOUCHSCREEN_USB_ZYTRONIC is not set
+# CONFIG_TOUCHSCREEN_WACOM_I2C is not set
+# CONFIG_TOUCHSCREEN_WACOM_W8001 is not set
+# CONFIG_TOUCHSCREEN_WDT87XX_I2C is not set
+# CONFIG_TOUCHSCREEN_WM831X is not set
+# CONFIG_TOUCHSCREEN_WM9705 is not set
+# CONFIG_TOUCHSCREEN_WM9712 is not set
+# CONFIG_TOUCHSCREEN_WM9713 is not set
+# CONFIG_TOUCHSCREEN_WM97XX is not set
+# CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE is not set
+# CONFIG_TOUCHSCREEN_ZET6223 is not set
+# CONFIG_TOUCHSCREEN_ZFORCE is not set
+# CONFIG_TOUCHSCREEN_ZINITIX is not set
+# CONFIG_TPL0102 is not set
+# CONFIG_TPS6105X is not set
+# CONFIG_TPS65010 is not set
+# CONFIG_TPS6507X is not set
+# CONFIG_TRACEPOINT_BENCHMARK is not set
+# CONFIG_TRACER_SNAPSHOT is not set
+# CONFIG_TRACER_SNAPSHOT_PER_CPU_SWAP is not set
+# CONFIG_TRACE_BRANCH_PROFILING is not set
+# CONFIG_TRACE_EVAL_MAP_FILE is not set
+# CONFIG_TRACE_EVENT_INJECT is not set
+CONFIG_TRACE_IRQFLAGS_SUPPORT=y
+# CONFIG_TRACE_MMIO_ACCESS is not set
+CONFIG_TRACING_SUPPORT=y
+CONFIG_TRAD_SIGNALS=y
+# CONFIG_TRANSPARENT_HUGEPAGE is not set
+# CONFIG_TREE_RCU is not set
+# CONFIG_TRIM_UNUSED_KSYMS is not set
+# CONFIG_TRUSTED_FOUNDATIONS is not set
+# CONFIG_TRUSTED_KEYS is not set
+# CONFIG_TRUSTED_KEYS_CAAM is not set
+# CONFIG_TRUSTED_KEYS_DCP is not set
+# CONFIG_TRUSTED_KEYS_TEE is not set
+# CONFIG_TRUSTED_KEYS_TPM is not set
+# CONFIG_TSL2583 is not set
+# CONFIG_TSL2591 is not set
+# CONFIG_TSL2772 is not set
+# CONFIG_TSL4531 is not set
+# CONFIG_TSNEP is not set
+# CONFIG_TSYS01 is not set
+# CONFIG_TSYS02D is not set
+# CONFIG_TTPCI_EEPROM is not set
+CONFIG_TTY=y
+# CONFIG_TTY_PRINTK is not set
+# CONFIG_TUN is not set
+# CONFIG_TUN_VNET_CROSS_LE is not set
+# CONFIG_TWL4030_CORE is not set
+# CONFIG_TWL4030_MADC is not set
+# CONFIG_TWL6030_GPADC is not set
+# CONFIG_TWL6040_CORE is not set
+# CONFIG_TXGBE is not set
+# CONFIG_TYPEC is not set
+# CONFIG_TYPEC_DP_ALTMODE is not set
+# CONFIG_TYPEC_TCPM is not set
+# CONFIG_TYPEC_UCSI is not set
+# CONFIG_TYPHOON is not set
+# CONFIG_UACCE is not set
+# CONFIG_UACCESS_WITH_MEMCPY is not set
+# CONFIG_UBIFS_ATIME_SUPPORT is not set
+# CONFIG_UBIFS_FS_ADVANCED_COMPR is not set
+# CONFIG_UBIFS_FS_AUTHENTICATION is not set
+CONFIG_UBIFS_FS_LZO=y
+# CONFIG_UBIFS_FS_SECURITY is not set
+CONFIG_UBIFS_FS_XATTR=y
+CONFIG_UBIFS_FS_ZLIB=y
+CONFIG_UBIFS_FS_ZSTD=y
+# CONFIG_UBSAN is not set
+CONFIG_UBSAN_ALIGNMENT=y
+CONFIG_UBSAN_BOOL=y
+# CONFIG_UBSAN_DIV_ZERO is not set
+CONFIG_UBSAN_ENUM=y
+CONFIG_UBSAN_SHIFT=y
+# CONFIG_UBSAN_UNREACHABLE is not set
+# CONFIG_UDF_FS is not set
+# CONFIG_UDMABUF is not set
+CONFIG_UEVENT_HELPER=y
+CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug"
+# CONFIG_UFS_FS is not set
+# CONFIG_UHID is not set
+CONFIG_UID16=y
+# CONFIG_UIMAGE_FIT_BLK is not set
+# CONFIG_UIO is not set
+# CONFIG_ULTRA is not set
+# CONFIG_ULTRIX_PARTITION is not set
+# CONFIG_UNICODE is not set
+CONFIG_UNIX=y
+CONFIG_UNIX98_PTYS=y
+# CONFIG_UNIXWARE_DISKLABEL is not set
+# CONFIG_UNIX_DIAG is not set
+# CONFIG_UNWINDER_FRAME_POINTER is not set
+# CONFIG_UPROBES is not set
+# CONFIG_UPROBE_EVENTS is not set
+# CONFIG_US5182D is not set
+# CONFIG_USB is not set
+# CONFIG_USB4 is not set
+# CONFIG_USBIP_CORE is not set
+CONFIG_USBIP_VHCI_HC_PORTS=8
+CONFIG_USBIP_VHCI_NR_HCS=1
+# CONFIG_USBIP_VUDC is not set
+# CONFIG_USBPCWATCHDOG is not set
+# CONFIG_USB_ACM is not set
+# CONFIG_USB_ADUTUX is not set
+# CONFIG_USB_AIRSPY is not set
+CONFIG_USB_ALI_M5632=y
+# CONFIG_USB_AMD5536UDC is not set
+CONFIG_USB_AN2720=y
+# CONFIG_USB_ANNOUNCE_NEW_DEVICES is not set
+# CONFIG_USB_APPLEDISPLAY is not set
+CONFIG_USB_ARCH_HAS_HCD=y
+CONFIG_USB_ARMLINUX=y
+# CONFIG_USB_ATM is not set
+# CONFIG_USB_AUDIO is not set
+CONFIG_USB_AUTOSUSPEND_DELAY=2
+# CONFIG_USB_BDC_UDC is not set
+CONFIG_USB_BELKIN=y
+# CONFIG_USB_C67X00_HCD is not set
+# CONFIG_USB_CATC is not set
+# CONFIG_USB_CDC_COMPOSITE is not set
+# CONFIG_USB_CDNS3 is not set
+# CONFIG_USB_CDNS3_IMX is not set
+# CONFIG_USB_CDNS3_PCI_WRAP is not set
+# CONFIG_USB_CDNSP_PCI is not set
+# CONFIG_USB_CDNS_SUPPORT is not set
+# CONFIG_USB_CHAOSKEY is not set
+# CONFIG_USB_CHIPIDEA is not set
+# CONFIG_USB_CHIPIDEA_GENERIC is not set
+# CONFIG_USB_CHIPIDEA_IMX is not set
+# CONFIG_USB_CHIPIDEA_MSM is not set
+# CONFIG_USB_CHIPIDEA_NPCM is not set
+# CONFIG_USB_CHIPIDEA_PCI is not set
+# CONFIG_USB_CHIPIDEA_TEGRA is not set
+# CONFIG_USB_CONFIGFS is not set
+# CONFIG_USB_CONN_GPIO is not set
+# CONFIG_USB_CXACRU is not set
+# CONFIG_USB_CYPRESS_CY7C63 is not set
+# CONFIG_USB_CYTHERM is not set
+CONFIG_USB_DEFAULT_AUTHORIZATION_MODE=1
+CONFIG_USB_DEFAULT_PERSIST=y
+# CONFIG_USB_DSBR is not set
+# CONFIG_USB_DUMMY_HCD is not set
+# CONFIG_USB_DWC2 is not set
+# CONFIG_USB_DWC2_DEBUG is not set
+# CONFIG_USB_DWC2_DUAL_ROLE is not set
+# CONFIG_USB_DWC2_HOST is not set
+# CONFIG_USB_DWC2_PERIPHERAL is not set
+# CONFIG_USB_DWC2_TRACK_MISSED_SOFS is not set
+# CONFIG_USB_DWC3 is not set
+# CONFIG_USB_DWC3_EXYNOS is not set
+# CONFIG_USB_DWC3_HAPS is not set
+# CONFIG_USB_DWC3_KEYSTONE is not set
+# CONFIG_USB_DWC3_OCTEON is not set
+# CONFIG_USB_DWC3_OF_SIMPLE is not set
+# CONFIG_USB_DWC3_PCI is not set
+# CONFIG_USB_DWC3_QCOM is not set
+# CONFIG_USB_DWC3_ULPI is not set
+# CONFIG_USB_DYNAMIC_MINORS is not set
+# CONFIG_USB_EG20T is not set
+# CONFIG_USB_EHCI_FSL is not set
+# CONFIG_USB_EHCI_HCD is not set
+# CONFIG_USB_EHCI_HCD_AT91 is not set
+# CONFIG_USB_EHCI_HCD_OMAP is not set
+# CONFIG_USB_EHCI_HCD_PPC_OF is not set
+# CONFIG_USB_EHCI_MV is not set
+CONFIG_USB_EHCI_ROOT_HUB_TT=y
+CONFIG_USB_EHCI_TT_NEWSCHED=y
+# CONFIG_USB_EHSET_TEST_FIXTURE is not set
+# CONFIG_USB_EMI26 is not set
+# CONFIG_USB_EMI62 is not set
+# CONFIG_USB_EPSON2888 is not set
+# CONFIG_USB_ETH is not set
+# CONFIG_USB_EZUSB_FX2 is not set
+# CONFIG_USB_FEW_INIT_RETRIES is not set
+# CONFIG_USB_FOTG210_HCD is not set
+# CONFIG_USB_FOTG210_UDC is not set
+# CONFIG_USB_FSL_USB2 is not set
+# CONFIG_USB_FUNCTIONFS is not set
+# CONFIG_USB_FUSB300 is not set
+# CONFIG_USB_GADGET is not set
+# CONFIG_USB_GADGETFS is not set
+# CONFIG_USB_GADGET_DEBUG is not set
+# CONFIG_USB_GADGET_DEBUG_FILES is not set
+# CONFIG_USB_GADGET_DEBUG_FS is not set
+CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS=2
+CONFIG_USB_GADGET_VBUS_DRAW=2
+# CONFIG_USB_GADGET_XILINX is not set
+# CONFIG_USB_GL860 is not set
+# CONFIG_USB_GOKU is not set
+# CONFIG_USB_GPIO_VBUS is not set
+# CONFIG_USB_GR_UDC is not set
+# CONFIG_USB_GSPCA is not set
+# CONFIG_USB_GSPCA_BENQ is not set
+# CONFIG_USB_GSPCA_CONEX is not set
+# CONFIG_USB_GSPCA_CPIA1 is not set
+# CONFIG_USB_GSPCA_DTCS033 is not set
+# CONFIG_USB_GSPCA_ETOMS is not set
+# CONFIG_USB_GSPCA_FINEPIX is not set
+# CONFIG_USB_GSPCA_JEILINJ is not set
+# CONFIG_USB_GSPCA_JL2005BCD is not set
+# CONFIG_USB_GSPCA_KINECT is not set
+# CONFIG_USB_GSPCA_KONICA is not set
+# CONFIG_USB_GSPCA_MARS is not set
+# CONFIG_USB_GSPCA_MR97310A is not set
+# CONFIG_USB_GSPCA_NW80X is not set
+# CONFIG_USB_GSPCA_OV519 is not set
+# CONFIG_USB_GSPCA_OV534 is not set
+# CONFIG_USB_GSPCA_OV534_9 is not set
+# CONFIG_USB_GSPCA_PAC207 is not set
+# CONFIG_USB_GSPCA_PAC7302 is not set
+# CONFIG_USB_GSPCA_PAC7311 is not set
+# CONFIG_USB_GSPCA_SE401 is not set
+# CONFIG_USB_GSPCA_SN9C2028 is not set
+# CONFIG_USB_GSPCA_SN9C20X is not set
+# CONFIG_USB_GSPCA_SONIXB is not set
+# CONFIG_USB_GSPCA_SONIXJ is not set
+# CONFIG_USB_GSPCA_SPCA1528 is not set
+# CONFIG_USB_GSPCA_SPCA500 is not set
+# CONFIG_USB_GSPCA_SPCA501 is not set
+# CONFIG_USB_GSPCA_SPCA505 is not set
+# CONFIG_USB_GSPCA_SPCA506 is not set
+# CONFIG_USB_GSPCA_SPCA508 is not set
+# CONFIG_USB_GSPCA_SPCA561 is not set
+# CONFIG_USB_GSPCA_SQ905 is not set
+# CONFIG_USB_GSPCA_SQ905C is not set
+# CONFIG_USB_GSPCA_SQ930X is not set
+# CONFIG_USB_GSPCA_STK014 is not set
+# CONFIG_USB_GSPCA_STK1135 is not set
+# CONFIG_USB_GSPCA_STV0680 is not set
+# CONFIG_USB_GSPCA_SUNPLUS is not set
+# CONFIG_USB_GSPCA_T613 is not set
+# CONFIG_USB_GSPCA_TOPRO is not set
+# CONFIG_USB_GSPCA_TOUPTEK is not set
+# CONFIG_USB_GSPCA_TV8532 is not set
+# CONFIG_USB_GSPCA_VC032X is not set
+# CONFIG_USB_GSPCA_VICAM is not set
+# CONFIG_USB_GSPCA_XIRLINK_CIT is not set
+# CONFIG_USB_GSPCA_ZC3XX is not set
+# CONFIG_USB_G_ACM_MS is not set
+# CONFIG_USB_G_DBGP is not set
+# CONFIG_USB_G_HID is not set
+# CONFIG_USB_G_MULTI is not set
+# CONFIG_USB_G_NCM is not set
+# CONFIG_USB_G_NOKIA is not set
+# CONFIG_USB_G_PRINTER is not set
+# CONFIG_USB_G_SERIAL is not set
+# CONFIG_USB_G_WEBCAM is not set
+# CONFIG_USB_HACKRF is not set
+# CONFIG_USB_HCD_TEST_MODE is not set
+# CONFIG_USB_HID is not set
+# CONFIG_USB_HIDDEV is not set
+# CONFIG_USB_HSIC_USB3503 is not set
+# CONFIG_USB_HSIC_USB4604 is not set
+# CONFIG_USB_HSO is not set
+# CONFIG_USB_HUB_USB251XB is not set
+# CONFIG_USB_IDMOUSE is not set
+# CONFIG_USB_IOWARRIOR is not set
+# CONFIG_USB_IPHETH is not set
+# CONFIG_USB_ISIGHTFW is not set
+# CONFIG_USB_ISP116X_HCD is not set
+# CONFIG_USB_ISP1301 is not set
+# CONFIG_USB_ISP1362_HCD is not set
+# CONFIG_USB_ISP1760 is not set
+# CONFIG_USB_ISP1760_HCD is not set
+# CONFIG_USB_KAWETH is not set
+# CONFIG_USB_KBD is not set
+# CONFIG_USB_KC2190 is not set
+# CONFIG_USB_LAN78XX is not set
+# CONFIG_USB_LCD is not set
+# CONFIG_USB_LD is not set
+# CONFIG_USB_LEDS_TRIGGER_USBPORT is not set
+# CONFIG_USB_LED_TRIG is not set
+# CONFIG_USB_LEGOTOWER is not set
+# CONFIG_USB_LGM_PHY is not set
+# CONFIG_USB_LINK_LAYER_TEST is not set
+# CONFIG_USB_M5602 is not set
+# CONFIG_USB_M66592 is not set
+# CONFIG_USB_MASS_STORAGE is not set
+# CONFIG_USB_MAX3420_UDC is not set
+# CONFIG_USB_MAX3421_HCD is not set
+# CONFIG_USB_MDC800 is not set
+# CONFIG_USB_MICROTEK is not set
+# CONFIG_USB_MIDI_GADGET is not set
+# CONFIG_USB_MON is not set
+# CONFIG_USB_MOUSE is not set
+# CONFIG_USB_MSI2500 is not set
+# CONFIG_USB_MTU3 is not set
+# CONFIG_USB_MUSB_GADGET is not set
+# CONFIG_USB_MUSB_HDRC is not set
+# CONFIG_USB_MUSB_HOST is not set
+# CONFIG_USB_MV_U3D is not set
+# CONFIG_USB_MV_UDC is not set
+# CONFIG_USB_MXS_PHY is not set
+# CONFIG_USB_NET2272 is not set
+# CONFIG_USB_NET2280 is not set
+# CONFIG_USB_NET_AQC111 is not set
+# CONFIG_USB_NET_AX88179_178A is not set
+# CONFIG_USB_NET_AX8817X is not set
+# CONFIG_USB_NET_CDCETHER is not set
+# CONFIG_USB_NET_CDC_EEM is not set
+# CONFIG_USB_NET_CDC_MBIM is not set
+# CONFIG_USB_NET_CDC_NCM is not set
+# CONFIG_USB_NET_CDC_SUBSET is not set
+# CONFIG_USB_NET_CH9200 is not set
+# CONFIG_USB_NET_CX82310_ETH is not set
+# CONFIG_USB_NET_DM9601 is not set
+CONFIG_USB_NET_DRIVERS=y
+# CONFIG_USB_NET_GL620A is not set
+# CONFIG_USB_NET_HUAWEI_CDC_NCM is not set
+# CONFIG_USB_NET_INT51X1 is not set
+# CONFIG_USB_NET_KALMIA is not set
+# CONFIG_USB_NET_MCS7830 is not set
+# CONFIG_USB_NET_NET1080 is not set
+# CONFIG_USB_NET_PLUSB is not set
+# CONFIG_USB_NET_QMI_WWAN is not set
+# CONFIG_USB_NET_RNDIS_HOST is not set
+# CONFIG_USB_NET_SMSC75XX is not set
+# CONFIG_USB_NET_SMSC95XX is not set
+# CONFIG_USB_NET_SR9700 is not set
+# CONFIG_USB_NET_SR9800 is not set
+# CONFIG_USB_NET_ZAURUS is not set
+# CONFIG_USB_OHCI_HCD is not set
+# CONFIG_USB_OHCI_HCD_PCI is not set
+# CONFIG_USB_OHCI_HCD_PPC_OF is not set
+# CONFIG_USB_OHCI_HCD_PPC_OF_BE is not set
+# CONFIG_USB_OHCI_HCD_PPC_OF_LE is not set
+# CONFIG_USB_OHCI_HCD_SSB is not set
+CONFIG_USB_OHCI_LITTLE_ENDIAN=y
+# CONFIG_USB_ONBOARD_DEV is not set
+# CONFIG_USB_OTG is not set
+# CONFIG_USB_OTG_DISABLE_EXTERNAL_HUB is not set
+# CONFIG_USB_OTG_FSM is not set
+# CONFIG_USB_OTG_PRODUCTLIST is not set
+# CONFIG_USB_OXU210HP_HCD is not set
+# CONFIG_USB_PCI is not set
+# CONFIG_USB_PCI_AMD is not set
+# CONFIG_USB_PEGASUS is not set
+# CONFIG_USB_PHY is not set
+# CONFIG_USB_PRINTER is not set
+# CONFIG_USB_PWC_INPUT_EVDEV is not set
+# CONFIG_USB_PXA27X is not set
+# CONFIG_USB_QCOM_EUD is not set
+# CONFIG_USB_R8A66597 is not set
+# CONFIG_USB_R8A66597_HCD is not set
+# CONFIG_USB_RAW_GADGET is not set
+# CONFIG_USB_RENESAS_USBHS is not set
+# CONFIG_USB_ROLES_INTEL_XHCI is not set
+# CONFIG_USB_ROLE_SWITCH is not set
+# CONFIG_USB_RTL8150 is not set
+# CONFIG_USB_RTL8152 is not set
+# CONFIG_USB_RTL8153_ECM is not set
+# CONFIG_USB_S2255 is not set
+# CONFIG_USB_SERIAL is not set
+# CONFIG_USB_SERIAL_AIRCABLE is not set
+# CONFIG_USB_SERIAL_ARK3116 is not set
+# CONFIG_USB_SERIAL_BELKIN is not set
+# CONFIG_USB_SERIAL_CH341 is not set
+# CONFIG_USB_SERIAL_CH348 is not set
+# CONFIG_USB_SERIAL_CP210X is not set
+# CONFIG_USB_SERIAL_CYBERJACK is not set
+# CONFIG_USB_SERIAL_CYPRESS_M8 is not set
+# CONFIG_USB_SERIAL_DEBUG is not set
+# CONFIG_USB_SERIAL_DIGI_ACCELEPORT is not set
+# CONFIG_USB_SERIAL_EDGEPORT is not set
+# CONFIG_USB_SERIAL_EDGEPORT_TI is not set
+# CONFIG_USB_SERIAL_EMPEG is not set
+# CONFIG_USB_SERIAL_F81232 is not set
+# CONFIG_USB_SERIAL_F8153X is not set
+# CONFIG_USB_SERIAL_FTDI_SIO is not set
+# CONFIG_USB_SERIAL_GARMIN is not set
+CONFIG_USB_SERIAL_GENERIC=y
+# CONFIG_USB_SERIAL_IPAQ is not set
+# CONFIG_USB_SERIAL_IPW is not set
+# CONFIG_USB_SERIAL_IR is not set
+# CONFIG_USB_SERIAL_IUU is not set
+# CONFIG_USB_SERIAL_KEYSPAN is not set
+# CONFIG_USB_SERIAL_KEYSPAN_PDA is not set
+# CONFIG_USB_SERIAL_KLSI is not set
+# CONFIG_USB_SERIAL_KOBIL_SCT is not set
+# CONFIG_USB_SERIAL_MCT_U232 is not set
+# CONFIG_USB_SERIAL_METRO is not set
+# CONFIG_USB_SERIAL_MOS7715_PARPORT is not set
+# CONFIG_USB_SERIAL_MOS7720 is not set
+# CONFIG_USB_SERIAL_MOS7840 is not set
+# CONFIG_USB_SERIAL_MXUPORT is not set
+# CONFIG_USB_SERIAL_NAVMAN is not set
+# CONFIG_USB_SERIAL_OMNINET is not set
+# CONFIG_USB_SERIAL_OPTICON is not set
+# CONFIG_USB_SERIAL_OPTION is not set
+# CONFIG_USB_SERIAL_OTI6858 is not set
+# CONFIG_USB_SERIAL_PL2303 is not set
+# CONFIG_USB_SERIAL_QCAUX is not set
+# CONFIG_USB_SERIAL_QT2 is not set
+# CONFIG_USB_SERIAL_QUALCOMM is not set
+# CONFIG_USB_SERIAL_SAFE is not set
+CONFIG_USB_SERIAL_SAFE_PADDED=y
+# CONFIG_USB_SERIAL_SIERRAWIRELESS is not set
+# CONFIG_USB_SERIAL_SIMPLE is not set
+# CONFIG_USB_SERIAL_SPCP8X5 is not set
+# CONFIG_USB_SERIAL_SSU100 is not set
+# CONFIG_USB_SERIAL_SYMBOL is not set
+# CONFIG_USB_SERIAL_TI is not set
+# CONFIG_USB_SERIAL_UPD78F0730 is not set
+# CONFIG_USB_SERIAL_VISOR is not set
+# CONFIG_USB_SERIAL_WHITEHEAT is not set
+# CONFIG_USB_SERIAL_WISHBONE is not set
+# CONFIG_USB_SERIAL_XR is not set
+# CONFIG_USB_SERIAL_XSENS_MT is not set
+# CONFIG_USB_SEVSEG is not set
+# CONFIG_USB_SIERRA_NET is not set
+# CONFIG_USB_SISUSBVGA is not set
+# CONFIG_USB_SL811_HCD is not set
+# CONFIG_USB_SNP_UDC_PLAT is not set
+# CONFIG_USB_SPEEDTOUCH is not set
+# CONFIG_USB_STORAGE is not set
+# CONFIG_USB_STORAGE_ALAUDA is not set
+# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set
+# CONFIG_USB_STORAGE_DATAFAB is not set
+# CONFIG_USB_STORAGE_DEBUG is not set
+# CONFIG_USB_STORAGE_ENE_UB6250 is not set
+# CONFIG_USB_STORAGE_FREECOM is not set
+# CONFIG_USB_STORAGE_ISD200 is not set
+# CONFIG_USB_STORAGE_JUMPSHOT is not set
+# CONFIG_USB_STORAGE_KARMA is not set
+# CONFIG_USB_STORAGE_ONETOUCH is not set
+# CONFIG_USB_STORAGE_REALTEK is not set
+# CONFIG_USB_STORAGE_SDDR09 is not set
+# CONFIG_USB_STORAGE_SDDR55 is not set
+# CONFIG_USB_STORAGE_USBAT is not set
+# CONFIG_USB_STV06XX is not set
+# CONFIG_USB_SUPPORT is not set
+# CONFIG_USB_TEST is not set
+# CONFIG_USB_TMC is not set
+# CONFIG_USB_TRANCEVIBRATOR is not set
+# CONFIG_USB_UAS is not set
+# CONFIG_USB_UEAGLEATM is not set
+# CONFIG_USB_ULPI is not set
+# CONFIG_USB_ULPI_BUS is not set
+# CONFIG_USB_USBNET is not set
+# CONFIG_USB_USS720 is not set
+# CONFIG_USB_VIDEO_CLASS is not set
+CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV=y
+# CONFIG_USB_VL600 is not set
+# CONFIG_USB_WDM is not set
+# CONFIG_USB_XEN_HCD is not set
+# CONFIG_USB_XHCI_DBGCAP is not set
+# CONFIG_USB_XHCI_HCD is not set
+# CONFIG_USB_XHCI_MVEBU is not set
+# CONFIG_USB_XHCI_PCI_RENESAS is not set
+# CONFIG_USB_XUSBATM is not set
+# CONFIG_USB_YUREX is not set
+# CONFIG_USB_ZERO is not set
+# CONFIG_USELIB is not set
+# CONFIG_USERFAULTFD is not set
+# CONFIG_USERIO is not set
+# CONFIG_USER_DECRYPTED_DATA is not set
+# CONFIG_USER_EVENTS is not set
+# CONFIG_USE_OF is not set
+# CONFIG_UTS_NS is not set
+# CONFIG_U_SERIAL_CONSOLE is not set
+# CONFIG_V4L_MEM2MEM_DRIVERS is not set
+# CONFIG_V4L_PLATFORM_DRIVERS is not set
+# CONFIG_V4L_TEST_DRIVERS is not set
+# CONFIG_VALIDATE_FS_PARSER is not set
+# CONFIG_VBOXGUEST is not set
+# CONFIG_VCAP is not set
+# CONFIG_VCNL3020 is not set
+# CONFIG_VCNL4000 is not set
+# CONFIG_VCNL4035 is not set
+# CONFIG_VCPU_STALL_DETECTOR is not set
+# CONFIG_VDPA is not set
+CONFIG_VDSO=y
+# CONFIG_VEML6030 is not set
+# CONFIG_VEML6040 is not set
+# CONFIG_VEML6070 is not set
+# CONFIG_VEML6075 is not set
+# CONFIG_VETH is not set
+# CONFIG_VEXPRESS_CONFIG is not set
+# CONFIG_VF610_ADC is not set
+# CONFIG_VF610_DAC is not set
+# CONFIG_VFAT_FS is not set
+# CONFIG_VFIO is not set
+# CONFIG_VFIO_DEBUGFS is not set
+# CONFIG_VFIO_FSL_MC is not set
+# CONFIG_VFIO_PLATFORM is not set
+# CONFIG_VGASTATE is not set
+# CONFIG_VGA_ARB is not set
+# CONFIG_VGA_CONSOLE is not set
+# CONFIG_VGA_SWITCHEROO is not set
+# CONFIG_VHOST_CROSS_ENDIAN_LEGACY is not set
+CONFIG_VHOST_ENABLE_FORK_OWNER_CONTROL=y
+CONFIG_VHOST_MENU=y
+# CONFIG_VHOST_NET is not set
+# CONFIG_VHOST_VSOCK is not set
+# CONFIG_VIA_RHINE is not set
+# CONFIG_VIA_VELOCITY is not set
+# CONFIG_VIDEO_AD5820 is not set
+# CONFIG_VIDEO_ADP1653 is not set
+# CONFIG_VIDEO_ADV7170 is not set
+# CONFIG_VIDEO_ADV7175 is not set
+# CONFIG_VIDEO_ADV7180 is not set
+# CONFIG_VIDEO_ADV7183 is not set
+# CONFIG_VIDEO_ADV7343 is not set
+# CONFIG_VIDEO_ADV7393 is not set
+# CONFIG_VIDEO_ADV748X is not set
+# CONFIG_VIDEO_ADV7511 is not set
+# CONFIG_VIDEO_ADV7604 is not set
+# CONFIG_VIDEO_ADV7842 is not set
+# CONFIG_VIDEO_ADV_DEBUG is not set
+# CONFIG_VIDEO_AK7375 is not set
+# CONFIG_VIDEO_AK881X is not set
+# CONFIG_VIDEO_ALVIUM_CSI2 is not set
+# CONFIG_VIDEO_AM437X_VPFE is not set
+# CONFIG_VIDEO_AR0521 is not set
+# CONFIG_VIDEO_ASPEED is not set
+# CONFIG_VIDEO_ATMEL_ISC is not set
+# CONFIG_VIDEO_ATMEL_ISI is not set
+# CONFIG_VIDEO_AU0828 is not set
+# CONFIG_VIDEO_BCM2835 is not set
+# CONFIG_VIDEO_BCM2835_UNICAM is not set
+# CONFIG_VIDEO_BT819 is not set
+# CONFIG_VIDEO_BT848 is not set
+# CONFIG_VIDEO_BT856 is not set
+# CONFIG_VIDEO_BT866 is not set
+# CONFIG_VIDEO_CADENCE_CSI2RX is not set
+# CONFIG_VIDEO_CADENCE_CSI2TX is not set
+# CONFIG_VIDEO_CAFE_CCIC is not set
+# CONFIG_VIDEO_CAMERA_SENSOR is not set
+# CONFIG_VIDEO_CCS is not set
+# CONFIG_VIDEO_COBALT is not set
+# CONFIG_VIDEO_CODA is not set
+# CONFIG_VIDEO_CS3308 is not set
+# CONFIG_VIDEO_CS5345 is not set
+# CONFIG_VIDEO_CS53L32A is not set
+# CONFIG_VIDEO_CX231XX is not set
+# CONFIG_VIDEO_CX2341X is not set
+# CONFIG_VIDEO_CX25821 is not set
+# CONFIG_VIDEO_CX25840 is not set
+# CONFIG_VIDEO_CX88 is not set
+# CONFIG_VIDEO_DEV is not set
+# CONFIG_VIDEO_DS90UB913 is not set
+# CONFIG_VIDEO_DS90UB953 is not set
+# CONFIG_VIDEO_DS90UB960 is not set
+# CONFIG_VIDEO_DT3155 is not set
+# CONFIG_VIDEO_DW9714 is not set
+# CONFIG_VIDEO_DW9719 is not set
+# CONFIG_VIDEO_DW9768 is not set
+# CONFIG_VIDEO_DW9807_VCM is not set
+# CONFIG_VIDEO_EM28XX is not set
+# CONFIG_VIDEO_ET8EK8 is not set
+# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set
+# CONFIG_VIDEO_GC0308 is not set
+# CONFIG_VIDEO_GC05A2 is not set
+# CONFIG_VIDEO_GC08A3 is not set
+# CONFIG_VIDEO_GC2145 is not set
+# CONFIG_VIDEO_GO7007 is not set
+# CONFIG_VIDEO_GS1662 is not set
+# CONFIG_VIDEO_HDPVR is not set
+# CONFIG_VIDEO_HEXIUM_GEMINI is not set
+# CONFIG_VIDEO_HEXIUM_ORION is not set
+# CONFIG_VIDEO_HI556 is not set
+# CONFIG_VIDEO_HI846 is not set
+# CONFIG_VIDEO_HI847 is not set
+# CONFIG_VIDEO_I2C is not set
+# CONFIG_VIDEO_IMX208 is not set
+# CONFIG_VIDEO_IMX214 is not set
+# CONFIG_VIDEO_IMX219 is not set
+# CONFIG_VIDEO_IMX258 is not set
+# CONFIG_VIDEO_IMX274 is not set
+# CONFIG_VIDEO_IMX283 is not set
+# CONFIG_VIDEO_IMX290 is not set
+# CONFIG_VIDEO_IMX296 is not set
+# CONFIG_VIDEO_IMX319 is not set
+# CONFIG_VIDEO_IMX334 is not set
+# CONFIG_VIDEO_IMX335 is not set
+# CONFIG_VIDEO_IMX355 is not set
+# CONFIG_VIDEO_IMX412 is not set
+# CONFIG_VIDEO_IMX415 is not set
+# CONFIG_VIDEO_IMX500 is not set
+# CONFIG_VIDEO_IMX7_CSI is not set
+# CONFIG_VIDEO_IMX8MQ_MIPI_CSI2 is not set
+# CONFIG_VIDEO_IMX8_ISI is not set
+# CONFIG_VIDEO_IMX8_JPEG is not set
+# CONFIG_VIDEO_IMX_MIPI_CSIS is not set
+# CONFIG_VIDEO_IMX_PXP is not set
+# CONFIG_VIDEO_INTEL_IPU6 is not set
+# CONFIG_VIDEO_IPU3_CIO2 is not set
+# CONFIG_VIDEO_IR_I2C is not set
+# CONFIG_VIDEO_ISL7998X is not set
+# CONFIG_VIDEO_IVTV is not set
+# CONFIG_VIDEO_KS0127 is not set
+# CONFIG_VIDEO_LM3560 is not set
+# CONFIG_VIDEO_LM3646 is not set
+# CONFIG_VIDEO_M52790 is not set
+# CONFIG_VIDEO_MAX9286 is not set
+# CONFIG_VIDEO_MAX96714 is not set
+# CONFIG_VIDEO_MAX96717 is not set
+# CONFIG_VIDEO_MEM2MEM_DEINTERLACE is not set
+# CONFIG_VIDEO_MGB4 is not set
+# CONFIG_VIDEO_ML86V7667 is not set
+# CONFIG_VIDEO_MSP3400 is not set
+# CONFIG_VIDEO_MT9M001 is not set
+# CONFIG_VIDEO_MT9M111 is not set
+# CONFIG_VIDEO_MT9M114 is not set
+# CONFIG_VIDEO_MT9P031 is not set
+# CONFIG_VIDEO_MT9T112 is not set
+# CONFIG_VIDEO_MT9V011 is not set
+# CONFIG_VIDEO_MT9V032 is not set
+# CONFIG_VIDEO_MT9V111 is not set
+# CONFIG_VIDEO_MUX is not set
+# CONFIG_VIDEO_MXB is not set
+# CONFIG_VIDEO_OG01A1B is not set
+# CONFIG_VIDEO_OMAP2_VOUT is not set
+# CONFIG_VIDEO_OV01A10 is not set
+# CONFIG_VIDEO_OV02A10 is not set
+# CONFIG_VIDEO_OV08D10 is not set
+# CONFIG_VIDEO_OV08X40 is not set
+# CONFIG_VIDEO_OV13858 is not set
+# CONFIG_VIDEO_OV13B10 is not set
+# CONFIG_VIDEO_OV2311 is not set
+# CONFIG_VIDEO_OV2640 is not set
+# CONFIG_VIDEO_OV2659 is not set
+# CONFIG_VIDEO_OV2680 is not set
+# CONFIG_VIDEO_OV2685 is not set
+# CONFIG_VIDEO_OV2740 is not set
+# CONFIG_VIDEO_OV4689 is not set
+# CONFIG_VIDEO_OV5640 is not set
+# CONFIG_VIDEO_OV5645 is not set
+# CONFIG_VIDEO_OV5647 is not set
+# CONFIG_VIDEO_OV5648 is not set
+# CONFIG_VIDEO_OV5670 is not set
+# CONFIG_VIDEO_OV5675 is not set
+# CONFIG_VIDEO_OV5693 is not set
+# CONFIG_VIDEO_OV5695 is not set
+# CONFIG_VIDEO_OV64A40 is not set
+# CONFIG_VIDEO_OV6650 is not set
+# CONFIG_VIDEO_OV7251 is not set
+# CONFIG_VIDEO_OV7640 is not set
+# CONFIG_VIDEO_OV7670 is not set
+# CONFIG_VIDEO_OV772X is not set
+# CONFIG_VIDEO_OV7740 is not set
+# CONFIG_VIDEO_OV8856 is not set
+# CONFIG_VIDEO_OV8858 is not set
+# CONFIG_VIDEO_OV8865 is not set
+# CONFIG_VIDEO_OV9282 is not set
+# CONFIG_VIDEO_OV9640 is not set
+# CONFIG_VIDEO_OV9650 is not set
+# CONFIG_VIDEO_OV9734 is not set
+# CONFIG_VIDEO_PVRUSB2 is not set
+# CONFIG_VIDEO_RASPBERRYPI_PISP_BE is not set
+# CONFIG_VIDEO_RCAR_CSI2 is not set
+# CONFIG_VIDEO_RCAR_ISP is not set
+# CONFIG_VIDEO_RCAR_VIN is not set
+# CONFIG_VIDEO_RDACM20 is not set
+# CONFIG_VIDEO_RDACM21 is not set
+# CONFIG_VIDEO_RJ54N1 is not set
+# CONFIG_VIDEO_ROCKCHIP_ISP1 is not set
+# CONFIG_VIDEO_S5C73M3 is not set
+# CONFIG_VIDEO_S5K5BAF is not set
+# CONFIG_VIDEO_S5K6A3 is not set
+# CONFIG_VIDEO_SAA6588 is not set
+# CONFIG_VIDEO_SAA6752HS is not set
+# CONFIG_VIDEO_SAA7110 is not set
+# CONFIG_VIDEO_SAA711X is not set
+# CONFIG_VIDEO_SAA7127 is not set
+# CONFIG_VIDEO_SAA7134 is not set
+# CONFIG_VIDEO_SAA7164 is not set
+# CONFIG_VIDEO_SAA717X is not set
+# CONFIG_VIDEO_SAA7185 is not set
+# CONFIG_VIDEO_SOLO6X10 is not set
+# CONFIG_VIDEO_SONY_BTF_MPX is not set
+# CONFIG_VIDEO_STK1160 is not set
+# CONFIG_VIDEO_ST_MIPID02 is not set
+# CONFIG_VIDEO_SUN4I_CSI is not set
+# CONFIG_VIDEO_SUN6I_CSI is not set
+# CONFIG_VIDEO_SUN8I_A83T_MIPI_CSI2 is not set
+# CONFIG_VIDEO_TC358743 is not set
+# CONFIG_VIDEO_TC358746 is not set
+# CONFIG_VIDEO_TDA1997X is not set
+# CONFIG_VIDEO_TDA7432 is not set
+# CONFIG_VIDEO_TDA9840 is not set
+# CONFIG_VIDEO_TEA6415C is not set
+# CONFIG_VIDEO_TEA6420 is not set
+# CONFIG_VIDEO_THP7312 is not set
+# CONFIG_VIDEO_THS7303 is not set
+# CONFIG_VIDEO_THS8200 is not set
+# CONFIG_VIDEO_TLV320AIC23B is not set
+# CONFIG_VIDEO_TVAUDIO is not set
+# CONFIG_VIDEO_TVP514X is not set
+# CONFIG_VIDEO_TVP5150 is not set
+# CONFIG_VIDEO_TVP7002 is not set
+# CONFIG_VIDEO_TW2804 is not set
+# CONFIG_VIDEO_TW5864 is not set
+# CONFIG_VIDEO_TW68 is not set
+# CONFIG_VIDEO_TW9900 is not set
+# CONFIG_VIDEO_TW9903 is not set
+# CONFIG_VIDEO_TW9906 is not set
+# CONFIG_VIDEO_TW9910 is not set
+# CONFIG_VIDEO_UDA1342 is not set
+# CONFIG_VIDEO_UPD64031A is not set
+# CONFIG_VIDEO_UPD64083 is not set
+# CONFIG_VIDEO_USBTV is not set
+# CONFIG_VIDEO_VGXY61 is not set
+# CONFIG_VIDEO_VP27SMPX is not set
+# CONFIG_VIDEO_VPX3220 is not set
+# CONFIG_VIDEO_WM8739 is not set
+# CONFIG_VIDEO_WM8775 is not set
+# CONFIG_VIDEO_XILINX is not set
+# CONFIG_VIDEO_ZORAN is not set
+# CONFIG_VIRTIO_BALLOON is not set
+# CONFIG_VIRTIO_CONSOLE is not set
+# CONFIG_VIRTIO_FS is not set
+# CONFIG_VIRTIO_INPUT is not set
+CONFIG_VIRTIO_MENU=y
+# CONFIG_VIRTIO_MMIO is not set
+# CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES is not set
+# CONFIG_VIRTIO_PCI is not set
+# CONFIG_VIRTIO_VFIO_PCI is not set
+# CONFIG_VIRTUALIZATION is not set
+# CONFIG_VIRT_CPU_ACCOUNTING_GEN is not set
+# CONFIG_VIRT_DRIVERS is not set
+# CONFIG_VITESSE_PHY is not set
+# CONFIG_VL53L0X_I2C is not set
+# CONFIG_VL6180 is not set
+CONFIG_VLAN_8021Q=y
+# CONFIG_VLAN_8021Q_GVRP is not set
+# CONFIG_VLAN_8021Q_MVRP is not set
+# CONFIG_VMAP_STACK is not set
+# CONFIG_VME_BUS is not set
+# CONFIG_VMLINUX_MAP is not set
+# CONFIG_VMSPLIT_1G is not set
+# CONFIG_VMSPLIT_2G is not set
+# CONFIG_VMSPLIT_2G_OPT is not set
+CONFIG_VMSPLIT_3G=y
+# CONFIG_VMSPLIT_3G_OPT is not set
+# CONFIG_VMWARE_PVSCSI is not set
+# CONFIG_VMWARE_VMCI is not set
+# CONFIG_VMXNET3 is not set
+# CONFIG_VM_EVENT_COUNTERS is not set
+# CONFIG_VORTEX is not set
+# CONFIG_VSOCKETS is not set
+# CONFIG_VSOCKETS_DIAG is not set
+# CONFIG_VT is not set
+# CONFIG_VT6655 is not set
+# CONFIG_VT6656 is not set
+# CONFIG_VXFS_FS is not set
+# CONFIG_VXLAN is not set
+# CONFIG_VZ89X is not set
+# CONFIG_W1 is not set
+# CONFIG_W1_CON is not set
+# CONFIG_W1_MASTER_AMD_AXI is not set
+# CONFIG_W1_MASTER_DS2482 is not set
+# CONFIG_W1_MASTER_DS2490 is not set
+# CONFIG_W1_MASTER_GPIO is not set
+# CONFIG_W1_MASTER_MATROX is not set
+# CONFIG_W1_MASTER_SGI is not set
+# CONFIG_W1_MASTER_UART is not set
+# CONFIG_W1_SLAVE_DS2405 is not set
+# CONFIG_W1_SLAVE_DS2406 is not set
+# CONFIG_W1_SLAVE_DS2408 is not set
+# CONFIG_W1_SLAVE_DS2413 is not set
+# CONFIG_W1_SLAVE_DS2423 is not set
+# CONFIG_W1_SLAVE_DS2430 is not set
+# CONFIG_W1_SLAVE_DS2431 is not set
+# CONFIG_W1_SLAVE_DS2433 is not set
+# CONFIG_W1_SLAVE_DS2438 is not set
+# CONFIG_W1_SLAVE_DS250X is not set
+# CONFIG_W1_SLAVE_DS2780 is not set
+# CONFIG_W1_SLAVE_DS2781 is not set
+# CONFIG_W1_SLAVE_DS2805 is not set
+# CONFIG_W1_SLAVE_DS28E04 is not set
+# CONFIG_W1_SLAVE_DS28E17 is not set
+# CONFIG_W1_SLAVE_SMEM is not set
+# CONFIG_W1_SLAVE_THERM is not set
+# CONFIG_W83627HF_WDT is not set
+# CONFIG_W83877F_WDT is not set
+# CONFIG_W83977F_WDT is not set
+# CONFIG_WAN is not set
+# CONFIG_WANXL is not set
+# CONFIG_WARN_ALL_UNSEEDED_RANDOM is not set
+CONFIG_WATCHDOG=y
+# CONFIG_WATCHDOG_CORE is not set
+CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED=y
+# CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT is not set
+# CONFIG_WATCHDOG_NOWAYOUT is not set
+CONFIG_WATCHDOG_OPEN_TIMEOUT=0
+# CONFIG_WATCHDOG_PRETIMEOUT_GOV is not set
+# CONFIG_WATCHDOG_SYSFS is not set
+# CONFIG_WATCH_QUEUE is not set
+# CONFIG_WD80x3 is not set
+# CONFIG_WDAT_WDT is not set
+# CONFIG_WDTPCI is not set
+# CONFIG_WERROR is not set
+# CONFIG_WEXT_CORE is not set
+# CONFIG_WEXT_PRIV is not set
+# CONFIG_WEXT_PROC is not set
+# CONFIG_WEXT_SPY is not set
+# CONFIG_WIREGUARD is not set
+CONFIG_WIRELESS=y
+# CONFIG_WIRELESS_EXT is not set
+# CONFIG_WIZNET_W5100 is not set
+# CONFIG_WIZNET_W5300 is not set
+# CONFIG_WL1251 is not set
+# CONFIG_WL12XX is not set
+# CONFIG_WL18XX is not set
+CONFIG_WLAN=y
+# CONFIG_WLAN_VENDOR_ADMTEK is not set
+# CONFIG_WLAN_VENDOR_ATH is not set
+# CONFIG_WLAN_VENDOR_ATMEL is not set
+# CONFIG_WLAN_VENDOR_BROADCOM is not set
+# CONFIG_WLAN_VENDOR_INTEL is not set
+# CONFIG_WLAN_VENDOR_INTERSIL is not set
+# CONFIG_WLAN_VENDOR_MARVELL is not set
+# CONFIG_WLAN_VENDOR_MEDIATEK is not set
+# CONFIG_WLAN_VENDOR_MICROCHIP is not set
+# CONFIG_WLAN_VENDOR_PURELIFI is not set
+# CONFIG_WLAN_VENDOR_QUANTENNA is not set
+# CONFIG_WLAN_VENDOR_RALINK is not set
+# CONFIG_WLAN_VENDOR_REALTEK is not set
+# CONFIG_WLAN_VENDOR_RSI is not set
+# CONFIG_WLAN_VENDOR_SILABS is not set
+# CONFIG_WLAN_VENDOR_ST is not set
+# CONFIG_WLAN_VENDOR_TI is not set
+# CONFIG_WLAN_VENDOR_ZYDAS is not set
+# CONFIG_WLCORE is not set
+# CONFIG_WMI_BMOF is not set
+# CONFIG_WPCM450_SOC is not set
+# CONFIG_WQ_CPU_INTENSIVE_REPORT is not set
+CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y
+# CONFIG_WQ_WATCHDOG is not set
+# CONFIG_WWAN is not set
+# CONFIG_WWAN_HWSIM is not set
+# CONFIG_WW_MUTEX_SELFTEST is not set
+# CONFIG_X25 is not set
+# CONFIG_X509_CERTIFICATE_PARSER is not set
+# CONFIG_X86_KERNEL_IBT is not set
+# CONFIG_X86_PKG_TEMP_THERMAL is not set
+# CONFIG_X86_USER_SHADOW_STACK is not set
+# CONFIG_X9250 is not set
+# CONFIG_XDP_SOCKETS is not set
+# CONFIG_XEN is not set
+# CONFIG_XEN_GRANT_DMA_ALLOC is not set
+# CONFIG_XEN_PVCALLS_FRONTEND is not set
+CONFIG_XEN_SCRUB_PAGES_DEFAULT=y
+CONFIG_XFRM=y
+# CONFIG_XFRM_INTERFACE is not set
+# CONFIG_XFRM_IPCOMP is not set
+# CONFIG_XFRM_MIGRATE is not set
+# CONFIG_XFRM_STATISTICS is not set
+# CONFIG_XFRM_SUB_POLICY is not set
+# CONFIG_XFRM_USER is not set
+# CONFIG_XFS_DEBUG is not set
+# CONFIG_XFS_FS is not set
+# CONFIG_XFS_ONLINE_SCRUB is not set
+# CONFIG_XFS_POSIX_ACL is not set
+# CONFIG_XFS_QUOTA is not set
+# CONFIG_XFS_RT is not set
+# CONFIG_XFS_SUPPORT_ASCII_CI is not set
+# CONFIG_XFS_SUPPORT_V4 is not set
+# CONFIG_XFS_WARN is not set
+# CONFIG_XIAOMI_WMI is not set
+# CONFIG_XILINX_AXI_EMAC is not set
+# CONFIG_XILINX_DMA is not set
+# CONFIG_XILINX_EMACLITE is not set
+# CONFIG_XILINX_GMII2RGMII is not set
+# CONFIG_XILINX_INTC is not set
+# CONFIG_XILINX_LL_TEMAC is not set
+# CONFIG_XILINX_SDFEC is not set
+# CONFIG_XILINX_VCU is not set
+# CONFIG_XILINX_WATCHDOG is not set
+# CONFIG_XILINX_WINDOW_WATCHDOG is not set
+# CONFIG_XILINX_XADC is not set
+# CONFIG_XILINX_XDMA is not set
+# CONFIG_XILINX_ZYNQMP_DMA is not set
+# CONFIG_XILINX_ZYNQMP_DPDMA is not set
+# CONFIG_XILLYBUS is not set
+# CONFIG_XILLYUSB is not set
+# CONFIG_XIL_AXIS_FIFO is not set
+# CONFIG_XIP_KERNEL is not set
+# CONFIG_XMON is not set
+CONFIG_XZ_DEC=y
+# CONFIG_XZ_DEC_ARM is not set
+# CONFIG_XZ_DEC_ARM64 is not set
+# CONFIG_XZ_DEC_ARMTHUMB is not set
+# CONFIG_XZ_DEC_BCJ is not set
+# CONFIG_XZ_DEC_MICROLZMA is not set
+# CONFIG_XZ_DEC_POWERPC is not set
+# CONFIG_XZ_DEC_RISCV is not set
+# CONFIG_XZ_DEC_SPARC is not set
+# CONFIG_XZ_DEC_TEST is not set
+# CONFIG_XZ_DEC_X86 is not set
+# CONFIG_YAM is not set
+# CONFIG_YAMAHA_YAS530 is not set
+# CONFIG_YELLOWFIN is not set
+# CONFIG_YENTA is not set
+# CONFIG_YENTA_O2 is not set
+# CONFIG_YENTA_RICOH is not set
+# CONFIG_YENTA_TI is not set
+# CONFIG_YENTA_TOSHIBA is not set
+# CONFIG_YOGABOOK is not set
+# CONFIG_ZBUD is not set
+# CONFIG_ZD1211RW is not set
+# CONFIG_ZD1211RW_DEBUG is not set
+# CONFIG_ZEROPLUS_FF is not set
+# CONFIG_ZERO_CALL_USED_REGS is not set
+# CONFIG_ZIIRAVE_WATCHDOG is not set
+# CONFIG_ZISOFS is not set
+# CONFIG_ZLIB_DEFLATE is not set
+# CONFIG_ZLIB_INFLATE is not set
+CONFIG_ZONE_DMA=y
+# CONFIG_ZOPT2201 is not set
+# CONFIG_ZPA2326 is not set
+# CONFIG_ZPOOL is not set
+# CONFIG_ZRAM is not set
+# CONFIG_ZRAM_BACKEND_842 is not set
+# CONFIG_ZRAM_BACKEND_DEFLATE is not set
+# CONFIG_ZRAM_BACKEND_LZ4 is not set
+# CONFIG_ZRAM_BACKEND_LZ4HC is not set
+# CONFIG_ZRAM_BACKEND_LZO is not set
+# CONFIG_ZRAM_BACKEND_ZSTD is not set
+CONFIG_ZRAM_DEF_COMP="unset-value"
+# CONFIG_ZRAM_DEF_COMP_842 is not set
+# CONFIG_ZRAM_DEF_COMP_LZ4 is not set
+# CONFIG_ZRAM_DEF_COMP_LZ4HC is not set
+# CONFIG_ZRAM_DEF_COMP_LZO is not set
+# CONFIG_ZRAM_DEF_COMP_LZORLE is not set
+# CONFIG_ZRAM_DEF_COMP_ZSTD is not set
+# CONFIG_ZRAM_MEMORY_TRACKING is not set
+# CONFIG_ZRAM_MULTI_COMP is not set
+# CONFIG_ZRAM_TRACK_ENTRY_ACTIME is not set
+# CONFIG_ZSMALLOC is not set
+CONFIG_ZSMALLOC_CHAIN_SIZE=8
+# CONFIG_ZSWAP is not set
diff --git a/target/linux/generic/hack-6.12/200-tools_portability.patch b/target/linux/generic/hack-6.12/200-tools_portability.patch
new file mode 100644 (file)
index 0000000..f25a132
--- /dev/null
@@ -0,0 +1,176 @@
+From a7ae4ed0a3951c45d4a59ee575951b64ae4a23fb Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 7 May 2024 12:22:15 +0200
+Subject: [PATCH] kernel: fix tools build breakage on macos with x86
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+--- a/tools/scripts/Makefile.include
++++ b/tools/scripts/Makefile.include
+@@ -72,8 +72,6 @@ $(call allow-override,CXX,$(CROSS_COMPIL
+ $(call allow-override,STRIP,$(CROSS_COMPILE)strip)
+ endif
+-CC_NO_CLANG := $(shell $(CC) -dM -E -x c /dev/null | grep -Fq "__clang__"; echo $$?)
+-
+ ifneq ($(LLVM),)
+ HOSTAR  ?= $(LLVM_PREFIX)llvm-ar$(LLVM_SUFFIX)
+ HOSTCC  ?= $(LLVM_PREFIX)clang$(LLVM_SUFFIX)
+@@ -84,6 +82,9 @@ HOSTCC  ?= gcc
+ HOSTLD  ?= ld
+ endif
++CC_NO_CLANG := $(shell $(CC) -dM -E -x c /dev/null | grep -Fq "__clang__"; echo $$?)
++HOSTCC_NO_CLANG := $(shell $(HOSTCC) -dM -E -x c /dev/null | grep -Fq "__clang__"; echo $$?)
++
+ # Some tools require Clang, LLC and/or LLVM utils
+ CLANG         ?= clang
+ LLC           ?= llc
+@@ -92,8 +93,9 @@ LLVM_OBJCOPY ?= llvm-objcopy
+ LLVM_STRIP    ?= llvm-strip
+ ifeq ($(CC_NO_CLANG), 1)
+-EXTRA_WARNINGS += -Wstrict-aliasing=3
+-
++  ifeq ($(HOSTCC_NO_CLANG), 1)
++    EXTRA_WARNINGS += -Wstrict-aliasing=3
++  endif
+ else ifneq ($(CROSS_COMPILE),)
+ # Allow userspace to override CLANG_CROSS_FLAGS to specify their own
+ # sysroots and flags or to avoid the GCC call in pure Clang builds.
+--- a/tools/include/linux/types.h
++++ b/tools/include/linux/types.h
+@@ -56,6 +56,7 @@ typedef __s8  s8;
+ #define __user
+ #endif
+ #define __must_check
++#undef __cold
+ #define __cold
+ typedef __u16 __bitwise __le16;
+--- a/tools/objtool/include/objtool/objtool.h
++++ b/tools/objtool/include/objtool/objtool.h
+@@ -12,6 +12,7 @@
+ #include <objtool/elf.h>
++#undef __weak
+ #define __weak __attribute__((weak))
+ struct pv_state {
+--- a/tools/include/asm-generic/bitops/fls.h
++++ b/tools/include/asm-generic/bitops/fls.h
+@@ -2,6 +2,8 @@
+ #ifndef _ASM_GENERIC_BITOPS_FLS_H_
+ #define _ASM_GENERIC_BITOPS_FLS_H_
++#include <string.h>
++
+ /**
+  * generic_fls - find last (most-significant) bit set
+  * @x: the word to search
+@@ -10,6 +12,7 @@
+  * Note fls(0) = 0, fls(1) = 1, fls(0x80000000) = 32.
+  */
++#define generic_fls __linux_fls
+ static __always_inline int generic_fls(unsigned int x)
+ {
+       int r = 32;
+--- a/tools/lib/string.c
++++ b/tools/lib/string.c
+@@ -96,6 +96,7 @@ int strtobool(const char *s, bool *res)
+  * If libc has strlcpy() then that version will override this
+  * implementation:
+  */
++#ifndef __APPLE__
+ #ifdef __clang__
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wignored-attributes"
+@@ -114,6 +115,7 @@ size_t __weak strlcpy(char *dest, const
+ #ifdef __clang__
+ #pragma clang diagnostic pop
+ #endif
++#endif
+ /**
+  * skip_spaces - Removes leading whitespace from @str.
+--- a/tools/arch/x86/include/asm/insn.h
++++ b/tools/arch/x86/include/asm/insn.h
+@@ -7,7 +7,7 @@
+  * Copyright (C) IBM Corporation, 2009
+  */
+-#include <asm/byteorder.h>
++#include <linux/kernel.h>
+ /* insn_attr_t is defined in inat.h */
+ #include "inat.h" /* __ignore_sync_check__ */
+--- a/tools/arch/x86/include/asm/orc_types.h
++++ b/tools/arch/x86/include/asm/orc_types.h
+@@ -46,7 +46,17 @@
+ #define ORC_TYPE_REGS_PARTIAL         4
+ #ifndef __ASSEMBLY__
++#ifdef __APPLE__
++#include <endian.h>
++
++#if __BYTE_ORDER == __LITTLE_ENDIAN
++#define __LITTLE_ENDIAN_BITFIELD
++#elif __BYTE_ORDER == __BIG_ENDIAN
++#define __BIG_ENDIAN_BITFIELD
++#endif
++#else
+ #include <asm/byteorder.h>
++#endif
+ /*
+  * This struct is more or less a vastly simplified version of the DWARF Call
+--- a/tools/include/linux/rbtree.h
++++ b/tools/include/linux/rbtree.h
+@@ -18,7 +18,6 @@
+ #define __TOOLS_LINUX_PERF_RBTREE_H
+ #include <linux/kernel.h>
+-#include <linux/stddef.h>
+ struct rb_node {
+       unsigned long  __rb_parent_color;
+--- a/tools/lib/subcmd/exec-cmd.c
++++ b/tools/lib/subcmd/exec-cmd.c
+@@ -12,7 +12,10 @@
+ #include "subcmd-config.h"
+ #define MAX_ARGS      32
++
++#ifndef PATH_MAX
+ #define PATH_MAX      4096
++#endif
+ static const char *argv_exec_path;
+ static const char *argv0_path;
+--- a/tools/objtool/Makefile
++++ b/tools/objtool/Makefile
+@@ -41,6 +41,8 @@ OBJTOOL_LDFLAGS := $(LIBELF_LIBS) $(LIBS
+ elfshdr := $(shell echo '$(pound)include <libelf.h>' | $(HOSTCC) $(OBJTOOL_CFLAGS) -x c -E - | grep elf_getshdr)
+ OBJTOOL_CFLAGS += $(if $(elfshdr),,-DLIBELF_USE_DEPRECATED)
++OBJTOOL_CFLAGS += $(HOST_EXTRACFLAGS)
++
+ # Always want host compilation.
+ HOST_OVERRIDES := CC="$(HOSTCC)" LD="$(HOSTLD)" AR="$(HOSTAR)"
+--- a/tools/arch/x86/lib/insn.c
++++ b/tools/arch/x86/lib/insn.c
+@@ -15,7 +15,11 @@
+ #include "../include/asm/insn.h" /* __ignore_sync_check__ */
+ #include <linux/unaligned.h> /* __ignore_sync_check__ */
++#ifdef __KERNEL__
+ #include <linux/errno.h>
++#else
++#include <errno.h>
++#endif
+ #include <linux/kconfig.h>
+ #include "../include/asm/emulate_prefix.h" /* __ignore_sync_check__ */
diff --git a/target/linux/generic/hack-6.12/204-module_strip.patch b/target/linux/generic/hack-6.12/204-module_strip.patch
new file mode 100644 (file)
index 0000000..6f6f6a7
--- /dev/null
@@ -0,0 +1,201 @@
+From a779a482fb9b9f8fcdf8b2519c789b4b9bb5dd05 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 7 Jul 2017 16:56:48 +0200
+Subject: build: add a hack for removing non-essential module info
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ include/linux/module.h      | 13 ++++++++-----
+ include/linux/moduleparam.h | 15 ++++++++++++---
+ init/Kconfig                |  7 +++++++
+ kernel/module.c             |  5 ++++-
+ scripts/mod/modpost.c       | 12 ++++++++++++
+ 5 files changed, 43 insertions(+), 9 deletions(-)
+
+--- a/include/linux/module.h
++++ b/include/linux/module.h
+@@ -166,6 +166,7 @@ struct module_kobject *lookup_or_create_
+ /* Generic info of form tag = "info" */
+ #define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)
++#define MODULE_INFO_STRIP(tag, info) __MODULE_INFO_STRIP(tag, tag, info)
+ /* For userspace: you can also call me... */
+ #define MODULE_ALIAS(_alias) MODULE_INFO(alias, _alias)
+@@ -241,12 +242,12 @@ struct module_kobject *lookup_or_create_
+  * Author(s), use "Name <email>" or just "Name", for multiple
+  * authors use multiple MODULE_AUTHOR() statements/lines.
+  */
+-#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)
++#define MODULE_AUTHOR(_author) MODULE_INFO_STRIP(author, _author)
+ /* What your module does. */
+-#define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description)
++#define MODULE_DESCRIPTION(_description) MODULE_INFO_STRIP(description, _description)
+-#ifdef MODULE
++#if defined(MODULE) && !defined(CONFIG_MODULE_STRIPPED)
+ /* Creates an alias so file2alias.c can find device table. */
+ #define MODULE_DEVICE_TABLE(type, name)                                       \
+ extern typeof(name) __mod_##type##__##name##_device_table             \
+@@ -273,7 +274,9 @@ extern typeof(name) __mod_##type##__##na
+  */
+ #if defined(MODULE) || !defined(CONFIG_SYSFS)
+-#define MODULE_VERSION(_version) MODULE_INFO(version, _version)
++#define MODULE_VERSION(_version) MODULE_INFO_STRIP(version, _version)
++#elif defined(CONFIG_MODULE_STRIPPED)
++#define MODULE_VERSION(_version) __MODULE_INFO_DISABLED(version)
+ #else
+ #define MODULE_VERSION(_version)                                      \
+       MODULE_INFO(version, _version);                                 \
+@@ -296,7 +299,7 @@ extern typeof(name) __mod_##type##__##na
+ /* Optional firmware file (or files) needed by the module
+  * format is simply firmware file name.  Multiple firmware
+  * files require multiple MODULE_FIRMWARE() specifiers */
+-#define MODULE_FIRMWARE(_firmware) MODULE_INFO(firmware, _firmware)
++#define MODULE_FIRMWARE(_firmware) MODULE_INFO_STRIP(firmware, _firmware)
+ #define MODULE_IMPORT_NS(ns)  MODULE_INFO(import_ns, __stringify(ns))
+--- a/include/linux/moduleparam.h
++++ b/include/linux/moduleparam.h
+@@ -20,6 +20,16 @@
+ /* Chosen so that structs with an unsigned long line up. */
+ #define MAX_PARAM_PREFIX_LEN (64 - sizeof(unsigned long))
++/* This struct is here for syntactic coherency, it is not used */
++#define __MODULE_INFO_DISABLED(name)                                    \
++  struct __UNIQUE_ID(name) {}
++
++#ifdef CONFIG_MODULE_STRIPPED
++#define __MODULE_INFO_STRIP(tag, name, info) __MODULE_INFO_DISABLED(name)
++#else
++#define __MODULE_INFO_STRIP(tag, name, info) __MODULE_INFO(tag, name, info)
++#endif
++
+ #define __MODULE_INFO(tag, name, info)                                          \
+       static const char __UNIQUE_ID(name)[]                             \
+               __used __section(".modinfo") __aligned(1)                 \
+@@ -31,7 +41,7 @@
+ /* One for each parameter, describing how to use it.  Some files do
+    multiple of these per line, so can't just use MODULE_INFO. */
+ #define MODULE_PARM_DESC(_parm, desc) \
+-      __MODULE_INFO(parm, _parm, #_parm ":" desc)
++      __MODULE_INFO_STRIP(parm, _parm, #_parm ":" desc)
+ struct kernel_param;
+--- a/kernel/module/Kconfig
++++ b/kernel/module/Kconfig
+@@ -402,4 +402,11 @@ config MODULES_TREE_LOOKUP
+       def_bool y
+       depends on PERF_EVENTS || TRACING || CFI_CLANG
++config MODULE_STRIPPED
++      bool "Reduce module size"
++      depends on MODULES
++      help
++        Remove module parameter descriptions, author info, version, aliases,
++        device tables, etc.
++
+ endif # MODULES
+--- a/kernel/module/main.c
++++ b/kernel/module/main.c
+@@ -1001,6 +1001,7 @@ size_t modinfo_attrs_count = ARRAY_SIZE(
+ static const char vermagic[] = VERMAGIC_STRING;
++#if defined(CONFIG_MODVERSIONS) || !defined(CONFIG_MODULE_STRIPPED)
+ int try_to_force_load(struct module *mod, const char *reason)
+ {
+ #ifdef CONFIG_MODULE_FORCE_LOAD
+@@ -1012,6 +1013,7 @@ int try_to_force_load(struct module *mod
+       return -ENOEXEC;
+ #endif
+ }
++#endif
+ /* Parse tag=value strings from .modinfo section */
+ char *module_next_tag_pair(char *string, unsigned long *secsize)
+@@ -2095,9 +2097,11 @@ static void module_augment_kernel_taints
+ static int check_modinfo(struct module *mod, struct load_info *info, int flags)
+ {
+-      const char *modmagic = get_modinfo(info, "vermagic");
+       int err;
++#ifndef CONFIG_MODULE_STRIPPED
++      const char *modmagic = get_modinfo(info, "vermagic");
++
+       if (flags & MODULE_INIT_IGNORE_VERMAGIC)
+               modmagic = NULL;
+@@ -2111,6 +2115,7 @@ static int check_modinfo(struct module *
+                      info->name, modmagic, vermagic);
+               return -ENOEXEC;
+       }
++#endif
+       err = check_modinfo_livepatch(mod, info);
+       if (err)
+--- a/scripts/mod/modpost.c
++++ b/scripts/mod/modpost.c
+@@ -1605,7 +1605,9 @@ static void read_symbols(const char *mod
+               symname = remove_dot(info.strtab + sym->st_name);
+               handle_symbol(mod, &info, sym, symname);
++#ifndef CONFIG_MODULE_STRIPPED
+               handle_moddevtable(mod, &info, sym, symname);
++#endif
+       }
+       check_sec_ref(mod, &info);
+@@ -1762,7 +1764,9 @@ static void add_header(struct buffer *b,
+       buf_printf(b, "#include <linux/export-internal.h>\n");
+       buf_printf(b, "#include <linux/compiler.h>\n");
+       buf_printf(b, "\n");
++#ifndef CONFIG_MODULE_STRIPPED
+       buf_printf(b, "MODULE_INFO(name, KBUILD_MODNAME);\n");
++#endif
+       buf_printf(b, "\n");
+       buf_printf(b, "__visible struct module __this_module\n");
+       buf_printf(b, "__section(\".gnu.linkonce.this_module\") = {\n");
+@@ -1776,11 +1780,13 @@ static void add_header(struct buffer *b,
+       buf_printf(b, "\t.arch = MODULE_ARCH_INIT,\n");
+       buf_printf(b, "};\n");
++#ifndef CONFIG_MODULE_STRIPPED
+       if (!external_module)
+               buf_printf(b, "\nMODULE_INFO(intree, \"Y\");\n");
+       if (strstarts(mod->name, "drivers/staging"))
+               buf_printf(b, "\nMODULE_INFO(staging, \"Y\");\n");
++#endif
+       if (strstarts(mod->name, "tools/testing"))
+               buf_printf(b, "\nMODULE_INFO(test, \"Y\");\n");
+@@ -1890,11 +1896,13 @@ static void add_depends(struct buffer *b
+ static void add_srcversion(struct buffer *b, struct module *mod)
+ {
++#ifndef CONFIG_MODULE_STRIPPED
+       if (mod->srcversion[0]) {
+               buf_printf(b, "\n");
+               buf_printf(b, "MODULE_INFO(srcversion, \"%s\");\n",
+                          mod->srcversion);
+       }
++#endif
+ }
+ static void write_buf(struct buffer *b, const char *fname)
+@@ -1977,7 +1985,9 @@ static void write_mod_c_file(struct modu
+       add_exported_symbols(&buf, mod);
+       add_versions(&buf, mod);
+       add_depends(&buf, mod);
++#ifndef CONFIG_MODULE_STRIPPED
+       add_moddevtable(&buf, mod);
++#endif
+       add_srcversion(&buf, mod);
+       ret = snprintf(fname, sizeof(fname), "%s.mod.c", mod->name);
diff --git a/target/linux/generic/hack-6.12/205-kconfig-abort-configuration-on-unset-symbol.patch b/target/linux/generic/hack-6.12/205-kconfig-abort-configuration-on-unset-symbol.patch
new file mode 100644 (file)
index 0000000..d7416d8
--- /dev/null
@@ -0,0 +1,41 @@
+From 310e8e04a05d9eb43fa9dd7f00143300afcaa37a Mon Sep 17 00:00:00 2001
+From: David Bauer <mail@david-bauer.net>
+Date: Fri, 11 Nov 2022 13:33:44 +0100
+Subject: [PATCH] kconfig: abort configuration on unset symbol
+
+When a target configuration has unset Kconfig symbols, the build will
+fail when OpenWrt is compiled with V=s and stdin is connected to a tty.
+
+In case OpenWrt is compiled without either of these preconditions, the
+build will succeed with the symbols in question being unset.
+
+Modify the kernel configuration in a way it fails on unset symbols
+regardless of the aforementioned preconditions.
+
+Signed-off-by: David Bauer <mail@david-bauer.net>
+---
+ scripts/kconfig/conf.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/scripts/kconfig/conf.c
++++ b/scripts/kconfig/conf.c
+@@ -312,6 +312,9 @@ static int conf_askvalue(struct symbol *
+               }
+               /* fall through */
+       default:
++              if (!tty_stdio && getenv("FAIL_ON_UNCONFIGURED")) {
++                      exit(1);
++              }
+               fflush(stdout);
+               xfgets(line, sizeof(line), stdin);
+               break;
+@@ -470,6 +473,9 @@ static void conf_choice(struct menu *men
+                       }
+                       /* fall through */
+               case oldaskconfig:
++                      if (!tty_stdio && getenv("FAIL_ON_UNCONFIGURED")) {
++                              exit(1);
++                      }
+                       fflush(stdout);
+                       xfgets(line, sizeof(line), stdin);
+                       strip(line);
diff --git a/target/linux/generic/hack-6.12/210-darwin_scripts_include.patch b/target/linux/generic/hack-6.12/210-darwin_scripts_include.patch
new file mode 100644 (file)
index 0000000..be59ca4
--- /dev/null
@@ -0,0 +1,3053 @@
+From db7c30dcd9a0391bf13b62c9f91e144d762ef43a Mon Sep 17 00:00:00 2001
+From: Florian Fainelli <f.fainelli@gmail.com>
+Date: Fri, 7 Jul 2017 17:00:49 +0200
+Subject: Add an OSX specific patch to make the kernel be compiled
+
+lede-commit: 3fc2a24f0422b2f55f9ed43f116db3111f700526
+Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
+---
+ scripts/kconfig/Makefile   |    3 +
+ scripts/mod/elf.h          | 3007 ++++++++++++++++++++++++++++++++++++++++++++
+ scripts/mod/mk_elfconfig.c |    4 +
+ scripts/mod/modpost.h      |    4 +
+ 4 files changed, 3018 insertions(+)
+ create mode 100644 scripts/mod/elf.h
+
+--- /dev/null
++++ b/scripts/mod/elf.h
+@@ -0,0 +1,3007 @@
++/* This file defines standard ELF types, structures, and macros.
++   Copyright (C) 1995-2012 Free Software Foundation, Inc.
++   This file is part of the GNU C Library.
++
++   The GNU C Library is free software; you can redistribute it and/or
++   modify it under the terms of the GNU Lesser General Public
++   License as published by the Free Software Foundation; either
++   version 2.1 of the License, or (at your option) any later version.
++
++   The GNU C Library is distributed in the hope that it will be useful,
++   but WITHOUT ANY WARRANTY; without even the implied warranty of
++   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++   Lesser General Public License for more details.
++
++   You should have received a copy of the GNU Lesser General Public
++   License along with the GNU C Library; if not, see
++   <http://www.gnu.org/licenses/>.  */
++
++#ifndef _ELF_H
++#define       _ELF_H 1
++
++/* Standard ELF types.  */
++
++#include <stdint.h>
++
++/* Type for a 16-bit quantity.  */
++typedef uint16_t Elf32_Half;
++typedef uint16_t Elf64_Half;
++
++/* Types for signed and unsigned 32-bit quantities.  */
++typedef uint32_t Elf32_Word;
++typedef       int32_t  Elf32_Sword;
++typedef uint32_t Elf64_Word;
++typedef       int32_t  Elf64_Sword;
++
++/* Types for signed and unsigned 64-bit quantities.  */
++typedef uint64_t Elf32_Xword;
++typedef       int64_t  Elf32_Sxword;
++typedef uint64_t Elf64_Xword;
++typedef       int64_t  Elf64_Sxword;
++
++/* Type of addresses.  */
++typedef uint32_t Elf32_Addr;
++typedef uint64_t Elf64_Addr;
++
++/* Type of file offsets.  */
++typedef uint32_t Elf32_Off;
++typedef uint64_t Elf64_Off;
++
++/* Type for section indices, which are 16-bit quantities.  */
++typedef uint16_t Elf32_Section;
++typedef uint16_t Elf64_Section;
++
++/* Type for version symbol information.  */
++typedef Elf32_Half Elf32_Versym;
++typedef Elf64_Half Elf64_Versym;
++
++
++/* The ELF file header.  This appears at the start of every ELF file.  */
++
++#define EI_NIDENT (16)
++
++typedef struct
++{
++  unsigned char       e_ident[EI_NIDENT];     /* Magic number and other info */
++  Elf32_Half  e_type;                 /* Object file type */
++  Elf32_Half  e_machine;              /* Architecture */
++  Elf32_Word  e_version;              /* Object file version */
++  Elf32_Addr  e_entry;                /* Entry point virtual address */
++  Elf32_Off   e_phoff;                /* Program header table file offset */
++  Elf32_Off   e_shoff;                /* Section header table file offset */
++  Elf32_Word  e_flags;                /* Processor-specific flags */
++  Elf32_Half  e_ehsize;               /* ELF header size in bytes */
++  Elf32_Half  e_phentsize;            /* Program header table entry size */
++  Elf32_Half  e_phnum;                /* Program header table entry count */
++  Elf32_Half  e_shentsize;            /* Section header table entry size */
++  Elf32_Half  e_shnum;                /* Section header table entry count */
++  Elf32_Half  e_shstrndx;             /* Section header string table index */
++} Elf32_Ehdr;
++
++typedef struct
++{
++  unsigned char       e_ident[EI_NIDENT];     /* Magic number and other info */
++  Elf64_Half  e_type;                 /* Object file type */
++  Elf64_Half  e_machine;              /* Architecture */
++  Elf64_Word  e_version;              /* Object file version */
++  Elf64_Addr  e_entry;                /* Entry point virtual address */
++  Elf64_Off   e_phoff;                /* Program header table file offset */
++  Elf64_Off   e_shoff;                /* Section header table file offset */
++  Elf64_Word  e_flags;                /* Processor-specific flags */
++  Elf64_Half  e_ehsize;               /* ELF header size in bytes */
++  Elf64_Half  e_phentsize;            /* Program header table entry size */
++  Elf64_Half  e_phnum;                /* Program header table entry count */
++  Elf64_Half  e_shentsize;            /* Section header table entry size */
++  Elf64_Half  e_shnum;                /* Section header table entry count */
++  Elf64_Half  e_shstrndx;             /* Section header string table index */
++} Elf64_Ehdr;
++
++/* Fields in the e_ident array.  The EI_* macros are indices into the
++   array.  The macros under each EI_* macro are the values the byte
++   may have.  */
++
++#define EI_MAG0               0               /* File identification byte 0 index */
++#define ELFMAG0               0x7f            /* Magic number byte 0 */
++
++#define EI_MAG1               1               /* File identification byte 1 index */
++#define ELFMAG1               'E'             /* Magic number byte 1 */
++
++#define EI_MAG2               2               /* File identification byte 2 index */
++#define ELFMAG2               'L'             /* Magic number byte 2 */
++
++#define EI_MAG3               3               /* File identification byte 3 index */
++#define ELFMAG3               'F'             /* Magic number byte 3 */
++
++/* Conglomeration of the identification bytes, for easy testing as a word.  */
++#define       ELFMAG          "\177ELF"
++#define       SELFMAG         4
++
++#define EI_CLASS      4               /* File class byte index */
++#define ELFCLASSNONE  0               /* Invalid class */
++#define ELFCLASS32    1               /* 32-bit objects */
++#define ELFCLASS64    2               /* 64-bit objects */
++#define ELFCLASSNUM   3
++
++#define EI_DATA               5               /* Data encoding byte index */
++#define ELFDATANONE   0               /* Invalid data encoding */
++#define ELFDATA2LSB   1               /* 2's complement, little endian */
++#define ELFDATA2MSB   2               /* 2's complement, big endian */
++#define ELFDATANUM    3
++
++#define EI_VERSION    6               /* File version byte index */
++                                      /* Value must be EV_CURRENT */
++
++#define EI_OSABI      7               /* OS ABI identification */
++#define ELFOSABI_NONE         0       /* UNIX System V ABI */
++#define ELFOSABI_SYSV         0       /* Alias.  */
++#define ELFOSABI_HPUX         1       /* HP-UX */
++#define ELFOSABI_NETBSD               2       /* NetBSD.  */
++#define ELFOSABI_GNU          3       /* Object uses GNU ELF extensions.  */
++#define ELFOSABI_LINUX                ELFOSABI_GNU /* Compatibility alias.  */
++#define ELFOSABI_SOLARIS      6       /* Sun Solaris.  */
++#define ELFOSABI_AIX          7       /* IBM AIX.  */
++#define ELFOSABI_IRIX         8       /* SGI Irix.  */
++#define ELFOSABI_FREEBSD      9       /* FreeBSD.  */
++#define ELFOSABI_TRU64                10      /* Compaq TRU64 UNIX.  */
++#define ELFOSABI_MODESTO      11      /* Novell Modesto.  */
++#define ELFOSABI_OPENBSD      12      /* OpenBSD.  */
++#define ELFOSABI_ARM_AEABI    64      /* ARM EABI */
++#define ELFOSABI_ARM          97      /* ARM */
++#define ELFOSABI_STANDALONE   255     /* Standalone (embedded) application */
++
++#define EI_ABIVERSION 8               /* ABI version */
++
++#define EI_PAD                9               /* Byte index of padding bytes */
++
++/* Legal values for e_type (object file type).  */
++
++#define ET_NONE               0               /* No file type */
++#define ET_REL                1               /* Relocatable file */
++#define ET_EXEC               2               /* Executable file */
++#define ET_DYN                3               /* Shared object file */
++#define ET_CORE               4               /* Core file */
++#define       ET_NUM          5               /* Number of defined types */
++#define ET_LOOS               0xfe00          /* OS-specific range start */
++#define ET_HIOS               0xfeff          /* OS-specific range end */
++#define ET_LOPROC     0xff00          /* Processor-specific range start */
++#define ET_HIPROC     0xffff          /* Processor-specific range end */
++
++/* Legal values for e_machine (architecture).  */
++
++#define EM_NONE                0              /* No machine */
++#define EM_M32                 1              /* AT&T WE 32100 */
++#define EM_SPARC       2              /* SUN SPARC */
++#define EM_386                 3              /* Intel 80386 */
++#define EM_68K                 4              /* Motorola m68k family */
++#define EM_88K                 5              /* Motorola m88k family */
++#define EM_860                 7              /* Intel 80860 */
++#define EM_MIPS                8              /* MIPS R3000 big-endian */
++#define EM_S370                9              /* IBM System/370 */
++#define EM_MIPS_RS3_LE        10              /* MIPS R3000 little-endian */
++
++#define EM_PARISC     15              /* HPPA */
++#define EM_VPP500     17              /* Fujitsu VPP500 */
++#define EM_SPARC32PLUS        18              /* Sun's "v8plus" */
++#define EM_960                19              /* Intel 80960 */
++#define EM_PPC                20              /* PowerPC */
++#define EM_PPC64      21              /* PowerPC 64-bit */
++#define EM_S390               22              /* IBM S390 */
++
++#define EM_V800               36              /* NEC V800 series */
++#define EM_FR20               37              /* Fujitsu FR20 */
++#define EM_RH32               38              /* TRW RH-32 */
++#define EM_RCE                39              /* Motorola RCE */
++#define EM_ARM                40              /* ARM */
++#define EM_FAKE_ALPHA 41              /* Digital Alpha */
++#define EM_SH         42              /* Hitachi SH */
++#define EM_SPARCV9    43              /* SPARC v9 64-bit */
++#define EM_TRICORE    44              /* Siemens Tricore */
++#define EM_ARC                45              /* Argonaut RISC Core */
++#define EM_H8_300     46              /* Hitachi H8/300 */
++#define EM_H8_300H    47              /* Hitachi H8/300H */
++#define EM_H8S                48              /* Hitachi H8S */
++#define EM_H8_500     49              /* Hitachi H8/500 */
++#define EM_IA_64      50              /* Intel Merced */
++#define EM_MIPS_X     51              /* Stanford MIPS-X */
++#define EM_COLDFIRE   52              /* Motorola Coldfire */
++#define EM_68HC12     53              /* Motorola M68HC12 */
++#define EM_MMA                54              /* Fujitsu MMA Multimedia Accelerator*/
++#define EM_PCP                55              /* Siemens PCP */
++#define EM_NCPU               56              /* Sony nCPU embeeded RISC */
++#define EM_NDR1               57              /* Denso NDR1 microprocessor */
++#define EM_STARCORE   58              /* Motorola Start*Core processor */
++#define EM_ME16               59              /* Toyota ME16 processor */
++#define EM_ST100      60              /* STMicroelectronic ST100 processor */
++#define EM_TINYJ      61              /* Advanced Logic Corp. Tinyj emb.fam*/
++#define EM_X86_64     62              /* AMD x86-64 architecture */
++#define EM_PDSP               63              /* Sony DSP Processor */
++
++#define EM_FX66               66              /* Siemens FX66 microcontroller */
++#define EM_ST9PLUS    67              /* STMicroelectronics ST9+ 8/16 mc */
++#define EM_ST7                68              /* STmicroelectronics ST7 8 bit mc */
++#define EM_68HC16     69              /* Motorola MC68HC16 microcontroller */
++#define EM_68HC11     70              /* Motorola MC68HC11 microcontroller */
++#define EM_68HC08     71              /* Motorola MC68HC08 microcontroller */
++#define EM_68HC05     72              /* Motorola MC68HC05 microcontroller */
++#define EM_SVX                73              /* Silicon Graphics SVx */
++#define EM_ST19               74              /* STMicroelectronics ST19 8 bit mc */
++#define EM_VAX                75              /* Digital VAX */
++#define EM_CRIS               76              /* Axis Communications 32-bit embedded processor */
++#define EM_JAVELIN    77              /* Infineon Technologies 32-bit embedded processor */
++#define EM_FIREPATH   78              /* Element 14 64-bit DSP Processor */
++#define EM_ZSP                79              /* LSI Logic 16-bit DSP Processor */
++#define EM_MMIX               80              /* Donald Knuth's educational 64-bit processor */
++#define EM_HUANY      81              /* Harvard University machine-independent object files */
++#define EM_PRISM      82              /* SiTera Prism */
++#define EM_AVR                83              /* Atmel AVR 8-bit microcontroller */
++#define EM_FR30               84              /* Fujitsu FR30 */
++#define EM_D10V               85              /* Mitsubishi D10V */
++#define EM_D30V               86              /* Mitsubishi D30V */
++#define EM_V850               87              /* NEC v850 */
++#define EM_M32R               88              /* Mitsubishi M32R */
++#define EM_MN10300    89              /* Matsushita MN10300 */
++#define EM_MN10200    90              /* Matsushita MN10200 */
++#define EM_PJ         91              /* picoJava */
++#define EM_OPENRISC   92              /* OpenRISC 32-bit embedded processor */
++#define EM_ARC_A5     93              /* ARC Cores Tangent-A5 */
++#define EM_XTENSA     94              /* Tensilica Xtensa Architecture */
++#define EM_TILEPRO    188             /* Tilera TILEPro */
++#define EM_TILEGX     191             /* Tilera TILE-Gx */
++#define EM_NUM                192
++
++/* If it is necessary to assign new unofficial EM_* values, please
++   pick large random numbers (0x8523, 0xa7f2, etc.) to minimize the
++   chances of collision with official or non-GNU unofficial values.  */
++
++#define EM_ALPHA      0x9026
++
++/* Legal values for e_version (version).  */
++
++#define EV_NONE               0               /* Invalid ELF version */
++#define EV_CURRENT    1               /* Current version */
++#define EV_NUM                2
++
++/* Section header.  */
++
++typedef struct
++{
++  Elf32_Word  sh_name;                /* Section name (string tbl index) */
++  Elf32_Word  sh_type;                /* Section type */
++  Elf32_Word  sh_flags;               /* Section flags */
++  Elf32_Addr  sh_addr;                /* Section virtual addr at execution */
++  Elf32_Off   sh_offset;              /* Section file offset */
++  Elf32_Word  sh_size;                /* Section size in bytes */
++  Elf32_Word  sh_link;                /* Link to another section */
++  Elf32_Word  sh_info;                /* Additional section information */
++  Elf32_Word  sh_addralign;           /* Section alignment */
++  Elf32_Word  sh_entsize;             /* Entry size if section holds table */
++} Elf32_Shdr;
++
++typedef struct
++{
++  Elf64_Word  sh_name;                /* Section name (string tbl index) */
++  Elf64_Word  sh_type;                /* Section type */
++  Elf64_Xword sh_flags;               /* Section flags */
++  Elf64_Addr  sh_addr;                /* Section virtual addr at execution */
++  Elf64_Off   sh_offset;              /* Section file offset */
++  Elf64_Xword sh_size;                /* Section size in bytes */
++  Elf64_Word  sh_link;                /* Link to another section */
++  Elf64_Word  sh_info;                /* Additional section information */
++  Elf64_Xword sh_addralign;           /* Section alignment */
++  Elf64_Xword sh_entsize;             /* Entry size if section holds table */
++} Elf64_Shdr;
++
++/* Special section indices.  */
++
++#define SHN_UNDEF     0               /* Undefined section */
++#define SHN_LORESERVE 0xff00          /* Start of reserved indices */
++#define SHN_LOPROC    0xff00          /* Start of processor-specific */
++#define SHN_BEFORE    0xff00          /* Order section before all others
++                                         (Solaris).  */
++#define SHN_AFTER     0xff01          /* Order section after all others
++                                         (Solaris).  */
++#define SHN_HIPROC    0xff1f          /* End of processor-specific */
++#define SHN_LOOS      0xff20          /* Start of OS-specific */
++#define SHN_HIOS      0xff3f          /* End of OS-specific */
++#define SHN_ABS               0xfff1          /* Associated symbol is absolute */
++#define SHN_COMMON    0xfff2          /* Associated symbol is common */
++#define SHN_XINDEX    0xffff          /* Index is in extra table.  */
++#define SHN_HIRESERVE 0xffff          /* End of reserved indices */
++
++/* Legal values for sh_type (section type).  */
++
++#define SHT_NULL        0             /* Section header table entry unused */
++#define SHT_PROGBITS    1             /* Program data */
++#define SHT_SYMTAB      2             /* Symbol table */
++#define SHT_STRTAB      3             /* String table */
++#define SHT_RELA        4             /* Relocation entries with addends */
++#define SHT_HASH        5             /* Symbol hash table */
++#define SHT_DYNAMIC     6             /* Dynamic linking information */
++#define SHT_NOTE        7             /* Notes */
++#define SHT_NOBITS      8             /* Program space with no data (bss) */
++#define SHT_REL                 9             /* Relocation entries, no addends */
++#define SHT_SHLIB       10            /* Reserved */
++#define SHT_DYNSYM      11            /* Dynamic linker symbol table */
++#define SHT_INIT_ARRAY          14            /* Array of constructors */
++#define SHT_FINI_ARRAY          15            /* Array of destructors */
++#define SHT_PREINIT_ARRAY 16          /* Array of pre-constructors */
++#define SHT_GROUP       17            /* Section group */
++#define SHT_SYMTAB_SHNDX  18          /* Extended section indeces */
++#define       SHT_NUM           19            /* Number of defined types.  */
++#define SHT_LOOS        0x60000000    /* Start OS-specific.  */
++#define SHT_GNU_ATTRIBUTES 0x6ffffff5 /* Object attributes.  */
++#define SHT_GNU_HASH    0x6ffffff6    /* GNU-style hash table.  */
++#define SHT_GNU_LIBLIST         0x6ffffff7    /* Prelink library list */
++#define SHT_CHECKSUM    0x6ffffff8    /* Checksum for DSO content.  */
++#define SHT_LOSUNW      0x6ffffffa    /* Sun-specific low bound.  */
++#define SHT_SUNW_move   0x6ffffffa
++#define SHT_SUNW_COMDAT   0x6ffffffb
++#define SHT_SUNW_syminfo  0x6ffffffc
++#define SHT_GNU_verdef          0x6ffffffd    /* Version definition section.  */
++#define SHT_GNU_verneed         0x6ffffffe    /* Version needs section.  */
++#define SHT_GNU_versym          0x6fffffff    /* Version symbol table.  */
++#define SHT_HISUNW      0x6fffffff    /* Sun-specific high bound.  */
++#define SHT_HIOS        0x6fffffff    /* End OS-specific type */
++#define SHT_LOPROC      0x70000000    /* Start of processor-specific */
++#define SHT_HIPROC      0x7fffffff    /* End of processor-specific */
++#define SHT_LOUSER      0x80000000    /* Start of application-specific */
++#define SHT_HIUSER      0x8fffffff    /* End of application-specific */
++
++/* Legal values for sh_flags (section flags).  */
++
++#define SHF_WRITE          (1 << 0)   /* Writable */
++#define SHF_ALLOC          (1 << 1)   /* Occupies memory during execution */
++#define SHF_EXECINSTR      (1 << 2)   /* Executable */
++#define SHF_MERGE          (1 << 4)   /* Might be merged */
++#define SHF_STRINGS        (1 << 5)   /* Contains nul-terminated strings */
++#define SHF_INFO_LINK      (1 << 6)   /* `sh_info' contains SHT index */
++#define SHF_LINK_ORDER             (1 << 7)   /* Preserve order after combining */
++#define SHF_OS_NONCONFORMING (1 << 8) /* Non-standard OS specific handling
++                                         required */
++#define SHF_GROUP          (1 << 9)   /* Section is member of a group.  */
++#define SHF_TLS                    (1 << 10)  /* Section hold thread-local data.  */
++#define SHF_MASKOS         0x0ff00000 /* OS-specific.  */
++#define SHF_MASKPROC       0xf0000000 /* Processor-specific */
++#define SHF_ORDERED        (1 << 30)  /* Special ordering requirement
++                                         (Solaris).  */
++#define SHF_EXCLUDE        (1 << 31)  /* Section is excluded unless
++                                         referenced or allocated (Solaris).*/
++
++/* Section group handling.  */
++#define GRP_COMDAT    0x1             /* Mark group as COMDAT.  */
++
++/* Symbol table entry.  */
++
++typedef struct
++{
++  Elf32_Word  st_name;                /* Symbol name (string tbl index) */
++  Elf32_Addr  st_value;               /* Symbol value */
++  Elf32_Word  st_size;                /* Symbol size */
++  unsigned char       st_info;                /* Symbol type and binding */
++  unsigned char       st_other;               /* Symbol visibility */
++  Elf32_Section       st_shndx;               /* Section index */
++} Elf32_Sym;
++
++typedef struct
++{
++  Elf64_Word  st_name;                /* Symbol name (string tbl index) */
++  unsigned char       st_info;                /* Symbol type and binding */
++  unsigned char st_other;             /* Symbol visibility */
++  Elf64_Section       st_shndx;               /* Section index */
++  Elf64_Addr  st_value;               /* Symbol value */
++  Elf64_Xword st_size;                /* Symbol size */
++} Elf64_Sym;
++
++/* The syminfo section if available contains additional information about
++   every dynamic symbol.  */
++
++typedef struct
++{
++  Elf32_Half si_boundto;              /* Direct bindings, symbol bound to */
++  Elf32_Half si_flags;                        /* Per symbol flags */
++} Elf32_Syminfo;
++
++typedef struct
++{
++  Elf64_Half si_boundto;              /* Direct bindings, symbol bound to */
++  Elf64_Half si_flags;                        /* Per symbol flags */
++} Elf64_Syminfo;
++
++/* Possible values for si_boundto.  */
++#define SYMINFO_BT_SELF               0xffff  /* Symbol bound to self */
++#define SYMINFO_BT_PARENT     0xfffe  /* Symbol bound to parent */
++#define SYMINFO_BT_LOWRESERVE 0xff00  /* Beginning of reserved entries */
++
++/* Possible bitmasks for si_flags.  */
++#define SYMINFO_FLG_DIRECT    0x0001  /* Direct bound symbol */
++#define SYMINFO_FLG_PASSTHRU  0x0002  /* Pass-thru symbol for translator */
++#define SYMINFO_FLG_COPY      0x0004  /* Symbol is a copy-reloc */
++#define SYMINFO_FLG_LAZYLOAD  0x0008  /* Symbol bound to object to be lazy
++                                         loaded */
++/* Syminfo version values.  */
++#define SYMINFO_NONE          0
++#define SYMINFO_CURRENT               1
++#define SYMINFO_NUM           2
++
++
++/* How to extract and insert information held in the st_info field.  */
++
++#define ELF32_ST_BIND(val)            (((unsigned char) (val)) >> 4)
++#define ELF32_ST_TYPE(val)            ((val) & 0xf)
++#define ELF32_ST_INFO(bind, type)     (((bind) << 4) + ((type) & 0xf))
++
++/* Both Elf32_Sym and Elf64_Sym use the same one-byte st_info field.  */
++#define ELF64_ST_BIND(val)            ELF32_ST_BIND (val)
++#define ELF64_ST_TYPE(val)            ELF32_ST_TYPE (val)
++#define ELF64_ST_INFO(bind, type)     ELF32_ST_INFO ((bind), (type))
++
++/* Legal values for ST_BIND subfield of st_info (symbol binding).  */
++
++#define STB_LOCAL     0               /* Local symbol */
++#define STB_GLOBAL    1               /* Global symbol */
++#define STB_WEAK      2               /* Weak symbol */
++#define       STB_NUM         3               /* Number of defined types.  */
++#define STB_LOOS      10              /* Start of OS-specific */
++#define STB_GNU_UNIQUE        10              /* Unique symbol.  */
++#define STB_HIOS      12              /* End of OS-specific */
++#define STB_LOPROC    13              /* Start of processor-specific */
++#define STB_HIPROC    15              /* End of processor-specific */
++
++/* Legal values for ST_TYPE subfield of st_info (symbol type).  */
++
++#define STT_NOTYPE    0               /* Symbol type is unspecified */
++#define STT_OBJECT    1               /* Symbol is a data object */
++#define STT_FUNC      2               /* Symbol is a code object */
++#define STT_SECTION   3               /* Symbol associated with a section */
++#define STT_FILE      4               /* Symbol's name is file name */
++#define STT_COMMON    5               /* Symbol is a common data object */
++#define STT_TLS               6               /* Symbol is thread-local data object*/
++#define       STT_NUM         7               /* Number of defined types.  */
++#define STT_LOOS      10              /* Start of OS-specific */
++#define STT_GNU_IFUNC 10              /* Symbol is indirect code object */
++#define STT_HIOS      12              /* End of OS-specific */
++#define STT_LOPROC    13              /* Start of processor-specific */
++#define STT_HIPROC    15              /* End of processor-specific */
++
++
++/* Symbol table indices are found in the hash buckets and chain table
++   of a symbol hash table section.  This special index value indicates
++   the end of a chain, meaning no further symbols are found in that bucket.  */
++
++#define STN_UNDEF     0               /* End of a chain.  */
++
++
++/* How to extract and insert information held in the st_other field.  */
++
++#define ELF32_ST_VISIBILITY(o)        ((o) & 0x03)
++
++/* For ELF64 the definitions are the same.  */
++#define ELF64_ST_VISIBILITY(o)        ELF32_ST_VISIBILITY (o)
++
++/* Symbol visibility specification encoded in the st_other field.  */
++#define STV_DEFAULT   0               /* Default symbol visibility rules */
++#define STV_INTERNAL  1               /* Processor specific hidden class */
++#define STV_HIDDEN    2               /* Sym unavailable in other modules */
++#define STV_PROTECTED 3               /* Not preemptible, not exported */
++
++
++/* Relocation table entry without addend (in section of type SHT_REL).  */
++
++typedef struct
++{
++  Elf32_Addr  r_offset;               /* Address */
++  Elf32_Word  r_info;                 /* Relocation type and symbol index */
++} Elf32_Rel;
++
++/* I have seen two different definitions of the Elf64_Rel and
++   Elf64_Rela structures, so we'll leave them out until Novell (or
++   whoever) gets their act together.  */
++/* The following, at least, is used on Sparc v9, MIPS, and Alpha.  */
++
++typedef struct
++{
++  Elf64_Addr  r_offset;               /* Address */
++  Elf64_Xword r_info;                 /* Relocation type and symbol index */
++} Elf64_Rel;
++
++/* Relocation table entry with addend (in section of type SHT_RELA).  */
++
++typedef struct
++{
++  Elf32_Addr  r_offset;               /* Address */
++  Elf32_Word  r_info;                 /* Relocation type and symbol index */
++  Elf32_Sword r_addend;               /* Addend */
++} Elf32_Rela;
++
++typedef struct
++{
++  Elf64_Addr  r_offset;               /* Address */
++  Elf64_Xword r_info;                 /* Relocation type and symbol index */
++  Elf64_Sxword        r_addend;               /* Addend */
++} Elf64_Rela;
++
++/* How to extract and insert information held in the r_info field.  */
++
++#define ELF32_R_SYM(val)              ((val) >> 8)
++#define ELF32_R_TYPE(val)             ((val) & 0xff)
++#define ELF32_R_INFO(sym, type)               (((sym) << 8) + ((type) & 0xff))
++
++#define ELF64_R_SYM(i)                        ((i) >> 32)
++#define ELF64_R_TYPE(i)                       ((i) & 0xffffffff)
++#define ELF64_R_INFO(sym,type)                ((((Elf64_Xword) (sym)) << 32) + (type))
++
++/* Program segment header.  */
++
++typedef struct
++{
++  Elf32_Word  p_type;                 /* Segment type */
++  Elf32_Off   p_offset;               /* Segment file offset */
++  Elf32_Addr  p_vaddr;                /* Segment virtual address */
++  Elf32_Addr  p_paddr;                /* Segment physical address */
++  Elf32_Word  p_filesz;               /* Segment size in file */
++  Elf32_Word  p_memsz;                /* Segment size in memory */
++  Elf32_Word  p_flags;                /* Segment flags */
++  Elf32_Word  p_align;                /* Segment alignment */
++} Elf32_Phdr;
++
++typedef struct
++{
++  Elf64_Word  p_type;                 /* Segment type */
++  Elf64_Word  p_flags;                /* Segment flags */
++  Elf64_Off   p_offset;               /* Segment file offset */
++  Elf64_Addr  p_vaddr;                /* Segment virtual address */
++  Elf64_Addr  p_paddr;                /* Segment physical address */
++  Elf64_Xword p_filesz;               /* Segment size in file */
++  Elf64_Xword p_memsz;                /* Segment size in memory */
++  Elf64_Xword p_align;                /* Segment alignment */
++} Elf64_Phdr;
++
++/* Special value for e_phnum.  This indicates that the real number of
++   program headers is too large to fit into e_phnum.  Instead the real
++   value is in the field sh_info of section 0.  */
++
++#define PN_XNUM               0xffff
++
++/* Legal values for p_type (segment type).  */
++
++#define       PT_NULL         0               /* Program header table entry unused */
++#define PT_LOAD               1               /* Loadable program segment */
++#define PT_DYNAMIC    2               /* Dynamic linking information */
++#define PT_INTERP     3               /* Program interpreter */
++#define PT_NOTE               4               /* Auxiliary information */
++#define PT_SHLIB      5               /* Reserved */
++#define PT_PHDR               6               /* Entry for header table itself */
++#define PT_TLS                7               /* Thread-local storage segment */
++#define       PT_NUM          8               /* Number of defined types */
++#define PT_LOOS               0x60000000      /* Start of OS-specific */
++#define PT_GNU_EH_FRAME       0x6474e550      /* GCC .eh_frame_hdr segment */
++#define PT_GNU_STACK  0x6474e551      /* Indicates stack executability */
++#define PT_GNU_RELRO  0x6474e552      /* Read-only after relocation */
++#define PT_LOSUNW     0x6ffffffa
++#define PT_SUNWBSS    0x6ffffffa      /* Sun Specific segment */
++#define PT_SUNWSTACK  0x6ffffffb      /* Stack segment */
++#define PT_HISUNW     0x6fffffff
++#define PT_HIOS               0x6fffffff      /* End of OS-specific */
++#define PT_LOPROC     0x70000000      /* Start of processor-specific */
++#define PT_HIPROC     0x7fffffff      /* End of processor-specific */
++
++/* Legal values for p_flags (segment flags).  */
++
++#define PF_X          (1 << 0)        /* Segment is executable */
++#define PF_W          (1 << 1)        /* Segment is writable */
++#define PF_R          (1 << 2)        /* Segment is readable */
++#define PF_MASKOS     0x0ff00000      /* OS-specific */
++#define PF_MASKPROC   0xf0000000      /* Processor-specific */
++
++/* Legal values for note segment descriptor types for core files. */
++
++#define NT_PRSTATUS   1               /* Contains copy of prstatus struct */
++#define NT_FPREGSET   2               /* Contains copy of fpregset struct */
++#define NT_PRPSINFO   3               /* Contains copy of prpsinfo struct */
++#define NT_PRXREG     4               /* Contains copy of prxregset struct */
++#define NT_TASKSTRUCT 4               /* Contains copy of task structure */
++#define NT_PLATFORM   5               /* String from sysinfo(SI_PLATFORM) */
++#define NT_AUXV               6               /* Contains copy of auxv array */
++#define NT_GWINDOWS   7               /* Contains copy of gwindows struct */
++#define NT_ASRS               8               /* Contains copy of asrset struct */
++#define NT_PSTATUS    10              /* Contains copy of pstatus struct */
++#define NT_PSINFO     13              /* Contains copy of psinfo struct */
++#define NT_PRCRED     14              /* Contains copy of prcred struct */
++#define NT_UTSNAME    15              /* Contains copy of utsname struct */
++#define NT_LWPSTATUS  16              /* Contains copy of lwpstatus struct */
++#define NT_LWPSINFO   17              /* Contains copy of lwpinfo struct */
++#define NT_PRFPXREG   20              /* Contains copy of fprxregset struct */
++#define NT_PRXFPREG   0x46e62b7f      /* Contains copy of user_fxsr_struct */
++#define NT_PPC_VMX    0x100           /* PowerPC Altivec/VMX registers */
++#define NT_PPC_SPE    0x101           /* PowerPC SPE/EVR registers */
++#define NT_PPC_VSX    0x102           /* PowerPC VSX registers */
++#define NT_386_TLS    0x200           /* i386 TLS slots (struct user_desc) */
++#define NT_386_IOPERM 0x201           /* x86 io permission bitmap (1=deny) */
++#define NT_X86_XSTATE 0x202           /* x86 extended state using xsave */
++
++/* Legal values for the note segment descriptor types for object files.  */
++
++#define NT_VERSION    1               /* Contains a version string.  */
++
++
++/* Dynamic section entry.  */
++
++typedef struct
++{
++  Elf32_Sword d_tag;                  /* Dynamic entry type */
++  union
++    {
++      Elf32_Word d_val;                       /* Integer value */
++      Elf32_Addr d_ptr;                       /* Address value */
++    } d_un;
++} Elf32_Dyn;
++
++typedef struct
++{
++  Elf64_Sxword        d_tag;                  /* Dynamic entry type */
++  union
++    {
++      Elf64_Xword d_val;              /* Integer value */
++      Elf64_Addr d_ptr;                       /* Address value */
++    } d_un;
++} Elf64_Dyn;
++
++/* Legal values for d_tag (dynamic entry type).  */
++
++#define DT_NULL               0               /* Marks end of dynamic section */
++#define DT_NEEDED     1               /* Name of needed library */
++#define DT_PLTRELSZ   2               /* Size in bytes of PLT relocs */
++#define DT_PLTGOT     3               /* Processor defined value */
++#define DT_HASH               4               /* Address of symbol hash table */
++#define DT_STRTAB     5               /* Address of string table */
++#define DT_SYMTAB     6               /* Address of symbol table */
++#define DT_RELA               7               /* Address of Rela relocs */
++#define DT_RELASZ     8               /* Total size of Rela relocs */
++#define DT_RELAENT    9               /* Size of one Rela reloc */
++#define DT_STRSZ      10              /* Size of string table */
++#define DT_SYMENT     11              /* Size of one symbol table entry */
++#define DT_INIT               12              /* Address of init function */
++#define DT_FINI               13              /* Address of termination function */
++#define DT_SONAME     14              /* Name of shared object */
++#define DT_RPATH      15              /* Library search path (deprecated) */
++#define DT_SYMBOLIC   16              /* Start symbol search here */
++#define DT_REL                17              /* Address of Rel relocs */
++#define DT_RELSZ      18              /* Total size of Rel relocs */
++#define DT_RELENT     19              /* Size of one Rel reloc */
++#define DT_PLTREL     20              /* Type of reloc in PLT */
++#define DT_DEBUG      21              /* For debugging; unspecified */
++#define DT_TEXTREL    22              /* Reloc might modify .text */
++#define DT_JMPREL     23              /* Address of PLT relocs */
++#define       DT_BIND_NOW     24              /* Process relocations of object */
++#define       DT_INIT_ARRAY   25              /* Array with addresses of init fct */
++#define       DT_FINI_ARRAY   26              /* Array with addresses of fini fct */
++#define       DT_INIT_ARRAYSZ 27              /* Size in bytes of DT_INIT_ARRAY */
++#define       DT_FINI_ARRAYSZ 28              /* Size in bytes of DT_FINI_ARRAY */
++#define DT_RUNPATH    29              /* Library search path */
++#define DT_FLAGS      30              /* Flags for the object being loaded */
++#define DT_ENCODING   32              /* Start of encoded range */
++#define DT_PREINIT_ARRAY 32           /* Array with addresses of preinit fct*/
++#define DT_PREINIT_ARRAYSZ 33         /* size in bytes of DT_PREINIT_ARRAY */
++#define       DT_NUM          34              /* Number used */
++#define DT_LOOS               0x6000000d      /* Start of OS-specific */
++#define DT_HIOS               0x6ffff000      /* End of OS-specific */
++#define DT_LOPROC     0x70000000      /* Start of processor-specific */
++#define DT_HIPROC     0x7fffffff      /* End of processor-specific */
++#define       DT_PROCNUM      DT_MIPS_NUM     /* Most used by any processor */
++
++/* DT_* entries which fall between DT_VALRNGHI & DT_VALRNGLO use the
++   Dyn.d_un.d_val field of the Elf*_Dyn structure.  This follows Sun's
++   approach.  */
++#define DT_VALRNGLO   0x6ffffd00
++#define DT_GNU_PRELINKED 0x6ffffdf5   /* Prelinking timestamp */
++#define DT_GNU_CONFLICTSZ 0x6ffffdf6  /* Size of conflict section */
++#define DT_GNU_LIBLISTSZ 0x6ffffdf7   /* Size of library list */
++#define DT_CHECKSUM   0x6ffffdf8
++#define DT_PLTPADSZ   0x6ffffdf9
++#define DT_MOVEENT    0x6ffffdfa
++#define DT_MOVESZ     0x6ffffdfb
++#define DT_FEATURE_1  0x6ffffdfc      /* Feature selection (DTF_*).  */
++#define DT_POSFLAG_1  0x6ffffdfd      /* Flags for DT_* entries, effecting
++                                         the following DT_* entry.  */
++#define DT_SYMINSZ    0x6ffffdfe      /* Size of syminfo table (in bytes) */
++#define DT_SYMINENT   0x6ffffdff      /* Entry size of syminfo */
++#define DT_VALRNGHI   0x6ffffdff
++#define DT_VALTAGIDX(tag)     (DT_VALRNGHI - (tag))   /* Reverse order! */
++#define DT_VALNUM 12
++
++/* DT_* entries which fall between DT_ADDRRNGHI & DT_ADDRRNGLO use the
++   Dyn.d_un.d_ptr field of the Elf*_Dyn structure.
++
++   If any adjustment is made to the ELF object after it has been
++   built these entries will need to be adjusted.  */
++#define DT_ADDRRNGLO  0x6ffffe00
++#define DT_GNU_HASH   0x6ffffef5      /* GNU-style hash table.  */
++#define DT_TLSDESC_PLT        0x6ffffef6
++#define DT_TLSDESC_GOT        0x6ffffef7
++#define DT_GNU_CONFLICT       0x6ffffef8      /* Start of conflict section */
++#define DT_GNU_LIBLIST        0x6ffffef9      /* Library list */
++#define DT_CONFIG     0x6ffffefa      /* Configuration information.  */
++#define DT_DEPAUDIT   0x6ffffefb      /* Dependency auditing.  */
++#define DT_AUDIT      0x6ffffefc      /* Object auditing.  */
++#define       DT_PLTPAD       0x6ffffefd      /* PLT padding.  */
++#define       DT_MOVETAB      0x6ffffefe      /* Move table.  */
++#define DT_SYMINFO    0x6ffffeff      /* Syminfo table.  */
++#define DT_ADDRRNGHI  0x6ffffeff
++#define DT_ADDRTAGIDX(tag)    (DT_ADDRRNGHI - (tag))  /* Reverse order! */
++#define DT_ADDRNUM 11
++
++/* The versioning entry types.  The next are defined as part of the
++   GNU extension.  */
++#define DT_VERSYM     0x6ffffff0
++
++#define DT_RELACOUNT  0x6ffffff9
++#define DT_RELCOUNT   0x6ffffffa
++
++/* These were chosen by Sun.  */
++#define DT_FLAGS_1    0x6ffffffb      /* State flags, see DF_1_* below.  */
++#define       DT_VERDEF       0x6ffffffc      /* Address of version definition
++                                         table */
++#define       DT_VERDEFNUM    0x6ffffffd      /* Number of version definitions */
++#define       DT_VERNEED      0x6ffffffe      /* Address of table with needed
++                                         versions */
++#define       DT_VERNEEDNUM   0x6fffffff      /* Number of needed versions */
++#define DT_VERSIONTAGIDX(tag) (DT_VERNEEDNUM - (tag)) /* Reverse order! */
++#define DT_VERSIONTAGNUM 16
++
++/* Sun added these machine-independent extensions in the "processor-specific"
++   range.  Be compatible.  */
++#define DT_AUXILIARY    0x7ffffffd      /* Shared object to load before self */
++#define DT_FILTER       0x7fffffff      /* Shared object to get values from */
++#define DT_EXTRATAGIDX(tag)   ((Elf32_Word)-((Elf32_Sword) (tag) <<1>>1)-1)
++#define DT_EXTRANUM   3
++
++/* Values of `d_un.d_val' in the DT_FLAGS entry.  */
++#define DF_ORIGIN     0x00000001      /* Object may use DF_ORIGIN */
++#define DF_SYMBOLIC   0x00000002      /* Symbol resolutions starts here */
++#define DF_TEXTREL    0x00000004      /* Object contains text relocations */
++#define DF_BIND_NOW   0x00000008      /* No lazy binding for this object */
++#define DF_STATIC_TLS 0x00000010      /* Module uses the static TLS model */
++
++/* State flags selectable in the `d_un.d_val' element of the DT_FLAGS_1
++   entry in the dynamic section.  */
++#define DF_1_NOW      0x00000001      /* Set RTLD_NOW for this object.  */
++#define DF_1_GLOBAL   0x00000002      /* Set RTLD_GLOBAL for this object.  */
++#define DF_1_GROUP    0x00000004      /* Set RTLD_GROUP for this object.  */
++#define DF_1_NODELETE 0x00000008      /* Set RTLD_NODELETE for this object.*/
++#define DF_1_LOADFLTR 0x00000010      /* Trigger filtee loading at runtime.*/
++#define DF_1_INITFIRST        0x00000020      /* Set RTLD_INITFIRST for this object*/
++#define DF_1_NOOPEN   0x00000040      /* Set RTLD_NOOPEN for this object.  */
++#define DF_1_ORIGIN   0x00000080      /* $ORIGIN must be handled.  */
++#define DF_1_DIRECT   0x00000100      /* Direct binding enabled.  */
++#define DF_1_TRANS    0x00000200
++#define DF_1_INTERPOSE        0x00000400      /* Object is used to interpose.  */
++#define DF_1_NODEFLIB 0x00000800      /* Ignore default lib search path.  */
++#define DF_1_NODUMP   0x00001000      /* Object can't be dldump'ed.  */
++#define DF_1_CONFALT  0x00002000      /* Configuration alternative created.*/
++#define DF_1_ENDFILTEE        0x00004000      /* Filtee terminates filters search. */
++#define       DF_1_DISPRELDNE 0x00008000      /* Disp reloc applied at build time. */
++#define       DF_1_DISPRELPND 0x00010000      /* Disp reloc applied at run-time.  */
++
++/* Flags for the feature selection in DT_FEATURE_1.  */
++#define DTF_1_PARINIT 0x00000001
++#define DTF_1_CONFEXP 0x00000002
++
++/* Flags in the DT_POSFLAG_1 entry effecting only the next DT_* entry.  */
++#define DF_P1_LAZYLOAD        0x00000001      /* Lazyload following object.  */
++#define DF_P1_GROUPPERM       0x00000002      /* Symbols from next object are not
++                                         generally available.  */
++
++/* Version definition sections.  */
++
++typedef struct
++{
++  Elf32_Half  vd_version;             /* Version revision */
++  Elf32_Half  vd_flags;               /* Version information */
++  Elf32_Half  vd_ndx;                 /* Version Index */
++  Elf32_Half  vd_cnt;                 /* Number of associated aux entries */
++  Elf32_Word  vd_hash;                /* Version name hash value */
++  Elf32_Word  vd_aux;                 /* Offset in bytes to verdaux array */
++  Elf32_Word  vd_next;                /* Offset in bytes to next verdef
++                                         entry */
++} Elf32_Verdef;
++
++typedef struct
++{
++  Elf64_Half  vd_version;             /* Version revision */
++  Elf64_Half  vd_flags;               /* Version information */
++  Elf64_Half  vd_ndx;                 /* Version Index */
++  Elf64_Half  vd_cnt;                 /* Number of associated aux entries */
++  Elf64_Word  vd_hash;                /* Version name hash value */
++  Elf64_Word  vd_aux;                 /* Offset in bytes to verdaux array */
++  Elf64_Word  vd_next;                /* Offset in bytes to next verdef
++                                         entry */
++} Elf64_Verdef;
++
++
++/* Legal values for vd_version (version revision).  */
++#define VER_DEF_NONE  0               /* No version */
++#define VER_DEF_CURRENT       1               /* Current version */
++#define VER_DEF_NUM   2               /* Given version number */
++
++/* Legal values for vd_flags (version information flags).  */
++#define VER_FLG_BASE  0x1             /* Version definition of file itself */
++#define VER_FLG_WEAK  0x2             /* Weak version identifier */
++
++/* Versym symbol index values.  */
++#define       VER_NDX_LOCAL           0       /* Symbol is local.  */
++#define       VER_NDX_GLOBAL          1       /* Symbol is global.  */
++#define       VER_NDX_LORESERVE       0xff00  /* Beginning of reserved entries.  */
++#define       VER_NDX_ELIMINATE       0xff01  /* Symbol is to be eliminated.  */
++
++/* Auxialiary version information.  */
++
++typedef struct
++{
++  Elf32_Word  vda_name;               /* Version or dependency names */
++  Elf32_Word  vda_next;               /* Offset in bytes to next verdaux
++                                         entry */
++} Elf32_Verdaux;
++
++typedef struct
++{
++  Elf64_Word  vda_name;               /* Version or dependency names */
++  Elf64_Word  vda_next;               /* Offset in bytes to next verdaux
++                                         entry */
++} Elf64_Verdaux;
++
++
++/* Version dependency section.  */
++
++typedef struct
++{
++  Elf32_Half  vn_version;             /* Version of structure */
++  Elf32_Half  vn_cnt;                 /* Number of associated aux entries */
++  Elf32_Word  vn_file;                /* Offset of filename for this
++                                         dependency */
++  Elf32_Word  vn_aux;                 /* Offset in bytes to vernaux array */
++  Elf32_Word  vn_next;                /* Offset in bytes to next verneed
++                                         entry */
++} Elf32_Verneed;
++
++typedef struct
++{
++  Elf64_Half  vn_version;             /* Version of structure */
++  Elf64_Half  vn_cnt;                 /* Number of associated aux entries */
++  Elf64_Word  vn_file;                /* Offset of filename for this
++                                         dependency */
++  Elf64_Word  vn_aux;                 /* Offset in bytes to vernaux array */
++  Elf64_Word  vn_next;                /* Offset in bytes to next verneed
++                                         entry */
++} Elf64_Verneed;
++
++
++/* Legal values for vn_version (version revision).  */
++#define VER_NEED_NONE  0              /* No version */
++#define VER_NEED_CURRENT 1            /* Current version */
++#define VER_NEED_NUM   2              /* Given version number */
++
++/* Auxiliary needed version information.  */
++
++typedef struct
++{
++  Elf32_Word  vna_hash;               /* Hash value of dependency name */
++  Elf32_Half  vna_flags;              /* Dependency specific information */
++  Elf32_Half  vna_other;              /* Unused */
++  Elf32_Word  vna_name;               /* Dependency name string offset */
++  Elf32_Word  vna_next;               /* Offset in bytes to next vernaux
++                                         entry */
++} Elf32_Vernaux;
++
++typedef struct
++{
++  Elf64_Word  vna_hash;               /* Hash value of dependency name */
++  Elf64_Half  vna_flags;              /* Dependency specific information */
++  Elf64_Half  vna_other;              /* Unused */
++  Elf64_Word  vna_name;               /* Dependency name string offset */
++  Elf64_Word  vna_next;               /* Offset in bytes to next vernaux
++                                         entry */
++} Elf64_Vernaux;
++
++
++/* Legal values for vna_flags.  */
++#define VER_FLG_WEAK  0x2             /* Weak version identifier */
++
++
++/* Auxiliary vector.  */
++
++/* This vector is normally only used by the program interpreter.  The
++   usual definition in an ABI supplement uses the name auxv_t.  The
++   vector is not usually defined in a standard <elf.h> file, but it
++   can't hurt.  We rename it to avoid conflicts.  The sizes of these
++   types are an arrangement between the exec server and the program
++   interpreter, so we don't fully specify them here.  */
++
++typedef struct
++{
++  uint32_t a_type;            /* Entry type */
++  union
++    {
++      uint32_t a_val;         /* Integer value */
++      /* We use to have pointer elements added here.  We cannot do that,
++       though, since it does not work when using 32-bit definitions
++       on 64-bit platforms and vice versa.  */
++    } a_un;
++} Elf32_auxv_t;
++
++typedef struct
++{
++  uint64_t a_type;            /* Entry type */
++  union
++    {
++      uint64_t a_val;         /* Integer value */
++      /* We use to have pointer elements added here.  We cannot do that,
++       though, since it does not work when using 32-bit definitions
++       on 64-bit platforms and vice versa.  */
++    } a_un;
++} Elf64_auxv_t;
++
++/* Legal values for a_type (entry type).  */
++
++#define AT_NULL               0               /* End of vector */
++#define AT_IGNORE     1               /* Entry should be ignored */
++#define AT_EXECFD     2               /* File descriptor of program */
++#define AT_PHDR               3               /* Program headers for program */
++#define AT_PHENT      4               /* Size of program header entry */
++#define AT_PHNUM      5               /* Number of program headers */
++#define AT_PAGESZ     6               /* System page size */
++#define AT_BASE               7               /* Base address of interpreter */
++#define AT_FLAGS      8               /* Flags */
++#define AT_ENTRY      9               /* Entry point of program */
++#define AT_NOTELF     10              /* Program is not ELF */
++#define AT_UID                11              /* Real uid */
++#define AT_EUID               12              /* Effective uid */
++#define AT_GID                13              /* Real gid */
++#define AT_EGID               14              /* Effective gid */
++#define AT_CLKTCK     17              /* Frequency of times() */
++
++/* Some more special a_type values describing the hardware.  */
++#define AT_PLATFORM   15              /* String identifying platform.  */
++#define AT_HWCAP      16              /* Machine dependent hints about
++                                         processor capabilities.  */
++
++/* This entry gives some information about the FPU initialization
++   performed by the kernel.  */
++#define AT_FPUCW      18              /* Used FPU control word.  */
++
++/* Cache block sizes.  */
++#define AT_DCACHEBSIZE        19              /* Data cache block size.  */
++#define AT_ICACHEBSIZE        20              /* Instruction cache block size.  */
++#define AT_UCACHEBSIZE        21              /* Unified cache block size.  */
++
++/* A special ignored value for PPC, used by the kernel to control the
++   interpretation of the AUXV. Must be > 16.  */
++#define AT_IGNOREPPC  22              /* Entry should be ignored.  */
++
++#define       AT_SECURE       23              /* Boolean, was exec setuid-like?  */
++
++#define AT_BASE_PLATFORM 24           /* String identifying real platforms.*/
++
++#define AT_RANDOM     25              /* Address of 16 random bytes.  */
++
++#define AT_EXECFN     31              /* Filename of executable.  */
++
++/* Pointer to the global system page used for system calls and other
++   nice things.  */
++#define AT_SYSINFO    32
++#define AT_SYSINFO_EHDR       33
++
++/* Shapes of the caches.  Bits 0-3 contains associativity; bits 4-7 contains
++   log2 of line size; mask those to get cache size.  */
++#define AT_L1I_CACHESHAPE     34
++#define AT_L1D_CACHESHAPE     35
++#define AT_L2_CACHESHAPE      36
++#define AT_L3_CACHESHAPE      37
++
++/* Note section contents.  Each entry in the note section begins with
++   a header of a fixed form.  */
++
++typedef struct
++{
++  Elf32_Word n_namesz;                        /* Length of the note's name.  */
++  Elf32_Word n_descsz;                        /* Length of the note's descriptor.  */
++  Elf32_Word n_type;                  /* Type of the note.  */
++} Elf32_Nhdr;
++
++typedef struct
++{
++  Elf64_Word n_namesz;                        /* Length of the note's name.  */
++  Elf64_Word n_descsz;                        /* Length of the note's descriptor.  */
++  Elf64_Word n_type;                  /* Type of the note.  */
++} Elf64_Nhdr;
++
++/* Known names of notes.  */
++
++/* Solaris entries in the note section have this name.  */
++#define ELF_NOTE_SOLARIS      "SUNW Solaris"
++
++/* Note entries for GNU systems have this name.  */
++#define ELF_NOTE_GNU          "GNU"
++
++
++/* Defined types of notes for Solaris.  */
++
++/* Value of descriptor (one word) is desired pagesize for the binary.  */
++#define ELF_NOTE_PAGESIZE_HINT        1
++
++
++/* Defined note types for GNU systems.  */
++
++/* ABI information.  The descriptor consists of words:
++   word 0: OS descriptor
++   word 1: major version of the ABI
++   word 2: minor version of the ABI
++   word 3: subminor version of the ABI
++*/
++#define NT_GNU_ABI_TAG        1
++#define ELF_NOTE_ABI  NT_GNU_ABI_TAG /* Old name.  */
++
++/* Known OSes.  These values can appear in word 0 of an
++   NT_GNU_ABI_TAG note section entry.  */
++#define ELF_NOTE_OS_LINUX     0
++#define ELF_NOTE_OS_GNU               1
++#define ELF_NOTE_OS_SOLARIS2  2
++#define ELF_NOTE_OS_FREEBSD   3
++
++/* Synthetic hwcap information.  The descriptor begins with two words:
++   word 0: number of entries
++   word 1: bitmask of enabled entries
++   Then follow variable-length entries, one byte followed by a
++   '\0'-terminated hwcap name string.  The byte gives the bit
++   number to test if enabled, (1U << bit) & bitmask.  */
++#define NT_GNU_HWCAP  2
++
++/* Build ID bits as generated by ld --build-id.
++   The descriptor consists of any nonzero number of bytes.  */
++#define NT_GNU_BUILD_ID       3
++
++/* Version note generated by GNU gold containing a version string.  */
++#define NT_GNU_GOLD_VERSION   4
++
++
++/* Move records.  */
++typedef struct
++{
++  Elf32_Xword m_value;                /* Symbol value.  */
++  Elf32_Word m_info;          /* Size and index.  */
++  Elf32_Word m_poffset;               /* Symbol offset.  */
++  Elf32_Half m_repeat;                /* Repeat count.  */
++  Elf32_Half m_stride;                /* Stride info.  */
++} Elf32_Move;
++
++typedef struct
++{
++  Elf64_Xword m_value;                /* Symbol value.  */
++  Elf64_Xword m_info;         /* Size and index.  */
++  Elf64_Xword m_poffset;      /* Symbol offset.  */
++  Elf64_Half m_repeat;                /* Repeat count.  */
++  Elf64_Half m_stride;                /* Stride info.  */
++} Elf64_Move;
++
++/* Macro to construct move records.  */
++#define ELF32_M_SYM(info)     ((info) >> 8)
++#define ELF32_M_SIZE(info)    ((unsigned char) (info))
++#define ELF32_M_INFO(sym, size)       (((sym) << 8) + (unsigned char) (size))
++
++#define ELF64_M_SYM(info)     ELF32_M_SYM (info)
++#define ELF64_M_SIZE(info)    ELF32_M_SIZE (info)
++#define ELF64_M_INFO(sym, size)       ELF32_M_INFO (sym, size)
++
++
++/* Motorola 68k specific definitions.  */
++
++/* Values for Elf32_Ehdr.e_flags.  */
++#define EF_CPU32      0x00810000
++
++/* m68k relocs.  */
++
++#define R_68K_NONE    0               /* No reloc */
++#define R_68K_32      1               /* Direct 32 bit  */
++#define R_68K_16      2               /* Direct 16 bit  */
++#define R_68K_8               3               /* Direct 8 bit  */
++#define R_68K_PC32    4               /* PC relative 32 bit */
++#define R_68K_PC16    5               /* PC relative 16 bit */
++#define R_68K_PC8     6               /* PC relative 8 bit */
++#define R_68K_GOT32   7               /* 32 bit PC relative GOT entry */
++#define R_68K_GOT16   8               /* 16 bit PC relative GOT entry */
++#define R_68K_GOT8    9               /* 8 bit PC relative GOT entry */
++#define R_68K_GOT32O  10              /* 32 bit GOT offset */
++#define R_68K_GOT16O  11              /* 16 bit GOT offset */
++#define R_68K_GOT8O   12              /* 8 bit GOT offset */
++#define R_68K_PLT32   13              /* 32 bit PC relative PLT address */
++#define R_68K_PLT16   14              /* 16 bit PC relative PLT address */
++#define R_68K_PLT8    15              /* 8 bit PC relative PLT address */
++#define R_68K_PLT32O  16              /* 32 bit PLT offset */
++#define R_68K_PLT16O  17              /* 16 bit PLT offset */
++#define R_68K_PLT8O   18              /* 8 bit PLT offset */
++#define R_68K_COPY    19              /* Copy symbol at runtime */
++#define R_68K_GLOB_DAT        20              /* Create GOT entry */
++#define R_68K_JMP_SLOT        21              /* Create PLT entry */
++#define R_68K_RELATIVE        22              /* Adjust by program base */
++#define R_68K_TLS_GD32      25          /* 32 bit GOT offset for GD */
++#define R_68K_TLS_GD16      26          /* 16 bit GOT offset for GD */
++#define R_68K_TLS_GD8       27          /* 8 bit GOT offset for GD */
++#define R_68K_TLS_LDM32     28          /* 32 bit GOT offset for LDM */
++#define R_68K_TLS_LDM16     29          /* 16 bit GOT offset for LDM */
++#define R_68K_TLS_LDM8      30          /* 8 bit GOT offset for LDM */
++#define R_68K_TLS_LDO32     31          /* 32 bit module-relative offset */
++#define R_68K_TLS_LDO16     32          /* 16 bit module-relative offset */
++#define R_68K_TLS_LDO8      33          /* 8 bit module-relative offset */
++#define R_68K_TLS_IE32      34          /* 32 bit GOT offset for IE */
++#define R_68K_TLS_IE16      35          /* 16 bit GOT offset for IE */
++#define R_68K_TLS_IE8       36          /* 8 bit GOT offset for IE */
++#define R_68K_TLS_LE32      37          /* 32 bit offset relative to
++                                         static TLS block */
++#define R_68K_TLS_LE16      38          /* 16 bit offset relative to
++                                         static TLS block */
++#define R_68K_TLS_LE8       39          /* 8 bit offset relative to
++                                         static TLS block */
++#define R_68K_TLS_DTPMOD32  40          /* 32 bit module number */
++#define R_68K_TLS_DTPREL32  41          /* 32 bit module-relative offset */
++#define R_68K_TLS_TPREL32   42          /* 32 bit TP-relative offset */
++/* Keep this the last entry.  */
++#define R_68K_NUM     43
++
++/* Intel 80386 specific definitions.  */
++
++/* i386 relocs.  */
++
++#define R_386_NONE       0            /* No reloc */
++#define R_386_32         1            /* Direct 32 bit  */
++#define R_386_PC32       2            /* PC relative 32 bit */
++#define R_386_GOT32      3            /* 32 bit GOT entry */
++#define R_386_PLT32      4            /* 32 bit PLT address */
++#define R_386_COPY       5            /* Copy symbol at runtime */
++#define R_386_GLOB_DAT           6            /* Create GOT entry */
++#define R_386_JMP_SLOT           7            /* Create PLT entry */
++#define R_386_RELATIVE           8            /* Adjust by program base */
++#define R_386_GOTOFF     9            /* 32 bit offset to GOT */
++#define R_386_GOTPC      10           /* 32 bit PC relative offset to GOT */
++#define R_386_32PLT      11
++#define R_386_TLS_TPOFF          14           /* Offset in static TLS block */
++#define R_386_TLS_IE     15           /* Address of GOT entry for static TLS
++                                         block offset */
++#define R_386_TLS_GOTIE          16           /* GOT entry for static TLS block
++                                         offset */
++#define R_386_TLS_LE     17           /* Offset relative to static TLS
++                                         block */
++#define R_386_TLS_GD     18           /* Direct 32 bit for GNU version of
++                                         general dynamic thread local data */
++#define R_386_TLS_LDM    19           /* Direct 32 bit for GNU version of
++                                         local dynamic thread local data
++                                         in LE code */
++#define R_386_16         20
++#define R_386_PC16       21
++#define R_386_8                  22
++#define R_386_PC8        23
++#define R_386_TLS_GD_32          24           /* Direct 32 bit for general dynamic
++                                         thread local data */
++#define R_386_TLS_GD_PUSH  25         /* Tag for pushl in GD TLS code */
++#define R_386_TLS_GD_CALL  26         /* Relocation for call to
++                                         __tls_get_addr() */
++#define R_386_TLS_GD_POP   27         /* Tag for popl in GD TLS code */
++#define R_386_TLS_LDM_32   28         /* Direct 32 bit for local dynamic
++                                         thread local data in LE code */
++#define R_386_TLS_LDM_PUSH 29         /* Tag for pushl in LDM TLS code */
++#define R_386_TLS_LDM_CALL 30         /* Relocation for call to
++                                         __tls_get_addr() in LDM code */
++#define R_386_TLS_LDM_POP  31         /* Tag for popl in LDM TLS code */
++#define R_386_TLS_LDO_32   32         /* Offset relative to TLS block */
++#define R_386_TLS_IE_32          33           /* GOT entry for negated static TLS
++                                         block offset */
++#define R_386_TLS_LE_32          34           /* Negated offset relative to static
++                                         TLS block */
++#define R_386_TLS_DTPMOD32 35         /* ID of module containing symbol */
++#define R_386_TLS_DTPOFF32 36         /* Offset in TLS block */
++#define R_386_TLS_TPOFF32  37         /* Negated offset in static TLS block */
++/* 38? */
++#define R_386_TLS_GOTDESC  39         /* GOT offset for TLS descriptor.  */
++#define R_386_TLS_DESC_CALL 40                /* Marker of call through TLS
++                                         descriptor for
++                                         relaxation.  */
++#define R_386_TLS_DESC     41         /* TLS descriptor containing
++                                         pointer to code and to
++                                         argument, returning the TLS
++                                         offset for the symbol.  */
++#define R_386_IRELATIVE          42           /* Adjust indirectly by program base */
++/* Keep this the last entry.  */
++#define R_386_NUM        43
++
++/* SUN SPARC specific definitions.  */
++
++/* Legal values for ST_TYPE subfield of st_info (symbol type).  */
++
++#define STT_SPARC_REGISTER    13      /* Global register reserved to app. */
++
++/* Values for Elf64_Ehdr.e_flags.  */
++
++#define EF_SPARCV9_MM         3
++#define EF_SPARCV9_TSO                0
++#define EF_SPARCV9_PSO                1
++#define EF_SPARCV9_RMO                2
++#define EF_SPARC_LEDATA               0x800000 /* little endian data */
++#define EF_SPARC_EXT_MASK     0xFFFF00
++#define EF_SPARC_32PLUS               0x000100 /* generic V8+ features */
++#define EF_SPARC_SUN_US1      0x000200 /* Sun UltraSPARC1 extensions */
++#define EF_SPARC_HAL_R1               0x000400 /* HAL R1 extensions */
++#define EF_SPARC_SUN_US3      0x000800 /* Sun UltraSPARCIII extensions */
++
++/* SPARC relocs.  */
++
++#define R_SPARC_NONE          0       /* No reloc */
++#define R_SPARC_8             1       /* Direct 8 bit */
++#define R_SPARC_16            2       /* Direct 16 bit */
++#define R_SPARC_32            3       /* Direct 32 bit */
++#define R_SPARC_DISP8         4       /* PC relative 8 bit */
++#define R_SPARC_DISP16                5       /* PC relative 16 bit */
++#define R_SPARC_DISP32                6       /* PC relative 32 bit */
++#define R_SPARC_WDISP30               7       /* PC relative 30 bit shifted */
++#define R_SPARC_WDISP22               8       /* PC relative 22 bit shifted */
++#define R_SPARC_HI22          9       /* High 22 bit */
++#define R_SPARC_22            10      /* Direct 22 bit */
++#define R_SPARC_13            11      /* Direct 13 bit */
++#define R_SPARC_LO10          12      /* Truncated 10 bit */
++#define R_SPARC_GOT10         13      /* Truncated 10 bit GOT entry */
++#define R_SPARC_GOT13         14      /* 13 bit GOT entry */
++#define R_SPARC_GOT22         15      /* 22 bit GOT entry shifted */
++#define R_SPARC_PC10          16      /* PC relative 10 bit truncated */
++#define R_SPARC_PC22          17      /* PC relative 22 bit shifted */
++#define R_SPARC_WPLT30                18      /* 30 bit PC relative PLT address */
++#define R_SPARC_COPY          19      /* Copy symbol at runtime */
++#define R_SPARC_GLOB_DAT      20      /* Create GOT entry */
++#define R_SPARC_JMP_SLOT      21      /* Create PLT entry */
++#define R_SPARC_RELATIVE      22      /* Adjust by program base */
++#define R_SPARC_UA32          23      /* Direct 32 bit unaligned */
++
++/* Additional Sparc64 relocs.  */
++
++#define R_SPARC_PLT32         24      /* Direct 32 bit ref to PLT entry */
++#define R_SPARC_HIPLT22               25      /* High 22 bit PLT entry */
++#define R_SPARC_LOPLT10               26      /* Truncated 10 bit PLT entry */
++#define R_SPARC_PCPLT32               27      /* PC rel 32 bit ref to PLT entry */
++#define R_SPARC_PCPLT22               28      /* PC rel high 22 bit PLT entry */
++#define R_SPARC_PCPLT10               29      /* PC rel trunc 10 bit PLT entry */
++#define R_SPARC_10            30      /* Direct 10 bit */
++#define R_SPARC_11            31      /* Direct 11 bit */
++#define R_SPARC_64            32      /* Direct 64 bit */
++#define R_SPARC_OLO10         33      /* 10bit with secondary 13bit addend */
++#define R_SPARC_HH22          34      /* Top 22 bits of direct 64 bit */
++#define R_SPARC_HM10          35      /* High middle 10 bits of ... */
++#define R_SPARC_LM22          36      /* Low middle 22 bits of ... */
++#define R_SPARC_PC_HH22               37      /* Top 22 bits of pc rel 64 bit */
++#define R_SPARC_PC_HM10               38      /* High middle 10 bit of ... */
++#define R_SPARC_PC_LM22               39      /* Low miggle 22 bits of ... */
++#define R_SPARC_WDISP16               40      /* PC relative 16 bit shifted */
++#define R_SPARC_WDISP19               41      /* PC relative 19 bit shifted */
++#define R_SPARC_GLOB_JMP      42      /* was part of v9 ABI but was removed */
++#define R_SPARC_7             43      /* Direct 7 bit */
++#define R_SPARC_5             44      /* Direct 5 bit */
++#define R_SPARC_6             45      /* Direct 6 bit */
++#define R_SPARC_DISP64                46      /* PC relative 64 bit */
++#define R_SPARC_PLT64         47      /* Direct 64 bit ref to PLT entry */
++#define R_SPARC_HIX22         48      /* High 22 bit complemented */
++#define R_SPARC_LOX10         49      /* Truncated 11 bit complemented */
++#define R_SPARC_H44           50      /* Direct high 12 of 44 bit */
++#define R_SPARC_M44           51      /* Direct mid 22 of 44 bit */
++#define R_SPARC_L44           52      /* Direct low 10 of 44 bit */
++#define R_SPARC_REGISTER      53      /* Global register usage */
++#define R_SPARC_UA64          54      /* Direct 64 bit unaligned */
++#define R_SPARC_UA16          55      /* Direct 16 bit unaligned */
++#define R_SPARC_TLS_GD_HI22   56
++#define R_SPARC_TLS_GD_LO10   57
++#define R_SPARC_TLS_GD_ADD    58
++#define R_SPARC_TLS_GD_CALL   59
++#define R_SPARC_TLS_LDM_HI22  60
++#define R_SPARC_TLS_LDM_LO10  61
++#define R_SPARC_TLS_LDM_ADD   62
++#define R_SPARC_TLS_LDM_CALL  63
++#define R_SPARC_TLS_LDO_HIX22 64
++#define R_SPARC_TLS_LDO_LOX10 65
++#define R_SPARC_TLS_LDO_ADD   66
++#define R_SPARC_TLS_IE_HI22   67
++#define R_SPARC_TLS_IE_LO10   68
++#define R_SPARC_TLS_IE_LD     69
++#define R_SPARC_TLS_IE_LDX    70
++#define R_SPARC_TLS_IE_ADD    71
++#define R_SPARC_TLS_LE_HIX22  72
++#define R_SPARC_TLS_LE_LOX10  73
++#define R_SPARC_TLS_DTPMOD32  74
++#define R_SPARC_TLS_DTPMOD64  75
++#define R_SPARC_TLS_DTPOFF32  76
++#define R_SPARC_TLS_DTPOFF64  77
++#define R_SPARC_TLS_TPOFF32   78
++#define R_SPARC_TLS_TPOFF64   79
++#define R_SPARC_GOTDATA_HIX22 80
++#define R_SPARC_GOTDATA_LOX10 81
++#define R_SPARC_GOTDATA_OP_HIX22      82
++#define R_SPARC_GOTDATA_OP_LOX10      83
++#define R_SPARC_GOTDATA_OP    84
++#define R_SPARC_H34           85
++#define R_SPARC_SIZE32                86
++#define R_SPARC_SIZE64                87
++#define R_SPARC_WDISP10               88
++#define R_SPARC_JMP_IREL      248
++#define R_SPARC_IRELATIVE     249
++#define R_SPARC_GNU_VTINHERIT 250
++#define R_SPARC_GNU_VTENTRY   251
++#define R_SPARC_REV32         252
++/* Keep this the last entry.  */
++#define R_SPARC_NUM           253
++
++/* For Sparc64, legal values for d_tag of Elf64_Dyn.  */
++
++#define DT_SPARC_REGISTER 0x70000001
++#define DT_SPARC_NUM  2
++
++/* MIPS R3000 specific definitions.  */
++
++/* Legal values for e_flags field of Elf32_Ehdr.  */
++
++#define EF_MIPS_NOREORDER   1         /* A .noreorder directive was used */
++#define EF_MIPS_PIC       2           /* Contains PIC code */
++#define EF_MIPS_CPIC      4           /* Uses PIC calling sequence */
++#define EF_MIPS_XGOT      8
++#define EF_MIPS_64BIT_WHIRL 16
++#define EF_MIPS_ABI2      32
++#define EF_MIPS_ABI_ON32    64
++#define EF_MIPS_ARCH      0xf0000000  /* MIPS architecture level */
++
++/* Legal values for MIPS architecture level.  */
++
++#define EF_MIPS_ARCH_1            0x00000000  /* -mips1 code.  */
++#define EF_MIPS_ARCH_2            0x10000000  /* -mips2 code.  */
++#define EF_MIPS_ARCH_3            0x20000000  /* -mips3 code.  */
++#define EF_MIPS_ARCH_4            0x30000000  /* -mips4 code.  */
++#define EF_MIPS_ARCH_5            0x40000000  /* -mips5 code.  */
++#define EF_MIPS_ARCH_32           0x60000000  /* MIPS32 code.  */
++#define EF_MIPS_ARCH_64           0x70000000  /* MIPS64 code.  */
++
++/* The following are non-official names and should not be used.  */
++
++#define E_MIPS_ARCH_1   0x00000000    /* -mips1 code.  */
++#define E_MIPS_ARCH_2   0x10000000    /* -mips2 code.  */
++#define E_MIPS_ARCH_3   0x20000000    /* -mips3 code.  */
++#define E_MIPS_ARCH_4   0x30000000    /* -mips4 code.  */
++#define E_MIPS_ARCH_5   0x40000000    /* -mips5 code.  */
++#define E_MIPS_ARCH_32          0x60000000    /* MIPS32 code.  */
++#define E_MIPS_ARCH_64          0x70000000    /* MIPS64 code.  */
++
++/* Special section indices.  */
++
++#define SHN_MIPS_ACOMMON    0xff00    /* Allocated common symbols */
++#define SHN_MIPS_TEXT     0xff01      /* Allocated test symbols.  */
++#define SHN_MIPS_DATA     0xff02      /* Allocated data symbols.  */
++#define SHN_MIPS_SCOMMON    0xff03    /* Small common symbols */
++#define SHN_MIPS_SUNDEFINED 0xff04    /* Small undefined symbols */
++
++/* Legal values for sh_type field of Elf32_Shdr.  */
++
++#define SHT_MIPS_LIBLIST       0x70000000 /* Shared objects used in link */
++#define SHT_MIPS_MSYM        0x70000001
++#define SHT_MIPS_CONFLICT      0x70000002 /* Conflicting symbols */
++#define SHT_MIPS_GPTAB               0x70000003 /* Global data area sizes */
++#define SHT_MIPS_UCODE               0x70000004 /* Reserved for SGI/MIPS compilers */
++#define SHT_MIPS_DEBUG               0x70000005 /* MIPS ECOFF debugging information*/
++#define SHT_MIPS_REGINFO       0x70000006 /* Register usage information */
++#define SHT_MIPS_PACKAGE       0x70000007
++#define SHT_MIPS_PACKSYM       0x70000008
++#define SHT_MIPS_RELD        0x70000009
++#define SHT_MIPS_IFACE         0x7000000b
++#define SHT_MIPS_CONTENT       0x7000000c
++#define SHT_MIPS_OPTIONS       0x7000000d /* Miscellaneous options.  */
++#define SHT_MIPS_SHDR        0x70000010
++#define SHT_MIPS_FDESC               0x70000011
++#define SHT_MIPS_EXTSYM              0x70000012
++#define SHT_MIPS_DENSE               0x70000013
++#define SHT_MIPS_PDESC               0x70000014
++#define SHT_MIPS_LOCSYM              0x70000015
++#define SHT_MIPS_AUXSYM              0x70000016
++#define SHT_MIPS_OPTSYM              0x70000017
++#define SHT_MIPS_LOCSTR              0x70000018
++#define SHT_MIPS_LINE        0x70000019
++#define SHT_MIPS_RFDESC              0x7000001a
++#define SHT_MIPS_DELTASYM      0x7000001b
++#define SHT_MIPS_DELTAINST     0x7000001c
++#define SHT_MIPS_DELTACLASS    0x7000001d
++#define SHT_MIPS_DWARF         0x7000001e /* DWARF debugging information.  */
++#define SHT_MIPS_DELTADECL     0x7000001f
++#define SHT_MIPS_SYMBOL_LIB    0x70000020
++#define SHT_MIPS_EVENTS              0x70000021 /* Event section.  */
++#define SHT_MIPS_TRANSLATE     0x70000022
++#define SHT_MIPS_PIXIE               0x70000023
++#define SHT_MIPS_XLATE               0x70000024
++#define SHT_MIPS_XLATE_DEBUG   0x70000025
++#define SHT_MIPS_WHIRL               0x70000026
++#define SHT_MIPS_EH_REGION     0x70000027
++#define SHT_MIPS_XLATE_OLD     0x70000028
++#define SHT_MIPS_PDR_EXCEPTION 0x70000029
++
++/* Legal values for sh_flags field of Elf32_Shdr.  */
++
++#define SHF_MIPS_GPREL         0x10000000     /* Must be part of global data area */
++#define SHF_MIPS_MERGE         0x20000000
++#define SHF_MIPS_ADDR  0x40000000
++#define SHF_MIPS_STRINGS 0x80000000
++#define SHF_MIPS_NOSTRIP 0x08000000
++#define SHF_MIPS_LOCAL         0x04000000
++#define SHF_MIPS_NAMES         0x02000000
++#define SHF_MIPS_NODUPE        0x01000000
++
++
++/* Symbol tables.  */
++
++/* MIPS specific values for `st_other'.  */
++#define STO_MIPS_DEFAULT              0x0
++#define STO_MIPS_INTERNAL             0x1
++#define STO_MIPS_HIDDEN                       0x2
++#define STO_MIPS_PROTECTED            0x3
++#define STO_MIPS_PLT                  0x8
++#define STO_MIPS_SC_ALIGN_UNUSED      0xff
++
++/* MIPS specific values for `st_info'.  */
++#define STB_MIPS_SPLIT_COMMON         13
++
++/* Entries found in sections of type SHT_MIPS_GPTAB.  */
++
++typedef union
++{
++  struct
++    {
++      Elf32_Word gt_current_g_value;  /* -G value used for compilation */
++      Elf32_Word gt_unused;           /* Not used */
++    } gt_header;                      /* First entry in section */
++  struct
++    {
++      Elf32_Word gt_g_value;          /* If this value were used for -G */
++      Elf32_Word gt_bytes;            /* This many bytes would be used */
++    } gt_entry;                               /* Subsequent entries in section */
++} Elf32_gptab;
++
++/* Entry found in sections of type SHT_MIPS_REGINFO.  */
++
++typedef struct
++{
++  Elf32_Word  ri_gprmask;             /* General registers used */
++  Elf32_Word  ri_cprmask[4];          /* Coprocessor registers used */
++  Elf32_Sword ri_gp_value;            /* $gp register value */
++} Elf32_RegInfo;
++
++/* Entries found in sections of type SHT_MIPS_OPTIONS.  */
++
++typedef struct
++{
++  unsigned char kind;         /* Determines interpretation of the
++                                 variable part of descriptor.  */
++  unsigned char size;         /* Size of descriptor, including header.  */
++  Elf32_Section section;      /* Section header index of section affected,
++                                 0 for global options.  */
++  Elf32_Word info;            /* Kind-specific information.  */
++} Elf_Options;
++
++/* Values for `kind' field in Elf_Options.  */
++
++#define ODK_NULL      0       /* Undefined.  */
++#define ODK_REGINFO   1       /* Register usage information.  */
++#define ODK_EXCEPTIONS        2       /* Exception processing options.  */
++#define ODK_PAD               3       /* Section padding options.  */
++#define ODK_HWPATCH   4       /* Hardware workarounds performed */
++#define ODK_FILL      5       /* record the fill value used by the linker. */
++#define ODK_TAGS      6       /* reserve space for desktop tools to write. */
++#define ODK_HWAND     7       /* HW workarounds.  'AND' bits when merging. */
++#define ODK_HWOR      8       /* HW workarounds.  'OR' bits when merging.  */
++
++/* Values for `info' in Elf_Options for ODK_EXCEPTIONS entries.  */
++
++#define OEX_FPU_MIN   0x1f    /* FPE's which MUST be enabled.  */
++#define OEX_FPU_MAX   0x1f00  /* FPE's which MAY be enabled.  */
++#define OEX_PAGE0     0x10000 /* page zero must be mapped.  */
++#define OEX_SMM               0x20000 /* Force sequential memory mode?  */
++#define OEX_FPDBUG    0x40000 /* Force floating point debug mode?  */
++#define OEX_PRECISEFP OEX_FPDBUG
++#define OEX_DISMISS   0x80000 /* Dismiss invalid address faults?  */
++
++#define OEX_FPU_INVAL 0x10
++#define OEX_FPU_DIV0  0x08
++#define OEX_FPU_OFLO  0x04
++#define OEX_FPU_UFLO  0x02
++#define OEX_FPU_INEX  0x01
++
++/* Masks for `info' in Elf_Options for an ODK_HWPATCH entry.  */
++
++#define OHW_R4KEOP    0x1     /* R4000 end-of-page patch.  */
++#define OHW_R8KPFETCH 0x2     /* may need R8000 prefetch patch.  */
++#define OHW_R5KEOP    0x4     /* R5000 end-of-page patch.  */
++#define OHW_R5KCVTL   0x8     /* R5000 cvt.[ds].l bug.  clean=1.  */
++
++#define OPAD_PREFIX   0x1
++#define OPAD_POSTFIX  0x2
++#define OPAD_SYMBOL   0x4
++
++/* Entry found in `.options' section.  */
++
++typedef struct
++{
++  Elf32_Word hwp_flags1;      /* Extra flags.  */
++  Elf32_Word hwp_flags2;      /* Extra flags.  */
++} Elf_Options_Hw;
++
++/* Masks for `info' in ElfOptions for ODK_HWAND and ODK_HWOR entries.  */
++
++#define OHWA0_R4KEOP_CHECKED  0x00000001
++#define OHWA1_R4KEOP_CLEAN    0x00000002
++
++/* MIPS relocs.  */
++
++#define R_MIPS_NONE           0       /* No reloc */
++#define R_MIPS_16             1       /* Direct 16 bit */
++#define R_MIPS_32             2       /* Direct 32 bit */
++#define R_MIPS_REL32          3       /* PC relative 32 bit */
++#define R_MIPS_26             4       /* Direct 26 bit shifted */
++#define R_MIPS_HI16           5       /* High 16 bit */
++#define R_MIPS_LO16           6       /* Low 16 bit */
++#define R_MIPS_GPREL16                7       /* GP relative 16 bit */
++#define R_MIPS_LITERAL                8       /* 16 bit literal entry */
++#define R_MIPS_GOT16          9       /* 16 bit GOT entry */
++#define R_MIPS_PC16           10      /* PC relative 16 bit */
++#define R_MIPS_CALL16         11      /* 16 bit GOT entry for function */
++#define R_MIPS_GPREL32                12      /* GP relative 32 bit */
++
++#define R_MIPS_SHIFT5         16
++#define R_MIPS_SHIFT6         17
++#define R_MIPS_64             18
++#define R_MIPS_GOT_DISP               19
++#define R_MIPS_GOT_PAGE               20
++#define R_MIPS_GOT_OFST               21
++#define R_MIPS_GOT_HI16               22
++#define R_MIPS_GOT_LO16               23
++#define R_MIPS_SUB            24
++#define R_MIPS_INSERT_A               25
++#define R_MIPS_INSERT_B               26
++#define R_MIPS_DELETE         27
++#define R_MIPS_HIGHER         28
++#define R_MIPS_HIGHEST                29
++#define R_MIPS_CALL_HI16      30
++#define R_MIPS_CALL_LO16      31
++#define R_MIPS_SCN_DISP               32
++#define R_MIPS_REL16          33
++#define R_MIPS_ADD_IMMEDIATE  34
++#define R_MIPS_PJUMP          35
++#define R_MIPS_RELGOT         36
++#define R_MIPS_JALR           37
++#define R_MIPS_TLS_DTPMOD32   38      /* Module number 32 bit */
++#define R_MIPS_TLS_DTPREL32   39      /* Module-relative offset 32 bit */
++#define R_MIPS_TLS_DTPMOD64   40      /* Module number 64 bit */
++#define R_MIPS_TLS_DTPREL64   41      /* Module-relative offset 64 bit */
++#define R_MIPS_TLS_GD         42      /* 16 bit GOT offset for GD */
++#define R_MIPS_TLS_LDM                43      /* 16 bit GOT offset for LDM */
++#define R_MIPS_TLS_DTPREL_HI16        44      /* Module-relative offset, high 16 bits */
++#define R_MIPS_TLS_DTPREL_LO16        45      /* Module-relative offset, low 16 bits */
++#define R_MIPS_TLS_GOTTPREL   46      /* 16 bit GOT offset for IE */
++#define R_MIPS_TLS_TPREL32    47      /* TP-relative offset, 32 bit */
++#define R_MIPS_TLS_TPREL64    48      /* TP-relative offset, 64 bit */
++#define R_MIPS_TLS_TPREL_HI16 49      /* TP-relative offset, high 16 bits */
++#define R_MIPS_TLS_TPREL_LO16 50      /* TP-relative offset, low 16 bits */
++#define R_MIPS_GLOB_DAT               51
++#define R_MIPS_COPY           126
++#define R_MIPS_JUMP_SLOT        127
++/* Keep this the last entry.  */
++#define R_MIPS_NUM            128
++
++/* Legal values for p_type field of Elf32_Phdr.  */
++
++#define PT_MIPS_REGINFO       0x70000000      /* Register usage information */
++#define PT_MIPS_RTPROC  0x70000001    /* Runtime procedure table. */
++#define PT_MIPS_OPTIONS 0x70000002
++
++/* Special program header types.  */
++
++#define PF_MIPS_LOCAL 0x10000000
++
++/* Legal values for d_tag field of Elf32_Dyn.  */
++
++#define DT_MIPS_RLD_VERSION  0x70000001       /* Runtime linker interface version */
++#define DT_MIPS_TIME_STAMP   0x70000002       /* Timestamp */
++#define DT_MIPS_ICHECKSUM    0x70000003       /* Checksum */
++#define DT_MIPS_IVERSION     0x70000004       /* Version string (string tbl index) */
++#define DT_MIPS_FLAGS      0x70000005 /* Flags */
++#define DT_MIPS_BASE_ADDRESS 0x70000006       /* Base address */
++#define DT_MIPS_MSYM       0x70000007
++#define DT_MIPS_CONFLICT     0x70000008       /* Address of CONFLICT section */
++#define DT_MIPS_LIBLIST            0x70000009 /* Address of LIBLIST section */
++#define DT_MIPS_LOCAL_GOTNO  0x7000000a       /* Number of local GOT entries */
++#define DT_MIPS_CONFLICTNO   0x7000000b       /* Number of CONFLICT entries */
++#define DT_MIPS_LIBLISTNO    0x70000010       /* Number of LIBLIST entries */
++#define DT_MIPS_SYMTABNO     0x70000011       /* Number of DYNSYM entries */
++#define DT_MIPS_UNREFEXTNO   0x70000012       /* First external DYNSYM */
++#define DT_MIPS_GOTSYM             0x70000013 /* First GOT entry in DYNSYM */
++#define DT_MIPS_HIPAGENO     0x70000014       /* Number of GOT page table entries */
++#define DT_MIPS_RLD_MAP            0x70000016 /* Address of run time loader map.  */
++#define DT_MIPS_DELTA_CLASS  0x70000017       /* Delta C++ class definition.  */
++#define DT_MIPS_DELTA_CLASS_NO    0x70000018 /* Number of entries in
++                                              DT_MIPS_DELTA_CLASS.  */
++#define DT_MIPS_DELTA_INSTANCE    0x70000019 /* Delta C++ class instances.  */
++#define DT_MIPS_DELTA_INSTANCE_NO 0x7000001a /* Number of entries in
++                                              DT_MIPS_DELTA_INSTANCE.  */
++#define DT_MIPS_DELTA_RELOC  0x7000001b /* Delta relocations.  */
++#define DT_MIPS_DELTA_RELOC_NO 0x7000001c /* Number of entries in
++                                           DT_MIPS_DELTA_RELOC.  */
++#define DT_MIPS_DELTA_SYM    0x7000001d /* Delta symbols that Delta
++                                         relocations refer to.  */
++#define DT_MIPS_DELTA_SYM_NO 0x7000001e /* Number of entries in
++                                         DT_MIPS_DELTA_SYM.  */
++#define DT_MIPS_DELTA_CLASSSYM 0x70000020 /* Delta symbols that hold the
++                                           class declaration.  */
++#define DT_MIPS_DELTA_CLASSSYM_NO 0x70000021 /* Number of entries in
++                                              DT_MIPS_DELTA_CLASSSYM.  */
++#define DT_MIPS_CXX_FLAGS    0x70000022 /* Flags indicating for C++ flavor.  */
++#define DT_MIPS_PIXIE_INIT   0x70000023
++#define DT_MIPS_SYMBOL_LIB   0x70000024
++#define DT_MIPS_LOCALPAGE_GOTIDX 0x70000025
++#define DT_MIPS_LOCAL_GOTIDX 0x70000026
++#define DT_MIPS_HIDDEN_GOTIDX 0x70000027
++#define DT_MIPS_PROTECTED_GOTIDX 0x70000028
++#define DT_MIPS_OPTIONS            0x70000029 /* Address of .options.  */
++#define DT_MIPS_INTERFACE    0x7000002a /* Address of .interface.  */
++#define DT_MIPS_DYNSTR_ALIGN 0x7000002b
++#define DT_MIPS_INTERFACE_SIZE 0x7000002c /* Size of the .interface section. */
++#define DT_MIPS_RLD_TEXT_RESOLVE_ADDR 0x7000002d /* Address of rld_text_rsolve
++                                                  function stored in GOT.  */
++#define DT_MIPS_PERF_SUFFIX  0x7000002e /* Default suffix of dso to be added
++                                         by rld on dlopen() calls.  */
++#define DT_MIPS_COMPACT_SIZE 0x7000002f /* (O32)Size of compact rel section. */
++#define DT_MIPS_GP_VALUE     0x70000030 /* GP value for aux GOTs.  */
++#define DT_MIPS_AUX_DYNAMIC  0x70000031 /* Address of aux .dynamic.  */
++/* The address of .got.plt in an executable using the new non-PIC ABI.  */
++#define DT_MIPS_PLTGOT             0x70000032
++/* The base of the PLT in an executable using the new non-PIC ABI if that
++   PLT is writable.  For a non-writable PLT, this is omitted or has a zero
++   value.  */
++#define DT_MIPS_RWPLT        0x70000034
++#define DT_MIPS_NUM        0x35
++
++/* Legal values for DT_MIPS_FLAGS Elf32_Dyn entry.  */
++
++#define RHF_NONE                 0            /* No flags */
++#define RHF_QUICKSTART                   (1 << 0)     /* Use quickstart */
++#define RHF_NOTPOT               (1 << 1)     /* Hash size not power of 2 */
++#define RHF_NO_LIBRARY_REPLACEMENT (1 << 2)   /* Ignore LD_LIBRARY_PATH */
++#define RHF_NO_MOVE              (1 << 3)
++#define RHF_SGI_ONLY             (1 << 4)
++#define RHF_GUARANTEE_INIT       (1 << 5)
++#define RHF_DELTA_C_PLUS_PLUS    (1 << 6)
++#define RHF_GUARANTEE_START_INIT   (1 << 7)
++#define RHF_PIXIE                (1 << 8)
++#define RHF_DEFAULT_DELAY_LOAD           (1 << 9)
++#define RHF_REQUICKSTART         (1 << 10)
++#define RHF_REQUICKSTARTED       (1 << 11)
++#define RHF_CORD                 (1 << 12)
++#define RHF_NO_UNRES_UNDEF       (1 << 13)
++#define RHF_RLD_ORDER_SAFE       (1 << 14)
++
++/* Entries found in sections of type SHT_MIPS_LIBLIST.  */
++
++typedef struct
++{
++  Elf32_Word l_name;          /* Name (string table index) */
++  Elf32_Word l_time_stamp;    /* Timestamp */
++  Elf32_Word l_checksum;      /* Checksum */
++  Elf32_Word l_version;               /* Interface version */
++  Elf32_Word l_flags;         /* Flags */
++} Elf32_Lib;
++
++typedef struct
++{
++  Elf64_Word l_name;          /* Name (string table index) */
++  Elf64_Word l_time_stamp;    /* Timestamp */
++  Elf64_Word l_checksum;      /* Checksum */
++  Elf64_Word l_version;               /* Interface version */
++  Elf64_Word l_flags;         /* Flags */
++} Elf64_Lib;
++
++
++/* Legal values for l_flags.  */
++
++#define LL_NONE                 0
++#define LL_EXACT_MATCH          (1 << 0)      /* Require exact match */
++#define LL_IGNORE_INT_VER (1 << 1)    /* Ignore interface version */
++#define LL_REQUIRE_MINOR  (1 << 2)
++#define LL_EXPORTS      (1 << 3)
++#define LL_DELAY_LOAD   (1 << 4)
++#define LL_DELTA        (1 << 5)
++
++/* Entries found in sections of type SHT_MIPS_CONFLICT.  */
++
++typedef Elf32_Addr Elf32_Conflict;
++
++
++/* HPPA specific definitions.  */
++
++/* Legal values for e_flags field of Elf32_Ehdr.  */
++
++#define EF_PARISC_TRAPNIL     0x00010000 /* Trap nil pointer dereference.  */
++#define EF_PARISC_EXT         0x00020000 /* Program uses arch. extensions. */
++#define EF_PARISC_LSB         0x00040000 /* Program expects little endian. */
++#define EF_PARISC_WIDE                0x00080000 /* Program expects wide mode.  */
++#define EF_PARISC_NO_KABP     0x00100000 /* No kernel assisted branch
++                                            prediction.  */
++#define EF_PARISC_LAZYSWAP    0x00400000 /* Allow lazy swapping.  */
++#define EF_PARISC_ARCH                0x0000ffff /* Architecture version.  */
++
++/* Defined values for `e_flags & EF_PARISC_ARCH' are:  */
++
++#define EFA_PARISC_1_0                    0x020b /* PA-RISC 1.0 big-endian.  */
++#define EFA_PARISC_1_1                    0x0210 /* PA-RISC 1.1 big-endian.  */
++#define EFA_PARISC_2_0                    0x0214 /* PA-RISC 2.0 big-endian.  */
++
++/* Additional section indeces.  */
++
++#define SHN_PARISC_ANSI_COMMON        0xff00     /* Section for tenatively declared
++                                            symbols in ANSI C.  */
++#define SHN_PARISC_HUGE_COMMON        0xff01     /* Common blocks in huge model.  */
++
++/* Legal values for sh_type field of Elf32_Shdr.  */
++
++#define SHT_PARISC_EXT                0x70000000 /* Contains product specific ext. */
++#define SHT_PARISC_UNWIND     0x70000001 /* Unwind information.  */
++#define SHT_PARISC_DOC                0x70000002 /* Debug info for optimized code. */
++
++/* Legal values for sh_flags field of Elf32_Shdr.  */
++
++#define SHF_PARISC_SHORT      0x20000000 /* Section with short addressing. */
++#define SHF_PARISC_HUGE               0x40000000 /* Section far from gp.  */
++#define SHF_PARISC_SBP                0x80000000 /* Static branch prediction code. */
++
++/* Legal values for ST_TYPE subfield of st_info (symbol type).  */
++
++#define STT_PARISC_MILLICODE  13      /* Millicode function entry point.  */
++
++#define STT_HP_OPAQUE         (STT_LOOS + 0x1)
++#define STT_HP_STUB           (STT_LOOS + 0x2)
++
++/* HPPA relocs.  */
++
++#define R_PARISC_NONE         0       /* No reloc.  */
++#define R_PARISC_DIR32                1       /* Direct 32-bit reference.  */
++#define R_PARISC_DIR21L               2       /* Left 21 bits of eff. address.  */
++#define R_PARISC_DIR17R               3       /* Right 17 bits of eff. address.  */
++#define R_PARISC_DIR17F               4       /* 17 bits of eff. address.  */
++#define R_PARISC_DIR14R               6       /* Right 14 bits of eff. address.  */
++#define R_PARISC_PCREL32      9       /* 32-bit rel. address.  */
++#define R_PARISC_PCREL21L     10      /* Left 21 bits of rel. address.  */
++#define R_PARISC_PCREL17R     11      /* Right 17 bits of rel. address.  */
++#define R_PARISC_PCREL17F     12      /* 17 bits of rel. address.  */
++#define R_PARISC_PCREL14R     14      /* Right 14 bits of rel. address.  */
++#define R_PARISC_DPREL21L     18      /* Left 21 bits of rel. address.  */
++#define R_PARISC_DPREL14R     22      /* Right 14 bits of rel. address.  */
++#define R_PARISC_GPREL21L     26      /* GP-relative, left 21 bits.  */
++#define R_PARISC_GPREL14R     30      /* GP-relative, right 14 bits.  */
++#define R_PARISC_LTOFF21L     34      /* LT-relative, left 21 bits.  */
++#define R_PARISC_LTOFF14R     38      /* LT-relative, right 14 bits.  */
++#define R_PARISC_SECREL32     41      /* 32 bits section rel. address.  */
++#define R_PARISC_SEGBASE      48      /* No relocation, set segment base.  */
++#define R_PARISC_SEGREL32     49      /* 32 bits segment rel. address.  */
++#define R_PARISC_PLTOFF21L    50      /* PLT rel. address, left 21 bits.  */
++#define R_PARISC_PLTOFF14R    54      /* PLT rel. address, right 14 bits.  */
++#define R_PARISC_LTOFF_FPTR32 57      /* 32 bits LT-rel. function pointer. */
++#define R_PARISC_LTOFF_FPTR21L        58      /* LT-rel. fct ptr, left 21 bits. */
++#define R_PARISC_LTOFF_FPTR14R        62      /* LT-rel. fct ptr, right 14 bits. */
++#define R_PARISC_FPTR64               64      /* 64 bits function address.  */
++#define R_PARISC_PLABEL32     65      /* 32 bits function address.  */
++#define R_PARISC_PLABEL21L    66      /* Left 21 bits of fdesc address.  */
++#define R_PARISC_PLABEL14R    70      /* Right 14 bits of fdesc address.  */
++#define R_PARISC_PCREL64      72      /* 64 bits PC-rel. address.  */
++#define R_PARISC_PCREL22F     74      /* 22 bits PC-rel. address.  */
++#define R_PARISC_PCREL14WR    75      /* PC-rel. address, right 14 bits.  */
++#define R_PARISC_PCREL14DR    76      /* PC rel. address, right 14 bits.  */
++#define R_PARISC_PCREL16F     77      /* 16 bits PC-rel. address.  */
++#define R_PARISC_PCREL16WF    78      /* 16 bits PC-rel. address.  */
++#define R_PARISC_PCREL16DF    79      /* 16 bits PC-rel. address.  */
++#define R_PARISC_DIR64                80      /* 64 bits of eff. address.  */
++#define R_PARISC_DIR14WR      83      /* 14 bits of eff. address.  */
++#define R_PARISC_DIR14DR      84      /* 14 bits of eff. address.  */
++#define R_PARISC_DIR16F               85      /* 16 bits of eff. address.  */
++#define R_PARISC_DIR16WF      86      /* 16 bits of eff. address.  */
++#define R_PARISC_DIR16DF      87      /* 16 bits of eff. address.  */
++#define R_PARISC_GPREL64      88      /* 64 bits of GP-rel. address.  */
++#define R_PARISC_GPREL14WR    91      /* GP-rel. address, right 14 bits.  */
++#define R_PARISC_GPREL14DR    92      /* GP-rel. address, right 14 bits.  */
++#define R_PARISC_GPREL16F     93      /* 16 bits GP-rel. address.  */
++#define R_PARISC_GPREL16WF    94      /* 16 bits GP-rel. address.  */
++#define R_PARISC_GPREL16DF    95      /* 16 bits GP-rel. address.  */
++#define R_PARISC_LTOFF64      96      /* 64 bits LT-rel. address.  */
++#define R_PARISC_LTOFF14WR    99      /* LT-rel. address, right 14 bits.  */
++#define R_PARISC_LTOFF14DR    100     /* LT-rel. address, right 14 bits.  */
++#define R_PARISC_LTOFF16F     101     /* 16 bits LT-rel. address.  */
++#define R_PARISC_LTOFF16WF    102     /* 16 bits LT-rel. address.  */
++#define R_PARISC_LTOFF16DF    103     /* 16 bits LT-rel. address.  */
++#define R_PARISC_SECREL64     104     /* 64 bits section rel. address.  */
++#define R_PARISC_SEGREL64     112     /* 64 bits segment rel. address.  */
++#define R_PARISC_PLTOFF14WR   115     /* PLT-rel. address, right 14 bits.  */
++#define R_PARISC_PLTOFF14DR   116     /* PLT-rel. address, right 14 bits.  */
++#define R_PARISC_PLTOFF16F    117     /* 16 bits LT-rel. address.  */
++#define R_PARISC_PLTOFF16WF   118     /* 16 bits PLT-rel. address.  */
++#define R_PARISC_PLTOFF16DF   119     /* 16 bits PLT-rel. address.  */
++#define R_PARISC_LTOFF_FPTR64 120     /* 64 bits LT-rel. function ptr.  */
++#define R_PARISC_LTOFF_FPTR14WR       123     /* LT-rel. fct. ptr., right 14 bits. */
++#define R_PARISC_LTOFF_FPTR14DR       124     /* LT-rel. fct. ptr., right 14 bits. */
++#define R_PARISC_LTOFF_FPTR16F        125     /* 16 bits LT-rel. function ptr.  */
++#define R_PARISC_LTOFF_FPTR16WF       126     /* 16 bits LT-rel. function ptr.  */
++#define R_PARISC_LTOFF_FPTR16DF       127     /* 16 bits LT-rel. function ptr.  */
++#define R_PARISC_LORESERVE    128
++#define R_PARISC_COPY         128     /* Copy relocation.  */
++#define R_PARISC_IPLT         129     /* Dynamic reloc, imported PLT */
++#define R_PARISC_EPLT         130     /* Dynamic reloc, exported PLT */
++#define R_PARISC_TPREL32      153     /* 32 bits TP-rel. address.  */
++#define R_PARISC_TPREL21L     154     /* TP-rel. address, left 21 bits.  */
++#define R_PARISC_TPREL14R     158     /* TP-rel. address, right 14 bits.  */
++#define R_PARISC_LTOFF_TP21L  162     /* LT-TP-rel. address, left 21 bits. */
++#define R_PARISC_LTOFF_TP14R  166     /* LT-TP-rel. address, right 14 bits.*/
++#define R_PARISC_LTOFF_TP14F  167     /* 14 bits LT-TP-rel. address.  */
++#define R_PARISC_TPREL64      216     /* 64 bits TP-rel. address.  */
++#define R_PARISC_TPREL14WR    219     /* TP-rel. address, right 14 bits.  */
++#define R_PARISC_TPREL14DR    220     /* TP-rel. address, right 14 bits.  */
++#define R_PARISC_TPREL16F     221     /* 16 bits TP-rel. address.  */
++#define R_PARISC_TPREL16WF    222     /* 16 bits TP-rel. address.  */
++#define R_PARISC_TPREL16DF    223     /* 16 bits TP-rel. address.  */
++#define R_PARISC_LTOFF_TP64   224     /* 64 bits LT-TP-rel. address.  */
++#define R_PARISC_LTOFF_TP14WR 227     /* LT-TP-rel. address, right 14 bits.*/
++#define R_PARISC_LTOFF_TP14DR 228     /* LT-TP-rel. address, right 14 bits.*/
++#define R_PARISC_LTOFF_TP16F  229     /* 16 bits LT-TP-rel. address.  */
++#define R_PARISC_LTOFF_TP16WF 230     /* 16 bits LT-TP-rel. address.  */
++#define R_PARISC_LTOFF_TP16DF 231     /* 16 bits LT-TP-rel. address.  */
++#define R_PARISC_GNU_VTENTRY  232
++#define R_PARISC_GNU_VTINHERIT        233
++#define R_PARISC_TLS_GD21L    234     /* GD 21-bit left.  */
++#define R_PARISC_TLS_GD14R    235     /* GD 14-bit right.  */
++#define R_PARISC_TLS_GDCALL   236     /* GD call to __t_g_a.  */
++#define R_PARISC_TLS_LDM21L   237     /* LD module 21-bit left.  */
++#define R_PARISC_TLS_LDM14R   238     /* LD module 14-bit right.  */
++#define R_PARISC_TLS_LDMCALL  239     /* LD module call to __t_g_a.  */
++#define R_PARISC_TLS_LDO21L   240     /* LD offset 21-bit left.  */
++#define R_PARISC_TLS_LDO14R   241     /* LD offset 14-bit right.  */
++#define R_PARISC_TLS_DTPMOD32 242     /* DTP module 32-bit.  */
++#define R_PARISC_TLS_DTPMOD64 243     /* DTP module 64-bit.  */
++#define R_PARISC_TLS_DTPOFF32 244     /* DTP offset 32-bit.  */
++#define R_PARISC_TLS_DTPOFF64 245     /* DTP offset 32-bit.  */
++#define R_PARISC_TLS_LE21L    R_PARISC_TPREL21L
++#define R_PARISC_TLS_LE14R    R_PARISC_TPREL14R
++#define R_PARISC_TLS_IE21L    R_PARISC_LTOFF_TP21L
++#define R_PARISC_TLS_IE14R    R_PARISC_LTOFF_TP14R
++#define R_PARISC_TLS_TPREL32  R_PARISC_TPREL32
++#define R_PARISC_TLS_TPREL64  R_PARISC_TPREL64
++#define R_PARISC_HIRESERVE    255
++
++/* Legal values for p_type field of Elf32_Phdr/Elf64_Phdr.  */
++
++#define PT_HP_TLS             (PT_LOOS + 0x0)
++#define PT_HP_CORE_NONE               (PT_LOOS + 0x1)
++#define PT_HP_CORE_VERSION    (PT_LOOS + 0x2)
++#define PT_HP_CORE_KERNEL     (PT_LOOS + 0x3)
++#define PT_HP_CORE_COMM               (PT_LOOS + 0x4)
++#define PT_HP_CORE_PROC               (PT_LOOS + 0x5)
++#define PT_HP_CORE_LOADABLE   (PT_LOOS + 0x6)
++#define PT_HP_CORE_STACK      (PT_LOOS + 0x7)
++#define PT_HP_CORE_SHM                (PT_LOOS + 0x8)
++#define PT_HP_CORE_MMF                (PT_LOOS + 0x9)
++#define PT_HP_PARALLEL                (PT_LOOS + 0x10)
++#define PT_HP_FASTBIND                (PT_LOOS + 0x11)
++#define PT_HP_OPT_ANNOT               (PT_LOOS + 0x12)
++#define PT_HP_HSL_ANNOT               (PT_LOOS + 0x13)
++#define PT_HP_STACK           (PT_LOOS + 0x14)
++
++#define PT_PARISC_ARCHEXT     0x70000000
++#define PT_PARISC_UNWIND      0x70000001
++
++/* Legal values for p_flags field of Elf32_Phdr/Elf64_Phdr.  */
++
++#define PF_PARISC_SBP         0x08000000
++
++#define PF_HP_PAGE_SIZE               0x00100000
++#define PF_HP_FAR_SHARED      0x00200000
++#define PF_HP_NEAR_SHARED     0x00400000
++#define PF_HP_CODE            0x01000000
++#define PF_HP_MODIFY          0x02000000
++#define PF_HP_LAZYSWAP                0x04000000
++#define PF_HP_SBP             0x08000000
++
++
++/* Alpha specific definitions.  */
++
++/* Legal values for e_flags field of Elf64_Ehdr.  */
++
++#define EF_ALPHA_32BIT                1       /* All addresses must be < 2GB.  */
++#define EF_ALPHA_CANRELAX     2       /* Relocations for relaxing exist.  */
++
++/* Legal values for sh_type field of Elf64_Shdr.  */
++
++/* These two are primerily concerned with ECOFF debugging info.  */
++#define SHT_ALPHA_DEBUG               0x70000001
++#define SHT_ALPHA_REGINFO     0x70000002
++
++/* Legal values for sh_flags field of Elf64_Shdr.  */
++
++#define SHF_ALPHA_GPREL               0x10000000
++
++/* Legal values for st_other field of Elf64_Sym.  */
++#define STO_ALPHA_NOPV                0x80    /* No PV required.  */
++#define STO_ALPHA_STD_GPLOAD  0x88    /* PV only used for initial ldgp.  */
++
++/* Alpha relocs.  */
++
++#define R_ALPHA_NONE          0       /* No reloc */
++#define R_ALPHA_REFLONG               1       /* Direct 32 bit */
++#define R_ALPHA_REFQUAD               2       /* Direct 64 bit */
++#define R_ALPHA_GPREL32               3       /* GP relative 32 bit */
++#define R_ALPHA_LITERAL               4       /* GP relative 16 bit w/optimization */
++#define R_ALPHA_LITUSE                5       /* Optimization hint for LITERAL */
++#define R_ALPHA_GPDISP                6       /* Add displacement to GP */
++#define R_ALPHA_BRADDR                7       /* PC+4 relative 23 bit shifted */
++#define R_ALPHA_HINT          8       /* PC+4 relative 16 bit shifted */
++#define R_ALPHA_SREL16                9       /* PC relative 16 bit */
++#define R_ALPHA_SREL32                10      /* PC relative 32 bit */
++#define R_ALPHA_SREL64                11      /* PC relative 64 bit */
++#define R_ALPHA_GPRELHIGH     17      /* GP relative 32 bit, high 16 bits */
++#define R_ALPHA_GPRELLOW      18      /* GP relative 32 bit, low 16 bits */
++#define R_ALPHA_GPREL16               19      /* GP relative 16 bit */
++#define R_ALPHA_COPY          24      /* Copy symbol at runtime */
++#define R_ALPHA_GLOB_DAT      25      /* Create GOT entry */
++#define R_ALPHA_JMP_SLOT      26      /* Create PLT entry */
++#define R_ALPHA_RELATIVE      27      /* Adjust by program base */
++#define R_ALPHA_TLS_GD_HI     28
++#define R_ALPHA_TLSGD         29
++#define R_ALPHA_TLS_LDM               30
++#define R_ALPHA_DTPMOD64      31
++#define R_ALPHA_GOTDTPREL     32
++#define R_ALPHA_DTPREL64      33
++#define R_ALPHA_DTPRELHI      34
++#define R_ALPHA_DTPRELLO      35
++#define R_ALPHA_DTPREL16      36
++#define R_ALPHA_GOTTPREL      37
++#define R_ALPHA_TPREL64               38
++#define R_ALPHA_TPRELHI               39
++#define R_ALPHA_TPRELLO               40
++#define R_ALPHA_TPREL16               41
++/* Keep this the last entry.  */
++#define R_ALPHA_NUM           46
++
++/* Magic values of the LITUSE relocation addend.  */
++#define LITUSE_ALPHA_ADDR     0
++#define LITUSE_ALPHA_BASE     1
++#define LITUSE_ALPHA_BYTOFF   2
++#define LITUSE_ALPHA_JSR      3
++#define LITUSE_ALPHA_TLS_GD   4
++#define LITUSE_ALPHA_TLS_LDM  5
++
++/* Legal values for d_tag of Elf64_Dyn.  */
++#define DT_ALPHA_PLTRO                (DT_LOPROC + 0)
++#define DT_ALPHA_NUM          1
++
++/* PowerPC specific declarations */
++
++/* Values for Elf32/64_Ehdr.e_flags.  */
++#define EF_PPC_EMB            0x80000000      /* PowerPC embedded flag */
++
++/* Cygnus local bits below */
++#define EF_PPC_RELOCATABLE    0x00010000      /* PowerPC -mrelocatable flag*/
++#define EF_PPC_RELOCATABLE_LIB        0x00008000      /* PowerPC -mrelocatable-lib
++                                                 flag */
++
++/* PowerPC relocations defined by the ABIs */
++#define R_PPC_NONE            0
++#define R_PPC_ADDR32          1       /* 32bit absolute address */
++#define R_PPC_ADDR24          2       /* 26bit address, 2 bits ignored.  */
++#define R_PPC_ADDR16          3       /* 16bit absolute address */
++#define R_PPC_ADDR16_LO               4       /* lower 16bit of absolute address */
++#define R_PPC_ADDR16_HI               5       /* high 16bit of absolute address */
++#define R_PPC_ADDR16_HA               6       /* adjusted high 16bit */
++#define R_PPC_ADDR14          7       /* 16bit address, 2 bits ignored */
++#define R_PPC_ADDR14_BRTAKEN  8
++#define R_PPC_ADDR14_BRNTAKEN 9
++#define R_PPC_REL24           10      /* PC relative 26 bit */
++#define R_PPC_REL14           11      /* PC relative 16 bit */
++#define R_PPC_REL14_BRTAKEN   12
++#define R_PPC_REL14_BRNTAKEN  13
++#define R_PPC_GOT16           14
++#define R_PPC_GOT16_LO                15
++#define R_PPC_GOT16_HI                16
++#define R_PPC_GOT16_HA                17
++#define R_PPC_PLTREL24                18
++#define R_PPC_COPY            19
++#define R_PPC_GLOB_DAT                20
++#define R_PPC_JMP_SLOT                21
++#define R_PPC_RELATIVE                22
++#define R_PPC_LOCAL24PC               23
++#define R_PPC_UADDR32         24
++#define R_PPC_UADDR16         25
++#define R_PPC_REL32           26
++#define R_PPC_PLT32           27
++#define R_PPC_PLTREL32                28
++#define R_PPC_PLT16_LO                29
++#define R_PPC_PLT16_HI                30
++#define R_PPC_PLT16_HA                31
++#define R_PPC_SDAREL16                32
++#define R_PPC_SECTOFF         33
++#define R_PPC_SECTOFF_LO      34
++#define R_PPC_SECTOFF_HI      35
++#define R_PPC_SECTOFF_HA      36
++
++/* PowerPC relocations defined for the TLS access ABI.  */
++#define R_PPC_TLS             67 /* none      (sym+add)@tls */
++#define R_PPC_DTPMOD32                68 /* word32    (sym+add)@dtpmod */
++#define R_PPC_TPREL16         69 /* half16*   (sym+add)@tprel */
++#define R_PPC_TPREL16_LO      70 /* half16    (sym+add)@tprel@l */
++#define R_PPC_TPREL16_HI      71 /* half16    (sym+add)@tprel@h */
++#define R_PPC_TPREL16_HA      72 /* half16    (sym+add)@tprel@ha */
++#define R_PPC_TPREL32         73 /* word32    (sym+add)@tprel */
++#define R_PPC_DTPREL16                74 /* half16*   (sym+add)@dtprel */
++#define R_PPC_DTPREL16_LO     75 /* half16    (sym+add)@dtprel@l */
++#define R_PPC_DTPREL16_HI     76 /* half16    (sym+add)@dtprel@h */
++#define R_PPC_DTPREL16_HA     77 /* half16    (sym+add)@dtprel@ha */
++#define R_PPC_DTPREL32                78 /* word32    (sym+add)@dtprel */
++#define R_PPC_GOT_TLSGD16     79 /* half16*   (sym+add)@got@tlsgd */
++#define R_PPC_GOT_TLSGD16_LO  80 /* half16    (sym+add)@got@tlsgd@l */
++#define R_PPC_GOT_TLSGD16_HI  81 /* half16    (sym+add)@got@tlsgd@h */
++#define R_PPC_GOT_TLSGD16_HA  82 /* half16    (sym+add)@got@tlsgd@ha */
++#define R_PPC_GOT_TLSLD16     83 /* half16*   (sym+add)@got@tlsld */
++#define R_PPC_GOT_TLSLD16_LO  84 /* half16    (sym+add)@got@tlsld@l */
++#define R_PPC_GOT_TLSLD16_HI  85 /* half16    (sym+add)@got@tlsld@h */
++#define R_PPC_GOT_TLSLD16_HA  86 /* half16    (sym+add)@got@tlsld@ha */
++#define R_PPC_GOT_TPREL16     87 /* half16*   (sym+add)@got@tprel */
++#define R_PPC_GOT_TPREL16_LO  88 /* half16    (sym+add)@got@tprel@l */
++#define R_PPC_GOT_TPREL16_HI  89 /* half16    (sym+add)@got@tprel@h */
++#define R_PPC_GOT_TPREL16_HA  90 /* half16    (sym+add)@got@tprel@ha */
++#define R_PPC_GOT_DTPREL16    91 /* half16*   (sym+add)@got@dtprel */
++#define R_PPC_GOT_DTPREL16_LO 92 /* half16*   (sym+add)@got@dtprel@l */
++#define R_PPC_GOT_DTPREL16_HI 93 /* half16*   (sym+add)@got@dtprel@h */
++#define R_PPC_GOT_DTPREL16_HA 94 /* half16*   (sym+add)@got@dtprel@ha */
++
++/* The remaining relocs are from the Embedded ELF ABI, and are not
++   in the SVR4 ELF ABI.  */
++#define R_PPC_EMB_NADDR32     101
++#define R_PPC_EMB_NADDR16     102
++#define R_PPC_EMB_NADDR16_LO  103
++#define R_PPC_EMB_NADDR16_HI  104
++#define R_PPC_EMB_NADDR16_HA  105
++#define R_PPC_EMB_SDAI16      106
++#define R_PPC_EMB_SDA2I16     107
++#define R_PPC_EMB_SDA2REL     108
++#define R_PPC_EMB_SDA21               109     /* 16 bit offset in SDA */
++#define R_PPC_EMB_MRKREF      110
++#define R_PPC_EMB_RELSEC16    111
++#define R_PPC_EMB_RELST_LO    112
++#define R_PPC_EMB_RELST_HI    113
++#define R_PPC_EMB_RELST_HA    114
++#define R_PPC_EMB_BIT_FLD     115
++#define R_PPC_EMB_RELSDA      116     /* 16 bit relative offset in SDA */
++
++/* Diab tool relocations.  */
++#define R_PPC_DIAB_SDA21_LO   180     /* like EMB_SDA21, but lower 16 bit */
++#define R_PPC_DIAB_SDA21_HI   181     /* like EMB_SDA21, but high 16 bit */
++#define R_PPC_DIAB_SDA21_HA   182     /* like EMB_SDA21, adjusted high 16 */
++#define R_PPC_DIAB_RELSDA_LO  183     /* like EMB_RELSDA, but lower 16 bit */
++#define R_PPC_DIAB_RELSDA_HI  184     /* like EMB_RELSDA, but high 16 bit */
++#define R_PPC_DIAB_RELSDA_HA  185     /* like EMB_RELSDA, adjusted high 16 */
++
++/* GNU extension to support local ifunc.  */
++#define R_PPC_IRELATIVE               248
++
++/* GNU relocs used in PIC code sequences.  */
++#define R_PPC_REL16           249     /* half16   (sym+add-.) */
++#define R_PPC_REL16_LO                250     /* half16   (sym+add-.)@l */
++#define R_PPC_REL16_HI                251     /* half16   (sym+add-.)@h */
++#define R_PPC_REL16_HA                252     /* half16   (sym+add-.)@ha */
++
++/* This is a phony reloc to handle any old fashioned TOC16 references
++   that may still be in object files.  */
++#define R_PPC_TOC16           255
++
++/* PowerPC specific values for the Dyn d_tag field.  */
++#define DT_PPC_GOT            (DT_LOPROC + 0)
++#define DT_PPC_NUM            1
++
++/* PowerPC64 relocations defined by the ABIs */
++#define R_PPC64_NONE          R_PPC_NONE
++#define R_PPC64_ADDR32                R_PPC_ADDR32 /* 32bit absolute address */
++#define R_PPC64_ADDR24                R_PPC_ADDR24 /* 26bit address, word aligned */
++#define R_PPC64_ADDR16                R_PPC_ADDR16 /* 16bit absolute address */
++#define R_PPC64_ADDR16_LO     R_PPC_ADDR16_LO /* lower 16bits of address */
++#define R_PPC64_ADDR16_HI     R_PPC_ADDR16_HI /* high 16bits of address. */
++#define R_PPC64_ADDR16_HA     R_PPC_ADDR16_HA /* adjusted high 16bits.  */
++#define R_PPC64_ADDR14                R_PPC_ADDR14 /* 16bit address, word aligned */
++#define R_PPC64_ADDR14_BRTAKEN        R_PPC_ADDR14_BRTAKEN
++#define R_PPC64_ADDR14_BRNTAKEN       R_PPC_ADDR14_BRNTAKEN
++#define R_PPC64_REL24         R_PPC_REL24 /* PC-rel. 26 bit, word aligned */
++#define R_PPC64_REL14         R_PPC_REL14 /* PC relative 16 bit */
++#define R_PPC64_REL14_BRTAKEN R_PPC_REL14_BRTAKEN
++#define R_PPC64_REL14_BRNTAKEN        R_PPC_REL14_BRNTAKEN
++#define R_PPC64_GOT16         R_PPC_GOT16
++#define R_PPC64_GOT16_LO      R_PPC_GOT16_LO
++#define R_PPC64_GOT16_HI      R_PPC_GOT16_HI
++#define R_PPC64_GOT16_HA      R_PPC_GOT16_HA
++
++#define R_PPC64_COPY          R_PPC_COPY
++#define R_PPC64_GLOB_DAT      R_PPC_GLOB_DAT
++#define R_PPC64_JMP_SLOT      R_PPC_JMP_SLOT
++#define R_PPC64_RELATIVE      R_PPC_RELATIVE
++
++#define R_PPC64_UADDR32               R_PPC_UADDR32
++#define R_PPC64_UADDR16               R_PPC_UADDR16
++#define R_PPC64_REL32         R_PPC_REL32
++#define R_PPC64_PLT32         R_PPC_PLT32
++#define R_PPC64_PLTREL32      R_PPC_PLTREL32
++#define R_PPC64_PLT16_LO      R_PPC_PLT16_LO
++#define R_PPC64_PLT16_HI      R_PPC_PLT16_HI
++#define R_PPC64_PLT16_HA      R_PPC_PLT16_HA
++
++#define R_PPC64_SECTOFF               R_PPC_SECTOFF
++#define R_PPC64_SECTOFF_LO    R_PPC_SECTOFF_LO
++#define R_PPC64_SECTOFF_HI    R_PPC_SECTOFF_HI
++#define R_PPC64_SECTOFF_HA    R_PPC_SECTOFF_HA
++#define R_PPC64_ADDR30                37 /* word30 (S + A - P) >> 2 */
++#define R_PPC64_ADDR64                38 /* doubleword64 S + A */
++#define R_PPC64_ADDR16_HIGHER 39 /* half16 #higher(S + A) */
++#define R_PPC64_ADDR16_HIGHERA        40 /* half16 #highera(S + A) */
++#define R_PPC64_ADDR16_HIGHEST        41 /* half16 #highest(S + A) */
++#define R_PPC64_ADDR16_HIGHESTA       42 /* half16 #highesta(S + A) */
++#define R_PPC64_UADDR64               43 /* doubleword64 S + A */
++#define R_PPC64_REL64         44 /* doubleword64 S + A - P */
++#define R_PPC64_PLT64         45 /* doubleword64 L + A */
++#define R_PPC64_PLTREL64      46 /* doubleword64 L + A - P */
++#define R_PPC64_TOC16         47 /* half16* S + A - .TOC */
++#define R_PPC64_TOC16_LO      48 /* half16 #lo(S + A - .TOC.) */
++#define R_PPC64_TOC16_HI      49 /* half16 #hi(S + A - .TOC.) */
++#define R_PPC64_TOC16_HA      50 /* half16 #ha(S + A - .TOC.) */
++#define R_PPC64_TOC           51 /* doubleword64 .TOC */
++#define R_PPC64_PLTGOT16      52 /* half16* M + A */
++#define R_PPC64_PLTGOT16_LO   53 /* half16 #lo(M + A) */
++#define R_PPC64_PLTGOT16_HI   54 /* half16 #hi(M + A) */
++#define R_PPC64_PLTGOT16_HA   55 /* half16 #ha(M + A) */
++
++#define R_PPC64_ADDR16_DS     56 /* half16ds* (S + A) >> 2 */
++#define R_PPC64_ADDR16_LO_DS  57 /* half16ds  #lo(S + A) >> 2 */
++#define R_PPC64_GOT16_DS      58 /* half16ds* (G + A) >> 2 */
++#define R_PPC64_GOT16_LO_DS   59 /* half16ds  #lo(G + A) >> 2 */
++#define R_PPC64_PLT16_LO_DS   60 /* half16ds  #lo(L + A) >> 2 */
++#define R_PPC64_SECTOFF_DS    61 /* half16ds* (R + A) >> 2 */
++#define R_PPC64_SECTOFF_LO_DS 62 /* half16ds  #lo(R + A) >> 2 */
++#define R_PPC64_TOC16_DS      63 /* half16ds* (S + A - .TOC.) >> 2 */
++#define R_PPC64_TOC16_LO_DS   64 /* half16ds  #lo(S + A - .TOC.) >> 2 */
++#define R_PPC64_PLTGOT16_DS   65 /* half16ds* (M + A) >> 2 */
++#define R_PPC64_PLTGOT16_LO_DS        66 /* half16ds  #lo(M + A) >> 2 */
++
++/* PowerPC64 relocations defined for the TLS access ABI.  */
++#define R_PPC64_TLS           67 /* none      (sym+add)@tls */
++#define R_PPC64_DTPMOD64      68 /* doubleword64 (sym+add)@dtpmod */
++#define R_PPC64_TPREL16               69 /* half16*   (sym+add)@tprel */
++#define R_PPC64_TPREL16_LO    70 /* half16    (sym+add)@tprel@l */
++#define R_PPC64_TPREL16_HI    71 /* half16    (sym+add)@tprel@h */
++#define R_PPC64_TPREL16_HA    72 /* half16    (sym+add)@tprel@ha */
++#define R_PPC64_TPREL64               73 /* doubleword64 (sym+add)@tprel */
++#define R_PPC64_DTPREL16      74 /* half16*   (sym+add)@dtprel */
++#define R_PPC64_DTPREL16_LO   75 /* half16    (sym+add)@dtprel@l */
++#define R_PPC64_DTPREL16_HI   76 /* half16    (sym+add)@dtprel@h */
++#define R_PPC64_DTPREL16_HA   77 /* half16    (sym+add)@dtprel@ha */
++#define R_PPC64_DTPREL64      78 /* doubleword64 (sym+add)@dtprel */
++#define R_PPC64_GOT_TLSGD16   79 /* half16*   (sym+add)@got@tlsgd */
++#define R_PPC64_GOT_TLSGD16_LO        80 /* half16    (sym+add)@got@tlsgd@l */
++#define R_PPC64_GOT_TLSGD16_HI        81 /* half16    (sym+add)@got@tlsgd@h */
++#define R_PPC64_GOT_TLSGD16_HA        82 /* half16    (sym+add)@got@tlsgd@ha */
++#define R_PPC64_GOT_TLSLD16   83 /* half16*   (sym+add)@got@tlsld */
++#define R_PPC64_GOT_TLSLD16_LO        84 /* half16    (sym+add)@got@tlsld@l */
++#define R_PPC64_GOT_TLSLD16_HI        85 /* half16    (sym+add)@got@tlsld@h */
++#define R_PPC64_GOT_TLSLD16_HA        86 /* half16    (sym+add)@got@tlsld@ha */
++#define R_PPC64_GOT_TPREL16_DS        87 /* half16ds* (sym+add)@got@tprel */
++#define R_PPC64_GOT_TPREL16_LO_DS 88 /* half16ds (sym+add)@got@tprel@l */
++#define R_PPC64_GOT_TPREL16_HI        89 /* half16    (sym+add)@got@tprel@h */
++#define R_PPC64_GOT_TPREL16_HA        90 /* half16    (sym+add)@got@tprel@ha */
++#define R_PPC64_GOT_DTPREL16_DS       91 /* half16ds* (sym+add)@got@dtprel */
++#define R_PPC64_GOT_DTPREL16_LO_DS 92 /* half16ds (sym+add)@got@dtprel@l */
++#define R_PPC64_GOT_DTPREL16_HI       93 /* half16    (sym+add)@got@dtprel@h */
++#define R_PPC64_GOT_DTPREL16_HA       94 /* half16    (sym+add)@got@dtprel@ha */
++#define R_PPC64_TPREL16_DS    95 /* half16ds* (sym+add)@tprel */
++#define R_PPC64_TPREL16_LO_DS 96 /* half16ds  (sym+add)@tprel@l */
++#define R_PPC64_TPREL16_HIGHER        97 /* half16    (sym+add)@tprel@higher */
++#define R_PPC64_TPREL16_HIGHERA       98 /* half16    (sym+add)@tprel@highera */
++#define R_PPC64_TPREL16_HIGHEST       99 /* half16    (sym+add)@tprel@highest */
++#define R_PPC64_TPREL16_HIGHESTA 100 /* half16        (sym+add)@tprel@highesta */
++#define R_PPC64_DTPREL16_DS   101 /* half16ds* (sym+add)@dtprel */
++#define R_PPC64_DTPREL16_LO_DS        102 /* half16ds (sym+add)@dtprel@l */
++#define R_PPC64_DTPREL16_HIGHER       103 /* half16   (sym+add)@dtprel@higher */
++#define R_PPC64_DTPREL16_HIGHERA 104 /* half16        (sym+add)@dtprel@highera */
++#define R_PPC64_DTPREL16_HIGHEST 105 /* half16        (sym+add)@dtprel@highest */
++#define R_PPC64_DTPREL16_HIGHESTA 106 /* half16       (sym+add)@dtprel@highesta */
++
++/* GNU extension to support local ifunc.  */
++#define R_PPC64_JMP_IREL      247
++#define R_PPC64_IRELATIVE     248
++#define R_PPC64_REL16         249     /* half16   (sym+add-.) */
++#define R_PPC64_REL16_LO      250     /* half16   (sym+add-.)@l */
++#define R_PPC64_REL16_HI      251     /* half16   (sym+add-.)@h */
++#define R_PPC64_REL16_HA      252     /* half16   (sym+add-.)@ha */
++
++/* PowerPC64 specific values for the Dyn d_tag field.  */
++#define DT_PPC64_GLINK  (DT_LOPROC + 0)
++#define DT_PPC64_OPD  (DT_LOPROC + 1)
++#define DT_PPC64_OPDSZ        (DT_LOPROC + 2)
++#define DT_PPC64_NUM    3
++
++
++/* ARM specific declarations */
++
++/* Processor specific flags for the ELF header e_flags field.  */
++#define EF_ARM_RELEXEC                0x01
++#define EF_ARM_HASENTRY               0x02
++#define EF_ARM_INTERWORK      0x04
++#define EF_ARM_APCS_26                0x08
++#define EF_ARM_APCS_FLOAT     0x10
++#define EF_ARM_PIC            0x20
++#define EF_ARM_ALIGN8         0x40 /* 8-bit structure alignment is in use */
++#define EF_ARM_NEW_ABI                0x80
++#define EF_ARM_OLD_ABI                0x100
++#define EF_ARM_SOFT_FLOAT     0x200
++#define EF_ARM_VFP_FLOAT      0x400
++#define EF_ARM_MAVERICK_FLOAT 0x800
++
++
++/* Other constants defined in the ARM ELF spec. version B-01.  */
++/* NB. These conflict with values defined above.  */
++#define EF_ARM_SYMSARESORTED  0x04
++#define EF_ARM_DYNSYMSUSESEGIDX       0x08
++#define EF_ARM_MAPSYMSFIRST   0x10
++#define EF_ARM_EABIMASK               0XFF000000
++
++/* Constants defined in AAELF.  */
++#define EF_ARM_BE8        0x00800000
++#define EF_ARM_LE8        0x00400000
++
++#define EF_ARM_EABI_VERSION(flags)    ((flags) & EF_ARM_EABIMASK)
++#define EF_ARM_EABI_UNKNOWN   0x00000000
++#define EF_ARM_EABI_VER1      0x01000000
++#define EF_ARM_EABI_VER2      0x02000000
++#define EF_ARM_EABI_VER3      0x03000000
++#define EF_ARM_EABI_VER4      0x04000000
++#define EF_ARM_EABI_VER5      0x05000000
++
++/* Additional symbol types for Thumb.  */
++#define STT_ARM_TFUNC         STT_LOPROC /* A Thumb function.  */
++#define STT_ARM_16BIT         STT_HIPROC /* A Thumb label.  */
++
++/* ARM-specific values for sh_flags */
++#define SHF_ARM_ENTRYSECT     0x10000000 /* Section contains an entry point */
++#define SHF_ARM_COMDEF                0x80000000 /* Section may be multiply defined
++                                            in the input to a link step.  */
++
++/* ARM-specific program header flags */
++#define PF_ARM_SB             0x10000000 /* Segment contains the location
++                                            addressed by the static base. */
++#define PF_ARM_PI             0x20000000 /* Position-independent segment.  */
++#define PF_ARM_ABS            0x40000000 /* Absolute segment.  */
++
++/* Processor specific values for the Phdr p_type field.  */
++#define PT_ARM_EXIDX          (PT_LOPROC + 1) /* ARM unwind segment.  */
++
++/* Processor specific values for the Shdr sh_type field.  */
++#define SHT_ARM_EXIDX         (SHT_LOPROC + 1) /* ARM unwind section.  */
++#define SHT_ARM_PREEMPTMAP    (SHT_LOPROC + 2) /* Preemption details.  */
++#define SHT_ARM_ATTRIBUTES    (SHT_LOPROC + 3) /* ARM attributes section.  */
++
++
++/* ARM relocs.  */
++
++#define R_ARM_NONE            0       /* No reloc */
++#define R_ARM_PC24            1       /* PC relative 26 bit branch */
++#define R_ARM_ABS32           2       /* Direct 32 bit  */
++#define R_ARM_REL32           3       /* PC relative 32 bit */
++#define R_ARM_PC13            4
++#define R_ARM_ABS16           5       /* Direct 16 bit */
++#define R_ARM_ABS12           6       /* Direct 12 bit */
++#define R_ARM_THM_ABS5                7
++#define R_ARM_ABS8            8       /* Direct 8 bit */
++#define R_ARM_SBREL32         9
++#define R_ARM_THM_PC22                10
++#define R_ARM_THM_PC8         11
++#define R_ARM_AMP_VCALL9      12
++#define R_ARM_SWI24           13      /* Obsolete static relocation.  */
++#define R_ARM_TLS_DESC                13      /* Dynamic relocation.  */
++#define R_ARM_THM_SWI8                14
++#define R_ARM_XPC25           15
++#define R_ARM_THM_XPC22               16
++#define R_ARM_TLS_DTPMOD32    17      /* ID of module containing symbol */
++#define R_ARM_TLS_DTPOFF32    18      /* Offset in TLS block */
++#define R_ARM_TLS_TPOFF32     19      /* Offset in static TLS block */
++#define R_ARM_COPY            20      /* Copy symbol at runtime */
++#define R_ARM_GLOB_DAT                21      /* Create GOT entry */
++#define R_ARM_JUMP_SLOT               22      /* Create PLT entry */
++#define R_ARM_RELATIVE                23      /* Adjust by program base */
++#define R_ARM_GOTOFF          24      /* 32 bit offset to GOT */
++#define R_ARM_GOTPC           25      /* 32 bit PC relative offset to GOT */
++#define R_ARM_GOT32           26      /* 32 bit GOT entry */
++#define R_ARM_PLT32           27      /* 32 bit PLT address */
++#define R_ARM_ALU_PCREL_7_0   32
++#define R_ARM_ALU_PCREL_15_8  33
++#define R_ARM_ALU_PCREL_23_15 34
++#define R_ARM_LDR_SBREL_11_0  35
++#define R_ARM_ALU_SBREL_19_12 36
++#define R_ARM_ALU_SBREL_27_20 37
++#define R_ARM_TLS_GOTDESC     90
++#define R_ARM_TLS_CALL                91
++#define R_ARM_TLS_DESCSEQ     92
++#define R_ARM_THM_TLS_CALL    93
++#define R_ARM_GNU_VTENTRY     100
++#define R_ARM_GNU_VTINHERIT   101
++#define R_ARM_THM_PC11                102     /* thumb unconditional branch */
++#define R_ARM_THM_PC9         103     /* thumb conditional branch */
++#define R_ARM_TLS_GD32                104     /* PC-rel 32 bit for global dynamic
++                                         thread local data */
++#define R_ARM_TLS_LDM32               105     /* PC-rel 32 bit for local dynamic
++                                         thread local data */
++#define R_ARM_TLS_LDO32               106     /* 32 bit offset relative to TLS
++                                         block */
++#define R_ARM_TLS_IE32                107     /* PC-rel 32 bit for GOT entry of
++                                         static TLS block offset */
++#define R_ARM_TLS_LE32                108     /* 32 bit offset relative to static
++                                         TLS block */
++#define       R_ARM_THM_TLS_DESCSEQ   129
++#define R_ARM_IRELATIVE               160
++#define R_ARM_RXPC25          249
++#define R_ARM_RSBREL32                250
++#define R_ARM_THM_RPC22               251
++#define R_ARM_RREL32          252
++#define R_ARM_RABS22          253
++#define R_ARM_RPC24           254
++#define R_ARM_RBASE           255
++/* Keep this the last entry.  */
++#define R_ARM_NUM             256
++
++/* IA-64 specific declarations.  */
++
++/* Processor specific flags for the Ehdr e_flags field.  */
++#define EF_IA_64_MASKOS               0x0000000f      /* os-specific flags */
++#define EF_IA_64_ABI64                0x00000010      /* 64-bit ABI */
++#define EF_IA_64_ARCH         0xff000000      /* arch. version mask */
++
++/* Processor specific values for the Phdr p_type field.  */
++#define PT_IA_64_ARCHEXT      (PT_LOPROC + 0) /* arch extension bits */
++#define PT_IA_64_UNWIND               (PT_LOPROC + 1) /* ia64 unwind bits */
++#define PT_IA_64_HP_OPT_ANOT  (PT_LOOS + 0x12)
++#define PT_IA_64_HP_HSL_ANOT  (PT_LOOS + 0x13)
++#define PT_IA_64_HP_STACK     (PT_LOOS + 0x14)
++
++/* Processor specific flags for the Phdr p_flags field.  */
++#define PF_IA_64_NORECOV      0x80000000      /* spec insns w/o recovery */
++
++/* Processor specific values for the Shdr sh_type field.  */
++#define SHT_IA_64_EXT         (SHT_LOPROC + 0) /* extension bits */
++#define SHT_IA_64_UNWIND      (SHT_LOPROC + 1) /* unwind bits */
++
++/* Processor specific flags for the Shdr sh_flags field.  */
++#define SHF_IA_64_SHORT               0x10000000      /* section near gp */
++#define SHF_IA_64_NORECOV     0x20000000      /* spec insns w/o recovery */
++
++/* Processor specific values for the Dyn d_tag field.  */
++#define DT_IA_64_PLT_RESERVE  (DT_LOPROC + 0)
++#define DT_IA_64_NUM          1
++
++/* IA-64 relocations.  */
++#define R_IA64_NONE           0x00    /* none */
++#define R_IA64_IMM14          0x21    /* symbol + addend, add imm14 */
++#define R_IA64_IMM22          0x22    /* symbol + addend, add imm22 */
++#define R_IA64_IMM64          0x23    /* symbol + addend, mov imm64 */
++#define R_IA64_DIR32MSB               0x24    /* symbol + addend, data4 MSB */
++#define R_IA64_DIR32LSB               0x25    /* symbol + addend, data4 LSB */
++#define R_IA64_DIR64MSB               0x26    /* symbol + addend, data8 MSB */
++#define R_IA64_DIR64LSB               0x27    /* symbol + addend, data8 LSB */
++#define R_IA64_GPREL22                0x2a    /* @gprel(sym + add), add imm22 */
++#define R_IA64_GPREL64I               0x2b    /* @gprel(sym + add), mov imm64 */
++#define R_IA64_GPREL32MSB     0x2c    /* @gprel(sym + add), data4 MSB */
++#define R_IA64_GPREL32LSB     0x2d    /* @gprel(sym + add), data4 LSB */
++#define R_IA64_GPREL64MSB     0x2e    /* @gprel(sym + add), data8 MSB */
++#define R_IA64_GPREL64LSB     0x2f    /* @gprel(sym + add), data8 LSB */
++#define R_IA64_LTOFF22                0x32    /* @ltoff(sym + add), add imm22 */
++#define R_IA64_LTOFF64I               0x33    /* @ltoff(sym + add), mov imm64 */
++#define R_IA64_PLTOFF22               0x3a    /* @pltoff(sym + add), add imm22 */
++#define R_IA64_PLTOFF64I      0x3b    /* @pltoff(sym + add), mov imm64 */
++#define R_IA64_PLTOFF64MSB    0x3e    /* @pltoff(sym + add), data8 MSB */
++#define R_IA64_PLTOFF64LSB    0x3f    /* @pltoff(sym + add), data8 LSB */
++#define R_IA64_FPTR64I                0x43    /* @fptr(sym + add), mov imm64 */
++#define R_IA64_FPTR32MSB      0x44    /* @fptr(sym + add), data4 MSB */
++#define R_IA64_FPTR32LSB      0x45    /* @fptr(sym + add), data4 LSB */
++#define R_IA64_FPTR64MSB      0x46    /* @fptr(sym + add), data8 MSB */
++#define R_IA64_FPTR64LSB      0x47    /* @fptr(sym + add), data8 LSB */
++#define R_IA64_PCREL60B               0x48    /* @pcrel(sym + add), brl */
++#define R_IA64_PCREL21B               0x49    /* @pcrel(sym + add), ptb, call */
++#define R_IA64_PCREL21M               0x4a    /* @pcrel(sym + add), chk.s */
++#define R_IA64_PCREL21F               0x4b    /* @pcrel(sym + add), fchkf */
++#define R_IA64_PCREL32MSB     0x4c    /* @pcrel(sym + add), data4 MSB */
++#define R_IA64_PCREL32LSB     0x4d    /* @pcrel(sym + add), data4 LSB */
++#define R_IA64_PCREL64MSB     0x4e    /* @pcrel(sym + add), data8 MSB */
++#define R_IA64_PCREL64LSB     0x4f    /* @pcrel(sym + add), data8 LSB */
++#define R_IA64_LTOFF_FPTR22   0x52    /* @ltoff(@fptr(s+a)), imm22 */
++#define R_IA64_LTOFF_FPTR64I  0x53    /* @ltoff(@fptr(s+a)), imm64 */
++#define R_IA64_LTOFF_FPTR32MSB        0x54    /* @ltoff(@fptr(s+a)), data4 MSB */
++#define R_IA64_LTOFF_FPTR32LSB        0x55    /* @ltoff(@fptr(s+a)), data4 LSB */
++#define R_IA64_LTOFF_FPTR64MSB        0x56    /* @ltoff(@fptr(s+a)), data8 MSB */
++#define R_IA64_LTOFF_FPTR64LSB        0x57    /* @ltoff(@fptr(s+a)), data8 LSB */
++#define R_IA64_SEGREL32MSB    0x5c    /* @segrel(sym + add), data4 MSB */
++#define R_IA64_SEGREL32LSB    0x5d    /* @segrel(sym + add), data4 LSB */
++#define R_IA64_SEGREL64MSB    0x5e    /* @segrel(sym + add), data8 MSB */
++#define R_IA64_SEGREL64LSB    0x5f    /* @segrel(sym + add), data8 LSB */
++#define R_IA64_SECREL32MSB    0x64    /* @secrel(sym + add), data4 MSB */
++#define R_IA64_SECREL32LSB    0x65    /* @secrel(sym + add), data4 LSB */
++#define R_IA64_SECREL64MSB    0x66    /* @secrel(sym + add), data8 MSB */
++#define R_IA64_SECREL64LSB    0x67    /* @secrel(sym + add), data8 LSB */
++#define R_IA64_REL32MSB               0x6c    /* data 4 + REL */
++#define R_IA64_REL32LSB               0x6d    /* data 4 + REL */
++#define R_IA64_REL64MSB               0x6e    /* data 8 + REL */
++#define R_IA64_REL64LSB               0x6f    /* data 8 + REL */
++#define R_IA64_LTV32MSB               0x74    /* symbol + addend, data4 MSB */
++#define R_IA64_LTV32LSB               0x75    /* symbol + addend, data4 LSB */
++#define R_IA64_LTV64MSB               0x76    /* symbol + addend, data8 MSB */
++#define R_IA64_LTV64LSB               0x77    /* symbol + addend, data8 LSB */
++#define R_IA64_PCREL21BI      0x79    /* @pcrel(sym + add), 21bit inst */
++#define R_IA64_PCREL22                0x7a    /* @pcrel(sym + add), 22bit inst */
++#define R_IA64_PCREL64I               0x7b    /* @pcrel(sym + add), 64bit inst */
++#define R_IA64_IPLTMSB                0x80    /* dynamic reloc, imported PLT, MSB */
++#define R_IA64_IPLTLSB                0x81    /* dynamic reloc, imported PLT, LSB */
++#define R_IA64_COPY           0x84    /* copy relocation */
++#define R_IA64_SUB            0x85    /* Addend and symbol difference */
++#define R_IA64_LTOFF22X               0x86    /* LTOFF22, relaxable.  */
++#define R_IA64_LDXMOV         0x87    /* Use of LTOFF22X.  */
++#define R_IA64_TPREL14                0x91    /* @tprel(sym + add), imm14 */
++#define R_IA64_TPREL22                0x92    /* @tprel(sym + add), imm22 */
++#define R_IA64_TPREL64I               0x93    /* @tprel(sym + add), imm64 */
++#define R_IA64_TPREL64MSB     0x96    /* @tprel(sym + add), data8 MSB */
++#define R_IA64_TPREL64LSB     0x97    /* @tprel(sym + add), data8 LSB */
++#define R_IA64_LTOFF_TPREL22  0x9a    /* @ltoff(@tprel(s+a)), imm2 */
++#define R_IA64_DTPMOD64MSB    0xa6    /* @dtpmod(sym + add), data8 MSB */
++#define R_IA64_DTPMOD64LSB    0xa7    /* @dtpmod(sym + add), data8 LSB */
++#define R_IA64_LTOFF_DTPMOD22 0xaa    /* @ltoff(@dtpmod(sym + add)), imm22 */
++#define R_IA64_DTPREL14               0xb1    /* @dtprel(sym + add), imm14 */
++#define R_IA64_DTPREL22               0xb2    /* @dtprel(sym + add), imm22 */
++#define R_IA64_DTPREL64I      0xb3    /* @dtprel(sym + add), imm64 */
++#define R_IA64_DTPREL32MSB    0xb4    /* @dtprel(sym + add), data4 MSB */
++#define R_IA64_DTPREL32LSB    0xb5    /* @dtprel(sym + add), data4 LSB */
++#define R_IA64_DTPREL64MSB    0xb6    /* @dtprel(sym + add), data8 MSB */
++#define R_IA64_DTPREL64LSB    0xb7    /* @dtprel(sym + add), data8 LSB */
++#define R_IA64_LTOFF_DTPREL22 0xba    /* @ltoff(@dtprel(s+a)), imm22 */
++
++/* SH specific declarations */
++
++/* Processor specific flags for the ELF header e_flags field.  */
++#define EF_SH_MACH_MASK               0x1f
++#define EF_SH_UNKNOWN         0x0
++#define EF_SH1                        0x1
++#define EF_SH2                        0x2
++#define EF_SH3                        0x3
++#define EF_SH_DSP             0x4
++#define EF_SH3_DSP            0x5
++#define EF_SH4AL_DSP          0x6
++#define EF_SH3E                       0x8
++#define EF_SH4                        0x9
++#define EF_SH2E                       0xb
++#define EF_SH4A                       0xc
++#define EF_SH2A                       0xd
++#define EF_SH4_NOFPU          0x10
++#define EF_SH4A_NOFPU         0x11
++#define EF_SH4_NOMMU_NOFPU    0x12
++#define EF_SH2A_NOFPU         0x13
++#define EF_SH3_NOMMU          0x14
++#define EF_SH2A_SH4_NOFPU     0x15
++#define EF_SH2A_SH3_NOFPU     0x16
++#define EF_SH2A_SH4           0x17
++#define EF_SH2A_SH3E          0x18
++
++/* SH relocs.  */
++#define       R_SH_NONE               0
++#define       R_SH_DIR32              1
++#define       R_SH_REL32              2
++#define       R_SH_DIR8WPN            3
++#define       R_SH_IND12W             4
++#define       R_SH_DIR8WPL            5
++#define       R_SH_DIR8WPZ            6
++#define       R_SH_DIR8BP             7
++#define       R_SH_DIR8W              8
++#define       R_SH_DIR8L              9
++#define       R_SH_SWITCH16           25
++#define       R_SH_SWITCH32           26
++#define       R_SH_USES               27
++#define       R_SH_COUNT              28
++#define       R_SH_ALIGN              29
++#define       R_SH_CODE               30
++#define       R_SH_DATA               31
++#define       R_SH_LABEL              32
++#define       R_SH_SWITCH8            33
++#define       R_SH_GNU_VTINHERIT      34
++#define       R_SH_GNU_VTENTRY        35
++#define       R_SH_TLS_GD_32          144
++#define       R_SH_TLS_LD_32          145
++#define       R_SH_TLS_LDO_32         146
++#define       R_SH_TLS_IE_32          147
++#define       R_SH_TLS_LE_32          148
++#define       R_SH_TLS_DTPMOD32       149
++#define       R_SH_TLS_DTPOFF32       150
++#define       R_SH_TLS_TPOFF32        151
++#define       R_SH_GOT32              160
++#define       R_SH_PLT32              161
++#define       R_SH_COPY               162
++#define       R_SH_GLOB_DAT           163
++#define       R_SH_JMP_SLOT           164
++#define       R_SH_RELATIVE           165
++#define       R_SH_GOTOFF             166
++#define       R_SH_GOTPC              167
++/* Keep this the last entry.  */
++#define       R_SH_NUM                256
++
++/* S/390 specific definitions.  */
++
++/* Valid values for the e_flags field.  */
++
++#define EF_S390_HIGH_GPRS    0x00000001  /* High GPRs kernel facility needed.  */
++
++/* Additional s390 relocs */
++
++#define R_390_NONE            0       /* No reloc.  */
++#define R_390_8                       1       /* Direct 8 bit.  */
++#define R_390_12              2       /* Direct 12 bit.  */
++#define R_390_16              3       /* Direct 16 bit.  */
++#define R_390_32              4       /* Direct 32 bit.  */
++#define R_390_PC32            5       /* PC relative 32 bit.  */
++#define R_390_GOT12           6       /* 12 bit GOT offset.  */
++#define R_390_GOT32           7       /* 32 bit GOT offset.  */
++#define R_390_PLT32           8       /* 32 bit PC relative PLT address.  */
++#define R_390_COPY            9       /* Copy symbol at runtime.  */
++#define R_390_GLOB_DAT                10      /* Create GOT entry.  */
++#define R_390_JMP_SLOT                11      /* Create PLT entry.  */
++#define R_390_RELATIVE                12      /* Adjust by program base.  */
++#define R_390_GOTOFF32                13      /* 32 bit offset to GOT.         */
++#define R_390_GOTPC           14      /* 32 bit PC relative offset to GOT.  */
++#define R_390_GOT16           15      /* 16 bit GOT offset.  */
++#define R_390_PC16            16      /* PC relative 16 bit.  */
++#define R_390_PC16DBL         17      /* PC relative 16 bit shifted by 1.  */
++#define R_390_PLT16DBL                18      /* 16 bit PC rel. PLT shifted by 1.  */
++#define R_390_PC32DBL         19      /* PC relative 32 bit shifted by 1.  */
++#define R_390_PLT32DBL                20      /* 32 bit PC rel. PLT shifted by 1.  */
++#define R_390_GOTPCDBL                21      /* 32 bit PC rel. GOT shifted by 1.  */
++#define R_390_64              22      /* Direct 64 bit.  */
++#define R_390_PC64            23      /* PC relative 64 bit.  */
++#define R_390_GOT64           24      /* 64 bit GOT offset.  */
++#define R_390_PLT64           25      /* 64 bit PC relative PLT address.  */
++#define R_390_GOTENT          26      /* 32 bit PC rel. to GOT entry >> 1. */
++#define R_390_GOTOFF16                27      /* 16 bit offset to GOT. */
++#define R_390_GOTOFF64                28      /* 64 bit offset to GOT. */
++#define R_390_GOTPLT12                29      /* 12 bit offset to jump slot.  */
++#define R_390_GOTPLT16                30      /* 16 bit offset to jump slot.  */
++#define R_390_GOTPLT32                31      /* 32 bit offset to jump slot.  */
++#define R_390_GOTPLT64                32      /* 64 bit offset to jump slot.  */
++#define R_390_GOTPLTENT               33      /* 32 bit rel. offset to jump slot.  */
++#define R_390_PLTOFF16                34      /* 16 bit offset from GOT to PLT. */
++#define R_390_PLTOFF32                35      /* 32 bit offset from GOT to PLT. */
++#define R_390_PLTOFF64                36      /* 16 bit offset from GOT to PLT. */
++#define R_390_TLS_LOAD                37      /* Tag for load insn in TLS code.  */
++#define R_390_TLS_GDCALL      38      /* Tag for function call in general
++                                         dynamic TLS code. */
++#define R_390_TLS_LDCALL      39      /* Tag for function call in local
++                                         dynamic TLS code. */
++#define R_390_TLS_GD32                40      /* Direct 32 bit for general dynamic
++                                         thread local data.  */
++#define R_390_TLS_GD64                41      /* Direct 64 bit for general dynamic
++                                        thread local data.  */
++#define R_390_TLS_GOTIE12     42      /* 12 bit GOT offset for static TLS
++                                         block offset.  */
++#define R_390_TLS_GOTIE32     43      /* 32 bit GOT offset for static TLS
++                                         block offset.  */
++#define R_390_TLS_GOTIE64     44      /* 64 bit GOT offset for static TLS
++                                         block offset. */
++#define R_390_TLS_LDM32               45      /* Direct 32 bit for local dynamic
++                                         thread local data in LE code.  */
++#define R_390_TLS_LDM64               46      /* Direct 64 bit for local dynamic
++                                         thread local data in LE code.  */
++#define R_390_TLS_IE32                47      /* 32 bit address of GOT entry for
++                                         negated static TLS block offset.  */
++#define R_390_TLS_IE64                48      /* 64 bit address of GOT entry for
++                                         negated static TLS block offset.  */
++#define R_390_TLS_IEENT               49      /* 32 bit rel. offset to GOT entry for
++                                         negated static TLS block offset.  */
++#define R_390_TLS_LE32                50      /* 32 bit negated offset relative to
++                                         static TLS block.  */
++#define R_390_TLS_LE64                51      /* 64 bit negated offset relative to
++                                         static TLS block.  */
++#define R_390_TLS_LDO32               52      /* 32 bit offset relative to TLS
++                                         block.  */
++#define R_390_TLS_LDO64               53      /* 64 bit offset relative to TLS
++                                         block.  */
++#define R_390_TLS_DTPMOD      54      /* ID of module containing symbol.  */
++#define R_390_TLS_DTPOFF      55      /* Offset in TLS block.  */
++#define R_390_TLS_TPOFF               56      /* Negated offset in static TLS
++                                         block.  */
++#define R_390_20              57      /* Direct 20 bit.  */
++#define R_390_GOT20           58      /* 20 bit GOT offset.  */
++#define R_390_GOTPLT20                59      /* 20 bit offset to jump slot.  */
++#define R_390_TLS_GOTIE20     60      /* 20 bit GOT offset for static TLS
++                                         block offset.  */
++#define R_390_IRELATIVE         61      /* STT_GNU_IFUNC relocation.  */
++/* Keep this the last entry.  */
++#define R_390_NUM             62
++
++
++/* CRIS relocations.  */
++#define R_CRIS_NONE           0
++#define R_CRIS_8              1
++#define R_CRIS_16             2
++#define R_CRIS_32             3
++#define R_CRIS_8_PCREL                4
++#define R_CRIS_16_PCREL               5
++#define R_CRIS_32_PCREL               6
++#define R_CRIS_GNU_VTINHERIT  7
++#define R_CRIS_GNU_VTENTRY    8
++#define R_CRIS_COPY           9
++#define R_CRIS_GLOB_DAT               10
++#define R_CRIS_JUMP_SLOT      11
++#define R_CRIS_RELATIVE               12
++#define R_CRIS_16_GOT         13
++#define R_CRIS_32_GOT         14
++#define R_CRIS_16_GOTPLT      15
++#define R_CRIS_32_GOTPLT      16
++#define R_CRIS_32_GOTREL      17
++#define R_CRIS_32_PLT_GOTREL  18
++#define R_CRIS_32_PLT_PCREL   19
++
++#define R_CRIS_NUM            20
++
++
++/* AMD x86-64 relocations.  */
++#define R_X86_64_NONE         0       /* No reloc */
++#define R_X86_64_64           1       /* Direct 64 bit  */
++#define R_X86_64_PC32         2       /* PC relative 32 bit signed */
++#define R_X86_64_GOT32                3       /* 32 bit GOT entry */
++#define R_X86_64_PLT32                4       /* 32 bit PLT address */
++#define R_X86_64_COPY         5       /* Copy symbol at runtime */
++#define R_X86_64_GLOB_DAT     6       /* Create GOT entry */
++#define R_X86_64_JUMP_SLOT    7       /* Create PLT entry */
++#define R_X86_64_RELATIVE     8       /* Adjust by program base */
++#define R_X86_64_GOTPCREL     9       /* 32 bit signed PC relative
++                                         offset to GOT */
++#define R_X86_64_32           10      /* Direct 32 bit zero extended */
++#define R_X86_64_32S          11      /* Direct 32 bit sign extended */
++#define R_X86_64_16           12      /* Direct 16 bit zero extended */
++#define R_X86_64_PC16         13      /* 16 bit sign extended pc relative */
++#define R_X86_64_8            14      /* Direct 8 bit sign extended  */
++#define R_X86_64_PC8          15      /* 8 bit sign extended pc relative */
++#define R_X86_64_DTPMOD64     16      /* ID of module containing symbol */
++#define R_X86_64_DTPOFF64     17      /* Offset in module's TLS block */
++#define R_X86_64_TPOFF64      18      /* Offset in initial TLS block */
++#define R_X86_64_TLSGD                19      /* 32 bit signed PC relative offset
++                                         to two GOT entries for GD symbol */
++#define R_X86_64_TLSLD                20      /* 32 bit signed PC relative offset
++                                         to two GOT entries for LD symbol */
++#define R_X86_64_DTPOFF32     21      /* Offset in TLS block */
++#define R_X86_64_GOTTPOFF     22      /* 32 bit signed PC relative offset
++                                         to GOT entry for IE symbol */
++#define R_X86_64_TPOFF32      23      /* Offset in initial TLS block */
++#define R_X86_64_PC64         24      /* PC relative 64 bit */
++#define R_X86_64_GOTOFF64     25      /* 64 bit offset to GOT */
++#define R_X86_64_GOTPC32      26      /* 32 bit signed pc relative
++                                         offset to GOT */
++#define R_X86_64_GOT64                27      /* 64-bit GOT entry offset */
++#define R_X86_64_GOTPCREL64   28      /* 64-bit PC relative offset
++                                         to GOT entry */
++#define R_X86_64_GOTPC64      29      /* 64-bit PC relative offset to GOT */
++#define R_X86_64_GOTPLT64     30      /* like GOT64, says PLT entry needed */
++#define R_X86_64_PLTOFF64     31      /* 64-bit GOT relative offset
++                                         to PLT entry */
++#define R_X86_64_SIZE32               32      /* Size of symbol plus 32-bit addend */
++#define R_X86_64_SIZE64               33      /* Size of symbol plus 64-bit addend */
++#define R_X86_64_GOTPC32_TLSDESC 34   /* GOT offset for TLS descriptor.  */
++#define R_X86_64_TLSDESC_CALL   35    /* Marker for call through TLS
++                                         descriptor.  */
++#define R_X86_64_TLSDESC        36    /* TLS descriptor.  */
++#define R_X86_64_IRELATIVE    37      /* Adjust indirectly by program base */
++#define R_X86_64_RELATIVE64   38      /* 64-bit adjust by program base */
++
++#define R_X86_64_NUM          39
++
++
++/* AM33 relocations.  */
++#define R_MN10300_NONE                0       /* No reloc.  */
++#define R_MN10300_32          1       /* Direct 32 bit.  */
++#define R_MN10300_16          2       /* Direct 16 bit.  */
++#define R_MN10300_8           3       /* Direct 8 bit.  */
++#define R_MN10300_PCREL32     4       /* PC-relative 32-bit.  */
++#define R_MN10300_PCREL16     5       /* PC-relative 16-bit signed.  */
++#define R_MN10300_PCREL8      6       /* PC-relative 8-bit signed.  */
++#define R_MN10300_GNU_VTINHERIT       7       /* Ancient C++ vtable garbage... */
++#define R_MN10300_GNU_VTENTRY 8       /* ... collection annotation.  */
++#define R_MN10300_24          9       /* Direct 24 bit.  */
++#define R_MN10300_GOTPC32     10      /* 32-bit PCrel offset to GOT.  */
++#define R_MN10300_GOTPC16     11      /* 16-bit PCrel offset to GOT.  */
++#define R_MN10300_GOTOFF32    12      /* 32-bit offset from GOT.  */
++#define R_MN10300_GOTOFF24    13      /* 24-bit offset from GOT.  */
++#define R_MN10300_GOTOFF16    14      /* 16-bit offset from GOT.  */
++#define R_MN10300_PLT32               15      /* 32-bit PCrel to PLT entry.  */
++#define R_MN10300_PLT16               16      /* 16-bit PCrel to PLT entry.  */
++#define R_MN10300_GOT32               17      /* 32-bit offset to GOT entry.  */
++#define R_MN10300_GOT24               18      /* 24-bit offset to GOT entry.  */
++#define R_MN10300_GOT16               19      /* 16-bit offset to GOT entry.  */
++#define R_MN10300_COPY                20      /* Copy symbol at runtime.  */
++#define R_MN10300_GLOB_DAT    21      /* Create GOT entry.  */
++#define R_MN10300_JMP_SLOT    22      /* Create PLT entry.  */
++#define R_MN10300_RELATIVE    23      /* Adjust by program base.  */
++
++#define R_MN10300_NUM         24
++
++
++/* M32R relocs.  */
++#define R_M32R_NONE           0       /* No reloc. */
++#define R_M32R_16             1       /* Direct 16 bit. */
++#define R_M32R_32             2       /* Direct 32 bit. */
++#define R_M32R_24             3       /* Direct 24 bit. */
++#define R_M32R_10_PCREL               4       /* PC relative 10 bit shifted. */
++#define R_M32R_18_PCREL               5       /* PC relative 18 bit shifted. */
++#define R_M32R_26_PCREL               6       /* PC relative 26 bit shifted. */
++#define R_M32R_HI16_ULO               7       /* High 16 bit with unsigned low. */
++#define R_M32R_HI16_SLO               8       /* High 16 bit with signed low. */
++#define R_M32R_LO16           9       /* Low 16 bit. */
++#define R_M32R_SDA16          10      /* 16 bit offset in SDA. */
++#define R_M32R_GNU_VTINHERIT  11
++#define R_M32R_GNU_VTENTRY    12
++/* M32R relocs use SHT_RELA.  */
++#define R_M32R_16_RELA                33      /* Direct 16 bit. */
++#define R_M32R_32_RELA                34      /* Direct 32 bit. */
++#define R_M32R_24_RELA                35      /* Direct 24 bit. */
++#define R_M32R_10_PCREL_RELA  36      /* PC relative 10 bit shifted. */
++#define R_M32R_18_PCREL_RELA  37      /* PC relative 18 bit shifted. */
++#define R_M32R_26_PCREL_RELA  38      /* PC relative 26 bit shifted. */
++#define R_M32R_HI16_ULO_RELA  39      /* High 16 bit with unsigned low */
++#define R_M32R_HI16_SLO_RELA  40      /* High 16 bit with signed low */
++#define R_M32R_LO16_RELA      41      /* Low 16 bit */
++#define R_M32R_SDA16_RELA     42      /* 16 bit offset in SDA */
++#define R_M32R_RELA_GNU_VTINHERIT     43
++#define R_M32R_RELA_GNU_VTENTRY       44
++#define R_M32R_REL32          45      /* PC relative 32 bit.  */
++
++#define R_M32R_GOT24          48      /* 24 bit GOT entry */
++#define R_M32R_26_PLTREL      49      /* 26 bit PC relative to PLT shifted */
++#define R_M32R_COPY           50      /* Copy symbol at runtime */
++#define R_M32R_GLOB_DAT               51      /* Create GOT entry */
++#define R_M32R_JMP_SLOT               52      /* Create PLT entry */
++#define R_M32R_RELATIVE               53      /* Adjust by program base */
++#define R_M32R_GOTOFF         54      /* 24 bit offset to GOT */
++#define R_M32R_GOTPC24                55      /* 24 bit PC relative offset to GOT */
++#define R_M32R_GOT16_HI_ULO   56      /* High 16 bit GOT entry with unsigned
++                                         low */
++#define R_M32R_GOT16_HI_SLO   57      /* High 16 bit GOT entry with signed
++                                         low */
++#define R_M32R_GOT16_LO               58      /* Low 16 bit GOT entry */
++#define R_M32R_GOTPC_HI_ULO   59      /* High 16 bit PC relative offset to
++                                         GOT with unsigned low */
++#define R_M32R_GOTPC_HI_SLO   60      /* High 16 bit PC relative offset to
++                                         GOT with signed low */
++#define R_M32R_GOTPC_LO               61      /* Low 16 bit PC relative offset to
++                                         GOT */
++#define R_M32R_GOTOFF_HI_ULO  62      /* High 16 bit offset to GOT
++                                         with unsigned low */
++#define R_M32R_GOTOFF_HI_SLO  63      /* High 16 bit offset to GOT
++                                         with signed low */
++#define R_M32R_GOTOFF_LO      64      /* Low 16 bit offset to GOT */
++#define R_M32R_NUM            256     /* Keep this the last entry. */
++
++
++/* TILEPro relocations.  */
++#define R_TILEPRO_NONE                0       /* No reloc */
++#define R_TILEPRO_32          1       /* Direct 32 bit */
++#define R_TILEPRO_16          2       /* Direct 16 bit */
++#define R_TILEPRO_8           3       /* Direct 8 bit */
++#define R_TILEPRO_32_PCREL    4       /* PC relative 32 bit */
++#define R_TILEPRO_16_PCREL    5       /* PC relative 16 bit */
++#define R_TILEPRO_8_PCREL     6       /* PC relative 8 bit */
++#define R_TILEPRO_LO16                7       /* Low 16 bit */
++#define R_TILEPRO_HI16                8       /* High 16 bit */
++#define R_TILEPRO_HA16                9       /* High 16 bit, adjusted */
++#define R_TILEPRO_COPY                10      /* Copy relocation */
++#define R_TILEPRO_GLOB_DAT    11      /* Create GOT entry */
++#define R_TILEPRO_JMP_SLOT    12      /* Create PLT entry */
++#define R_TILEPRO_RELATIVE    13      /* Adjust by program base */
++#define R_TILEPRO_BROFF_X1    14      /* X1 pipe branch offset */
++#define R_TILEPRO_JOFFLONG_X1 15      /* X1 pipe jump offset */
++#define R_TILEPRO_JOFFLONG_X1_PLT 16  /* X1 pipe jump offset to PLT */
++#define R_TILEPRO_IMM8_X0     17      /* X0 pipe 8-bit */
++#define R_TILEPRO_IMM8_Y0     18      /* Y0 pipe 8-bit */
++#define R_TILEPRO_IMM8_X1     19      /* X1 pipe 8-bit */
++#define R_TILEPRO_IMM8_Y1     20      /* Y1 pipe 8-bit */
++#define R_TILEPRO_MT_IMM15_X1 21      /* X1 pipe mtspr */
++#define R_TILEPRO_MF_IMM15_X1 22      /* X1 pipe mfspr */
++#define R_TILEPRO_IMM16_X0    23      /* X0 pipe 16-bit */
++#define R_TILEPRO_IMM16_X1    24      /* X1 pipe 16-bit */
++#define R_TILEPRO_IMM16_X0_LO 25      /* X0 pipe low 16-bit */
++#define R_TILEPRO_IMM16_X1_LO 26      /* X1 pipe low 16-bit */
++#define R_TILEPRO_IMM16_X0_HI 27      /* X0 pipe high 16-bit */
++#define R_TILEPRO_IMM16_X1_HI 28      /* X1 pipe high 16-bit */
++#define R_TILEPRO_IMM16_X0_HA 29      /* X0 pipe high 16-bit, adjusted */
++#define R_TILEPRO_IMM16_X1_HA 30      /* X1 pipe high 16-bit, adjusted */
++#define R_TILEPRO_IMM16_X0_PCREL 31   /* X0 pipe PC relative 16 bit */
++#define R_TILEPRO_IMM16_X1_PCREL 32   /* X1 pipe PC relative 16 bit */
++#define R_TILEPRO_IMM16_X0_LO_PCREL 33        /* X0 pipe PC relative low 16 bit */
++#define R_TILEPRO_IMM16_X1_LO_PCREL 34        /* X1 pipe PC relative low 16 bit */
++#define R_TILEPRO_IMM16_X0_HI_PCREL 35        /* X0 pipe PC relative high 16 bit */
++#define R_TILEPRO_IMM16_X1_HI_PCREL 36        /* X1 pipe PC relative high 16 bit */
++#define R_TILEPRO_IMM16_X0_HA_PCREL 37        /* X0 pipe PC relative ha() 16 bit */
++#define R_TILEPRO_IMM16_X1_HA_PCREL 38        /* X1 pipe PC relative ha() 16 bit */
++#define R_TILEPRO_IMM16_X0_GOT        39      /* X0 pipe 16-bit GOT offset */
++#define R_TILEPRO_IMM16_X1_GOT        40      /* X1 pipe 16-bit GOT offset */
++#define R_TILEPRO_IMM16_X0_GOT_LO 41  /* X0 pipe low 16-bit GOT offset */
++#define R_TILEPRO_IMM16_X1_GOT_LO 42  /* X1 pipe low 16-bit GOT offset */
++#define R_TILEPRO_IMM16_X0_GOT_HI 43  /* X0 pipe high 16-bit GOT offset */
++#define R_TILEPRO_IMM16_X1_GOT_HI 44  /* X1 pipe high 16-bit GOT offset */
++#define R_TILEPRO_IMM16_X0_GOT_HA 45  /* X0 pipe ha() 16-bit GOT offset */
++#define R_TILEPRO_IMM16_X1_GOT_HA 46  /* X1 pipe ha() 16-bit GOT offset */
++#define R_TILEPRO_MMSTART_X0  47      /* X0 pipe mm "start" */
++#define R_TILEPRO_MMEND_X0    48      /* X0 pipe mm "end" */
++#define R_TILEPRO_MMSTART_X1  49      /* X1 pipe mm "start" */
++#define R_TILEPRO_MMEND_X1    50      /* X1 pipe mm "end" */
++#define R_TILEPRO_SHAMT_X0    51      /* X0 pipe shift amount */
++#define R_TILEPRO_SHAMT_X1    52      /* X1 pipe shift amount */
++#define R_TILEPRO_SHAMT_Y0    53      /* Y0 pipe shift amount */
++#define R_TILEPRO_SHAMT_Y1    54      /* Y1 pipe shift amount */
++#define R_TILEPRO_DEST_IMM8_X1        55      /* X1 pipe destination 8-bit */
++/* Relocs 56-59 are currently not defined.  */
++#define R_TILEPRO_TLS_GD_CALL 60      /* "jal" for TLS GD */
++#define R_TILEPRO_IMM8_X0_TLS_GD_ADD 61       /* X0 pipe "addi" for TLS GD */
++#define R_TILEPRO_IMM8_X1_TLS_GD_ADD 62       /* X1 pipe "addi" for TLS GD */
++#define R_TILEPRO_IMM8_Y0_TLS_GD_ADD 63       /* Y0 pipe "addi" for TLS GD */
++#define R_TILEPRO_IMM8_Y1_TLS_GD_ADD 64       /* Y1 pipe "addi" for TLS GD */
++#define R_TILEPRO_TLS_IE_LOAD 65      /* "lw_tls" for TLS IE */
++#define R_TILEPRO_IMM16_X0_TLS_GD 66  /* X0 pipe 16-bit TLS GD offset */
++#define R_TILEPRO_IMM16_X1_TLS_GD 67  /* X1 pipe 16-bit TLS GD offset */
++#define R_TILEPRO_IMM16_X0_TLS_GD_LO 68       /* X0 pipe low 16-bit TLS GD offset */
++#define R_TILEPRO_IMM16_X1_TLS_GD_LO 69       /* X1 pipe low 16-bit TLS GD offset */
++#define R_TILEPRO_IMM16_X0_TLS_GD_HI 70       /* X0 pipe high 16-bit TLS GD offset */
++#define R_TILEPRO_IMM16_X1_TLS_GD_HI 71       /* X1 pipe high 16-bit TLS GD offset */
++#define R_TILEPRO_IMM16_X0_TLS_GD_HA 72       /* X0 pipe ha() 16-bit TLS GD offset */
++#define R_TILEPRO_IMM16_X1_TLS_GD_HA 73       /* X1 pipe ha() 16-bit TLS GD offset */
++#define R_TILEPRO_IMM16_X0_TLS_IE 74  /* X0 pipe 16-bit TLS IE offset */
++#define R_TILEPRO_IMM16_X1_TLS_IE 75  /* X1 pipe 16-bit TLS IE offset */
++#define R_TILEPRO_IMM16_X0_TLS_IE_LO 76       /* X0 pipe low 16-bit TLS IE offset */
++#define R_TILEPRO_IMM16_X1_TLS_IE_LO 77       /* X1 pipe low 16-bit TLS IE offset */
++#define R_TILEPRO_IMM16_X0_TLS_IE_HI 78       /* X0 pipe high 16-bit TLS IE offset */
++#define R_TILEPRO_IMM16_X1_TLS_IE_HI 79       /* X1 pipe high 16-bit TLS IE offset */
++#define R_TILEPRO_IMM16_X0_TLS_IE_HA 80       /* X0 pipe ha() 16-bit TLS IE offset */
++#define R_TILEPRO_IMM16_X1_TLS_IE_HA 81       /* X1 pipe ha() 16-bit TLS IE offset */
++#define R_TILEPRO_TLS_DTPMOD32        82      /* ID of module containing symbol */
++#define R_TILEPRO_TLS_DTPOFF32        83      /* Offset in TLS block */
++#define R_TILEPRO_TLS_TPOFF32 84      /* Offset in static TLS block */
++#define R_TILEPRO_IMM16_X0_TLS_LE 85  /* X0 pipe 16-bit TLS LE offset */
++#define R_TILEPRO_IMM16_X1_TLS_LE 86  /* X1 pipe 16-bit TLS LE offset */
++#define R_TILEPRO_IMM16_X0_TLS_LE_LO 87       /* X0 pipe low 16-bit TLS LE offset */
++#define R_TILEPRO_IMM16_X1_TLS_LE_LO 88       /* X1 pipe low 16-bit TLS LE offset */
++#define R_TILEPRO_IMM16_X0_TLS_LE_HI 89       /* X0 pipe high 16-bit TLS LE offset */
++#define R_TILEPRO_IMM16_X1_TLS_LE_HI 90       /* X1 pipe high 16-bit TLS LE offset */
++#define R_TILEPRO_IMM16_X0_TLS_LE_HA 91       /* X0 pipe ha() 16-bit TLS LE offset */
++#define R_TILEPRO_IMM16_X1_TLS_LE_HA 92       /* X1 pipe ha() 16-bit TLS LE offset */
++
++#define R_TILEPRO_GNU_VTINHERIT       128     /* GNU C++ vtable hierarchy */
++#define R_TILEPRO_GNU_VTENTRY 129     /* GNU C++ vtable member usage */
++
++#define R_TILEPRO_NUM         130
++
++
++/* TILE-Gx relocations.  */
++#define R_TILEGX_NONE         0       /* No reloc */
++#define R_TILEGX_64           1       /* Direct 64 bit */
++#define R_TILEGX_32           2       /* Direct 32 bit */
++#define R_TILEGX_16           3       /* Direct 16 bit */
++#define R_TILEGX_8            4       /* Direct 8 bit */
++#define R_TILEGX_64_PCREL     5       /* PC relative 64 bit */
++#define R_TILEGX_32_PCREL     6       /* PC relative 32 bit */
++#define R_TILEGX_16_PCREL     7       /* PC relative 16 bit */
++#define R_TILEGX_8_PCREL      8       /* PC relative 8 bit */
++#define R_TILEGX_HW0          9       /* hword 0 16-bit */
++#define R_TILEGX_HW1          10      /* hword 1 16-bit */
++#define R_TILEGX_HW2          11      /* hword 2 16-bit */
++#define R_TILEGX_HW3          12      /* hword 3 16-bit */
++#define R_TILEGX_HW0_LAST     13      /* last hword 0 16-bit */
++#define R_TILEGX_HW1_LAST     14      /* last hword 1 16-bit */
++#define R_TILEGX_HW2_LAST     15      /* last hword 2 16-bit */
++#define R_TILEGX_COPY         16      /* Copy relocation */
++#define R_TILEGX_GLOB_DAT     17      /* Create GOT entry */
++#define R_TILEGX_JMP_SLOT     18      /* Create PLT entry */
++#define R_TILEGX_RELATIVE     19      /* Adjust by program base */
++#define R_TILEGX_BROFF_X1     20      /* X1 pipe branch offset */
++#define R_TILEGX_JUMPOFF_X1   21      /* X1 pipe jump offset */
++#define R_TILEGX_JUMPOFF_X1_PLT       22      /* X1 pipe jump offset to PLT */
++#define R_TILEGX_IMM8_X0      23      /* X0 pipe 8-bit */
++#define R_TILEGX_IMM8_Y0      24      /* Y0 pipe 8-bit */
++#define R_TILEGX_IMM8_X1      25      /* X1 pipe 8-bit */
++#define R_TILEGX_IMM8_Y1      26      /* Y1 pipe 8-bit */
++#define R_TILEGX_DEST_IMM8_X1 27      /* X1 pipe destination 8-bit */
++#define R_TILEGX_MT_IMM14_X1  28      /* X1 pipe mtspr */
++#define R_TILEGX_MF_IMM14_X1  29      /* X1 pipe mfspr */
++#define R_TILEGX_MMSTART_X0   30      /* X0 pipe mm "start" */
++#define R_TILEGX_MMEND_X0     31      /* X0 pipe mm "end" */
++#define R_TILEGX_SHAMT_X0     32      /* X0 pipe shift amount */
++#define R_TILEGX_SHAMT_X1     33      /* X1 pipe shift amount */
++#define R_TILEGX_SHAMT_Y0     34      /* Y0 pipe shift amount */
++#define R_TILEGX_SHAMT_Y1     35      /* Y1 pipe shift amount */
++#define R_TILEGX_IMM16_X0_HW0 36      /* X0 pipe hword 0 */
++#define R_TILEGX_IMM16_X1_HW0 37      /* X1 pipe hword 0 */
++#define R_TILEGX_IMM16_X0_HW1 38      /* X0 pipe hword 1 */
++#define R_TILEGX_IMM16_X1_HW1 39      /* X1 pipe hword 1 */
++#define R_TILEGX_IMM16_X0_HW2 40      /* X0 pipe hword 2 */
++#define R_TILEGX_IMM16_X1_HW2 41      /* X1 pipe hword 2 */
++#define R_TILEGX_IMM16_X0_HW3 42      /* X0 pipe hword 3 */
++#define R_TILEGX_IMM16_X1_HW3 43      /* X1 pipe hword 3 */
++#define R_TILEGX_IMM16_X0_HW0_LAST 44 /* X0 pipe last hword 0 */
++#define R_TILEGX_IMM16_X1_HW0_LAST 45 /* X1 pipe last hword 0 */
++#define R_TILEGX_IMM16_X0_HW1_LAST 46 /* X0 pipe last hword 1 */
++#define R_TILEGX_IMM16_X1_HW1_LAST 47 /* X1 pipe last hword 1 */
++#define R_TILEGX_IMM16_X0_HW2_LAST 48 /* X0 pipe last hword 2 */
++#define R_TILEGX_IMM16_X1_HW2_LAST 49 /* X1 pipe last hword 2 */
++#define R_TILEGX_IMM16_X0_HW0_PCREL 50        /* X0 pipe PC relative hword 0 */
++#define R_TILEGX_IMM16_X1_HW0_PCREL 51        /* X1 pipe PC relative hword 0 */
++#define R_TILEGX_IMM16_X0_HW1_PCREL 52        /* X0 pipe PC relative hword 1 */
++#define R_TILEGX_IMM16_X1_HW1_PCREL 53        /* X1 pipe PC relative hword 1 */
++#define R_TILEGX_IMM16_X0_HW2_PCREL 54        /* X0 pipe PC relative hword 2 */
++#define R_TILEGX_IMM16_X1_HW2_PCREL 55        /* X1 pipe PC relative hword 2 */
++#define R_TILEGX_IMM16_X0_HW3_PCREL 56        /* X0 pipe PC relative hword 3 */
++#define R_TILEGX_IMM16_X1_HW3_PCREL 57        /* X1 pipe PC relative hword 3 */
++#define R_TILEGX_IMM16_X0_HW0_LAST_PCREL 58 /* X0 pipe PC-rel last hword 0 */
++#define R_TILEGX_IMM16_X1_HW0_LAST_PCREL 59 /* X1 pipe PC-rel last hword 0 */
++#define R_TILEGX_IMM16_X0_HW1_LAST_PCREL 60 /* X0 pipe PC-rel last hword 1 */
++#define R_TILEGX_IMM16_X1_HW1_LAST_PCREL 61 /* X1 pipe PC-rel last hword 1 */
++#define R_TILEGX_IMM16_X0_HW2_LAST_PCREL 62 /* X0 pipe PC-rel last hword 2 */
++#define R_TILEGX_IMM16_X1_HW2_LAST_PCREL 63 /* X1 pipe PC-rel last hword 2 */
++#define R_TILEGX_IMM16_X0_HW0_GOT 64  /* X0 pipe hword 0 GOT offset */
++#define R_TILEGX_IMM16_X1_HW0_GOT 65  /* X1 pipe hword 0 GOT offset */
++/* Relocs 66-71 are currently not defined.  */
++#define R_TILEGX_IMM16_X0_HW0_LAST_GOT 72 /* X0 pipe last hword 0 GOT offset */
++#define R_TILEGX_IMM16_X1_HW0_LAST_GOT 73 /* X1 pipe last hword 0 GOT offset */
++#define R_TILEGX_IMM16_X0_HW1_LAST_GOT 74 /* X0 pipe last hword 1 GOT offset */
++#define R_TILEGX_IMM16_X1_HW1_LAST_GOT 75 /* X1 pipe last hword 1 GOT offset */
++/* Relocs 76-77 are currently not defined.  */
++#define R_TILEGX_IMM16_X0_HW0_TLS_GD 78       /* X0 pipe hword 0 TLS GD offset */
++#define R_TILEGX_IMM16_X1_HW0_TLS_GD 79       /* X1 pipe hword 0 TLS GD offset */
++#define R_TILEGX_IMM16_X0_HW0_TLS_LE 80       /* X0 pipe hword 0 TLS LE offset */
++#define R_TILEGX_IMM16_X1_HW0_TLS_LE 81       /* X1 pipe hword 0 TLS LE offset */
++#define R_TILEGX_IMM16_X0_HW0_LAST_TLS_LE 82 /* X0 pipe last hword 0 LE off */
++#define R_TILEGX_IMM16_X1_HW0_LAST_TLS_LE 83 /* X1 pipe last hword 0 LE off */
++#define R_TILEGX_IMM16_X0_HW1_LAST_TLS_LE 84 /* X0 pipe last hword 1 LE off */
++#define R_TILEGX_IMM16_X1_HW1_LAST_TLS_LE 85 /* X1 pipe last hword 1 LE off */
++#define R_TILEGX_IMM16_X0_HW0_LAST_TLS_GD 86 /* X0 pipe last hword 0 GD off */
++#define R_TILEGX_IMM16_X1_HW0_LAST_TLS_GD 87 /* X1 pipe last hword 0 GD off */
++#define R_TILEGX_IMM16_X0_HW1_LAST_TLS_GD 88 /* X0 pipe last hword 1 GD off */
++#define R_TILEGX_IMM16_X1_HW1_LAST_TLS_GD 89 /* X1 pipe last hword 1 GD off */
++/* Relocs 90-91 are currently not defined.  */
++#define R_TILEGX_IMM16_X0_HW0_TLS_IE 92       /* X0 pipe hword 0 TLS IE offset */
++#define R_TILEGX_IMM16_X1_HW0_TLS_IE 93       /* X1 pipe hword 0 TLS IE offset */
++/* Relocs 94-99 are currently not defined.  */
++#define R_TILEGX_IMM16_X0_HW0_LAST_TLS_IE 100 /* X0 pipe last hword 0 IE off */
++#define R_TILEGX_IMM16_X1_HW0_LAST_TLS_IE 101 /* X1 pipe last hword 0 IE off */
++#define R_TILEGX_IMM16_X0_HW1_LAST_TLS_IE 102 /* X0 pipe last hword 1 IE off */
++#define R_TILEGX_IMM16_X1_HW1_LAST_TLS_IE 103 /* X1 pipe last hword 1 IE off */
++/* Relocs 104-105 are currently not defined.  */
++#define R_TILEGX_TLS_DTPMOD64 106     /* 64-bit ID of symbol's module */
++#define R_TILEGX_TLS_DTPOFF64 107     /* 64-bit offset in TLS block */
++#define R_TILEGX_TLS_TPOFF64  108     /* 64-bit offset in static TLS block */
++#define R_TILEGX_TLS_DTPMOD32 109     /* 32-bit ID of symbol's module */
++#define R_TILEGX_TLS_DTPOFF32 110     /* 32-bit offset in TLS block */
++#define R_TILEGX_TLS_TPOFF32  111     /* 32-bit offset in static TLS block */
++#define R_TILEGX_TLS_GD_CALL  112     /* "jal" for TLS GD */
++#define R_TILEGX_IMM8_X0_TLS_GD_ADD 113       /* X0 pipe "addi" for TLS GD */
++#define R_TILEGX_IMM8_X1_TLS_GD_ADD 114       /* X1 pipe "addi" for TLS GD */
++#define R_TILEGX_IMM8_Y0_TLS_GD_ADD 115       /* Y0 pipe "addi" for TLS GD */
++#define R_TILEGX_IMM8_Y1_TLS_GD_ADD 116       /* Y1 pipe "addi" for TLS GD */
++#define R_TILEGX_TLS_IE_LOAD  117     /* "ld_tls" for TLS IE */
++#define R_TILEGX_IMM8_X0_TLS_ADD 118  /* X0 pipe "addi" for TLS GD/IE */
++#define R_TILEGX_IMM8_X1_TLS_ADD 119  /* X1 pipe "addi" for TLS GD/IE */
++#define R_TILEGX_IMM8_Y0_TLS_ADD 120  /* Y0 pipe "addi" for TLS GD/IE */
++#define R_TILEGX_IMM8_Y1_TLS_ADD 121  /* Y1 pipe "addi" for TLS GD/IE */
++
++#define R_TILEGX_GNU_VTINHERIT        128     /* GNU C++ vtable hierarchy */
++#define R_TILEGX_GNU_VTENTRY  129     /* GNU C++ vtable member usage */
++
++#define R_TILEGX_NUM          130
++
++#endif        /* elf.h */
+--- a/scripts/mod/mk_elfconfig.c
++++ b/scripts/mod/mk_elfconfig.c
+@@ -2,7 +2,11 @@
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
++#ifndef __APPLE__
+ #include <elf.h>
++#else
++#include "elf.h"
++#endif
+ int
+ main(int argc, char **argv)
+--- a/scripts/mod/modpost.h
++++ b/scripts/mod/modpost.h
+@@ -10,7 +10,11 @@
+ #include <sys/mman.h>
+ #include <fcntl.h>
+ #include <unistd.h>
++#if !(defined(__APPLE__) || defined(__CYGWIN__))
+ #include <elf.h>
++#else
++#include "elf.h"
++#endif
+ #include "../../include/linux/module_symbol.h"
+ #include <list_types.h>
diff --git a/target/linux/generic/hack-6.12/211-darwin-uuid-typedef-clash.patch b/target/linux/generic/hack-6.12/211-darwin-uuid-typedef-clash.patch
new file mode 100644 (file)
index 0000000..c0e0b24
--- /dev/null
@@ -0,0 +1,22 @@
+From e44fc2af1ddc452b6659d08c16973d65c73b7d0a Mon Sep 17 00:00:00 2001
+From: Kevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>
+Date: Wed, 5 Feb 2020 18:36:43 +0000
+Subject: [PATCH] file2alias: build on macos
+
+Signed-off-by: Kevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>
+---
+ scripts/mod/file2alias.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/scripts/mod/file2alias.c
++++ b/scripts/mod/file2alias.c
+@@ -35,6 +35,9 @@ typedef uint32_t     __u32;
+ typedef uint16_t      __u16;
+ typedef unsigned char __u8;
++#ifdef __APPLE__
++#define uuid_t compat_uuid_t
++#endif
+ /* UUID types for backward compatibility, don't use in new code */
+ typedef struct {
+       __u8 b[16];
diff --git a/target/linux/generic/hack-6.12/214-spidev_h_portability.patch b/target/linux/generic/hack-6.12/214-spidev_h_portability.patch
new file mode 100644 (file)
index 0000000..db754a2
--- /dev/null
@@ -0,0 +1,24 @@
+From be9be95ff10e16a5b4ad36f903978d0cc5747024 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 7 Jul 2017 17:04:08 +0200
+Subject: kernel: fix linux/spi/spidev.h portability issues with musl
+
+Felix will try to get this define included into musl
+
+lede-commit: 795e7cf60de19e7a076a46874fab7bb88b43bbff
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ include/uapi/linux/spi/spidev.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/include/uapi/linux/spi/spidev.h
++++ b/include/uapi/linux/spi/spidev.h
+@@ -93,7 +93,7 @@ struct spi_ioc_transfer {
+ /* not all platforms use <asm-generic/ioctl.h> or _IOC_TYPECHECK() ... */
+ #define SPI_MSGSIZE(N) \
+-      ((((N)*(sizeof (struct spi_ioc_transfer))) < (1 << _IOC_SIZEBITS)) \
++      ((((N)*(sizeof (struct spi_ioc_transfer))) < (1 << 13)) \
+               ? ((N)*(sizeof (struct spi_ioc_transfer))) : 0)
+ #define SPI_IOC_MESSAGE(N) _IOW(SPI_IOC_MAGIC, 0, char[SPI_MSGSIZE(N)])
diff --git a/target/linux/generic/hack-6.12/230-openwrt_lzma_options.patch b/target/linux/generic/hack-6.12/230-openwrt_lzma_options.patch
new file mode 100644 (file)
index 0000000..20f2b29
--- /dev/null
@@ -0,0 +1,38 @@
+From b3d00b452467f621317953d9e4c6f9ae8dcfd271 Mon Sep 17 00:00:00 2001
+From: Imre Kaloz <kaloz@openwrt.org>
+Date: Fri, 7 Jul 2017 17:06:55 +0200
+Subject: use the openwrt lzma options for now
+
+lede-commit: 548de949f392049420a6a1feeef118b30ab8ea8c
+Signed-off-by: Imre Kaloz <kaloz@openwrt.org>
+---
+ lib/decompress.c              |  1 +
+ scripts/Makefile.lib          |  2 +-
+ usr/gen_initramfs_list.sh | 10 +++++-----
+ 3 files changed, 7 insertions(+), 6 deletions(-)
+
+--- a/lib/decompress.c
++++ b/lib/decompress.c
+@@ -53,6 +53,7 @@ static const struct compress_format comp
+       { {0x1f, 0x9e}, "gzip", gunzip },
+       { {0x42, 0x5a}, "bzip2", bunzip2 },
+       { {0x5d, 0x00}, "lzma", unlzma },
++      { {0x6d, 0x00}, "lzma-openwrt", unlzma },
+       { {0xfd, 0x37}, "xz", unxz },
+       { {0x89, 0x4c}, "lzo", unlzo },
+       { {0x02, 0x21}, "lz4", unlz4 },
+--- a/scripts/Makefile.lib
++++ b/scripts/Makefile.lib
+@@ -359,10 +359,10 @@ quiet_cmd_bzip2_with_size = BZIP2   $@
+ # ---------------------------------------------------------------------------
+ quiet_cmd_lzma = LZMA    $@
+-      cmd_lzma = cat $(real-prereqs) | $(LZMA) -9 > $@
++      cmd_lzma = cat $(real-prereqs) | $(LZMA) e -d20 -lc1 -lp2 -pb2 -eos -si -so > $@
+ quiet_cmd_lzma_with_size = LZMA    $@
+-      cmd_lzma_with_size = { cat $(real-prereqs) | $(LZMA) -9; $(size_append); } > $@
++      cmd_lzma_with_size = { cat $(real-prereqs) | $(LZMA) e -d20 -lc1 -lp2 -pb2 -eos -si -so; $(size_append); } > $@
+ quiet_cmd_lzo = LZO     $@
+       cmd_lzo = cat $(real-prereqs) | $(KLZOP) -9 > $@
diff --git a/target/linux/generic/hack-6.12/249-udp-tunnel-selection.patch b/target/linux/generic/hack-6.12/249-udp-tunnel-selection.patch
new file mode 100644 (file)
index 0000000..7f7e5f1
--- /dev/null
@@ -0,0 +1,36 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Andy Chiang <AndyChiang_git@outlook.com>
+Date: Thu, 20 Nov 2025 20:37:53 +0700
+Subject: [PATCH] kernel: fix build of kmod-udptunnel4 and kmod-udptunnel6
+
+This fixes the following errors:
+```
+make[4]: Entering directory '/home/runner/work/OP/OP/openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/linux-6.12.57'
+  MODPOST /home/runner/work/OP/OP/openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/ovpn-dco-0.2.20250801/drivers/net/ovpn-dco/Module.symvers
+ERROR: modpost: "udp_tunnel6_xmit_skb" [/home/runner/work/OP/OP/openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/ovpn-dco-0.2.20250801/drivers/net/ovpn-dco/ovpn-dco-v2.ko] undefined!
+ERROR: modpost: "setup_udp_tunnel_sock" [/home/runner/work/OP/OP/openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/ovpn-dco-0.2.20250801/drivers/net/ovpn-dco/ovpn-dco-v2.ko] undefined!
+ERROR: modpost: "udp_tunnel_xmit_skb" [/home/runner/work/OP/OP/openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/ovpn-dco-0.2.20250801/drivers/net/ovpn-dco/ovpn-dco-v2.ko] undefined!
+make[6]: *** [scripts/Makefile.modpost:145: /home/runner/work/OP/OP/openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/ovpn-dco-0.2.20250801/drivers/net/ovpn-dco/Module.symvers] Error 1
+make[5]: *** [/home/runner/work/OP/OP/openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/linux-6.12.57/Makefile:1897: modpost] Error 2
+make[4]: *** [Makefile:224: __sub-make] Error 2
+```
+
+ref https://github.com/openwrt/openwrt/commit/1d15a96b29dcd0947690951a7c36aead79a27129
+fixes: https://github.com/openwrt/packages/issues/22998
+
+Signed-off-by: Andy Chiang <AndyChiang_git@outlook.com>
+---
+ net/ipv4/Kconfig | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/net/ipv4/Kconfig
++++ b/net/ipv4/Kconfig
+@@ -315,7 +315,7 @@ config NET_IPVTI
+         on top.
+ config NET_UDP_TUNNEL
+-      tristate
++      tristate "IP: UDP tunneling support"
+       select NET_IP_TUNNEL
+       default n
diff --git a/target/linux/generic/hack-6.12/250-netfilter_depends.patch b/target/linux/generic/hack-6.12/250-netfilter_depends.patch
new file mode 100644 (file)
index 0000000..43faa99
--- /dev/null
@@ -0,0 +1,27 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: hack: net: remove bogus netfilter dependencies
+
+lede-commit: 589d2a377dee27d206fc3725325309cf649e4df6
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ net/netfilter/Kconfig | 2 --
+ 1 file changed, 2 deletions(-)
+
+--- a/net/netfilter/Kconfig
++++ b/net/netfilter/Kconfig
+@@ -259,7 +259,6 @@ config NF_CONNTRACK_FTP
+ config NF_CONNTRACK_H323
+       tristate "H.323 protocol support"
+-      depends on IPV6 || IPV6=n
+       depends on NETFILTER_ADVANCED
+       help
+         H.323 is a VoIP signalling protocol from ITU-T. As one of the most
+@@ -1120,7 +1119,6 @@ config NETFILTER_XT_TARGET_SECMARK
+ config NETFILTER_XT_TARGET_TCPMSS
+       tristate '"TCPMSS" target support'
+-      depends on IPV6 || IPV6=n
+       default m if NETFILTER_ADVANCED=n
+       help
+         This option adds a `TCPMSS' target, which allows you to alter the
diff --git a/target/linux/generic/hack-6.12/251-kconfig.patch b/target/linux/generic/hack-6.12/251-kconfig.patch
new file mode 100644 (file)
index 0000000..dd73e79
--- /dev/null
@@ -0,0 +1,157 @@
+From da3c50704f14132f4adf80d48e9a4cd5d46e54c9 Mon Sep 17 00:00:00 2001
+From: John Crispin <john@phrozen.org>
+Date: Fri, 7 Jul 2017 17:09:21 +0200
+Subject: kconfig: owrt specifc dependencies
+
+Signed-off-by: John Crispin <john@phrozen.org>
+---
+ crypto/Kconfig        | 10 +++++-----
+ drivers/bcma/Kconfig  |  1 +
+ drivers/ssb/Kconfig   |  3 ++-
+ lib/Kconfig           |  8 ++++----
+ net/netfilter/Kconfig |  2 +-
+ net/wireless/Kconfig  | 17 ++++++++++-------
+ sound/core/Kconfig    |  4 ++--
+ 7 files changed, 25 insertions(+), 20 deletions(-)
+
+--- a/crypto/Kconfig
++++ b/crypto/Kconfig
+@@ -55,7 +55,7 @@ config CRYPTO_FIPS_VERSION
+         By default the KERNELRELEASE value is used.
+ config CRYPTO_ALGAPI
+-      tristate
++      tristate "ALGAPI"
+       select CRYPTO_ALGAPI2
+       help
+         This option provides the API for cryptographic algorithms.
+@@ -64,7 +64,7 @@ config CRYPTO_ALGAPI2
+       tristate
+ config CRYPTO_AEAD
+-      tristate
++      tristate "AEAD"
+       select CRYPTO_AEAD2
+       select CRYPTO_ALGAPI
+@@ -82,7 +82,7 @@ config CRYPTO_SIG2
+       select CRYPTO_ALGAPI2
+ config CRYPTO_SKCIPHER
+-      tristate
++      tristate "SKCIPHER"
+       select CRYPTO_SKCIPHER2
+       select CRYPTO_ALGAPI
+       select CRYPTO_ECB
+@@ -92,7 +92,7 @@ config CRYPTO_SKCIPHER2
+       select CRYPTO_ALGAPI2
+ config CRYPTO_HASH
+-      tristate
++      tristate "HASH"
+       select CRYPTO_HASH2
+       select CRYPTO_ALGAPI
+@@ -101,7 +101,7 @@ config CRYPTO_HASH2
+       select CRYPTO_ALGAPI2
+ config CRYPTO_RNG
+-      tristate
++      tristate "RNG"
+       select CRYPTO_RNG2
+       select CRYPTO_ALGAPI
+--- a/drivers/bcma/Kconfig
++++ b/drivers/bcma/Kconfig
+@@ -16,6 +16,7 @@ if BCMA
+ # Support for Block-I/O. SELECT this from the driver that needs it.
+ config BCMA_BLOCKIO
+       bool
++      default y
+ config BCMA_HOST_PCI_POSSIBLE
+       bool
+--- a/drivers/ssb/Kconfig
++++ b/drivers/ssb/Kconfig
+@@ -29,6 +29,7 @@ config SSB_SPROM
+ config SSB_BLOCKIO
+       bool
+       depends on SSB
++      default y
+ config SSB_PCIHOST_POSSIBLE
+       bool
+@@ -49,7 +50,7 @@ config SSB_PCIHOST
+ config SSB_B43_PCI_BRIDGE
+       bool
+       depends on SSB_PCIHOST
+-      default n
++      default y
+ config SSB_PCMCIAHOST_POSSIBLE
+       bool
+--- a/lib/Kconfig
++++ b/lib/Kconfig
+@@ -457,16 +457,16 @@ config BCH_CONST_T
+ # Textsearch support is select'ed if needed
+ #
+ config TEXTSEARCH
+-      bool
++      bool "Textsearch support"
+ config TEXTSEARCH_KMP
+-      tristate
++      tristate "Textsearch KMP"
+ config TEXTSEARCH_BM
+-      tristate
++      tristate "Textsearch BM"
+ config TEXTSEARCH_FSM
+-      tristate
++      tristate "Textsearch FSM"
+ config BTREE
+       bool
+--- a/net/netfilter/Kconfig
++++ b/net/netfilter/Kconfig
+@@ -22,7 +22,7 @@ config NETFILTER_SKIP_EGRESS
+       def_bool NETFILTER_EGRESS && (NET_CLS_ACT || IFB)
+ config NETFILTER_NETLINK
+-      tristate
++      tristate "Netfilter NFNETLINK interface"
+ config NETFILTER_FAMILY_BRIDGE
+       bool
+--- a/sound/core/Kconfig
++++ b/sound/core/Kconfig
+@@ -17,7 +17,7 @@ config SND_DMAENGINE_PCM
+       tristate
+ config SND_HWDEP
+-      tristate
++      tristate "Sound hardware support"
+ config SND_SEQ_DEVICE
+       tristate
+@@ -57,7 +57,7 @@ config SND_CORE_TEST
+ config SND_COMPRESS_OFFLOAD
+-      tristate
++      tristate "Compression offloading support"
+ config SND_JACK
+       bool
+--- a/net/Kconfig
++++ b/net/Kconfig
+@@ -484,7 +484,7 @@ config NET_DEVLINK
+       default n
+ config PAGE_POOL
+-      bool
++      bool "Page pool support"
+ config PAGE_POOL_STATS
+       default n
diff --git a/target/linux/generic/hack-6.12/253-ksmbd-config.patch b/target/linux/generic/hack-6.12/253-ksmbd-config.patch
new file mode 100644 (file)
index 0000000..d9587fb
--- /dev/null
@@ -0,0 +1,32 @@
+From dcd966fa7ca63f38cf7147e1184d13d66e2ca340 Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 13:33:30 +0200
+Subject: [PATCH] Kconfig: add tristate for OID and ASNI string
+
+---
+ init/Kconfig | 2 +-
+ lib/Kconfig  | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+--- a/init/Kconfig
++++ b/init/Kconfig
+@@ -2063,7 +2063,7 @@ config PADATA
+       bool
+ config ASN1
+-      tristate
++      tristate "ASN1"
+       help
+         Build a simple ASN.1 grammar compiler that produces a bytecode output
+         that can be interpreted by the ASN.1 stream decoder and used to
+--- a/lib/Kconfig
++++ b/lib/Kconfig
+@@ -642,7 +642,7 @@ config LIBFDT
+       bool
+ config OID_REGISTRY
+-      tristate
++      tristate "OID"
+       help
+         Enable fast lookup object identifier registry.
diff --git a/target/linux/generic/hack-6.12/259-regmap_dynamic.patch b/target/linux/generic/hack-6.12/259-regmap_dynamic.patch
new file mode 100644 (file)
index 0000000..4f48fc0
--- /dev/null
@@ -0,0 +1,156 @@
+From 811d9e2268a62b830cfe93cd8bc929afcb8b198b Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sat, 15 Jul 2017 21:12:38 +0200
+Subject: kernel: move regmap bloat out of the kernel image if it is only being used in modules
+
+lede-commit: 96f39119815028073583e4fca3a9c5fe9141e998
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ drivers/base/regmap/Kconfig  | 15 ++++++++++-----
+ drivers/base/regmap/Makefile | 12 ++++++++----
+ drivers/base/regmap/regmap.c |  3 +++
+ include/linux/regmap.h       |  2 +-
+ 4 files changed, 22 insertions(+), 10 deletions(-)
+
+--- a/drivers/base/regmap/Kconfig
++++ b/drivers/base/regmap/Kconfig
+@@ -4,8 +4,7 @@
+ # subsystems should select the appropriate symbols.
+ config REGMAP
+-      bool
+-      default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_W1 || REGMAP_AC97 || REGMAP_MMIO || REGMAP_IRQ || REGMAP_SOUNDWIRE || REGMAP_SOUNDWIRE_MBQ || REGMAP_SCCB || REGMAP_I3C || REGMAP_SPI_AVMM || REGMAP_MDIO || REGMAP_FSI)
++      tristate
+       select IRQ_DOMAIN if REGMAP_IRQ
+       select MDIO_BUS if REGMAP_MDIO
+       help
+@@ -19,7 +18,7 @@ config REGMAP
+ config REGMAP_KUNIT
+       tristate "KUnit tests for regmap"
+-      depends on KUNIT && REGMAP
++      depends on KUNIT
+       default KUNIT_ALL_TESTS
+       select REGMAP_RAM
+@@ -34,60 +33,76 @@ config REGMAP_BUILD
+         normally enabled.
+ config REGMAP_AC97
++      select REGMAP
+       tristate
+ config REGMAP_I2C
++      select REGMAP
+       tristate
+       depends on I2C
+ config REGMAP_SLIMBUS
++      select REGMAP
+       tristate
+       depends on SLIMBUS
+ config REGMAP_SPI
++      select REGMAP
+       tristate
+       depends on SPI
+ config REGMAP_SPMI
++      select REGMAP
+       tristate
+       depends on SPMI
+ config REGMAP_W1
++      select REGMAP
+       tristate
+       depends on W1
+ config REGMAP_MDIO
++      select REGMAP
+       tristate
+ config REGMAP_MMIO
++      select REGMAP
+       tristate
+ config REGMAP_IRQ
++      select REGMAP
+       bool
+ config REGMAP_RAM
++      select REGMAP
+       tristate
+ config REGMAP_SOUNDWIRE
++      select REGMAP
+       tristate
+       depends on SOUNDWIRE
+ config REGMAP_SOUNDWIRE_MBQ
++      select REGMAP
+       tristate
+       depends on SOUNDWIRE
+ config REGMAP_SCCB
++      select REGMAP
+       tristate
+       depends on I2C
+ config REGMAP_I3C
++      select REGMAP
+       tristate
+       depends on I3C
+ config REGMAP_SPI_AVMM
++      select REGMAP
+       tristate
+       depends on SPI
+ config REGMAP_FSI
++      select REGMAP
+       tristate
+       depends on FSI
+--- a/drivers/base/regmap/Makefile
++++ b/drivers/base/regmap/Makefile
+@@ -2,9 +2,11 @@
+ # For include/trace/define_trace.h to include trace.h
+ CFLAGS_regmap.o := -I$(src)
+-obj-$(CONFIG_REGMAP) += regmap.o regcache.o
+-obj-$(CONFIG_REGMAP) += regcache-rbtree.o regcache-flat.o regcache-maple.o
+-obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o
++regmap-core-objs = regmap.o regcache.o regcache-rbtree.o regcache-flat.o regcache-maple.o
++ifdef CONFIG_DEBUG_FS
++regmap-core-objs += regmap-debugfs.o
++endif
++obj-$(CONFIG_REGMAP) += regmap-core.o
+ obj-$(CONFIG_REGMAP_KUNIT) += regmap-kunit.o
+ obj-$(CONFIG_REGMAP_AC97) += regmap-ac97.o
+ obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o
+--- a/drivers/base/regmap/regmap.c
++++ b/drivers/base/regmap/regmap.c
+@@ -9,6 +9,7 @@
+ #include <linux/device.h>
+ #include <linux/slab.h>
+ #include <linux/export.h>
++#include <linux/module.h>
+ #include <linux/mutex.h>
+ #include <linux/err.h>
+ #include <linux/property.h>
+@@ -3523,3 +3524,5 @@ static int __init regmap_initcall(void)
+       return 0;
+ }
+ postcore_initcall(regmap_initcall);
++
++MODULE_LICENSE("GPL");
+--- a/include/linux/regmap.h
++++ b/include/linux/regmap.h
+@@ -197,7 +197,7 @@ struct reg_sequence {
+       __ret ?: __tmp; \
+ })
+-#ifdef CONFIG_REGMAP
++#if IS_REACHABLE(CONFIG_REGMAP)
+ enum regmap_endian {
+       /* Unspecified -> 0 -> Backwards compatible default */
diff --git a/target/linux/generic/hack-6.12/260-crypto_test_dependencies.patch b/target/linux/generic/hack-6.12/260-crypto_test_dependencies.patch
new file mode 100644 (file)
index 0000000..ce900a8
--- /dev/null
@@ -0,0 +1,54 @@
+From fd1799b0bf5efa46dd3e6dfbbf3955564807e508 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 7 Jul 2017 17:12:51 +0200
+Subject: kernel: prevent cryptomgr from pulling in useless extra dependencies for tests that are not run
+
+Reduces kernel size after LZMA by about 5k on MIPS
+
+lede-commit: 044c316167e076479a344c59905e5b435b84a77f
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ crypto/Kconfig   | 13 ++++++-------
+ crypto/algboss.c |  4 ++++
+ 2 files changed, 10 insertions(+), 7 deletions(-)
+
+--- a/crypto/Kconfig
++++ b/crypto/Kconfig
+@@ -149,15 +149,15 @@ config CRYPTO_MANAGER
+         cbc(aes).
+ config CRYPTO_MANAGER2
+-      def_tristate CRYPTO_MANAGER || (CRYPTO_MANAGER!=n && CRYPTO_ALGAPI=y)
+-      select CRYPTO_ACOMP2
+-      select CRYPTO_AEAD2
+-      select CRYPTO_AKCIPHER2
+-      select CRYPTO_SIG2
+-      select CRYPTO_HASH2
+-      select CRYPTO_KPP2
+-      select CRYPTO_RNG2
+-      select CRYPTO_SKCIPHER2
++      def_tristate CRYPTO_MANAGER || (CRYPTO_MANAGER!=n && CRYPTO_ALGAPI=y && !CRYPTO_MANAGER_DISABLE_TESTS)
++      select CRYPTO_ACOMP2 if !CRYPTO_MANAGER_DISABLE_TESTS
++      select CRYPTO_AEAD2 if !CRYPTO_MANAGER_DISABLE_TESTS
++      select CRYPTO_AKCIPHER2 if !CRYPTO_MANAGER_DISABLE_TESTS
++      select CRYPTO_SIG2 if !CRYPTO_MANAGER_DISABLE_TESTS
++      select CRYPTO_HASH2 if !CRYPTO_MANAGER_DISABLE_TESTS
++      select CRYPTO_KPP2 if !CRYPTO_MANAGER_DISABLE_TESTS
++      select CRYPTO_RNG2 if !CRYPTO_MANAGER_DISABLE_TESTS
++      select CRYPTO_SKCIPHER2 if !CRYPTO_MANAGER_DISABLE_TESTS
+ config CRYPTO_USER
+       tristate "Userspace cryptographic algorithm configuration"
+--- a/crypto/algboss.c
++++ b/crypto/algboss.c
+@@ -203,6 +203,10 @@ static int cryptomgr_schedule_test(struc
+       memcpy(param->alg, alg->cra_name, sizeof(param->alg));
+       param->type = alg->cra_flags;
++#ifdef CONFIG_CRYPTO_MANAGER_DISABLE_TESTS
++      param->type |= CRYPTO_ALG_TESTED;
++#endif
++
+       thread = kthread_run(cryptomgr_test, param, "cryptomgr_test");
+       if (IS_ERR(thread))
+               goto err_free_param;
diff --git a/target/linux/generic/hack-6.12/261-lib-arc4-unhide.patch b/target/linux/generic/hack-6.12/261-lib-arc4-unhide.patch
new file mode 100644 (file)
index 0000000..f1e1ad4
--- /dev/null
@@ -0,0 +1,24 @@
+From 241e5d3f7b0dd3c01f8c7fa83cbc9a3882286d53 Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 13:35:18 +0200
+Subject: [PATCH] lib/crypto: add tristate string for ARC4
+
+This makes it possible to select CONFIG_CRYPTO_LIB_ARC4 directly. We 
+need this to be able to compile this into the kernel and make use of it 
+from backports.
+
+---
+ lib/crypto/Kconfig | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/lib/crypto/Kconfig
++++ b/lib/crypto/Kconfig
+@@ -20,7 +20,7 @@ config CRYPTO_LIB_AESGCM
+       select CRYPTO_LIB_UTILS
+ config CRYPTO_LIB_ARC4
+-      tristate
++      tristate "ARC4 cipher library"
+ config CRYPTO_LIB_GF128MUL
+       tristate
diff --git a/target/linux/generic/hack-6.12/280-rfkill-stubs.patch b/target/linux/generic/hack-6.12/280-rfkill-stubs.patch
new file mode 100644 (file)
index 0000000..ff6638f
--- /dev/null
@@ -0,0 +1,84 @@
+From 236c1acdfef5958010ac9814a9872e0a46fd78ee Mon Sep 17 00:00:00 2001
+From: John Crispin <john@phrozen.org>
+Date: Fri, 7 Jul 2017 17:13:44 +0200
+Subject: rfkill: add fake rfkill support
+
+allow building of modules depending on RFKILL even if RFKILL is not enabled.
+
+Signed-off-by: John Crispin <john@phrozen.org>
+---
+ include/linux/rfkill.h |  2 +-
+ net/Makefile           |  2 +-
+ net/rfkill/Kconfig     | 14 +++++++++-----
+ net/rfkill/Makefile    |  2 +-
+ 4 files changed, 12 insertions(+), 8 deletions(-)
+
+--- a/include/linux/rfkill.h
++++ b/include/linux/rfkill.h
+@@ -64,7 +64,7 @@ struct rfkill_ops {
+       int     (*set_block)(void *data, bool blocked);
+ };
+-#if defined(CONFIG_RFKILL) || defined(CONFIG_RFKILL_MODULE)
++#if defined(CONFIG_RFKILL_FULL) || defined(CONFIG_RFKILL_FULL_MODULE)
+ /**
+  * rfkill_alloc - Allocate rfkill structure
+  * @name: name of the struct -- the string is not copied internally
+--- a/net/Makefile
++++ b/net/Makefile
+@@ -51,7 +51,7 @@ obj-$(CONFIG_TIPC)           += tipc/
+ obj-$(CONFIG_NETLABEL)                += netlabel/
+ obj-$(CONFIG_IUCV)            += iucv/
+ obj-$(CONFIG_SMC)             += smc/
+-obj-$(CONFIG_RFKILL)          += rfkill/
++obj-$(CONFIG_RFKILL_FULL)     += rfkill/
+ obj-$(CONFIG_NET_9P)          += 9p/
+ obj-$(CONFIG_CAIF)            += caif/
+ obj-$(CONFIG_DCB)             += dcb/
+--- a/net/rfkill/Kconfig
++++ b/net/rfkill/Kconfig
+@@ -2,7 +2,11 @@
+ #
+ # RF switch subsystem configuration
+ #
+-menuconfig RFKILL
++config RFKILL
++      bool
++      default y
++
++menuconfig RFKILL_FULL
+       tristate "RF switch subsystem support"
+       help
+         Say Y here if you want to have control over RF switches
+@@ -14,19 +18,19 @@ menuconfig RFKILL
+ # LED trigger support
+ config RFKILL_LEDS
+       bool
+-      depends on RFKILL
++      depends on RFKILL_FULL
+       depends on LEDS_TRIGGERS = y || RFKILL = LEDS_TRIGGERS
+       default y
+ config RFKILL_INPUT
+       bool "RF switch input support" if EXPERT
+-      depends on RFKILL
++      depends on RFKILL_FULL
+       depends on INPUT = y || RFKILL = INPUT
+       default y if !EXPERT
+ config RFKILL_GPIO
+       tristate "GPIO RFKILL driver"
+-      depends on RFKILL
++      depends on RFKILL_FULL
+       depends on GPIOLIB || COMPILE_TEST
+       default n
+       help
+--- a/net/rfkill/Makefile
++++ b/net/rfkill/Makefile
+@@ -5,5 +5,5 @@
+ rfkill-y                      += core.o
+ rfkill-$(CONFIG_RFKILL_INPUT) += input.o
+-obj-$(CONFIG_RFKILL)          += rfkill.o
++obj-$(CONFIG_RFKILL_FULL)     += rfkill.o
+ obj-$(CONFIG_RFKILL_GPIO)     += rfkill-gpio.o
diff --git a/target/linux/generic/hack-6.12/300-MIPS-r4k_cache-use-more-efficient-cache-blast.patch b/target/linux/generic/hack-6.12/300-MIPS-r4k_cache-use-more-efficient-cache-blast.patch
new file mode 100644 (file)
index 0000000..6ee98eb
--- /dev/null
@@ -0,0 +1,64 @@
+From: Ben Menchaca <ben.menchaca@qca.qualcomm.com>
+Date: Fri, 7 Jun 2013 18:35:22 -0500
+Subject: MIPS: r4k_cache: use more efficient cache blast
+
+Optimize the compiler output for larger cache blast cases that are
+common for DMA-based networking.
+
+Signed-off-by: Ben Menchaca <ben.menchaca@qca.qualcomm.com>
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+--- a/arch/mips/include/asm/r4kcache.h
++++ b/arch/mips/include/asm/r4kcache.h
+@@ -290,14 +290,46 @@ static inline void prot##extra##blast_##
+                                                   unsigned long end)  \
+ {                                                                     \
+       unsigned long lsize = cpu_##desc##_line_size();                 \
++      unsigned long lsize_2 = lsize * 2;                              \
++      unsigned long lsize_3 = lsize * 3;                              \
++      unsigned long lsize_4 = lsize * 4;                              \
++      unsigned long lsize_5 = lsize * 5;                              \
++      unsigned long lsize_6 = lsize * 6;                              \
++      unsigned long lsize_7 = lsize * 7;                              \
++      unsigned long lsize_8 = lsize * 8;                              \
+       unsigned long addr = start & ~(lsize - 1);                      \
+-      unsigned long aend = (end - 1) & ~(lsize - 1);                  \
++      unsigned long aend = (end + lsize - 1) & ~(lsize - 1);          \
++      int lines = (aend - addr) / lsize;                              \
+                                                                       \
+-      while (1) {                                                     \
++      while (lines >= 8) {                                            \
++              prot##cache_op(hitop, addr);                            \
++              prot##cache_op(hitop, addr + lsize);                    \
++              prot##cache_op(hitop, addr + lsize_2);                  \
++              prot##cache_op(hitop, addr + lsize_3);                  \
++              prot##cache_op(hitop, addr + lsize_4);                  \
++              prot##cache_op(hitop, addr + lsize_5);                  \
++              prot##cache_op(hitop, addr + lsize_6);                  \
++              prot##cache_op(hitop, addr + lsize_7);                  \
++              addr += lsize_8;                                        \
++              lines -= 8;                                             \
++      }                                                               \
++                                                                      \
++      if (lines & 0x4) {                                              \
++              prot##cache_op(hitop, addr);                            \
++              prot##cache_op(hitop, addr + lsize);                    \
++              prot##cache_op(hitop, addr + lsize_2);                  \
++              prot##cache_op(hitop, addr + lsize_3);                  \
++              addr += lsize_4;                                        \
++      }                                                               \
++                                                                      \
++      if (lines & 0x2) {                                              \
++              prot##cache_op(hitop, addr);                            \
++              prot##cache_op(hitop, addr + lsize);                    \
++              addr += lsize_2;                                        \
++      }                                                               \
++                                                                      \
++      if (lines & 0x1) {                                              \
+               prot##cache_op(hitop, addr);                            \
+-              if (addr == aend)                                       \
+-                      break;                                          \
+-              addr += lsize;                                          \
+       }                                                               \
+ }
diff --git a/target/linux/generic/hack-6.12/301-01-mm-permit-to-declare-custom-execmem-alloc-free-funct.patch b/target/linux/generic/hack-6.12/301-01-mm-permit-to-declare-custom-execmem-alloc-free-funct.patch
new file mode 100644 (file)
index 0000000..3128ed3
--- /dev/null
@@ -0,0 +1,62 @@
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 14 Apr 2025 18:04:25 +0200
+Subject: [PATCH 1/2] mm: permit to declare custom execmem alloc/free function
+
+Permit to declare custom execmem alloc/free function that bypass the
+execmem API. This works by making the alloc/free function weak
+permitting an arch to declare a replacement for them.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Co-authored-by: Shiji Yang <yangshiji66@outlook.com>
+---
+ include/linux/moduleloader.h |  4 ++++
+ mm/execmem.c                 | 14 ++++++++++++--
+ 2 files changed, 16 insertions(+), 2 deletions(-)
+
+--- a/include/linux/moduleloader.h
++++ b/include/linux/moduleloader.h
+@@ -122,4 +122,8 @@ void module_arch_cleanup(struct module *
+ /* Any cleanup before freeing mod->module_init */
+ void module_arch_freeing_init(struct module *mod);
++enum execmem_type;
++void *arch_execmem_alloc(enum execmem_type type, size_t size);
++void arch_execmem_free(void *ptr);
++
+ #endif
+--- a/mm/execmem.c
++++ b/mm/execmem.c
+@@ -52,14 +52,19 @@ static void *__execmem_alloc(struct exec
+       return kasan_reset_tag(p);
+ }
+-void *execmem_alloc(enum execmem_type type, size_t size)
++void *__weak arch_execmem_alloc(enum execmem_type type, size_t size)
+ {
+       struct execmem_range *range = &execmem_info->ranges[type];
+       return __execmem_alloc(range, size);
+ }
+-void execmem_free(void *ptr)
++void *execmem_alloc(enum execmem_type type, size_t size)
++{
++      return arch_execmem_alloc(type, size);
++}
++
++void __weak arch_execmem_free(void *ptr)
+ {
+       /*
+        * This memory may be RO, and freeing RO memory in an interrupt is not
+@@ -69,6 +74,11 @@ void execmem_free(void *ptr)
+       vfree(ptr);
+ }
++void execmem_free(void *ptr)
++{
++      arch_execmem_free(ptr);
++}
++
+ static bool execmem_validate(struct execmem_info *info)
+ {
+       struct execmem_range *r = &info->ranges[EXECMEM_DEFAULT];
diff --git a/target/linux/generic/hack-6.12/301-02-mips-replace-mlong-calls-with-mno-long-calls-if-poss.patch b/target/linux/generic/hack-6.12/301-02-mips-replace-mlong-calls-with-mno-long-calls-if-poss.patch
new file mode 100644 (file)
index 0000000..eaf4de1
--- /dev/null
@@ -0,0 +1,396 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Mon, 14 Apr 2025 18:05:45 +0200
+Subject: [PATCH 2/2] mips: replace -mlong-calls with -mno-long-calls if
+ possible
+
+This is a really old patch ported from MikroTik. It needs a an
+additional patch to actually be implemented and both this and the old
+one are considered HACK as they bypass normal kernel linux to make it
+work.
+
+The original message quote:
+
+replace -mlong-calls with -mno-long-calls to make function
+calls faster in kernel modules to achieve this, try to load
+kernel modules to KSEG0 and if that doesn't work, use vmalloc
+and fix up relocations with a jump table based on code from a
+kernel patch by MikroTik.
+
+SVN-Revision: 16772
+
+lede-commit: 3b3d64743ba2a874df9d70cd19e242205b0a788c
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ arch/mips/Makefile             |  10 ++
+ arch/mips/include/asm/module.h |   5 +
+ arch/mips/kernel/module.c      | 281 ++++++++++++++++++++++++++++++++-
+ 3 files changed, 292 insertions(+), 4 deletions(-)
+
+--- a/arch/mips/Makefile
++++ b/arch/mips/Makefile
+@@ -97,8 +97,18 @@ all-$(CONFIG_SYS_SUPPORTS_ZBOOT)+= vmlin
+ cflags-y                      += -G 0 -mno-abicalls -fno-pic -pipe -mno-branch-likely
+ cflags-y                      += -msoft-float -Wa,-msoft-float
+ LDFLAGS_vmlinux                       += -G 0 -static -n -nostdlib
++ifdef CONFIG_64BIT
+ KBUILD_AFLAGS_MODULE          += -mlong-calls
+ KBUILD_CFLAGS_MODULE          += -mlong-calls
++else
++  ifdef CONFIG_DYNAMIC_FTRACE
++    KBUILD_AFLAGS_MODULE      += -mlong-calls
++    KBUILD_CFLAGS_MODULE      += -mlong-calls
++  else
++    KBUILD_AFLAGS_MODULE      += -mno-long-calls
++    KBUILD_CFLAGS_MODULE      += -mno-long-calls
++  endif
++endif
+ ifeq ($(CONFIG_RELOCATABLE),y)
+ LDFLAGS_vmlinux                       += --emit-relocs
+--- a/arch/mips/include/asm/module.h
++++ b/arch/mips/include/asm/module.h
+@@ -12,6 +12,11 @@ struct mod_arch_specific {
+       const struct exception_table_entry *dbe_start;
+       const struct exception_table_entry *dbe_end;
+       struct mips_hi16 *r_mips_hi16_list;
++
++      void *phys_plt_tbl;
++      void *virt_plt_tbl;
++      unsigned int phys_plt_offset;
++      unsigned int virt_plt_offset;
+ };
+ typedef uint8_t Elf64_Byte;           /* Type for a 8-bit quantity.  */
+--- a/arch/mips/kernel/module.c
++++ b/arch/mips/kernel/module.c
+@@ -8,6 +8,7 @@
+ #undef DEBUG
++#include <linux/execmem.h>
+ #include <linux/extable.h>
+ #include <linux/moduleloader.h>
+ #include <linux/elf.h>
+@@ -19,6 +20,7 @@
+ #include <linux/kernel.h>
+ #include <linux/spinlock.h>
+ #include <linux/jump_label.h>
++#include <linux/vmalloc.h>
+ #include <asm/jump_label.h>
+ struct mips_hi16 {
+@@ -30,14 +32,254 @@ struct mips_hi16 {
+ static LIST_HEAD(dbe_list);
+ static DEFINE_SPINLOCK(dbe_lock);
++/*
++ * Get the potential max trampolines size required of the init and
++ * non-init sections. Only used if we cannot find enough contiguous
++ * physically mapped memory to put the module into.
++ */
++static unsigned int
++get_plt_size(const Elf_Ehdr *hdr, const Elf_Shdr *sechdrs,
++             const char *secstrings, unsigned int symindex, bool is_init)
++{
++      unsigned long ret = 0;
++      unsigned int i, j;
++      Elf_Sym *syms;
++
++      /* Everything marked ALLOC (this includes the exported symbols) */
++      for (i = 1; i < hdr->e_shnum; ++i) {
++              unsigned int info = sechdrs[i].sh_info;
++
++              if (sechdrs[i].sh_type != SHT_REL
++                  && sechdrs[i].sh_type != SHT_RELA)
++                      continue;
++
++              /* Not a valid relocation section? */
++              if (info >= hdr->e_shnum)
++                      continue;
++
++              /* Don't bother with non-allocated sections */
++              if (!(sechdrs[info].sh_flags & SHF_ALLOC))
++                      continue;
++
++              /* If it's called *.init*, and we're not init, we're
++                   not interested */
++              if ((strstr(secstrings + sechdrs[i].sh_name, ".init") != 0)
++                  != is_init)
++                      continue;
++
++              syms = (Elf_Sym *) sechdrs[symindex].sh_addr;
++              if (sechdrs[i].sh_type == SHT_REL) {
++                      Elf_Mips_Rel *rel = (void *) sechdrs[i].sh_addr;
++                      unsigned int size = sechdrs[i].sh_size / sizeof(*rel);
++
++                      for (j = 0; j < size; ++j) {
++                              Elf_Sym *sym;
++
++                              if (ELF_MIPS_R_TYPE(rel[j]) != R_MIPS_26)
++                                      continue;
++
++                              sym = syms + ELF_MIPS_R_SYM(rel[j]);
++                              if (!is_init && sym->st_shndx != SHN_UNDEF)
++                                      continue;
++
++                              ret += 4 * sizeof(int);
++                      }
++              } else {
++                      Elf_Mips_Rela *rela = (void *) sechdrs[i].sh_addr;
++                      unsigned int size = sechdrs[i].sh_size / sizeof(*rela);
++
++                      for (j = 0; j < size; ++j) {
++                              Elf_Sym *sym;
++
++                              if (ELF_MIPS_R_TYPE(rela[j]) != R_MIPS_26)
++                                      continue;
++
++                              sym = syms + ELF_MIPS_R_SYM(rela[j]);
++                              if (!is_init && sym->st_shndx != SHN_UNDEF)
++                                      continue;
++
++                              ret += 4 * sizeof(int);
++                      }
++              }
++      }
++
++      return ret;
++}
++
++#ifndef MODULES_VADDR
++static void *alloc_phys(unsigned long size)
++{
++      unsigned order;
++      struct page *page;
++      struct page *p;
++
++      size = PAGE_ALIGN(size);
++      order = get_order(size);
++
++      page = alloc_pages(GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN |
++                      __GFP_THISNODE, order);
++      if (!page)
++              return NULL;
++
++      split_page(page, order);
++
++      /* mark all pages except for the last one */
++      for (p = page; p + 1 < page + (size >> PAGE_SHIFT); ++p)
++              set_bit(PG_owner_priv_1, &p->flags);
++
++      for (p = page + (size >> PAGE_SHIFT); p < page + (1 << order); ++p)
++              __free_page(p);
++
++      return page_address(page);
++}
++
++static void free_phys(void *ptr)
++{
++      struct page *page;
++      bool free;
++
++      page = virt_to_page(ptr);
++      do {
++              free = test_and_clear_bit(PG_owner_priv_1, &page->flags);
++              __free_page(page);
++              page++;
++      } while (free);
++}
++
++void *arch_execmem_alloc(enum execmem_type type,
++                       size_t size)
++{
++      void *ptr;
++
++      ptr = alloc_phys(size);
++
++      /* If we failed to allocate physically contiguous memory,
++       * fall back to regular vmalloc. The module loader code will
++       * create jump tables to handle long jumps */
++      if (!ptr)
++              return vmalloc(size);
++
++      return ptr;
++}
++#endif
++
++static inline bool is_phys_addr(void *ptr)
++{
++#ifdef CONFIG_64BIT
++      return (KSEGX((unsigned long)ptr) == CKSEG0);
++#else
++      return (KSEGX(ptr) == KSEG0);
++#endif
++}
++
++#ifndef MODULES_VADDR
++/* Free memory returned from module_alloc */
++void arch_execmem_free(void *ptr)
++{
++      if (is_phys_addr(ptr))
++              free_phys(ptr);
++      else
++              vfree(ptr);
++}
++#endif
++
++static void *__module_alloc(int size, bool phys)
++{
++      void *ptr;
++
++      if (phys)
++              ptr = kmalloc(size, GFP_KERNEL);
++      else
++              ptr = vmalloc(size);
++      return ptr;
++}
++
++static void __module_free(void *ptr)
++{
++      if (is_phys_addr(ptr))
++              kfree(ptr);
++      else
++              vfree(ptr);
++}
++
++int module_frob_arch_sections(Elf_Ehdr *hdr, Elf_Shdr *sechdrs,
++                            char *secstrings, struct module *mod)
++{
++      unsigned int symindex = 0;
++      unsigned int core_size, init_size;
++      int i;
++
++      mod->arch.phys_plt_offset = 0;
++      mod->arch.virt_plt_offset = 0;
++      mod->arch.phys_plt_tbl = NULL;
++      mod->arch.virt_plt_tbl = NULL;
++
++      if (IS_ENABLED(CONFIG_64BIT))
++              return 0;
++
++      for (i = 1; i < hdr->e_shnum; i++)
++              if (sechdrs[i].sh_type == SHT_SYMTAB)
++                      symindex = i;
++
++      core_size = get_plt_size(hdr, sechdrs, secstrings, symindex, false);
++      init_size = get_plt_size(hdr, sechdrs, secstrings, symindex, true);
++
++      if ((core_size + init_size) == 0)
++              return 0;
++
++      mod->arch.phys_plt_tbl = __module_alloc(core_size + init_size, 1);
++      if (!mod->arch.phys_plt_tbl)
++              return -ENOMEM;
++
++      mod->arch.virt_plt_tbl = __module_alloc(core_size + init_size, 0);
++      if (!mod->arch.virt_plt_tbl) {
++              __module_free(mod->arch.phys_plt_tbl);
++              mod->arch.phys_plt_tbl = NULL;
++              return -ENOMEM;
++      }
++
++      return 0;
++}
++
+ static void apply_r_mips_32(u32 *location, u32 base, Elf_Addr v)
+ {
+       *location = base + v;
+ }
++static Elf_Addr add_plt_entry_to(unsigned *plt_offset,
++                               void *start, Elf_Addr v)
++{
++      unsigned *tramp = start + *plt_offset;
++      *plt_offset += 4 * sizeof(int);
++
++      /* adjust carry for addiu */
++      if (v & 0x00008000)
++              v += 0x10000;
++
++      tramp[0] = 0x3c190000 | (v >> 16);      /* lui t9, hi16 */
++      tramp[1] = 0x27390000 | (v & 0xffff);   /* addiu t9, t9, lo16 */
++      tramp[2] = 0x03200008;                  /* jr t9 */
++      tramp[3] = 0x00000000;                  /* nop */
++
++      return (Elf_Addr) tramp;
++}
++
++static Elf_Addr add_plt_entry(struct module *me, void *location, Elf_Addr v)
++{
++      if (is_phys_addr(location))
++              return add_plt_entry_to(&me->arch.phys_plt_offset,
++                              me->arch.phys_plt_tbl, v);
++      else
++              return add_plt_entry_to(&me->arch.virt_plt_offset,
++                              me->arch.virt_plt_tbl, v);
++
++}
++
+ static int apply_r_mips_26(struct module *me, u32 *location, u32 base,
+                          Elf_Addr v)
+ {
++      u32 ofs = base & 0x03ffffff;
++
+       if (v % 4) {
+               pr_err("module %s: dangerous R_MIPS_26 relocation\n",
+                      me->name);
+@@ -45,13 +287,17 @@ static int apply_r_mips_26(struct module
+       }
+       if ((v & 0xf0000000) != (((unsigned long)location + 4) & 0xf0000000)) {
+-              pr_err("module %s: relocation overflow\n",
+-                     me->name);
+-              return -ENOEXEC;
++              v = add_plt_entry(me, location, v + (ofs << 2));
++              if (!v) {
++                      pr_err("module %s: relocation overflow\n",
++                             me->name);
++                      return -ENOEXEC;
++              }
++              ofs = 0;
+       }
+       *location = (*location & ~0x03ffffff) |
+-                  ((base + (v >> 2)) & 0x03ffffff);
++                  ((ofs + (v >> 2)) & 0x03ffffff);
+       return 0;
+ }
+@@ -431,9 +677,36 @@ int module_finalize(const Elf_Ehdr *hdr,
+               list_add(&me->arch.dbe_list, &dbe_list);
+               spin_unlock_irq(&dbe_lock);
+       }
++
++      /* Get rid of the fixup trampoline if we're running the module
++       * from physically mapped address space */
++      if (me->arch.phys_plt_offset == 0) {
++              __module_free(me->arch.phys_plt_tbl);
++              me->arch.phys_plt_tbl = NULL;
++      }
++      if (me->arch.virt_plt_offset == 0) {
++              __module_free(me->arch.virt_plt_tbl);
++              me->arch.virt_plt_tbl = NULL;
++      }
++
+       return 0;
+ }
++void module_arch_freeing_init(struct module *mod)
++{
++      if (mod->state == MODULE_STATE_LIVE)
++              return;
++
++      if (mod->arch.phys_plt_tbl) {
++              __module_free(mod->arch.phys_plt_tbl);
++              mod->arch.phys_plt_tbl = NULL;
++      }
++      if (mod->arch.virt_plt_tbl) {
++              __module_free(mod->arch.virt_plt_tbl);
++              mod->arch.virt_plt_tbl = NULL;
++      }
++}
++
+ void module_arch_cleanup(struct module *mod)
+ {
+       spin_lock_irq(&dbe_lock);
diff --git a/target/linux/generic/hack-6.12/402-mtd-blktrans-call-add-disks-after-mtd-device.patch b/target/linux/generic/hack-6.12/402-mtd-blktrans-call-add-disks-after-mtd-device.patch
new file mode 100644 (file)
index 0000000..d5a68d6
--- /dev/null
@@ -0,0 +1,112 @@
+From 0bccc3722bdd88e8ae995e77ef9f7b77ee4cbdee Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 7 Apr 2021 22:45:54 +0100
+Subject: [PATCH 2/2] mtd: blktrans: call add disks after mtd device
+To: linux-mtd@lists.infradead.org
+Cc: Vignesh Raghavendra <vigneshr@ti.com>,
+    Richard Weinberger <richard@nod.at>,
+    Miquel Raynal <miquel.raynal@bootlin.com>,
+    David Woodhouse <dwmw2@infradead.org>
+
+Calling device_add_disk while holding mtd_table_mutex leads
+to deadlock in case part_bits!=0 as block partition parsers
+will try to open the newly created disks, trying to acquire
+mutex once again.
+Move device_add_disk to additional function called after
+add partitions of an MTD device have been added and locks
+have been released.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/mtd/mtd_blkdevs.c    | 33 ++++++++++++++++++++++++++-------
+ drivers/mtd/mtdcore.c        |  3 +++
+ include/linux/mtd/blktrans.h |  1 +
+ 3 files changed, 30 insertions(+), 7 deletions(-)
+
+--- a/drivers/mtd/mtd_blkdevs.c
++++ b/drivers/mtd/mtd_blkdevs.c
+@@ -379,19 +379,8 @@ int add_mtd_blktrans_dev(struct mtd_blkt
+       if (new->readonly)
+               set_disk_ro(gd, 1);
+-      ret = device_add_disk(&new->mtd->dev, gd, NULL);
+-      if (ret)
+-              goto out_cleanup_disk;
+-
+-      if (new->disk_attributes) {
+-              ret = sysfs_create_group(&disk_to_dev(gd)->kobj,
+-                                      new->disk_attributes);
+-              WARN_ON(ret);
+-      }
+       return 0;
+-out_cleanup_disk:
+-      put_disk(new->disk);
+ out_free_tag_set:
+       blk_mq_free_tag_set(new->tag_set);
+ out_kfree_tag_set:
+@@ -401,6 +390,35 @@ out_list_del:
+       return ret;
+ }
++void register_mtd_blktrans_devs(void)
++{
++      struct mtd_blktrans_ops *tr;
++      struct mtd_blktrans_dev *dev, *next;
++      int ret;
++
++      list_for_each_entry(tr, &blktrans_majors, list) {
++              list_for_each_entry_safe(dev, next, &tr->devs, list) {
++                      if (disk_live(dev->disk))
++                              continue;
++
++                      ret = device_add_disk(&dev->mtd->dev, dev->disk, NULL);
++                      if (ret)
++                              goto out_cleanup_disk;
++
++                      if (dev->disk_attributes) {
++                              ret = sysfs_create_group(&disk_to_dev(dev->disk)->kobj,
++                                                      dev->disk_attributes);
++                              WARN_ON(ret);
++                      }
++              }
++      }
++
++      return;
++
++out_cleanup_disk:
++      put_disk(dev->disk);
++}
++
+ int del_mtd_blktrans_dev(struct mtd_blktrans_dev *old)
+ {
+       unsigned long flags;
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -34,6 +34,7 @@
+ #include <linux/mtd/mtd.h>
+ #include <linux/mtd/partitions.h>
++#include <linux/mtd/blktrans.h>
+ #include "mtdcore.h"
+@@ -1109,6 +1110,8 @@ int mtd_device_parse_register(struct mtd
+               register_reboot_notifier(&mtd->reboot_notifier);
+       }
++      register_mtd_blktrans_devs();
++
+ out:
+       if (ret) {
+               nvmem_unregister(mtd->otp_user_nvmem);
+--- a/include/linux/mtd/blktrans.h
++++ b/include/linux/mtd/blktrans.h
+@@ -76,6 +76,7 @@ extern int deregister_mtd_blktrans(struc
+ extern int add_mtd_blktrans_dev(struct mtd_blktrans_dev *dev);
+ extern int del_mtd_blktrans_dev(struct mtd_blktrans_dev *dev);
+ extern int mtd_blktrans_cease_background(struct mtd_blktrans_dev *dev);
++extern void register_mtd_blktrans_devs(void);
+ /**
+  * module_mtd_blktrans() - Helper macro for registering a mtd blktrans driver
diff --git a/target/linux/generic/hack-6.12/420-mtd-support-OpenWrt-s-MTD_ROOTFS_ROOT_DEV.patch b/target/linux/generic/hack-6.12/420-mtd-support-OpenWrt-s-MTD_ROOTFS_ROOT_DEV.patch
new file mode 100644 (file)
index 0000000..1b17c2f
--- /dev/null
@@ -0,0 +1,24 @@
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Mon, 7 Nov 2022 23:48:24 +0100
+Subject: [PATCH] mtd: support OpenWrt's MTD_ROOTFS_ROOT_DEV
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This allows setting ROOT_DEV to MTD partition named "rootfs".
+
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+---
+
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -780,7 +780,8 @@ int add_mtd_device(struct mtd_info *mtd)
+       mutex_unlock(&mtd_table_mutex);
+-      if (of_property_read_bool(mtd_get_of_node(mtd), "linux,rootfs")) {
++      if (of_property_read_bool(mtd_get_of_node(mtd), "linux,rootfs") ||
++          (IS_ENABLED(CONFIG_MTD_ROOTFS_ROOT_DEV) && !strcmp(mtd->name, "rootfs") && ROOT_DEV == 0)) {
+               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);
diff --git a/target/linux/generic/hack-6.12/421-drivers-mtd-parsers-add-nvmem-support-to-cmdlinepart.patch b/target/linux/generic/hack-6.12/421-drivers-mtd-parsers-add-nvmem-support-to-cmdlinepart.patch
new file mode 100644 (file)
index 0000000..190fecc
--- /dev/null
@@ -0,0 +1,120 @@
+From 6fa9e3678eb002246df1280322b6a024853950a5 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Mon, 11 Oct 2021 00:53:14 +0200
+Subject: [PATCH] drivers: mtd: parsers: add nvmem support to cmdlinepart
+
+Assuming cmdlinepart is only one level deep partition scheme and that
+static partition are also defined in DTS, we can assign an of_node for
+partition declared from bootargs. cmdlinepart have priority than
+fiexed-partition parser so in this specific case the parser doesn't
+assign an of_node. Fix this by searching a defined of_node using a
+similar fixed_partition parser and if a partition is found with the same
+label, check that it has the same offset and size and return the DT
+of_node to correctly use NVMEM cells.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+---
+ drivers/mtd/parsers/cmdlinepart.c | 71 +++++++++++++++++++++++++++++++
+ 1 file changed, 71 insertions(+)
+
+--- a/drivers/mtd/parsers/cmdlinepart.c
++++ b/drivers/mtd/parsers/cmdlinepart.c
+@@ -43,6 +43,7 @@
+ #include <linux/mtd/partitions.h>
+ #include <linux/module.h>
+ #include <linux/err.h>
++#include <linux/of.h>
+ /* special size referring to all the remaining space in a partition */
+ #define SIZE_REMAINING ULLONG_MAX
+@@ -315,6 +316,68 @@ static int mtdpart_setup_real(char *s)
+       return 0;
+ }
++static int search_fixed_partition(struct mtd_info *master,
++                                struct mtd_partition *target_part,
++                                struct mtd_partition *fixed_part)
++{
++      struct device_node *mtd_node;
++      struct device_node *ofpart_node;
++      struct device_node *pp;
++      struct mtd_partition part;
++      const char *partname;
++
++      mtd_node = mtd_get_of_node(master);
++      if (!mtd_node)
++              return -EINVAL;
++
++      ofpart_node = of_get_child_by_name(mtd_node, "partitions");
++
++      for_each_child_of_node(ofpart_node,  pp) {
++              const __be32 *reg;
++              int len;
++              int a_cells, s_cells;
++
++              reg = of_get_property(pp, "reg", &len);
++              if (!reg) {
++                      pr_debug("%s: ofpart partition %pOF (%pOF) missing reg property.\n",
++                               master->name, pp,
++                               mtd_node);
++                      continue;
++              }
++
++              a_cells = of_n_addr_cells(pp);
++              s_cells = of_n_size_cells(pp);
++              if (len / 4 != a_cells + s_cells) {
++                      pr_debug("%s: ofpart partition %pOF (%pOF) error parsing reg property.\n",
++                               master->name, pp,
++                               mtd_node);
++                      continue;
++              }
++
++              part.offset = of_read_number(reg, a_cells);
++              part.size = of_read_number(reg + a_cells, s_cells);
++              part.of_node = pp;
++
++              partname = of_get_property(pp, "label", &len);
++              if (!partname)
++                      partname = of_get_property(pp, "name", &len);
++              part.name = partname;
++
++              if (!strncmp(target_part->name, part.name, len)) {
++                      if (part.offset != target_part->offset)
++                              return -EINVAL;
++
++                      if (part.size != target_part->size)
++                              return -EINVAL;
++
++                      memcpy(fixed_part, &part, sizeof(struct mtd_partition));
++                      return 0;
++              }
++      }
++
++      return -EINVAL;
++}
++
+ /*
+  * Main function to be called from the MTD mapping driver/device to
+  * obtain the partitioning information. At this point the command line
+@@ -330,6 +393,7 @@ static int parse_cmdline_partitions(stru
+       int i, err;
+       struct cmdline_mtd_partition *part;
+       const char *mtd_id = master->name;
++      struct mtd_partition fixed_part;
+       /* parse command line */
+       if (!cmdline_parsed) {
+@@ -374,6 +438,13 @@ static int parse_cmdline_partitions(stru
+                               sizeof(*part->parts) * (part->num_parts - i));
+                       i--;
+               }
++
++              err = search_fixed_partition(master, &part->parts[i], &fixed_part);
++              if (!err) {
++                      part->parts[i].of_node = fixed_part.of_node;
++                      pr_info("Found partition defined in DT for %s. Assigning OF node to support nvmem.",
++                              part->parts[i].name);
++              }
+       }
+       *pparts = kmemdup(part->parts, sizeof(*part->parts) * part->num_parts,
diff --git a/target/linux/generic/hack-6.12/430-mtk-bmt-support.patch b/target/linux/generic/hack-6.12/430-mtk-bmt-support.patch
new file mode 100644 (file)
index 0000000..8d1c165
--- /dev/null
@@ -0,0 +1,33 @@
+From ac84397efb3b3868c71c10ad7521161773228a17 Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 13:41:44 +0200
+Subject: [PATCH] mtd/nand: add MediaTek NAND bad block managment table
+
+---
+ drivers/mtd/nand/Kconfig  | 4 ++++
+ drivers/mtd/nand/Makefile | 1 +
+ 2 files changed, 5 insertions(+)
+
+--- a/drivers/mtd/nand/Kconfig
++++ b/drivers/mtd/nand/Kconfig
+@@ -46,6 +46,10 @@ config MTD_NAND_ECC_SW_BCH
+         ECC codes. They are used with NAND devices requiring more than 1 bit
+         of error correction.
++config MTD_NAND_MTK_BMT
++      bool "Support MediaTek NAND Bad-block Management Table"
++      default n
++
+ config MTD_NAND_ECC_MXIC
+       bool "Macronix external hardware ECC engine"
+       depends on HAS_IOMEM
+--- a/drivers/mtd/nand/Makefile
++++ b/drivers/mtd/nand/Makefile
+@@ -3,6 +3,7 @@
+ nandcore-objs := core.o bbt.o
+ obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
+ obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o
++obj-$(CONFIG_MTD_NAND_MTK_BMT)        += mtk_bmt.o mtk_bmt_v2.o mtk_bmt_bbt.o mtk_bmt_nmbm.o
+ obj-$(CONFIG_SPI_QPIC_SNAND) += qpic_common.o
+ obj-$(CONFIG_MTD_NAND_QCOM) += qpic_common.o
+ obj-y += onenand/
diff --git a/target/linux/generic/hack-6.12/499-LEGACY-block-partitions-populate-fwnode.patch b/target/linux/generic/hack-6.12/499-LEGACY-block-partitions-populate-fwnode.patch
new file mode 100644 (file)
index 0000000..f7ec9ef
--- /dev/null
@@ -0,0 +1,130 @@
+From: Daniel Golle <daniel@makrotopia.org>
+Subject: [PATCH] LEGACY block: partitions: populate fwnode
+
+Assign matching firmware nodes to block partitions in order to allow
+them to be referenced e.g. as NVMEM providers.
+
+REMOVE THIS PATCH ONCE ALL TARGETS ARE USING LINUX 6.12 AND ALL BOARDS
+HAVE MIGRATED TO UPSTREAM DT BINDINGS.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+--- a/block/partitions/core.c
++++ b/block/partitions/core.c
+@@ -11,6 +11,8 @@
+ #include <linux/vmalloc.h>
+ #include <linux/device.h>
+ #include <linux/raid/detect.h>
++#include <linux/property.h>
++
+ #include "check.h"
+ static int (*const check_part[])(struct parsed_partitions *) = {
+@@ -285,6 +287,74 @@ static ssize_t whole_disk_show(struct de
+ }
+ static const DEVICE_ATTR(whole_disk, 0444, whole_disk_show, NULL);
++static bool part_meta_match(const char *attr, const char *member, size_t length)
++{
++      /* check if length of attr exceeds specified maximum length */
++      if (strnlen(attr, length) == length)
++              return false;
++
++      /* return true if strings match */
++      return !strncmp(attr, member, length);
++}
++
++static struct fwnode_handle *find_partition_fwnode(struct block_device *bdev)
++{
++      struct fwnode_handle *fw_parts, *fw_part;
++      struct device *ddev = disk_to_dev(bdev->bd_disk);
++      const char *partname, *uuid;
++      u32 partno;
++      bool got_uuid, got_partname, got_partno;
++
++      fw_parts = device_get_named_child_node(ddev, "partitions");
++      if (!fw_parts)
++              return NULL;
++
++      fwnode_for_each_child_node(fw_parts, fw_part) {
++              got_uuid = false;
++              got_partname = false;
++              got_partno = false;
++              /*
++               * In case 'uuid' is defined in the partitions firmware node
++               * require partition meta info being present and the specified
++               * uuid to match.
++               */
++              got_uuid = !fwnode_property_read_string(fw_part, "uuid", &uuid);
++              if (got_uuid && (!bdev->bd_meta_info ||
++                               !part_meta_match(uuid, bdev->bd_meta_info->uuid,
++                                                PARTITION_META_INFO_UUIDLTH)))
++                      continue;
++
++              /*
++               * In case 'partname' is defined in the partitions firmware node
++               * require partition meta info being present and the specified
++               * volname to match.
++               */
++              got_partname = !fwnode_property_read_string(fw_part, "partname",
++                                                          &partname);
++              if (got_partname && (!bdev->bd_meta_info ||
++                                   !part_meta_match(partname,
++                                                    bdev->bd_meta_info->volname,
++                                                    PARTITION_META_INFO_VOLNAMELTH)))
++                      continue;
++
++              /*
++               * In case 'partno' is defined in the partitions firmware node
++               * the specified partno needs to match.
++               */
++              got_partno = !fwnode_property_read_u32(fw_part, "partno", &partno);
++              if (got_partno && bdev_partno(bdev) != partno)
++                      continue;
++
++              /* Skip if no matching criteria is present in firmware node */
++              if (!got_uuid && !got_partname && !got_partno)
++                      continue;
++
++              return fw_part;
++      }
++
++      return NULL;
++}
++
+ /*
+  * Must be called either with open_mutex held, before a disk can be opened or
+  * after all disk users are gone.
+@@ -361,6 +431,9 @@ static struct block_device *add_partitio
+                       goto out_put;
+       }
++      if (!pdev->fwnode && !pdev->of_node)
++              device_set_node(pdev, find_partition_fwnode(bdev));
++
+       /* delay uevent until 'holders' subdir is created */
+       dev_set_uevent_suppress(pdev, 1);
+       err = device_add(pdev);
+--- a/drivers/mmc/core/bus.c
++++ b/drivers/mmc/core/bus.c
+@@ -368,6 +368,8 @@ int mmc_add_card(struct mmc_card *card)
+       mmc_add_card_debugfs(card);
+       card->dev.of_node = mmc_of_find_child_device(card->host, 0);
++      if (card->dev.of_node && !card->dev.fwnode)
++              card->dev.fwnode = &card->dev.of_node->fwnode;
+       device_enable_async_suspend(&card->dev);
+--- a/drivers/mmc/core/block.c
++++ b/drivers/mmc/core/block.c
+@@ -2679,6 +2679,10 @@ static struct mmc_blk_data *mmc_blk_allo
+       if (area_type == MMC_BLK_DATA_AREA_MAIN)
+               dev_set_drvdata(&card->dev, md);
+       disk_fwnode = mmc_blk_get_partitions_node(parent, subname);
++      if (!disk_fwnode)
++              disk_fwnode = device_get_named_child_node(subname ? md->parent->parent :
++                                                        md->parent,
++                                                        subname ? subname : "block");
+       ret = add_disk_fwnode(md->parent, md->disk, mmc_disk_attr_groups,
+                             disk_fwnode);
+       if (ret)
diff --git a/target/linux/generic/hack-6.12/600-net-enable-fraglist-GRO-by-default.patch b/target/linux/generic/hack-6.12/600-net-enable-fraglist-GRO-by-default.patch
new file mode 100644 (file)
index 0000000..feed2cd
--- /dev/null
@@ -0,0 +1,24 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 23 Apr 2024 12:35:21 +0200
+Subject: [PATCH] net: enable fraglist GRO by default
+
+This can significantly improve performance for packet forwarding/bridging
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/include/linux/netdev_features.h
++++ b/include/linux/netdev_features.h
+@@ -235,10 +235,10 @@ static inline int find_next_netdev_featu
+ #define NETIF_F_UPPER_DISABLES        NETIF_F_LRO
+ /* changeable features with no special hardware requirements */
+-#define NETIF_F_SOFT_FEATURES (NETIF_F_GSO | NETIF_F_GRO)
++#define NETIF_F_SOFT_FEATURES (NETIF_F_GSO | NETIF_F_GRO | NETIF_F_GRO_FRAGLIST)
+ /* Changeable features with no special hardware requirements that defaults to off. */
+-#define NETIF_F_SOFT_FEATURES_OFF     (NETIF_F_GRO_FRAGLIST | NETIF_F_GRO_UDP_FWD)
++#define NETIF_F_SOFT_FEATURES_OFF     (NETIF_F_GRO_UDP_FWD)
+ #define NETIF_F_VLAN_FEATURES (NETIF_F_HW_VLAN_CTAG_FILTER | \
+                                NETIF_F_HW_VLAN_CTAG_RX | \
diff --git a/target/linux/generic/hack-6.12/610-net-page_pool-try-to-free-deferred-skbs-while-waitin.patch b/target/linux/generic/hack-6.12/610-net-page_pool-try-to-free-deferred-skbs-while-waitin.patch
new file mode 100644 (file)
index 0000000..7f79e04
--- /dev/null
@@ -0,0 +1,51 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 3 Jan 2025 19:29:00 +0100
+Subject: [PATCH] net: page_pool: try to free deferred skbs while waiting for
+ pool release
+
+The NAPI defer list can accumulate no longer used skbs, which can be reused
+during alloc. If this happens on a CPU that otherwise does not do any
+rx softirq processing, skbs can be held indefinitely, causing warnings
+on releasing page pools.
+Deal with this by scheduling rx softirq on all CPUs.
+
+Patch by Lorenzo Bianconi
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/core/page_pool.c
++++ b/net/core/page_pool.c
+@@ -1155,8 +1155,9 @@ static void page_pool_release_retry(stru
+ {
+       struct delayed_work *dwq = to_delayed_work(wq);
+       struct page_pool *pool = container_of(dwq, typeof(*pool), release_dw);
++      unsigned long flags;
+       void *netdev;
+-      int inflight;
++      int cpu, inflight;
+       inflight = page_pool_release(pool);
+       /* In rare cases, a driver bug may cause inflight to go negative.
+@@ -1168,6 +1169,21 @@ static void page_pool_release_retry(stru
+       if (inflight <= 0)
+               return;
++      /* Run NET_RX_SOFTIRQ in order to free pending skbs in softnet_data
++       * defer_list that can stay in the list until we have enough queued
++       * traffic.
++       */
++      local_irq_save(flags);
++      for_each_online_cpu(cpu) {
++              struct softnet_data *sd = &per_cpu(softnet_data, cpu);
++
++              if (cpu == raw_smp_processor_id())
++                      raise_softirq_irqoff(NET_RX_SOFTIRQ);
++              else if (!cmpxchg(&sd->defer_ipi_scheduled, 0, 1))
++                      smp_call_function_single_async(cpu, &sd->defer_csd);
++      }
++      local_irq_restore(flags);
++
+       /* Periodic warning for page pools the user can't see */
+       netdev = READ_ONCE(pool->slow.netdev);
+       if (time_after_eq(jiffies, pool->defer_warn) &&
diff --git a/target/linux/generic/hack-6.12/645-netfilter-connmark-introduce-set-dscpmark.patch b/target/linux/generic/hack-6.12/645-netfilter-connmark-introduce-set-dscpmark.patch
new file mode 100644 (file)
index 0000000..bb80285
--- /dev/null
@@ -0,0 +1,231 @@
+From eda40b8c8c82e0f2789d6bc8bf63846dce2e8f32 Mon Sep 17 00:00:00 2001
+From: Kevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>
+Date: Sat, 23 Mar 2019 09:29:49 +0000
+Subject: [PATCH] netfilter: connmark: introduce set-dscpmark
+
+set-dscpmark is a method of storing the DSCP of an ip packet into
+conntrack mark.  In combination with a suitable tc filter action
+(act_ctinfo) DSCP values are able to be stored in the mark on egress and
+restored on ingress across links that otherwise alter or bleach DSCP.
+
+This is useful for qdiscs such as CAKE which are able to shape according
+to policies based on DSCP.
+
+Ingress classification is traditionally a challenging task since
+iptables rules haven't yet run and tc filter/eBPF programs are pre-NAT
+lookups, hence are unable to see internal IPv4 addresses as used on the
+typical home masquerading gateway.
+
+x_tables CONNMARK set-dscpmark target solves the problem of storing the
+DSCP to the conntrack mark in a way suitable for the new act_ctinfo tc
+action to restore.
+
+The set-dscpmark option accepts 2 parameters, a 32bit 'dscpmask' and a
+32bit 'statemask'.  The dscp mask must be 6 contiguous bits and
+represents the area where the DSCP will be stored in the connmark.  The
+state mask is a minimum 1 bit length mask that must not overlap with the
+dscpmask.  It represents a flag which is set when the DSCP has been
+stored in the conntrack mark. This is useful to implement a 'one shot'
+iptables based classification where the 'complicated' iptables rules are
+only run once to classify the connection on initial (egress) packet and
+subsequent packets are all marked/restored with the same DSCP.  A state
+mask of zero disables the setting of a status bit/s.
+
+example syntax with a suitably modified iptables user space application:
+
+iptables -A QOS_MARK_eth0 -t mangle -j CONNMARK --set-dscpmark 0xfc000000/0x01000000
+
+Would store the DSCP in the top 6 bits of the 32bit mark field, and use
+the LSB of the top byte as the 'DSCP has been stored' marker.
+
+|----0xFC----conntrack mark----000000---|
+| Bits 31-26 | bit 25 | bit24 |~~~ Bit 0|
+| DSCP       | unused | flag  |unused   |
+|-----------------------0x01---000000---|
+      ^                   ^
+      |                   |
+      ---|             Conditional flag
+         |             set this when dscp
+|-ip diffserv-|        stored in mark
+| 6 bits      |
+|-------------|
+
+an identically configured tc action to restore looks like:
+
+tc filter show dev eth0 ingress
+filter parent ffff: protocol all pref 10 u32 chain 0
+filter parent ffff: protocol all pref 10 u32 chain 0 fh 800: ht divisor 1
+filter parent ffff: protocol all pref 10 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1: not_in_hw
+  match 00000000/00000000 at 0
+       action order 1: ctinfo zone 0 pipe
+        index 2 ref 1 bind 1 dscp 0xfc000000/0x1000000
+
+       action order 2: mirred (Egress Redirect to device ifb4eth0) stolen
+       index 1 ref 1 bind 1
+
+|----0xFC----conntrack mark----000000---|
+| Bits 31-26 | bit 25 | bit24 |~~~ Bit 0|
+| DSCP       | unused | flag  |unused   |
+|-----------------------0x01---000000---|
+      |                   |
+      |                   |
+      ---|             Conditional flag
+         v             only restore if set
+|-ip diffserv-|
+| 6 bits      |
+|-------------|
+
+Signed-off-by: Kevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>
+---
+ include/uapi/linux/netfilter/xt_connmark.h | 10 ++++
+ net/netfilter/xt_connmark.c                | 55 ++++++++++++++++++----
+ 2 files changed, 57 insertions(+), 8 deletions(-)
+
+--- a/include/uapi/linux/netfilter/xt_connmark.h
++++ b/include/uapi/linux/netfilter/xt_connmark.h
+@@ -15,6 +15,11 @@ enum {
+ };
+ enum {
++      XT_CONNMARK_VALUE =     (1 << 0),
++      XT_CONNMARK_DSCP =      (1 << 1)
++};
++
++enum {
+       D_SHIFT_LEFT = 0,
+       D_SHIFT_RIGHT,
+ };
+@@ -29,6 +34,11 @@ struct xt_connmark_tginfo2 {
+       __u8 shift_dir, shift_bits, mode;
+ };
++struct xt_connmark_tginfo3 {
++      __u32 ctmark, ctmask, nfmask;
++      __u8 shift_dir, shift_bits, mode, func;
++};
++
+ struct xt_connmark_mtinfo1 {
+       __u32 mark, mask;
+       __u8 invert;
+--- a/net/netfilter/xt_connmark.c
++++ b/net/netfilter/xt_connmark.c
+@@ -24,13 +24,13 @@ MODULE_ALIAS("ipt_connmark");
+ MODULE_ALIAS("ip6t_connmark");
+ static unsigned int
+-connmark_tg_shift(struct sk_buff *skb, const struct xt_connmark_tginfo2 *info)
++connmark_tg_shift(struct sk_buff *skb, const struct xt_connmark_tginfo3 *info)
+ {
+       enum ip_conntrack_info ctinfo;
+       u_int32_t new_targetmark;
+       struct nf_conn *ct;
+       u_int32_t newmark;
+-      u_int32_t oldmark;
++      u_int8_t dscp;
+       ct = nf_ct_get(skb, &ctinfo);
+       if (ct == NULL)
+@@ -38,13 +38,24 @@ connmark_tg_shift(struct sk_buff *skb, c
+       switch (info->mode) {
+       case XT_CONNMARK_SET:
+-              oldmark = READ_ONCE(ct->mark);
+-              newmark = (oldmark & ~info->ctmask) ^ info->ctmark;
+-              if (info->shift_dir == D_SHIFT_RIGHT)
+-                      newmark >>= info->shift_bits;
+-              else
+-                      newmark <<= info->shift_bits;
++              newmark = READ_ONCE(ct->mark);
++              if (info->func & XT_CONNMARK_VALUE) {
++                      newmark = (newmark & ~info->ctmask) ^ info->ctmark;
++                      if (info->shift_dir == D_SHIFT_RIGHT)
++                              newmark >>= info->shift_bits;
++                      else
++                              newmark <<= info->shift_bits;
++              } else if (info->func & XT_CONNMARK_DSCP) {
++                      if (skb->protocol == htons(ETH_P_IP))
++                              dscp = ipv4_get_dsfield(ip_hdr(skb)) >> 2;
++                      else if (skb->protocol == htons(ETH_P_IPV6))
++                              dscp = ipv6_get_dsfield(ipv6_hdr(skb)) >> 2;
++                      else    /* protocol doesn't have diffserv */
++                              break;
++                      newmark = (newmark & ~info->ctmark) |
++                                (info->ctmask | (dscp << info->shift_bits));
++              }
+               if (READ_ONCE(ct->mark) != newmark) {
+                       WRITE_ONCE(ct->mark, newmark);
+                       nf_conntrack_event_cache(IPCT_MARK, ct);
+@@ -83,20 +94,36 @@ static unsigned int
+ connmark_tg(struct sk_buff *skb, const struct xt_action_param *par)
+ {
+       const struct xt_connmark_tginfo1 *info = par->targinfo;
+-      const struct xt_connmark_tginfo2 info2 = {
++      const struct xt_connmark_tginfo3 info3 = {
+               .ctmark = info->ctmark,
+               .ctmask = info->ctmask,
+               .nfmask = info->nfmask,
+               .mode   = info->mode,
++              .func   = XT_CONNMARK_VALUE
+       };
+-      return connmark_tg_shift(skb, &info2);
++      return connmark_tg_shift(skb, &info3);
+ }
+ static unsigned int
+ connmark_tg_v2(struct sk_buff *skb, const struct xt_action_param *par)
+ {
+       const struct xt_connmark_tginfo2 *info = par->targinfo;
++      const struct xt_connmark_tginfo3 info3 = {
++              .ctmark = info->ctmark,
++              .ctmask = info->ctmask,
++              .nfmask = info->nfmask,
++              .mode   = info->mode,
++              .func   = XT_CONNMARK_VALUE
++      };
++
++      return connmark_tg_shift(skb, &info3);
++}
++
++static unsigned int
++connmark_tg_v3(struct sk_buff *skb, const struct xt_action_param *par)
++{
++      const struct xt_connmark_tginfo3 *info = par->targinfo;
+       return connmark_tg_shift(skb, info);
+ }
+@@ -168,6 +195,16 @@ static struct xt_target connmark_tg_reg[
+               .destroy        = connmark_tg_destroy,
+               .me             = THIS_MODULE,
+       },
++      {
++              .name           = "CONNMARK",
++              .revision       = 3,
++              .family         = NFPROTO_IPV4,
++              .checkentry     = connmark_tg_check,
++              .target         = connmark_tg_v3,
++              .targetsize     = sizeof(struct xt_connmark_tginfo3),
++              .destroy        = connmark_tg_destroy,
++              .me             = THIS_MODULE,
++      },
+ #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
+       {
+               .name           = "CONNMARK",
+@@ -189,6 +226,16 @@ static struct xt_target connmark_tg_reg[
+               .destroy        = connmark_tg_destroy,
+               .me             = THIS_MODULE,
+       },
++      {
++              .name           = "CONNMARK",
++              .revision       = 3,
++              .family         = NFPROTO_IPV6,
++              .checkentry     = connmark_tg_check,
++              .target         = connmark_tg_v3,
++              .targetsize     = sizeof(struct xt_connmark_tginfo3),
++              .destroy        = connmark_tg_destroy,
++              .me             = THIS_MODULE,
++      },
+ #endif
+ };
diff --git a/target/linux/generic/hack-6.12/650-netfilter-add-xt_FLOWOFFLOAD-target.patch b/target/linux/generic/hack-6.12/650-netfilter-add-xt_FLOWOFFLOAD-target.patch
new file mode 100644 (file)
index 0000000..53093da
--- /dev/null
@@ -0,0 +1,812 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 20 Feb 2018 15:56:02 +0100
+Subject: [PATCH] netfilter: add xt_FLOWOFFLOAD target
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ create mode 100644 net/netfilter/xt_OFFLOAD.c
+
+--- a/net/netfilter/Kconfig
++++ b/net/netfilter/Kconfig
+@@ -729,7 +729,6 @@ config NF_FLOW_TABLE
+       tristate "Netfilter flow table module"
+       depends on NETFILTER_INGRESS
+       depends on NF_CONNTRACK
+-      depends on NF_TABLES
+       help
+         This option adds the flow table core infrastructure.
+@@ -1025,6 +1024,15 @@ config NETFILTER_XT_TARGET_NOTRACK
+       depends on NETFILTER_ADVANCED
+       select NETFILTER_XT_TARGET_CT
++config NETFILTER_XT_TARGET_FLOWOFFLOAD
++      tristate '"FLOWOFFLOAD" target support'
++      depends on NF_FLOW_TABLE
++      depends on NETFILTER_INGRESS
++      help
++        This option adds a `FLOWOFFLOAD' target, which uses the nf_flow_offload
++        module to speed up processing of packets by bypassing the usual
++        netfilter chains
++
+ config NETFILTER_XT_TARGET_RATEEST
+       tristate '"RATEEST" target support'
+       depends on NETFILTER_ADVANCED
+--- a/net/netfilter/Makefile
++++ b/net/netfilter/Makefile
+@@ -168,6 +168,7 @@ obj-$(CONFIG_NETFILTER_XT_TARGET_CLASSIF
+ obj-$(CONFIG_NETFILTER_XT_TARGET_CONNSECMARK) += xt_CONNSECMARK.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_CT) += xt_CT.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_DSCP) += xt_DSCP.o
++obj-$(CONFIG_NETFILTER_XT_TARGET_FLOWOFFLOAD) += xt_FLOWOFFLOAD.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_HL) += xt_HL.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_HMARK) += xt_HMARK.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_LED) += xt_LED.o
+--- /dev/null
++++ b/net/netfilter/xt_FLOWOFFLOAD.c
+@@ -0,0 +1,703 @@
++/*
++ * Copyright (C) 2018-2021 Felix Fietkau <nbd@nbd.name>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation.
++ */
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/netfilter.h>
++#include <linux/netfilter/xt_FLOWOFFLOAD.h>
++#include <linux/if_vlan.h>
++#include <net/ip.h>
++#include <net/netfilter/nf_conntrack.h>
++#include <net/netfilter/nf_conntrack_extend.h>
++#include <net/netfilter/nf_conntrack_helper.h>
++#include <net/netfilter/nf_flow_table.h>
++
++struct xt_flowoffload_hook {
++      struct hlist_node list;
++      struct nf_hook_ops ops;
++      struct net *net;
++      bool registered;
++      bool used;
++};
++
++struct xt_flowoffload_table {
++      struct nf_flowtable ft;
++      struct hlist_head hooks;
++      struct delayed_work work;
++};
++
++struct nf_forward_info {
++      const struct net_device *indev;
++      const struct net_device *outdev;
++      const struct net_device *hw_outdev;
++      struct id {
++              __u16   id;
++              __be16  proto;
++      } encap[NF_FLOW_TABLE_ENCAP_MAX];
++      u8 num_encaps;
++      u8 ingress_vlans;
++      u8 h_source[ETH_ALEN];
++      u8 h_dest[ETH_ALEN];
++      enum flow_offload_xmit_type xmit_type;
++};
++
++static DEFINE_SPINLOCK(hooks_lock);
++
++struct xt_flowoffload_table flowtable[2];
++
++static unsigned int
++xt_flowoffload_net_hook(void *priv, struct sk_buff *skb,
++                      const struct nf_hook_state *state)
++{
++      struct vlan_ethhdr *veth;
++      __be16 proto;
++
++      switch (skb->protocol) {
++      case htons(ETH_P_8021Q):
++              veth = (struct vlan_ethhdr *)skb_mac_header(skb);
++              proto = veth->h_vlan_encapsulated_proto;
++              break;
++      case htons(ETH_P_PPP_SES):
++              if (!nf_flow_pppoe_proto(skb, &proto))
++                      return NF_ACCEPT;
++              break;
++      default:
++              proto = skb->protocol;
++              break;
++      }
++
++      switch (proto) {
++      case htons(ETH_P_IP):
++              return nf_flow_offload_ip_hook(priv, skb, state);
++      case htons(ETH_P_IPV6):
++              return nf_flow_offload_ipv6_hook(priv, skb, state);
++      }
++
++      return NF_ACCEPT;
++}
++
++static int
++xt_flowoffload_create_hook(struct xt_flowoffload_table *table,
++                         struct net_device *dev)
++{
++      struct xt_flowoffload_hook *hook;
++      struct nf_hook_ops *ops;
++
++      hook = kzalloc(sizeof(*hook), GFP_ATOMIC);
++      if (!hook)
++              return -ENOMEM;
++
++      ops = &hook->ops;
++      ops->pf = NFPROTO_NETDEV;
++      ops->hooknum = NF_NETDEV_INGRESS;
++      ops->priority = 10;
++      ops->priv = &table->ft;
++      ops->hook = xt_flowoffload_net_hook;
++      ops->dev = dev;
++
++      hlist_add_head(&hook->list, &table->hooks);
++      mod_delayed_work(system_power_efficient_wq, &table->work, 0);
++
++      return 0;
++}
++
++static struct xt_flowoffload_hook *
++flow_offload_lookup_hook(struct xt_flowoffload_table *table,
++                       struct net_device *dev)
++{
++      struct xt_flowoffload_hook *hook;
++
++      hlist_for_each_entry(hook, &table->hooks, list) {
++              if (hook->ops.dev == dev)
++                      return hook;
++      }
++
++      return NULL;
++}
++
++static void
++xt_flowoffload_check_device(struct xt_flowoffload_table *table,
++                          struct net_device *dev)
++{
++      struct xt_flowoffload_hook *hook;
++
++      if (!dev)
++              return;
++
++      spin_lock_bh(&hooks_lock);
++      hook = flow_offload_lookup_hook(table, dev);
++      if (hook)
++              hook->used = true;
++      else
++              xt_flowoffload_create_hook(table, dev);
++      spin_unlock_bh(&hooks_lock);
++}
++
++static void
++xt_flowoffload_register_hooks(struct xt_flowoffload_table *table)
++{
++      struct xt_flowoffload_hook *hook;
++
++restart:
++      hlist_for_each_entry(hook, &table->hooks, list) {
++              if (hook->registered)
++                      continue;
++
++              hook->registered = true;
++              hook->net = dev_net(hook->ops.dev);
++              spin_unlock_bh(&hooks_lock);
++              nf_register_net_hook(hook->net, &hook->ops);
++              if (table->ft.flags & NF_FLOWTABLE_HW_OFFLOAD)
++                      table->ft.type->setup(&table->ft, hook->ops.dev,
++                                            FLOW_BLOCK_BIND);
++              spin_lock_bh(&hooks_lock);
++              goto restart;
++      }
++
++}
++
++static bool
++xt_flowoffload_cleanup_hooks(struct xt_flowoffload_table *table)
++{
++      struct xt_flowoffload_hook *hook;
++      bool active = false;
++
++restart:
++      spin_lock_bh(&hooks_lock);
++      hlist_for_each_entry(hook, &table->hooks, list) {
++              if (hook->used || !hook->registered) {
++                      active = true;
++                      continue;
++              }
++
++              hlist_del(&hook->list);
++              spin_unlock_bh(&hooks_lock);
++              if (table->ft.flags & NF_FLOWTABLE_HW_OFFLOAD)
++                      table->ft.type->setup(&table->ft, hook->ops.dev,
++                                            FLOW_BLOCK_UNBIND);
++              nf_unregister_net_hook(hook->net, &hook->ops);
++              kfree(hook);
++              goto restart;
++      }
++      spin_unlock_bh(&hooks_lock);
++
++      return active;
++}
++
++static void
++xt_flowoffload_check_hook(struct nf_flowtable *flowtable,
++                        struct flow_offload *flow, void *data)
++{
++      struct xt_flowoffload_table *table;
++      struct flow_offload_tuple *tuple0 = &flow->tuplehash[0].tuple;
++      struct flow_offload_tuple *tuple1 = &flow->tuplehash[1].tuple;
++      struct xt_flowoffload_hook *hook;
++
++      table = container_of(flowtable, struct xt_flowoffload_table, ft);
++
++      spin_lock_bh(&hooks_lock);
++      hlist_for_each_entry(hook, &table->hooks, list) {
++              if (hook->ops.dev->ifindex != tuple0->iifidx &&
++                  hook->ops.dev->ifindex != tuple1->iifidx)
++                      continue;
++
++              hook->used = true;
++      }
++      spin_unlock_bh(&hooks_lock);
++}
++
++static void
++xt_flowoffload_hook_work(struct work_struct *work)
++{
++      struct xt_flowoffload_table *table;
++      struct xt_flowoffload_hook *hook;
++      int err;
++
++      table = container_of(work, struct xt_flowoffload_table, work.work);
++
++      spin_lock_bh(&hooks_lock);
++      xt_flowoffload_register_hooks(table);
++      hlist_for_each_entry(hook, &table->hooks, list)
++              hook->used = false;
++      spin_unlock_bh(&hooks_lock);
++
++      err = nf_flow_table_iterate(&table->ft, xt_flowoffload_check_hook,
++                                  NULL);
++      if (err && err != -EAGAIN)
++              goto out;
++
++      if (!xt_flowoffload_cleanup_hooks(table))
++              return;
++
++out:
++      queue_delayed_work(system_power_efficient_wq, &table->work, HZ);
++}
++
++static bool
++xt_flowoffload_skip(struct sk_buff *skb, int family)
++{
++      if (skb_sec_path(skb))
++              return true;
++
++      if (family == NFPROTO_IPV4) {
++              const struct ip_options *opt = &(IPCB(skb)->opt);
++
++              if (unlikely(opt->optlen))
++                      return true;
++      }
++
++      return false;
++}
++
++static enum flow_offload_xmit_type nf_xmit_type(struct dst_entry *dst)
++{
++      if (dst_xfrm(dst))
++              return FLOW_OFFLOAD_XMIT_XFRM;
++
++      return FLOW_OFFLOAD_XMIT_NEIGH;
++}
++
++static void nf_default_forward_path(struct nf_flow_route *route,
++                                  struct dst_entry *dst_cache,
++                                  enum ip_conntrack_dir dir,
++                                  struct net_device **dev)
++{
++      dev[!dir] = dst_cache->dev;
++      route->tuple[!dir].in.ifindex   = dst_cache->dev->ifindex;
++      route->tuple[dir].dst           = dst_cache;
++      route->tuple[dir].xmit_type     = nf_xmit_type(dst_cache);
++}
++
++static bool nf_is_valid_ether_device(const struct net_device *dev)
++{
++      if (!dev || (dev->flags & IFF_LOOPBACK) || dev->type != ARPHRD_ETHER ||
++          dev->addr_len != ETH_ALEN || !is_valid_ether_addr(dev->dev_addr))
++              return false;
++
++      return true;
++}
++
++static void nf_dev_path_info(const struct net_device_path_stack *stack,
++                           struct nf_forward_info *info,
++                           unsigned char *ha)
++{
++      const struct net_device_path *path;
++      int i;
++
++      memcpy(info->h_dest, ha, ETH_ALEN);
++
++      for (i = 0; i < stack->num_paths; i++) {
++              path = &stack->path[i];
++              switch (path->type) {
++              case DEV_PATH_ETHERNET:
++              case DEV_PATH_DSA:
++              case DEV_PATH_VLAN:
++              case DEV_PATH_PPPOE:
++                      info->indev = path->dev;
++                      if (is_zero_ether_addr(info->h_source))
++                              memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN);
++
++                      if (path->type == DEV_PATH_ETHERNET)
++                              break;
++                      if (path->type == DEV_PATH_DSA) {
++                              i = stack->num_paths;
++                              break;
++                      }
++
++                      /* DEV_PATH_VLAN and DEV_PATH_PPPOE */
++                      if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) {
++                              info->indev = NULL;
++                              break;
++                      }
++                      if (!info->outdev)
++                              info->outdev = path->dev;
++                      info->encap[info->num_encaps].id = path->encap.id;
++                      info->encap[info->num_encaps].proto = path->encap.proto;
++                      info->num_encaps++;
++                      if (path->type == DEV_PATH_PPPOE)
++                              memcpy(info->h_dest, path->encap.h_dest, ETH_ALEN);
++                      break;
++              case DEV_PATH_BRIDGE:
++                      if (is_zero_ether_addr(info->h_source))
++                              memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN);
++
++                      switch (path->bridge.vlan_mode) {
++                      case DEV_PATH_BR_VLAN_UNTAG_HW:
++                              info->ingress_vlans |= BIT(info->num_encaps - 1);
++                              break;
++                      case DEV_PATH_BR_VLAN_TAG:
++                              info->encap[info->num_encaps].id = path->bridge.vlan_id;
++                              info->encap[info->num_encaps].proto = path->bridge.vlan_proto;
++                              info->num_encaps++;
++                              break;
++                      case DEV_PATH_BR_VLAN_UNTAG:
++                              info->num_encaps--;
++                              break;
++                      case DEV_PATH_BR_VLAN_KEEP:
++                              break;
++                      }
++                      break;
++              default:
++                      info->indev = NULL;
++                      break;
++              }
++      }
++      if (!info->outdev)
++              info->outdev = info->indev;
++
++      info->hw_outdev = info->indev;
++
++      if (nf_is_valid_ether_device(info->indev))
++              info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
++}
++
++static int nf_dev_fill_forward_path(const struct nf_flow_route *route,
++                                   const struct dst_entry *dst_cache,
++                                   const struct nf_conn *ct,
++                                   enum ip_conntrack_dir dir, u8 *ha,
++                                   struct net_device_path_stack *stack)
++{
++      const void *daddr = &ct->tuplehash[!dir].tuple.src.u3;
++      struct net_device *dev = dst_cache->dev;
++      struct neighbour *n;
++      u8 nud_state;
++
++      if (!nf_is_valid_ether_device(dev))
++              goto out;
++
++      n = dst_neigh_lookup(dst_cache, daddr);
++      if (!n)
++              return -1;
++
++      read_lock_bh(&n->lock);
++      nud_state = n->nud_state;
++      ether_addr_copy(ha, n->ha);
++      read_unlock_bh(&n->lock);
++      neigh_release(n);
++
++      if (!(nud_state & NUD_VALID))
++              return -1;
++
++out:
++      return dev_fill_forward_path(dev, ha, stack);
++}
++
++static void nf_dev_forward_path(struct nf_flow_route *route,
++                              const struct nf_conn *ct,
++                              enum ip_conntrack_dir dir,
++                              struct net_device **devs)
++{
++      const struct dst_entry *dst = route->tuple[dir].dst;
++      struct net_device_path_stack stack;
++      struct nf_forward_info info = {};
++      unsigned char ha[ETH_ALEN];
++      int i;
++
++      if (nf_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) >= 0)
++              nf_dev_path_info(&stack, &info, ha);
++
++      devs[!dir] = (struct net_device *)info.indev;
++      if (!info.indev)
++              return;
++
++      route->tuple[!dir].in.ifindex = info.indev->ifindex;
++      for (i = 0; i < info.num_encaps; i++) {
++              route->tuple[!dir].in.encap[i].id = info.encap[i].id;
++              route->tuple[!dir].in.encap[i].proto = info.encap[i].proto;
++      }
++      route->tuple[!dir].in.num_encaps = info.num_encaps;
++      route->tuple[!dir].in.ingress_vlans = info.ingress_vlans;
++
++      if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT) {
++              memcpy(route->tuple[dir].out.h_source, info.h_source, ETH_ALEN);
++              memcpy(route->tuple[dir].out.h_dest, info.h_dest, ETH_ALEN);
++              route->tuple[dir].out.ifindex = info.outdev->ifindex;
++              route->tuple[dir].out.hw_ifindex = info.hw_outdev->ifindex;
++              route->tuple[dir].xmit_type = info.xmit_type;
++      }
++}
++
++static int
++xt_flowoffload_route(struct sk_buff *skb, const struct nf_conn *ct,
++                   const struct xt_action_param *par,
++                   struct nf_flow_route *route, enum ip_conntrack_dir dir,
++                   struct net_device **devs)
++{
++      struct dst_entry *this_dst = skb_dst(skb);
++      struct dst_entry *other_dst = NULL;
++      struct flowi fl;
++
++      memset(&fl, 0, sizeof(fl));
++      switch (xt_family(par)) {
++      case NFPROTO_IPV4:
++              fl.u.ip4.daddr = ct->tuplehash[dir].tuple.src.u3.ip;
++              fl.u.ip4.flowi4_oif = xt_in(par)->ifindex;
++              break;
++      case NFPROTO_IPV6:
++              fl.u.ip6.saddr = ct->tuplehash[!dir].tuple.dst.u3.in6;
++              fl.u.ip6.daddr = ct->tuplehash[dir].tuple.src.u3.in6;
++              fl.u.ip6.flowi6_oif = xt_in(par)->ifindex;
++              break;
++      }
++
++      if (!dst_hold_safe(this_dst))
++              return -ENOENT;
++
++      nf_route(xt_net(par), &other_dst, &fl, false, xt_family(par));
++      if (!other_dst) {
++              dst_release(this_dst);
++              return -ENOENT;
++      }
++
++      nf_default_forward_path(route, this_dst, dir, devs);
++      nf_default_forward_path(route, other_dst, !dir, devs);
++
++      if (route->tuple[dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH &&
++          route->tuple[!dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH) {
++              nf_dev_forward_path(route, ct, dir, devs);
++              nf_dev_forward_path(route, ct, !dir, devs);
++      }
++
++      return 0;
++}
++
++static unsigned int
++flowoffload_tg(struct sk_buff *skb, const struct xt_action_param *par)
++{
++      struct xt_flowoffload_table *table;
++      const struct xt_flowoffload_target_info *info = par->targinfo;
++      struct tcphdr _tcph, *tcph = NULL;
++      enum ip_conntrack_info ctinfo;
++      enum ip_conntrack_dir dir;
++      struct nf_flow_route route = {};
++      struct flow_offload *flow = NULL;
++      struct net_device *devs[2] = {};
++      struct nf_conn *ct;
++      struct net *net;
++
++      if (xt_flowoffload_skip(skb, xt_family(par)))
++              return XT_CONTINUE;
++
++      ct = nf_ct_get(skb, &ctinfo);
++      if (ct == NULL)
++              return XT_CONTINUE;
++
++      switch (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum) {
++      case IPPROTO_TCP:
++              if (ct->proto.tcp.state != TCP_CONNTRACK_ESTABLISHED)
++                      return XT_CONTINUE;
++
++              tcph = skb_header_pointer(skb, par->thoff,
++                                        sizeof(_tcph), &_tcph);
++              if (unlikely(!tcph || tcph->fin || tcph->rst))
++                      return XT_CONTINUE;
++              break;
++      case IPPROTO_UDP:
++              break;
++      default:
++              return XT_CONTINUE;
++      }
++
++      if (nf_ct_ext_exist(ct, NF_CT_EXT_HELPER) ||
++          ct->status & (IPS_SEQ_ADJUST | IPS_NAT_CLASH))
++              return XT_CONTINUE;
++
++      if (!nf_ct_is_confirmed(ct))
++              return XT_CONTINUE;
++
++      dir = CTINFO2DIR(ctinfo);
++
++      devs[dir] = xt_out(par);
++      devs[!dir] = xt_in(par);
++
++      if (!devs[dir] || !devs[!dir])
++              return XT_CONTINUE;
++
++      if (test_and_set_bit(IPS_OFFLOAD_BIT, &ct->status))
++              return XT_CONTINUE;
++
++      if (xt_flowoffload_route(skb, ct, par, &route, dir, devs) < 0)
++              goto err_flow_route;
++
++      flow = flow_offload_alloc(ct);
++      if (!flow)
++              goto err_flow_alloc;
++
++      flow_offload_route_init(flow, &route);
++
++      if (tcph) {
++              ct->proto.tcp.seen[0].flags |= IP_CT_TCP_FLAG_BE_LIBERAL;
++              ct->proto.tcp.seen[1].flags |= IP_CT_TCP_FLAG_BE_LIBERAL;
++      }
++
++      table = &flowtable[!!(info->flags & XT_FLOWOFFLOAD_HW)];
++
++      net = read_pnet(&table->ft.net);
++      if (!net)
++              write_pnet(&table->ft.net, xt_net(par));
++
++      __set_bit(NF_FLOW_HW_BIDIRECTIONAL, &flow->flags);
++      if (flow_offload_add(&table->ft, flow) < 0)
++              goto err_flow_add;
++
++      xt_flowoffload_check_device(table, devs[0]);
++      xt_flowoffload_check_device(table, devs[1]);
++
++      return XT_CONTINUE;
++
++err_flow_add:
++      flow_offload_free(flow);
++err_flow_alloc:
++      dst_release(route.tuple[dir].dst);
++      dst_release(route.tuple[!dir].dst);
++err_flow_route:
++      clear_bit(IPS_OFFLOAD_BIT, &ct->status);
++
++      return XT_CONTINUE;
++}
++
++static int flowoffload_chk(const struct xt_tgchk_param *par)
++{
++      struct xt_flowoffload_target_info *info = par->targinfo;
++
++      if (info->flags & ~XT_FLOWOFFLOAD_MASK)
++              return -EINVAL;
++
++      return 0;
++}
++
++static struct xt_target offload_tg_reg __read_mostly = {
++      .family         = NFPROTO_UNSPEC,
++      .name           = "FLOWOFFLOAD",
++      .revision       = 0,
++      .targetsize     = sizeof(struct xt_flowoffload_target_info),
++      .usersize       = sizeof(struct xt_flowoffload_target_info),
++      .checkentry     = flowoffload_chk,
++      .target         = flowoffload_tg,
++      .me             = THIS_MODULE,
++};
++
++static int flow_offload_netdev_event(struct notifier_block *this,
++                                   unsigned long event, void *ptr)
++{
++      struct xt_flowoffload_hook *hook0, *hook1;
++      struct net_device *dev = netdev_notifier_info_to_dev(ptr);
++
++      if (event != NETDEV_UNREGISTER)
++              return NOTIFY_DONE;
++
++      spin_lock_bh(&hooks_lock);
++      hook0 = flow_offload_lookup_hook(&flowtable[0], dev);
++      if (hook0)
++              hlist_del(&hook0->list);
++
++      hook1 = flow_offload_lookup_hook(&flowtable[1], dev);
++      if (hook1)
++              hlist_del(&hook1->list);
++      spin_unlock_bh(&hooks_lock);
++
++      if (hook0) {
++              nf_unregister_net_hook(hook0->net, &hook0->ops);
++              kfree(hook0);
++      }
++
++      if (hook1) {
++              nf_unregister_net_hook(hook1->net, &hook1->ops);
++              kfree(hook1);
++      }
++
++      nf_flow_table_cleanup(dev);
++
++      return NOTIFY_DONE;
++}
++
++static struct notifier_block flow_offload_netdev_notifier = {
++      .notifier_call  = flow_offload_netdev_event,
++};
++
++static int nf_flow_rule_route_inet(struct net *net,
++                                 struct flow_offload *flow,
++                                 enum flow_offload_tuple_dir dir,
++                                 struct nf_flow_rule *flow_rule)
++{
++      const struct flow_offload_tuple *flow_tuple = &flow->tuplehash[dir].tuple;
++      int err;
++
++      switch (flow_tuple->l3proto) {
++      case NFPROTO_IPV4:
++              err = nf_flow_rule_route_ipv4(net, flow, dir, flow_rule);
++              break;
++      case NFPROTO_IPV6:
++              err = nf_flow_rule_route_ipv6(net, flow, dir, flow_rule);
++              break;
++      default:
++              err = -1;
++              break;
++      }
++
++      return err;
++}
++
++static struct nf_flowtable_type flowtable_inet = {
++      .family         = NFPROTO_INET,
++      .init           = nf_flow_table_init,
++      .setup          = nf_flow_table_offload_setup,
++      .action         = nf_flow_rule_route_inet,
++      .free           = nf_flow_table_free,
++      .hook           = xt_flowoffload_net_hook,
++      .owner          = THIS_MODULE,
++};
++
++static int init_flowtable(struct xt_flowoffload_table *tbl)
++{
++      INIT_DELAYED_WORK(&tbl->work, xt_flowoffload_hook_work);
++      tbl->ft.type = &flowtable_inet;
++      tbl->ft.flags = NF_FLOWTABLE_COUNTER;
++
++      return nf_flow_table_init(&tbl->ft);
++}
++
++static int __init xt_flowoffload_tg_init(void)
++{
++      int ret;
++
++      register_netdevice_notifier(&flow_offload_netdev_notifier);
++
++      ret = init_flowtable(&flowtable[0]);
++      if (ret)
++              return ret;
++
++      ret = init_flowtable(&flowtable[1]);
++      if (ret)
++              goto cleanup;
++
++      flowtable[1].ft.flags |= NF_FLOWTABLE_HW_OFFLOAD;
++
++      ret = xt_register_target(&offload_tg_reg);
++      if (ret)
++              goto cleanup2;
++
++      return 0;
++
++cleanup2:
++      nf_flow_table_free(&flowtable[1].ft);
++cleanup:
++      nf_flow_table_free(&flowtable[0].ft);
++      return ret;
++}
++
++static void __exit xt_flowoffload_tg_exit(void)
++{
++      xt_unregister_target(&offload_tg_reg);
++      unregister_netdevice_notifier(&flow_offload_netdev_notifier);
++      nf_flow_table_free(&flowtable[0].ft);
++      nf_flow_table_free(&flowtable[1].ft);
++}
++
++MODULE_LICENSE("GPL");
++module_init(xt_flowoffload_tg_init);
++module_exit(xt_flowoffload_tg_exit);
+--- a/net/netfilter/nf_flow_table_core.c
++++ b/net/netfilter/nf_flow_table_core.c
+@@ -7,7 +7,6 @@
+ #include <linux/netdevice.h>
+ #include <net/ip.h>
+ #include <net/ip6_route.h>
+-#include <net/netfilter/nf_tables.h>
+ #include <net/netfilter/nf_flow_table.h>
+ #include <net/netfilter/nf_conntrack.h>
+ #include <net/netfilter/nf_conntrack_core.h>
+@@ -373,8 +372,7 @@ flow_offload_lookup(struct nf_flowtable
+ }
+ EXPORT_SYMBOL_GPL(flow_offload_lookup);
+-static int
+-nf_flow_table_iterate(struct nf_flowtable *flow_table,
++int nf_flow_table_iterate(struct nf_flowtable *flow_table,
+                     void (*iter)(struct nf_flowtable *flowtable,
+                                  struct flow_offload *flow, void *data),
+                     void *data)
+@@ -435,6 +433,7 @@ static void nf_flow_offload_gc_step(stru
+               nf_flow_offload_stats(flow_table, flow);
+       }
+ }
++EXPORT_SYMBOL_GPL(nf_flow_table_iterate);
+ void nf_flow_table_gc_run(struct nf_flowtable *flow_table)
+ {
+--- /dev/null
++++ b/include/uapi/linux/netfilter/xt_FLOWOFFLOAD.h
+@@ -0,0 +1,17 @@
++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
++#ifndef _XT_FLOWOFFLOAD_H
++#define _XT_FLOWOFFLOAD_H
++
++#include <linux/types.h>
++
++enum {
++      XT_FLOWOFFLOAD_HW       = 1 << 0,
++
++      XT_FLOWOFFLOAD_MASK     = XT_FLOWOFFLOAD_HW
++};
++
++struct xt_flowoffload_target_info {
++      __u32 flags;
++};
++
++#endif /* _XT_FLOWOFFLOAD_H */
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -294,6 +294,11 @@ void nf_flow_table_free(struct nf_flowta
+ void flow_offload_teardown(struct flow_offload *flow);
++int nf_flow_table_iterate(struct nf_flowtable *flow_table,
++                      void (*iter)(struct nf_flowtable *flowtable,
++                                   struct flow_offload *flow, void *data),
++                      void *data);
++
+ void nf_flow_snat_port(const struct flow_offload *flow,
+                      struct sk_buff *skb, unsigned int thoff,
+                      u8 protocol, enum flow_offload_tuple_dir dir);
diff --git a/target/linux/generic/hack-6.12/651-wireless_mesh_header.patch b/target/linux/generic/hack-6.12/651-wireless_mesh_header.patch
new file mode 100644 (file)
index 0000000..d3f85ea
--- /dev/null
@@ -0,0 +1,24 @@
+From 6d3bc769657b0ee7c7506dad9911111c4226a7ea Mon Sep 17 00:00:00 2001
+From: Imre Kaloz <kaloz@openwrt.org>
+Date: Fri, 7 Jul 2017 17:21:05 +0200
+Subject: mac80211: increase wireless mesh header size
+
+lede-commit 3d4466cfd8f75f717efdb1f96fdde3c70d865fc1
+Signed-off-by: Imre Kaloz <kaloz@openwrt.org>
+---
+ include/linux/netdevice.h | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/include/linux/netdevice.h
++++ b/include/linux/netdevice.h
+@@ -159,8 +159,8 @@ static inline bool dev_xmit_complete(int
+ #if defined(CONFIG_HYPERV_NET)
+ # define LL_MAX_HEADER 128
+-#elif defined(CONFIG_WLAN) || IS_ENABLED(CONFIG_AX25)
+-# if defined(CONFIG_MAC80211_MESH)
++#elif defined(CONFIG_WLAN) || IS_ENABLED(CONFIG_AX25) || 1
++# if defined(CONFIG_MAC80211_MESH) || 1
+ #  define LL_MAX_HEADER 128
+ # else
+ #  define LL_MAX_HEADER 96
diff --git a/target/linux/generic/hack-6.12/660-fq_codel_defaults.patch b/target/linux/generic/hack-6.12/660-fq_codel_defaults.patch
new file mode 100644 (file)
index 0000000..4d7b01d
--- /dev/null
@@ -0,0 +1,27 @@
+From a6ccb238939b25851474a279b20367fd24a0e816 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 7 Jul 2017 17:21:53 +0200
+Subject:  hack: net: fq_codel: tune defaults for small devices
+
+Assume that x86_64 devices always have a big memory and do not need this 
+optimization compared to devices with only 32 MB or 64 MB RAM.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ net/sched/sch_fq_codel.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/net/sched/sch_fq_codel.c
++++ b/net/sched/sch_fq_codel.c
+@@ -472,7 +472,11 @@ static int fq_codel_init(struct Qdisc *s
+       sch->limit = 10*1024;
+       q->flows_cnt = 1024;
++#ifdef CONFIG_X86_64
+       q->memory_limit = 32 << 20; /* 32 MBytes */
++#else
++      q->memory_limit = 4 << 20; /* 4 MBytes */
++#endif
+       q->drop_batch_size = 64;
+       q->quantum = psched_mtu(qdisc_dev(sch));
+       INIT_LIST_HEAD(&q->new_flows);
diff --git a/target/linux/generic/hack-6.12/661-kernel-ct-size-the-hashtable-more-adequately.patch b/target/linux/generic/hack-6.12/661-kernel-ct-size-the-hashtable-more-adequately.patch
new file mode 100644 (file)
index 0000000..b4bf160
--- /dev/null
@@ -0,0 +1,25 @@
+From 804fbb3f2ec9283f7b778e057a68bfff440a0be6 Mon Sep 17 00:00:00 2001
+From: Rui Salvaterra <rsalvaterra@gmail.com>
+Date: Wed, 30 Mar 2022 22:51:55 +0100
+Subject: [PATCH] kernel: ct: size the hashtable more adequately
+
+To set the default size of the connection tracking hash table, a divider of
+16384 becomes inadequate for a router handling lots of connections. Divide by
+2048 instead, making the default size scale better with the available RAM.
+
+Signed-off-by: Rui Salvaterra <rsalvaterra@gmail.com>
+---
+ net/netfilter/nf_conntrack_core.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/net/netfilter/nf_conntrack_core.c
++++ b/net/netfilter/nf_conntrack_core.c
+@@ -2648,7 +2648,7 @@ int nf_conntrack_init_start(void)
+       if (!nf_conntrack_htable_size) {
+               nf_conntrack_htable_size
+-                      = (((nr_pages << PAGE_SHIFT) / 16384)
++                      = (((nr_pages << PAGE_SHIFT) / 2048)
+                          / sizeof(struct hlist_head));
+               if (BITS_PER_LONG >= 64 &&
+                   nr_pages > (4 * (1024 * 1024 * 1024 / PAGE_SIZE)))
diff --git a/target/linux/generic/hack-6.12/700-swconfig_switch_drivers.patch b/target/linux/generic/hack-6.12/700-swconfig_switch_drivers.patch
new file mode 100644 (file)
index 0000000..ff235dd
--- /dev/null
@@ -0,0 +1,131 @@
+From 36e516290611e613aa92996cb4339561452695b4 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 7 Jul 2017 17:24:23 +0200
+Subject: net: swconfig: adds openwrt switch layer
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ drivers/net/phy/Kconfig   | 83 +++++++++++++++++++++++++++++++++++++++++++++++
+ drivers/net/phy/Makefile  | 15 +++++++++
+ include/uapi/linux/Kbuild |  1 +
+ 3 files changed, 99 insertions(+)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -77,6 +77,80 @@ config SFP
+       depends on HWMON || HWMON=n
+       select MDIO_I2C
++comment "Switch configuration API + drivers"
++
++config SWCONFIG
++      tristate "Switch configuration API"
++      help
++        Switch configuration API using netlink. This allows
++        you to configure the VLAN features of certain switches.
++
++config SWCONFIG_LEDS
++      bool "Switch LED trigger support"
++      depends on (SWCONFIG && LEDS_TRIGGERS)
++
++config ADM6996_PHY
++      tristate "Driver for ADM6996 switches"
++      select SWCONFIG
++      help
++        Currently supports the ADM6996FC and ADM6996M switches.
++        Support for FC is very limited.
++
++config AR8216_PHY
++      tristate "Driver for Atheros AR8216/8327 switches"
++      select SWCONFIG
++      select ETHERNET_PACKET_MANGLE
++
++config AR8216_PHY_LEDS
++      bool "Atheros AR8216 switch LED support"
++      depends on (AR8216_PHY && LEDS_CLASS)
++
++source "drivers/net/phy/b53/Kconfig"
++
++config IP17XX_PHY
++      tristate "Driver for IC+ IP17xx switches"
++      select SWCONFIG
++
++config PSB6970_PHY
++      tristate "Lantiq XWAY Tantos (PSB6970) Ethernet switch"
++      select SWCONFIG
++
++config RTL8306_PHY
++      tristate "Driver for Realtek RTL8306S switches"
++      select SWCONFIG
++
++config RTL8366_SMI
++      tristate "Driver for the RTL8366 SMI interface"
++      depends on GPIOLIB
++      help
++        This module implements the SMI interface protocol which is used
++        by some RTL8366 ethernet switch devices via the generic GPIO API.
++
++if RTL8366_SMI
++
++config RTL8366_SMI_DEBUG_FS
++      bool "RTL8366 SMI interface debugfs support"
++        depends on DEBUG_FS
++        default n
++
++config RTL8366S_PHY
++      tristate "Driver for the Realtek RTL8366S switch"
++      select SWCONFIG
++
++config RTL8366RB_PHY
++      tristate "Driver for the Realtek RTL8366RB switch"
++      select SWCONFIG
++
++config RTL8367_PHY
++      tristate "Driver for the Realtek RTL8367R/M switches"
++      select SWCONFIG
++
++config RTL8367B_PHY
++      tristate "Driver for the Realtek RTL8367R-VB switch"
++      select SWCONFIG
++
++endif # RTL8366_SMI
++
+ comment "MII PHY device drivers"
+ config AS21XXX_PHY
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -27,6 +27,21 @@ libphy-$(CONFIG_OPEN_ALLIANCE_HELPERS) +
+ obj-$(CONFIG_PHYLINK)         += phylink.o
+ obj-$(CONFIG_PHYLIB)          += libphy.o
++obj-$(CONFIG_SWCONFIG)                += swconfig.o
++obj-$(CONFIG_ADM6996_PHY)     += adm6996.o
++obj-$(CONFIG_AR8216_PHY)      += ar8xxx.o
++ar8xxx-y                      += ar8216.o
++ar8xxx-y                      += ar8327.o
++obj-$(CONFIG_SWCONFIG_B53)    += b53/
++obj-$(CONFIG_IP17XX_PHY)      += ip17xx.o
++obj-$(CONFIG_PSB6970_PHY)     += psb6970.o
++obj-$(CONFIG_RTL8306_PHY)     += rtl8306.o
++obj-$(CONFIG_RTL8366_SMI)     += rtl8366_smi.o
++obj-$(CONFIG_RTL8366S_PHY)    += rtl8366s.o
++obj-$(CONFIG_RTL8366RB_PHY)   += rtl8366rb.o
++obj-$(CONFIG_RTL8367_PHY)     += rtl8367.o
++obj-$(CONFIG_RTL8367B_PHY)    += rtl8367b.o
++
+ obj-$(CONFIG_NETWORK_PHY_TIMESTAMPING) += mii_timestamper.o
+ obj-$(CONFIG_SFP)             += sfp.o
+--- a/include/linux/platform_data/b53.h
++++ b/include/linux/platform_data/b53.h
+@@ -29,6 +29,9 @@ struct b53_platform_data {
+       u32 chip_id;
+       u16 enabled_ports;
++      /* allow to specify an ethX alias */
++      const char *alias;
++
+       /* only used by MMAP'd driver */
+       unsigned big_endian:1;
+       void __iomem *regs;
diff --git a/target/linux/generic/hack-6.12/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch b/target/linux/generic/hack-6.12/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch
new file mode 100644 (file)
index 0000000..29ac3e9
--- /dev/null
@@ -0,0 +1,21 @@
+From ebd924d773223593142d417c41d4ee6fa16f1805 Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 13:45:56 +0200
+Subject: [PATCH] net/dsa/mv88e6xxx: disable ATU violation
+
+---
+ drivers/net/dsa/mv88e6xxx/chip.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -3583,6 +3583,9 @@ static int mv88e6xxx_setup_port(struct m
+       else
+               reg = 1 << port;
++      /* Disable ATU member violation interrupt */
++      reg |= MV88E6XXX_PORT_ASSOC_VECTOR_IGNORE_WRONG;
++
+       err = mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_ASSOC_VECTOR,
+                                  reg);
+       if (err)
diff --git a/target/linux/generic/hack-6.12/721-net-add-packet-mangeling.patch b/target/linux/generic/hack-6.12/721-net-add-packet-mangeling.patch
new file mode 100644 (file)
index 0000000..32228bb
--- /dev/null
@@ -0,0 +1,159 @@
+From ffe387740bbe88dd88bbe04d6375902708003d6e Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 7 Jul 2017 17:25:00 +0200
+Subject: net: add packet mangeling
+
+ar8216 switches have a hardware bug, which renders normal 802.1q support
+unusable. Packet mangling is required to fix up the vlan for incoming
+packets.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ include/linux/netdevice.h | 10 ++++++++++
+ include/linux/skbuff.h    | 14 ++++----------
+ net/Kconfig               |  6 ++++++
+ net/core/dev.c            | 20 +++++++++++++++-----
+ net/core/skbuff.c         | 17 +++++++++++++++++
+ net/ethernet/eth.c        |  6 ++++++
+ 6 files changed, 58 insertions(+), 15 deletions(-)
+
+--- a/include/linux/netdevice.h
++++ b/include/linux/netdevice.h
+@@ -1687,6 +1687,7 @@ enum netdev_priv_flags {
+       IFF_L3MDEV_RX_HANDLER           = 1<<29,
+       IFF_NO_ADDRCONF                 = BIT_ULL(30),
+       IFF_TX_SKB_NO_LINEAR            = BIT_ULL(31),
++      IFF_NO_IP_ALIGN                 = BIT_ULL(32),
+ };
+ /* Specifies the type of the struct net_device::ml_priv pointer */
+@@ -2168,6 +2169,11 @@ struct net_device {
+       const struct tlsdev_ops *tlsdev_ops;
+ #endif
++#ifdef CONFIG_ETHERNET_PACKET_MANGLE
++      void (*eth_mangle_rx)(struct net_device *dev, struct sk_buff *skb);
++      struct sk_buff *(*eth_mangle_tx)(struct net_device *dev, struct sk_buff *skb);
++#endif
++
+       unsigned int            operstate;
+       unsigned char           link_mode;
+@@ -2237,6 +2243,10 @@ struct net_device {
+       struct mctp_dev __rcu   *mctp_ptr;
+ #endif
++#ifdef CONFIG_ETHERNET_PACKET_MANGLE
++      void                    *phy_ptr; /* PHY device specific data */
++#endif
++
+ /*
+  * Cache lines mostly used on receive path (including eth_type_trans())
+  */
+--- a/include/linux/skbuff.h
++++ b/include/linux/skbuff.h
+@@ -3274,6 +3274,10 @@ static inline int pskb_trim(struct sk_bu
+       return (len < skb->len) ? __pskb_trim(skb, len) : 0;
+ }
++extern struct sk_buff *__netdev_alloc_skb_ip_align(struct net_device *dev,
++              unsigned int length, gfp_t gfp);
++
++
+ /**
+  *    pskb_trim_unique - remove end from a paged unique (not cloned) buffer
+  *    @skb: buffer to alter
+@@ -3439,16 +3443,6 @@ static inline struct sk_buff *dev_alloc_
+ }
+-static inline struct sk_buff *__netdev_alloc_skb_ip_align(struct net_device *dev,
+-              unsigned int length, gfp_t gfp)
+-{
+-      struct sk_buff *skb = __netdev_alloc_skb(dev, length + NET_IP_ALIGN, gfp);
+-
+-      if (NET_IP_ALIGN && skb)
+-              skb_reserve(skb, NET_IP_ALIGN);
+-      return skb;
+-}
+-
+ static inline struct sk_buff *netdev_alloc_skb_ip_align(struct net_device *dev,
+               unsigned int length)
+ {
+--- a/net/Kconfig
++++ b/net/Kconfig
+@@ -26,6 +26,12 @@ menuconfig NET
+ if NET
++config ETHERNET_PACKET_MANGLE
++      bool
++      help
++        This option can be selected by phy drivers that need to mangle
++        packets going in or out of an ethernet device.
++
+ config WANT_COMPAT_NETLINK_MESSAGES
+       bool
+       help
+--- a/net/core/dev.c
++++ b/net/core/dev.c
+@@ -3671,6 +3671,11 @@ static int xmit_one(struct sk_buff *skb,
+       if (dev_nit_active(dev))
+               dev_queue_xmit_nit(skb, dev);
++#ifdef CONFIG_ETHERNET_PACKET_MANGLE
++      if (dev->eth_mangle_tx && !(skb = dev->eth_mangle_tx(dev, skb)))
++              return NETDEV_TX_OK;
++#endif
++
+       len = skb->len;
+       trace_net_dev_start_xmit(skb, dev);
+       rc = netdev_start_xmit(skb, dev, txq, more);
+--- a/net/core/skbuff.c
++++ b/net/core/skbuff.c
+@@ -64,6 +64,7 @@
+ #include <linux/mpls.h>
+ #include <linux/kcov.h>
+ #include <linux/iov_iter.h>
++#include <linux/if.h>
+ #include <net/protocol.h>
+ #include <net/dst.h>
+@@ -326,6 +327,22 @@ void *__napi_alloc_frag_align(unsigned i
+ }
+ EXPORT_SYMBOL(__napi_alloc_frag_align);
++struct sk_buff *__netdev_alloc_skb_ip_align(struct net_device *dev,
++              unsigned int length, gfp_t gfp)
++{
++      struct sk_buff *skb = __netdev_alloc_skb(dev, length + NET_IP_ALIGN, gfp);
++
++#ifdef CONFIG_ETHERNET_PACKET_MANGLE
++      if (dev && (dev->priv_flags & IFF_NO_IP_ALIGN))
++              return skb;
++#endif
++
++      if (NET_IP_ALIGN && skb)
++              skb_reserve(skb, NET_IP_ALIGN);
++      return skb;
++}
++EXPORT_SYMBOL(__netdev_alloc_skb_ip_align);
++
+ void *__netdev_alloc_frag_align(unsigned int fragsz, unsigned int align_mask)
+ {
+       void *data;
+--- a/net/ethernet/eth.c
++++ b/net/ethernet/eth.c
+@@ -159,6 +159,12 @@ __be16 eth_type_trans(struct sk_buff *sk
+       const struct ethhdr *eth;
+       skb->dev = dev;
++
++#ifdef CONFIG_ETHERNET_PACKET_MANGLE
++      if (dev->eth_mangle_rx)
++              dev->eth_mangle_rx(dev, skb);
++#endif
++
+       skb_reset_mac_header(skb);
+       eth = eth_skb_pull_mac(skb);
diff --git a/target/linux/generic/hack-6.12/722-net-phy-aquantia-enable-AQR112-and-AQR412.patch b/target/linux/generic/hack-6.12/722-net-phy-aquantia-enable-AQR112-and-AQR412.patch
new file mode 100644 (file)
index 0000000..2d7edc9
--- /dev/null
@@ -0,0 +1,117 @@
+From 5f62951fba63a9f9cfff564209426bdea5fcc371 Mon Sep 17 00:00:00 2001
+From: Alex Marginean <alexandru.marginean@nxp.com>
+Date: Tue, 27 Aug 2019 15:16:56 +0300
+Subject: [PATCH] drivers: net: phy: aquantia: enable AQR112 and AQR412
+
+Adds support for AQR112 and AQR412 which is mostly based on existing code
+with the addition of code configuring the protocol on system side.
+This allows changing the system side protocol without having to deploy a
+different firmware on the PHY.
+
+Signed-off-by: Alex Marginean <alexandru.marginean@nxp.com>
+---
+ drivers/net/phy/aquantia/aquantia_main.c | 88 +++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 88 insertions(+)
+
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -97,6 +97,29 @@
+ #define AQR107_OP_IN_PROG_SLEEP               1000
+ #define AQR107_OP_IN_PROG_TIMEOUT     100000
++/* registers in MDIO_MMD_VEND1 region */
++#define AQUANTIA_VND1_GLOBAL_SC                       0x000
++#define  AQUANTIA_VND1_GLOBAL_SC_LP           BIT(0xb)
++
++/* global start rate, the protocol associated with this speed is used by default
++ * on SI.
++ */
++#define AQUANTIA_VND1_GSTART_RATE             0x31a
++#define  AQUANTIA_VND1_GSTART_RATE_OFF                0
++#define  AQUANTIA_VND1_GSTART_RATE_100M               1
++#define  AQUANTIA_VND1_GSTART_RATE_1G         2
++#define  AQUANTIA_VND1_GSTART_RATE_10G                3
++#define  AQUANTIA_VND1_GSTART_RATE_2_5G               4
++#define  AQUANTIA_VND1_GSTART_RATE_5G         5
++
++/* SYSCFG registers for 100M, 1G, 2.5G, 5G, 10G */
++#define AQUANTIA_VND1_GSYSCFG_BASE            0x31b
++#define AQUANTIA_VND1_GSYSCFG_100M            0
++#define AQUANTIA_VND1_GSYSCFG_1G              1
++#define AQUANTIA_VND1_GSYSCFG_2_5G            2
++#define AQUANTIA_VND1_GSYSCFG_5G              3
++#define AQUANTIA_VND1_GSYSCFG_10G             4
++
+ static int aqr107_get_sset_count(struct phy_device *phydev)
+ {
+       return AQR107_SGMII_STAT_SZ;
+@@ -203,6 +226,51 @@ static int aqr_config_aneg(struct phy_de
+       return genphy_c45_check_and_restart_aneg(phydev, changed);
+ }
++static struct {
++      u16 syscfg;
++      int cnt;
++      u16 start_rate;
++} aquantia_syscfg[PHY_INTERFACE_MODE_MAX] = {
++      [PHY_INTERFACE_MODE_SGMII] =      {0x04b, AQUANTIA_VND1_GSYSCFG_1G,
++                                         AQUANTIA_VND1_GSTART_RATE_1G},
++      [PHY_INTERFACE_MODE_2500BASEX] = {0x144, AQUANTIA_VND1_GSYSCFG_2_5G,
++                                         AQUANTIA_VND1_GSTART_RATE_2_5G},
++      [PHY_INTERFACE_MODE_XGMII] =      {0x100, AQUANTIA_VND1_GSYSCFG_10G,
++                                         AQUANTIA_VND1_GSTART_RATE_10G},
++      [PHY_INTERFACE_MODE_USXGMII] =    {0x080, AQUANTIA_VND1_GSYSCFG_10G,
++                                         AQUANTIA_VND1_GSTART_RATE_10G},
++};
++
++/* Sets up protocol on system side before calling aqr_config_aneg */
++static int aqr_config_aneg_set_prot(struct phy_device *phydev)
++{
++      int if_type = phydev->interface;
++      int i;
++
++      if (!aquantia_syscfg[if_type].cnt)
++              return 0;
++
++      /* set PHY in low power mode so we can configure protocols */
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, AQUANTIA_VND1_GLOBAL_SC,
++                    AQUANTIA_VND1_GLOBAL_SC_LP);
++      mdelay(10);
++
++      /* set the default rate to enable the SI link */
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, AQUANTIA_VND1_GSTART_RATE,
++                    aquantia_syscfg[if_type].start_rate);
++
++      for (i = 0; i <= aquantia_syscfg[if_type].cnt; i++)
++              phy_write_mmd(phydev, MDIO_MMD_VEND1,
++                            AQUANTIA_VND1_GSYSCFG_BASE + i,
++                            aquantia_syscfg[if_type].syscfg);
++
++      /* wake PHY back up */
++      phy_write_mmd(phydev, MDIO_MMD_VEND1, AQUANTIA_VND1_GLOBAL_SC, 0);
++      mdelay(10);
++
++      return aqr_config_aneg(phydev);
++}
++
+ static int aqr_config_intr(struct phy_device *phydev)
+ {
+       bool en = phydev->interrupts == PHY_INTERRUPT_ENABLED;
+@@ -972,7 +1040,7 @@ static struct phy_driver aqr_driver[] =
+       PHY_ID_MATCH_MODEL(PHY_ID_AQR112),
+       .name           = "Aquantia AQR112",
+       .probe          = aqr107_probe,
+-      .config_aneg    = aqr_config_aneg,
++      .config_aneg    = aqr_config_aneg_set_prot,
+       .config_intr    = aqr_config_intr,
+       .handle_interrupt = aqr_handle_interrupt,
+       .get_tunable    = aqr107_get_tunable,
+@@ -995,7 +1063,7 @@ static struct phy_driver aqr_driver[] =
+       PHY_ID_MATCH_MODEL(PHY_ID_AQR412),
+       .name           = "Aquantia AQR412",
+       .probe          = aqr107_probe,
+-      .config_aneg    = aqr_config_aneg,
++      .config_aneg    = aqr_config_aneg_set_prot,
+       .config_intr    = aqr_config_intr,
+       .handle_interrupt = aqr_handle_interrupt,
+       .get_tunable    = aqr107_get_tunable,
diff --git a/target/linux/generic/hack-6.12/723-net-phy-aquantia-fix-system-side-protocol-mi.patch b/target/linux/generic/hack-6.12/723-net-phy-aquantia-fix-system-side-protocol-mi.patch
new file mode 100644 (file)
index 0000000..eaeb3b6
--- /dev/null
@@ -0,0 +1,34 @@
+From 5f008cb22f60da4e10375f22266c1a4e20b1252e Mon Sep 17 00:00:00 2001
+From: Alex Marginean <alexandru.marginean@nxp.com>
+Date: Fri, 20 Sep 2019 18:22:52 +0300
+Subject: [PATCH] drivers: net: phy: aquantia: fix system side protocol
+ misconfiguration
+
+Do not set up protocols for speeds that are not supported by FW.  Enabling
+these protocols leads to link issues on system side.
+
+Signed-off-by: Alex Marginean <alexandru.marginean@nxp.com>
+---
+ drivers/net/phy/aquantia/aquantia_main.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -259,10 +259,16 @@ static int aqr_config_aneg_set_prot(stru
+       phy_write_mmd(phydev, MDIO_MMD_VEND1, AQUANTIA_VND1_GSTART_RATE,
+                     aquantia_syscfg[if_type].start_rate);
+-      for (i = 0; i <= aquantia_syscfg[if_type].cnt; i++)
++      for (i = 0; i <= aquantia_syscfg[if_type].cnt; i++) {
++              u16 reg = phy_read_mmd(phydev, MDIO_MMD_VEND1,
++                                     AQUANTIA_VND1_GSYSCFG_BASE + i);
++              if (!reg)
++                      continue;
++
+               phy_write_mmd(phydev, MDIO_MMD_VEND1,
+                             AQUANTIA_VND1_GSYSCFG_BASE + i,
+                             aquantia_syscfg[if_type].syscfg);
++      }
+       /* wake PHY back up */
+       phy_write_mmd(phydev, MDIO_MMD_VEND1, AQUANTIA_VND1_GLOBAL_SC, 0);
diff --git a/target/linux/generic/hack-6.12/725-net-phy-aquantia-add-PHY_IDs-for-AQR112-variants.patch b/target/linux/generic/hack-6.12/725-net-phy-aquantia-add-PHY_IDs-for-AQR112-variants.patch
new file mode 100644 (file)
index 0000000..98c4186
--- /dev/null
@@ -0,0 +1,63 @@
+From 3b92ee7b7899b6beffb2b484c58326e36612a873 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 23 Dec 2021 14:52:56 +0000
+Subject: [PATCH] net: phy: aquantia: add PHY_ID for AQR112R
+
+As advised by Ian Chang this PHY is used in Puzzle devices.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/phy/aquantia/aquantia_main.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -32,6 +32,8 @@
+ #define PHY_ID_AQR114C        0x31c31c22
+ #define PHY_ID_AQR115C        0x31c31c33
+ #define PHY_ID_AQR813 0x31c31cb2
++#define PHY_ID_AQR112C        0x03a1b790
++#define PHY_ID_AQR112R        0x31c31d12
+ #define MDIO_PHYXS_VEND_IF_STATUS             0xe812
+ #define MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK   GENMASK(7, 3)
+@@ -1205,6 +1207,30 @@ static struct phy_driver aqr_driver[] =
+       .led_hw_control_get = aqr_phy_led_hw_control_get,
+       .led_polarity_set = aqr_phy_led_polarity_set,
+ },
++{
++      PHY_ID_MATCH_MODEL(PHY_ID_AQR112C),
++      .name           = "Aquantia AQR112C",
++      .probe          = aqr107_probe,
++      .config_aneg    = aqr_config_aneg_set_prot,
++      .config_intr    = aqr_config_intr,
++      .handle_interrupt = aqr_handle_interrupt,
++      .read_status    = aqr107_read_status,
++      .get_sset_count = aqr107_get_sset_count,
++      .get_strings    = aqr107_get_strings,
++      .get_stats      = aqr107_get_stats,
++},
++{
++      PHY_ID_MATCH_MODEL(PHY_ID_AQR112R),
++      .name           = "Aquantia AQR112R",
++      .probe          = aqr107_probe,
++      .config_aneg    = aqr_config_aneg_set_prot,
++      .config_intr    = aqr_config_intr,
++      .handle_interrupt = aqr_handle_interrupt,
++      .read_status    = aqr107_read_status,
++      .get_sset_count = aqr107_get_sset_count,
++      .get_strings    = aqr107_get_strings,
++      .get_stats      = aqr107_get_stats,
++},
+ };
+ module_phy_driver(aqr_driver);
+@@ -1226,6 +1252,8 @@ static const struct mdio_device_id __may
+       { PHY_ID_MATCH_MODEL(PHY_ID_AQR114C) },
+       { PHY_ID_MATCH_MODEL(PHY_ID_AQR115C) },
+       { PHY_ID_MATCH_MODEL(PHY_ID_AQR813) },
++      { PHY_ID_MATCH_MODEL(PHY_ID_AQR112C) },
++      { PHY_ID_MATCH_MODEL(PHY_ID_AQR112R) },
+       { }
+ };
diff --git a/target/linux/generic/hack-6.12/730-net-ethernet-mtk_eth_soc-add-hw-dump-for-forced-rese.patch b/target/linux/generic/hack-6.12/730-net-ethernet-mtk_eth_soc-add-hw-dump-for-forced-rese.patch
new file mode 100644 (file)
index 0000000..39446f6
--- /dev/null
@@ -0,0 +1,115 @@
+From bc51c337a3147c4a02c743489885a6657bc5371c Mon Sep 17 00:00:00 2001
+From: Bo-Cun Chen <bc-bocun.chen@mediatek.com>
+Date: Wed, 27 Nov 2024 13:36:49 +0800
+Subject: [PATCH] net: ethernet: mtk_eth_soc: add hw dump for forced reset
+
+Without this patch, the ETH driver is unable to dump the registers
+before triggering a forced reset.
+
+Signed-off-by: Bo-Cun Chen <bc-bocun.chen@mediatek.com>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 55 +++++++++++++++++++++
+ 1 files changed, 55 insertions(+)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -74,6 +74,7 @@ static const struct mtk_reg_map mtk_reg_
+               .rx_ptr         = 0x1900,
+               .rx_cnt_cfg     = 0x1904,
+               .qcrx_ptr       = 0x1908,
++              .page           = 0x19f0,
+               .glo_cfg        = 0x1a04,
+               .rst_idx        = 0x1a08,
+               .delay_irq      = 0x1a0c,
+@@ -140,6 +141,7 @@ static const struct mtk_reg_map mt7986_r
+               .rx_ptr         = 0x4500,
+               .rx_cnt_cfg     = 0x4504,
+               .qcrx_ptr       = 0x4508,
++              .page           = 0x45f0,
+               .glo_cfg        = 0x4604,
+               .rst_idx        = 0x4608,
+               .delay_irq      = 0x460c,
+@@ -191,6 +193,7 @@ static const struct mtk_reg_map mt7988_r
+               .rx_ptr         = 0x4500,
+               .rx_cnt_cfg     = 0x4504,
+               .qcrx_ptr       = 0x4508,
++              .page           = 0x45f0,
+               .glo_cfg        = 0x4604,
+               .rst_idx        = 0x4608,
+               .delay_irq      = 0x460c,
+@@ -4069,6 +4072,56 @@ static void mtk_set_mcr_max_rx(struct mt
+               mtk_w32(mac->hw, mcr_new, MTK_MAC_MCR(mac->id));
+ }
++static void mtk_hw_dump_reg(struct mtk_eth *eth, char *name, u32 offset, u32 range)
++{
++      u32 cur = offset;
++
++      pr_info("\n==================== %s ====================\n", name);
++      while (cur < offset + range) {
++              pr_info("0x%08x: %08x %08x %08x %08x\n",
++                      cur, mtk_r32(eth, cur), mtk_r32(eth, cur + 0x4),
++                      mtk_r32(eth, cur + 0x8), mtk_r32(eth, cur + 0xc));
++              cur += 0x10;
++      }
++}
++
++static void mtk_hw_dump(struct mtk_eth *eth)
++{
++      const struct mtk_reg_map *reg_map = eth->soc->reg_map;
++      u32 id;
++
++      mtk_hw_dump_reg(eth, "FE", 0x0, 0x600);
++      mtk_hw_dump_reg(eth, "FE", 0x1400, 0x300);
++      mtk_hw_dump_reg(eth, "ADMA", reg_map->pdma.rx_ptr, 0x300);
++      if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
++              for (id = 0; id < MTK_QDMA_NUM_QUEUES / 16; id++) {
++                      mtk_w32(eth, id, reg_map->qdma.page);
++                      pr_info("\nQDMA PAGE:%x ", mtk_r32(eth, reg_map->qdma.page));
++                      mtk_hw_dump_reg(eth, "QDMA", reg_map->qdma.qtx_cfg, 0x100);
++                      mtk_w32(eth, 0, reg_map->qdma.page);
++              }
++              mtk_hw_dump_reg(eth, "QDMA", reg_map->qdma.rx_ptr, 0x300);
++      }
++      if (!MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628)) {
++              mtk_hw_dump_reg(eth, "WDMA0", reg_map->wdma_base[0], 0x400);
++              mtk_hw_dump_reg(eth, "WDMA1", reg_map->wdma_base[1], 0x400);
++              if (mtk_is_netsys_v3_or_greater(eth))
++                      mtk_hw_dump_reg(eth, "WDMA2", reg_map->wdma_base[2], 0x400);
++      }
++      mtk_hw_dump_reg(eth, "PPE0", reg_map->ppe_base + 0x200, 0x200);
++      if (!mtk_is_netsys_v1(eth))
++              mtk_hw_dump_reg(eth, "PPE1", reg_map->ppe_base + 0x600, 0x200);
++      if (mtk_is_netsys_v3_or_greater(eth))
++              mtk_hw_dump_reg(eth, "PPE2", reg_map->ppe_base + 0xE00, 0x200);
++      mtk_hw_dump_reg(eth, "GMAC", 0x10000, 0x300);
++      if (mtk_is_netsys_v3_or_greater(eth))
++              mtk_hw_dump_reg(eth, "GMAC", 0x10300, 0x100);
++      if (mtk_is_netsys_v3_or_greater(eth)) {
++              mtk_hw_dump_reg(eth, "XGMAC0", 0x12000, 0x300);
++              mtk_hw_dump_reg(eth, "XGMAC1", 0x13000, 0x300);
++      }
++}
++
+ static void mtk_hw_reset(struct mtk_eth *eth)
+ {
+       u32 val;
+@@ -4548,6 +4601,8 @@ static void mtk_pending_work(struct work
+       rtnl_lock();
+       set_bit(MTK_RESETTING, &eth->state);
++      mtk_hw_dump(eth);
++
+       mtk_prepare_for_reset(eth);
+       mtk_wed_fe_reset();
+       /* Run again reset preliminary configuration in order to avoid any
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -1185,6 +1185,7 @@ struct mtk_reg_map {
+               u32     rx_ptr;         /* rx base pointer */
+               u32     rx_cnt_cfg;     /* rx max count configuration */
+               u32     qcrx_ptr;       /* rx cpu pointer */
++              u32     page;           /* page configuration */
+               u32     glo_cfg;        /* global configuration */
+               u32     rst_idx;        /* reset index */
+               u32     delay_irq;      /* delay interrupt */
diff --git a/target/linux/generic/hack-6.12/735-net-phy-realtek-rtl8261n.patch b/target/linux/generic/hack-6.12/735-net-phy-realtek-rtl8261n.patch
new file mode 100644 (file)
index 0000000..c9dc250
--- /dev/null
@@ -0,0 +1,21 @@
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -431,6 +431,8 @@ config QSEMI_PHY
+ source "drivers/net/phy/realtek/Kconfig"
++source "drivers/net/phy/rtl8261n/Kconfig"
++
+ config RENESAS_PHY
+       tristate "Renesas PHYs"
+       help
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -110,6 +110,7 @@ obj-$(CONFIG_NXP_TJA11XX_PHY)      += nxp-tja
+ obj-y                         += qcom/
+ obj-$(CONFIG_QSEMI_PHY)               += qsemi.o
+ obj-$(CONFIG_REALTEK_PHY)     += realtek/
++obj-y                         += rtl8261n/
+ obj-$(CONFIG_RENESAS_PHY)     += uPD60620.o
+ obj-$(CONFIG_ROCKCHIP_PHY)    += rockchip.o
+ obj-$(CONFIG_SMSC_PHY)                += smsc.o
diff --git a/target/linux/generic/hack-6.12/750-net-pcs-mtk-lynxi-workaround-2500BaseX-no-an.patch b/target/linux/generic/hack-6.12/750-net-pcs-mtk-lynxi-workaround-2500BaseX-no-an.patch
new file mode 100644 (file)
index 0000000..bf26a2f
--- /dev/null
@@ -0,0 +1,64 @@
+From 880d1311335120f64447ca9d11933872d734e19a Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 27 Mar 2023 18:41:54 +0100
+Subject: [PATCH] generic: pcs-mtk-lynxi: add hack to use 2500Base-X without AN
+
+Using 2500Base-T SFP modules e.g. on the BananaPi R3 requires manually
+disabling auto-negotiation, e.g. using ethtool. While a proper fix
+using SFP quirks is being discussed upstream, bring a work-around to
+restore user experience to what it was before the switch to the
+dedicated SGMII PCS driver.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+
+--- a/drivers/net/pcs/pcs-mtk-lynxi.c
++++ b/drivers/net/pcs/pcs-mtk-lynxi.c
+@@ -129,14 +129,23 @@ static void mtk_pcs_lynxi_get_state(stru
+                                   struct phylink_link_state *state)
+ {
+       struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
+-      unsigned int bm, adv;
++      unsigned int bm, bmsr, adv;
+       /* Read the BMSR and LPA */
+       regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm);
+-      regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv);
++      bmsr = FIELD_GET(SGMII_BMSR, bm);
++
++      if (state->interface == PHY_INTERFACE_MODE_2500BASEX) {
++              state->link = !!(bmsr & BMSR_LSTATUS);
++              state->an_complete = !!(bmsr & BMSR_ANEGCOMPLETE);
++              state->speed = SPEED_2500;
++              state->duplex = DUPLEX_FULL;
++
++              return;
++      }
+-      phylink_mii_c22_pcs_decode_state(state, FIELD_GET(SGMII_BMSR, bm),
+-                                       FIELD_GET(SGMII_LPA, adv));
++      regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv);
++      phylink_mii_c22_pcs_decode_state(state, bmsr, FIELD_GET(SGMII_LPA, adv));
+ }
+ static void mtk_sgmii_reset(struct mtk_pcs_lynxi *mpcs)
+@@ -157,7 +166,7 @@ static int mtk_pcs_lynxi_config(struct p
+ {
+       struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
+       bool mode_changed = false, changed;
+-      unsigned int rgc3, sgm_mode, bmcr;
++      unsigned int rgc3, sgm_mode, bmcr = 0;
+       int advertise, link_timer;
+       advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
+@@ -180,9 +189,8 @@ static int mtk_pcs_lynxi_config(struct p
+       if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
+               if (interface == PHY_INTERFACE_MODE_SGMII)
+                       sgm_mode |= SGMII_SPEED_DUPLEX_AN;
+-              bmcr = BMCR_ANENABLE;
+-      } else {
+-              bmcr = 0;
++              if (interface != PHY_INTERFACE_MODE_2500BASEX)
++                      bmcr = BMCR_ANENABLE;
+       }
+       if (mpcs->interface != interface) {
diff --git a/target/linux/generic/hack-6.12/755-net-phy-motorcomm-yt8821-bus-collision-workaround.patch b/target/linux/generic/hack-6.12/755-net-phy-motorcomm-yt8821-bus-collision-workaround.patch
new file mode 100644 (file)
index 0000000..28c4c70
--- /dev/null
@@ -0,0 +1,85 @@
+From 63161fb6353493c648a260244f6ac7eca65fd48e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jakub=20Van=C4=9Bk?= <linuxtardis@gmail.com>
+Date: Mon, 2 Mar 2026 20:31:37 +0100
+Subject: [PATCH] net: phy: Work around MDIO collisions in the YT8821 driver
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The Cudy M3000 router suffers from a MDIO bus collision:
+- The MT7981B internal gigabit PHY listens on address 0.
+- The Motorcomm YT8821 is strapped to listen on address 1,
+  but it initially also listens on address 0 (Motorcomm
+  incorrectly considers that address a broadcast address).
+
+Without this patch, the YT8821 reacts to MDIO commands intended
+for the internal gigabit PHY and that makes it work unreliably.
+The YT8821 state somehow gets corrupted by the commands for the MT7981
+(e.g. we get 100Mbps speeds on gigabit links).
+
+This dirty workaround resets the PHY to cancel out any earlier
+YT8821 register corruption and then it disables the address 0
+as soon as possible. This should make the YT8821 work reliably.
+
+BEWARE though: the PHY will be reset only if the device tree
+node that defines the PHY carries the reset-gpios attribute.
+The PHY will not be reset if the reset GPIO is either on the MDIO
+bus node or is not defined at all. This might not always be a problem --
+some routers do not require the PHY to be reset (e.g. Cudy WR3000H,
+where there likely is less communication with the MT7981B PHY,
+and so the YT8821 PHY works reasonably well there even without this patch).
+
+This patch also does not address the possible MDIO bus collisions
+when the MT7981B PHY driver initially configures that PHY and invokes
+some read transactions on the bus. I am hoping that the MT7981B MDIO
+bus controller gives priority to the internal PHY and so it would
+not be affected by these collisions. However, I don't have anything
+to base this on apart from it "seeming to be working okay".
+
+This patch is unlikely to be mergeable into the mainline kernel.
+Upstream has strongly indicated that they would prefer this problem
+to be resolved by the platform bootloader (e.g. U-Boot), see
+- https://lore.kernel.org/all/d3bb9c36-5a0e-4339-901d-2dd21bdba395@gmail.com/
+- https://lore.kernel.org/all/20260228232241.1274236-1-linuxtardis@gmail.com/
+
+Signed-off-by: Jakub Vaněk <linuxtardis@gmail.com>
+---
+ drivers/net/phy/motorcomm.c | 20 ++++++++++++++++++++
+ 1 file changed, 20 insertions(+)
+
+--- a/drivers/net/phy/motorcomm.c
++++ b/drivers/net/phy/motorcomm.c
+@@ -214,6 +214,9 @@
+ #define YT8521_RC1R_RGMII_2_100_NS            14
+ #define YT8521_RC1R_RGMII_2_250_NS            15
++#define YTPHY_MDIO_ADDRESS_CONTROL_REG                0xA005
++#define YTPHY_MACR_EN_PHY_ADDR_0              BIT(6)
++
+ #define YTPHY_MISC_CONFIG_REG                 0xA006
+ #define YTPHY_MCR_FIBER_SPEED_MASK            BIT(0)
+ #define YTPHY_MCR_FIBER_1000BX                        (0x1 << 0)
+@@ -2659,6 +2662,23 @@ static int yt8821_config_init(struct phy
+       int ret;
+       u16 set;
++      /* Hard-reset the PHY to clear out any register corruption
++       * from preceding MDIO bus conflicts.
++       */
++      phy_device_reset(phydev, 1);
++
++      /* Deassert the reset GPIO under the MDIO bus lock to make
++       * sure that nothing will communicate on the bus until we
++       * disable the broadcast address in the YT8821.
++       */
++      phy_lock_mdio_bus(phydev);
++      phy_device_reset(phydev, 0);
++      ret = ytphy_modify_ext(phydev,
++                             YTPHY_MDIO_ADDRESS_CONTROL_REG,
++                             YTPHY_MACR_EN_PHY_ADDR_0,
++                             0);
++      phy_unlock_mdio_bus(phydev);
++
+       if (phydev->interface == PHY_INTERFACE_MODE_2500BASEX)
+               mode = YT8821_CHIP_MODE_FORCE_BX2500;
diff --git a/target/linux/generic/hack-6.12/760-net-usb-r8152-add-LED-configuration-from-OF.patch b/target/linux/generic/hack-6.12/760-net-usb-r8152-add-LED-configuration-from-OF.patch
new file mode 100644 (file)
index 0000000..14024e0
--- /dev/null
@@ -0,0 +1,74 @@
+From 82985725e071f2a5735052f18e109a32aeac3a0b Mon Sep 17 00:00:00 2001
+From: David Bauer <mail@david-bauer.net>
+Date: Sun, 26 Jul 2020 02:38:31 +0200
+Subject: [PATCH] net: usb: r8152: add LED configuration from OF
+
+This adds the ability to configure the LED configuration register using
+OF. This way, the correct value for board specific LED configuration can
+be determined.
+
+Signed-off-by: David Bauer <mail@david-bauer.net>
+---
+ drivers/net/usb/r8152.c | 23 +++++++++++++++++++++++
+ 1 file changed, 23 insertions(+)
+
+--- a/drivers/net/usb/r8152.c
++++ b/drivers/net/usb/r8152.c
+@@ -12,6 +12,7 @@
+ #include <linux/ethtool.h>
+ #include <linux/phy.h>
+ #include <linux/usb.h>
++#include <linux/of.h>
+ #include <linux/crc32.h>
+ #include <linux/if_vlan.h>
+ #include <linux/uaccess.h>
+@@ -7049,6 +7050,22 @@ static void rtl_tally_reset(struct r8152
+       ocp_write_word(tp, MCU_TYPE_PLA, PLA_RSTTALLY, ocp_data);
+ }
++static int r8152_led_configuration(struct r8152 *tp)
++{
++      u32 led_data;
++      int ret;
++
++      ret = of_property_read_u32(tp->udev->dev.of_node, "realtek,led-data",
++                                                              &led_data);
++
++      if (ret)
++              return ret;
++
++      ocp_write_word(tp, MCU_TYPE_PLA, PLA_LEDSEL, led_data);
++
++      return 0;
++}
++
+ static void r8152b_init(struct r8152 *tp)
+ {
+       u32 ocp_data;
+@@ -7090,6 +7107,8 @@ static void r8152b_init(struct r8152 *tp
+       ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_USB_CTRL);
+       ocp_data &= ~(RX_AGG_DISABLE | RX_ZERO_EN);
+       ocp_write_word(tp, MCU_TYPE_USB, USB_USB_CTRL, ocp_data);
++
++      r8152_led_configuration(tp);
+ }
+ static void r8153_init(struct r8152 *tp)
+@@ -7230,6 +7249,8 @@ static void r8153_init(struct r8152 *tp)
+               tp->coalesce = COALESCE_SLOW;
+               break;
+       }
++
++      r8152_led_configuration(tp);
+ }
+ static void r8153b_init(struct r8152 *tp)
+@@ -7312,6 +7333,8 @@ static void r8153b_init(struct r8152 *tp
+       rtl_tally_reset(tp);
+       tp->coalesce = 15000;   /* 15 us */
++
++      r8152_led_configuration(tp);
+ }
+ static void r8153c_init(struct r8152 *tp)
diff --git a/target/linux/generic/hack-6.12/761-dt-bindings-net-add-RTL8152-binding-documentation.patch b/target/linux/generic/hack-6.12/761-dt-bindings-net-add-RTL8152-binding-documentation.patch
new file mode 100644 (file)
index 0000000..be262b9
--- /dev/null
@@ -0,0 +1,54 @@
+From 3ee05f4aa64fc86af3be5bc176ba5808de9260a7 Mon Sep 17 00:00:00 2001
+From: David Bauer <mail@david-bauer.net>
+Date: Sun, 26 Jul 2020 15:30:33 +0200
+Subject: [PATCH] dt-bindings: net: add RTL8152 binding documentation
+
+Add binding documentation for the Realtek RTL8152 / RTL8153 USB ethernet
+adapters.
+
+Signed-off-by: David Bauer <mail@david-bauer.net>
+---
+ .../bindings/net/realtek,rtl8152.yaml         | 36 +++++++++++++++++++
+ 1 file changed, 36 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/net/realtek,rtl8152.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/net/realtek,rtl8152.yaml
+@@ -0,0 +1,36 @@
++# SPDX-License-Identifier: GPL-2.0
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/net/realtek,rtl8152.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Realtek RTL8152/RTL8153 series USB ethernet
++
++maintainers:
++  - David Bauer <mail@david-bauer.net>
++
++properties:
++  compatible:
++    oneOf:
++      - items:
++          - enum:
++              - realtek,rtl8152
++              - realtek,rtl8153
++
++  reg:
++    description: The device number on the USB bus
++
++  realtek,led-data:
++    description: Value to be written to the LED configuration register.
++
++required:
++  - compatible
++  - reg
++
++examples:
++  - |
++    usb-eth@2 {
++      compatible = "realtek,rtl8153";
++      reg = <2>;
++      realtek,led-data = <0x87>;
++    };
+\ No newline at end of file
diff --git a/target/linux/generic/hack-6.12/766-net-phy-mediatek-ge-add-LED-configuration-interface.patch b/target/linux/generic/hack-6.12/766-net-phy-mediatek-ge-add-LED-configuration-interface.patch
new file mode 100644 (file)
index 0000000..2eeca8f
--- /dev/null
@@ -0,0 +1,72 @@
+From cc225d163b5a4f7a0d1968298bf7927306646a47 Mon Sep 17 00:00:00 2001
+From: David Bauer <mail@david-bauer.net>
+Date: Fri, 28 Apr 2023 01:53:01 +0200
+Subject: [PATCH] net: phy: mediatek-ge: add LED configuration interface
+
+This adds a small hack similar to the one used for ar8xxx switches to
+read a reg:value map for configuring the LED configuration registers.
+
+This allows OpenWrt to write device-specific LED action as well as blink
+configurations. It is unlikely to be accepted upstream, as upstream
+plans on integrating their own framework for handling these LEDs.
+
+Signed-off-by: David Bauer <mail@david-bauer.net>
+---
+ drivers/net/phy/mediatek/mtk-ge.c | 33 +++++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+--- a/drivers/net/phy/mediatek/mtk-ge.c
++++ b/drivers/net/phy/mediatek/mtk-ge.c
+@@ -1,4 +1,5 @@
+ // SPDX-License-Identifier: GPL-2.0+
++#include <linux/of.h>
+ #include <linux/bitfield.h>
+ #include <linux/module.h>
+ #include <linux/phy.h>
+@@ -73,6 +74,36 @@ static int mt7530_phy_config_init(struct
+       return 0;
+ }
++static int mt7530_led_config_of(struct phy_device *phydev)
++{
++      struct device_node *np = phydev->mdio.dev.of_node;
++      const __be32 *paddr;
++      int len;
++      int i;
++
++      paddr = of_get_property(np, "mediatek,led-config", &len);
++      if (!paddr)
++              return 0;
++
++      if (len < (2 * sizeof(*paddr)))
++              return -EINVAL;
++
++      len /= sizeof(*paddr);
++
++      phydev_warn(phydev, "Configure LED registers (num=%d)\n", len);
++      for (i = 0; i < len - 1; i += 2) {
++              u32 reg;
++              u32 val;
++
++              reg = be32_to_cpup(paddr + i);
++              val = be32_to_cpup(paddr + i + 1);
++
++              phy_write_mmd(phydev, MDIO_MMD_VEND2, reg, val);
++      }
++
++      return 0;
++}
++
+ static int mt7531_phy_config_init(struct phy_device *phydev)
+ {
+       mtk_gephy_config_init(phydev);
+@@ -93,6 +124,9 @@ static int mt7531_phy_config_init(struct
+                      FIELD_PREP(MTK_TX_DELAY_PAIR_B_MASK, 0x4) |
+                      FIELD_PREP(MTK_TX_DELAY_PAIR_D_MASK, 0x4));
++      /* LED Config*/
++      mt7530_led_config_of(phydev);
++
+       return 0;
+ }
diff --git a/target/linux/generic/hack-6.12/770-r8169-LED-uses-original-network-port-name.patch b/target/linux/generic/hack-6.12/770-r8169-LED-uses-original-network-port-name.patch
new file mode 100644 (file)
index 0000000..55d4b48
--- /dev/null
@@ -0,0 +1,89 @@
+From 5f8e52f3991c794be69af13170e5c54e5afe0674 Mon Sep 17 00:00:00 2001
+From: Chukun Pan <amadeus@jmu.edu.cn>
+Date: Sun, 5 Jan 2025 18:08:29 +0800
+Subject: [PATCH] r8169: LED uses original network port name
+
+Most Linux distributions use Predictable Network Interface Names
+to name PCIe network interfaces (systemd-udevd renames them).
+Since OpenWrt doesn't use this, let's use the original network
+port name in the driver to match the LED.
+
+Signed-off-by: Chukun Pan <amadeus@jmu.edu.cn>
+---
+
+--- a/drivers/net/ethernet/realtek/r8169.h
++++ b/drivers/net/ethernet/realtek/r8169.h
+@@ -84,8 +84,6 @@ u8 rtl8168d_efuse_read(struct rtl8169_pr
+ void r8169_hw_phy_config(struct rtl8169_private *tp, struct phy_device *phydev,
+                        enum mac_version ver);
+-void r8169_get_led_name(struct rtl8169_private *tp, int idx,
+-                      char *buf, int buf_len);
+ int rtl8168_get_led_mode(struct rtl8169_private *tp);
+ int rtl8168_led_mod_ctrl(struct rtl8169_private *tp, u16 mask, u16 val);
+ struct r8169_led_classdev *rtl8168_init_leds(struct net_device *ndev);
+--- a/drivers/net/ethernet/realtek/r8169_leds.c
++++ b/drivers/net/ethernet/realtek/r8169_leds.c
+@@ -129,14 +129,13 @@ static struct device *
+ static void rtl8168_setup_ldev(struct r8169_led_classdev *ldev,
+                              struct net_device *ndev, int index)
+ {
+-      struct rtl8169_private *tp = netdev_priv(ndev);
+       struct led_classdev *led_cdev = &ldev->led;
+       char led_name[LED_MAX_NAME_SIZE];
+       ldev->ndev = ndev;
+       ldev->index = index;
+-      r8169_get_led_name(tp, index, led_name, LED_MAX_NAME_SIZE);
++      snprintf(led_name, sizeof(led_name), "%s-%d::lan", ndev->name, index);
+       led_cdev->name = led_name;
+       led_cdev->hw_control_trigger = "netdev";
+       led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN;
+@@ -228,14 +227,13 @@ static int rtl8125_led_hw_control_get(st
+ static void rtl8125_setup_led_ldev(struct r8169_led_classdev *ldev,
+                                  struct net_device *ndev, int index)
+ {
+-      struct rtl8169_private *tp = netdev_priv(ndev);
+       struct led_classdev *led_cdev = &ldev->led;
+       char led_name[LED_MAX_NAME_SIZE];
+       ldev->ndev = ndev;
+       ldev->index = index;
+-      r8169_get_led_name(tp, index, led_name, LED_MAX_NAME_SIZE);
++      snprintf(led_name, sizeof(led_name), "%s-%d::lan", ndev->name, index);
+       led_cdev->name = led_name;
+       led_cdev->hw_control_trigger = "netdev";
+       led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN;
+--- a/drivers/net/ethernet/realtek/r8169_main.c
++++ b/drivers/net/ethernet/realtek/r8169_main.c
+@@ -980,28 +980,6 @@ int rtl8125_get_led_mode(struct rtl8169_
+       return ret;
+ }
+-void r8169_get_led_name(struct rtl8169_private *tp, int idx,
+-                      char *buf, int buf_len)
+-{
+-      struct pci_dev *pdev = tp->pci_dev;
+-      char pdom[8], pfun[8];
+-      int domain;
+-
+-      domain = pci_domain_nr(pdev->bus);
+-      if (domain)
+-              snprintf(pdom, sizeof(pdom), "P%d", domain);
+-      else
+-              pdom[0] = '\0';
+-
+-      if (pdev->multifunction)
+-              snprintf(pfun, sizeof(pfun), "f%d", PCI_FUNC(pdev->devfn));
+-      else
+-              pfun[0] = '\0';
+-
+-      snprintf(buf, buf_len, "en%sp%ds%d%s-%d::lan", pdom, pdev->bus->number,
+-               PCI_SLOT(pdev->devfn), pfun, idx);
+-}
+-
+ static void r8168fp_adjust_ocp_cmd(struct rtl8169_private *tp, u32 *cmd, int type)
+ {
+       /* based on RTL8168FP_OOBMAC_BASE in vendor driver */
diff --git a/target/linux/generic/hack-6.12/773-bgmac-add-srab-switch.patch b/target/linux/generic/hack-6.12/773-bgmac-add-srab-switch.patch
new file mode 100644 (file)
index 0000000..40634f9
--- /dev/null
@@ -0,0 +1,98 @@
+From 3cb240533ab787899dc7f17aa7d6c5b4810e2e58 Mon Sep 17 00:00:00 2001
+From: Hauke Mehrtens <hauke@hauke-m.de>
+Date: Fri, 7 Jul 2017 17:26:01 +0200
+Subject: bcm53xx: bgmac: use srab switch driver
+
+use the srab switch driver on these SoCs.
+
+Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
+---
+ drivers/net/ethernet/broadcom/bgmac-bcma.c |  1 +
+ drivers/net/ethernet/broadcom/bgmac.c      | 24 ++++++++++++++++++++++++
+ drivers/net/ethernet/broadcom/bgmac.h      |  4 ++++
+ 3 files changed, 29 insertions(+)
+
+--- a/drivers/net/ethernet/broadcom/bgmac-bcma.c
++++ b/drivers/net/ethernet/broadcom/bgmac-bcma.c
+@@ -280,6 +280,7 @@ static int bgmac_probe(struct bcma_devic
+               bgmac->feature_flags |= BGMAC_FEAT_CLKCTLST;
+               bgmac->feature_flags |= BGMAC_FEAT_NO_RESET;
+               bgmac->feature_flags |= BGMAC_FEAT_FORCE_SPEED_2500;
++              bgmac->feature_flags |= BGMAC_FEAT_SRAB;
+               break;
+       default:
+               bgmac->feature_flags |= BGMAC_FEAT_CLKCTLST;
+--- a/drivers/net/ethernet/broadcom/bgmac.c
++++ b/drivers/net/ethernet/broadcom/bgmac.c
+@@ -12,6 +12,7 @@
+ #include <linux/bcma/bcma.h>
+ #include <linux/etherdevice.h>
+ #include <linux/interrupt.h>
++#include <linux/platform_data/b53.h>
+ #include <linux/bcm47xx_nvram.h>
+ #include <linux/phy.h>
+ #include <linux/phy_fixed.h>
+@@ -1408,6 +1409,17 @@ static const struct ethtool_ops bgmac_et
+       .set_link_ksettings     = phy_ethtool_set_link_ksettings,
+ };
++static struct b53_platform_data bgmac_b53_pdata = {
++};
++
++static struct platform_device bgmac_b53_dev = {
++      .name           = "b53-srab-switch",
++      .id             = -1,
++      .dev            = {
++              .platform_data = &bgmac_b53_pdata,
++      },
++};
++
+ /**************************************************
+  * MII
+  **************************************************/
+@@ -1546,6 +1558,14 @@ int bgmac_enet_probe(struct bgmac *bgmac
+       bgmac->in_init = false;
++      if ((bgmac->feature_flags & BGMAC_FEAT_SRAB) && !bgmac_b53_pdata.regs) {
++              bgmac_b53_pdata.regs = ioremap(0x18007000, 0x1000);
++
++              err = platform_device_register(&bgmac_b53_dev);
++              if (!err)
++                      bgmac->b53_device = &bgmac_b53_dev;
++      }
++
+       err = register_netdev(bgmac->net_dev);
+       if (err) {
+               dev_err(bgmac->dev, "Cannot register net device\n");
+@@ -1568,6 +1588,10 @@ EXPORT_SYMBOL_GPL(bgmac_enet_probe);
+ void bgmac_enet_remove(struct bgmac *bgmac)
+ {
++      if (bgmac->b53_device)
++              platform_device_unregister(&bgmac_b53_dev);
++      bgmac->b53_device = NULL;
++
+       unregister_netdev(bgmac->net_dev);
+       phy_disconnect(bgmac->net_dev->phydev);
+       netif_napi_del(&bgmac->napi);
+--- a/drivers/net/ethernet/broadcom/bgmac.h
++++ b/drivers/net/ethernet/broadcom/bgmac.h
+@@ -387,6 +387,7 @@
+ #define BGMAC_FEAT_CC4_IF_SW_TYPE_RGMII       BIT(18)
+ #define BGMAC_FEAT_CC7_IF_TYPE_RGMII  BIT(19)
+ #define BGMAC_FEAT_IDM_MASK           BIT(20)
++#define BGMAC_FEAT_SRAB                       BIT(21)
+ struct bgmac_slot_info {
+       union {
+@@ -494,6 +495,9 @@ struct bgmac {
+       void (*cmn_maskset32)(struct bgmac *bgmac, u16 offset, u32 mask,
+                             u32 set);
+       int (*phy_connect)(struct bgmac *bgmac);
++
++      /* platform device for associated switch */
++      struct platform_device *b53_device;
+ };
+ struct bgmac *bgmac_alloc(struct device *dev);
diff --git a/target/linux/generic/hack-6.12/780-usb-net-MeigLink_modem_support.patch b/target/linux/generic/hack-6.12/780-usb-net-MeigLink_modem_support.patch
new file mode 100644 (file)
index 0000000..eff038f
--- /dev/null
@@ -0,0 +1,70 @@
+From f81700b6bb2eda3756247bce472d8eaf6f466f61 Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 13:49:26 +0200
+Subject: [PATCH] net/usb/qmi_wwan: add MeigLink modem support
+
+---
+ drivers/net/usb/qmi_wwan.c  | 1 +
+ drivers/usb/serial/option.c | 7 +++++++
+ 2 files changed, 8 insertions(+)
+
+--- a/drivers/net/usb/qmi_wwan.c
++++ b/drivers/net/usb/qmi_wwan.c
+@@ -1082,6 +1082,11 @@ static const struct usb_device_id produc
+               USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0x581d, USB_CLASS_VENDOR_SPEC, 1, 7),
+               .driver_info = (unsigned long)&qmi_wwan_info,
+       },
++      {       /* Meiglink SGM828 */
++              USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d49, USB_CLASS_VENDOR_SPEC, 0x10, 0x05),
++              .driver_info = (unsigned long)&qmi_wwan_info,
++      },
++
+       {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0122)},   /* Quectel RG650V */
+       {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0125)},   /* Quectel EC25, EC20 R2.0  Mini PCIe */
+       {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0306)},   /* Quectel EP06/EG06/EM06 */
+@@ -1089,6 +1094,7 @@ static const struct usb_device_id produc
+       {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0620)},   /* Quectel EM160R-GL */
+       {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0800)},   /* Quectel RM500Q-GL */
+       {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0801)},   /* Quectel RM520N */
++      {QMI_MATCH_FF_FF_FF(0x05c6, 0xf601)},   /* MeigLink SLM750 */
+       /* 3. Combined interface devices matching on interface number */
+       {QMI_FIXED_INTF(0x0408, 0xea42, 4)},    /* Yota / Megafon M100-1 */
+--- a/drivers/usb/serial/option.c
++++ b/drivers/usb/serial/option.c
+@@ -247,6 +247,11 @@ static void option_instat_callback(struc
+ #define UBLOX_PRODUCT_R410M                   0x90b2
+ /* These Yuga products use Qualcomm's vendor ID */
+ #define YUGA_PRODUCT_CLM920_NC5                       0x9625
++/* These MeigLink products use Qualcomm's vendor ID */
++#define MEIGLINK_PRODUCT_SLM750                       0xf601
++
++#define MEIGLINK_VENDOR_ID                    0x2dee
++#define MEIGLINK_PRODUCT_SLM828                       0x4d49
+ #define QUECTEL_VENDOR_ID                     0x2c7c
+ /* These Quectel products use Quectel's vendor ID */
+@@ -1156,6 +1161,11 @@ static const struct usb_device_id option
+       { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x0023)}, /* ONYX 3G device */
+       { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x9000), /* SIMCom SIM5218 */
+         .driver_info = NCTRL(0) | NCTRL(1) | NCTRL(2) | NCTRL(3) | RSVD(4) },
++      /* MeiG */
++      { USB_DEVICE_AND_INTERFACE_INFO(MEIGLINK_VENDOR_ID, MEIGLINK_PRODUCT_SLM828, USB_CLASS_VENDOR_SPEC, 0x10, 0x01) },
++      { USB_DEVICE_AND_INTERFACE_INFO(MEIGLINK_VENDOR_ID, MEIGLINK_PRODUCT_SLM828, USB_CLASS_VENDOR_SPEC, 0x10, 0x02) },
++      { USB_DEVICE_AND_INTERFACE_INFO(MEIGLINK_VENDOR_ID, MEIGLINK_PRODUCT_SLM828, USB_CLASS_VENDOR_SPEC, 0x10, 0x03) },
++      { USB_DEVICE_AND_INTERFACE_INFO(MEIGLINK_VENDOR_ID, MEIGLINK_PRODUCT_SLM828, USB_CLASS_VENDOR_SPEC, 0x10, 0x04) },
+       /* Quectel products using Qualcomm vendor ID */
+       { USB_DEVICE(QUALCOMM_VENDOR_ID, QUECTEL_PRODUCT_UC15)},
+       { USB_DEVICE(QUALCOMM_VENDOR_ID, QUECTEL_PRODUCT_UC20),
+@@ -1197,6 +1207,11 @@ static const struct usb_device_id option
+         .driver_info = ZLP },
+       { USB_DEVICE(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_BG96),
+         .driver_info = RSVD(4) },
++      /* Meiglink products using Qualcomm vendor ID */
++      // Works OK. In case of some issues check macros that are used by Quectel Products
++      { USB_DEVICE_AND_INTERFACE_INFO(QUALCOMM_VENDOR_ID, MEIGLINK_PRODUCT_SLM750, 0xff, 0xff, 0xff),
++        .driver_info = NUMEP2 },
++      { USB_DEVICE_AND_INTERFACE_INFO(QUALCOMM_VENDOR_ID, MEIGLINK_PRODUCT_SLM750, 0xff, 0, 0) },
+       { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EP06, 0xff, 0xff, 0xff),
+         .driver_info = RSVD(1) | RSVD(2) | RSVD(3) | RSVD(4) | NUMEP2 },
+       { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EP06, 0xff, 0, 0) },
diff --git a/target/linux/generic/hack-6.12/781-usb-net-rndis-support-asr.patch b/target/linux/generic/hack-6.12/781-usb-net-rndis-support-asr.patch
new file mode 100644 (file)
index 0000000..47339b6
--- /dev/null
@@ -0,0 +1,69 @@
+From 9fabf60187f1fa19e6f6bb5441587d485bd534b0 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 9 Apr 2024 17:06:38 +0100
+Subject: [PATCH] rndis_host: add a bunch of USB IDs
+
+Add a bunch of USB IDs found in various places online to the
+RNDIS USB network driver.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/usb/rndis_host.c | 40 ++++++++++++++++++++++
+ 1 file changed, 40 insertions(+)
+
+--- a/drivers/net/usb/rndis_host.c
++++ b/drivers/net/usb/rndis_host.c
+@@ -630,6 +630,16 @@ static const struct driver_info   zte_rndi
+       .tx_fixup =     rndis_tx_fixup,
+ };
++static const struct driver_info asr_rndis_info = {
++      .description =  "Asr RNDIS device",
++      .flags =        FLAG_WWAN | FLAG_POINTTOPOINT | FLAG_FRAMING_RN | FLAG_NO_SETINT | FLAG_NOARP,
++      .bind =         rndis_bind,
++      .unbind =       rndis_unbind,
++      .status =       rndis_status,
++      .rx_fixup =     rndis_rx_fixup,
++      .tx_fixup =     rndis_tx_fixup,
++};
++
+ /*-------------------------------------------------------------------------*/
+ static const struct usb_device_id     products [] = {
+@@ -666,6 +676,36 @@ static const struct usb_device_id produc
+       USB_INTERFACE_INFO(USB_CLASS_WIRELESS_CONTROLLER, 1, 3),
+       .driver_info = (unsigned long) &rndis_info,
+ }, {
++      /* Quectel EG060V rndis device */
++      USB_DEVICE_AND_INTERFACE_INFO(0x2c7c, 0x6004,
++                                    USB_CLASS_WIRELESS_CONTROLLER, 1, 3),
++      .driver_info = (unsigned long) &asr_rndis_info,
++}, {
++      /* Quectel EC200A rndis device */
++      USB_DEVICE_AND_INTERFACE_INFO(0x2c7c, 0x6005,
++                                    USB_CLASS_WIRELESS_CONTROLLER, 1, 3),
++      .driver_info = (unsigned long) &asr_rndis_info,
++}, {
++      /* Quectel EC200T rndis device */
++      USB_DEVICE_AND_INTERFACE_INFO(0x2c7c, 0x6026,
++                                    USB_CLASS_WIRELESS_CONTROLLER, 1, 3),
++      .driver_info = (unsigned long) &asr_rndis_info,
++}, {
++      /* Simcom A7906E rndis device */
++      USB_DEVICE_AND_INTERFACE_INFO(0x1e0e, 0x9011,
++                                    USB_CLASS_WIRELESS_CONTROLLER, 1, 3),
++      .driver_info = (unsigned long) &asr_rndis_info,
++}, {
++      /* Meig SLM770A */
++      USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d57,
++                                    USB_CLASS_WIRELESS_CONTROLLER, 1, 3),
++      .driver_info = (unsigned long) &asr_rndis_info,
++}, {
++      /* Meig SLM828 */
++      USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d49,
++                                    USB_CLASS_WIRELESS_CONTROLLER, 1, 3),
++      .driver_info = (unsigned long) &asr_rndis_info,
++}, {
+       /* Novatel Verizon USB730L */
+       USB_INTERFACE_INFO(USB_CLASS_MISC, 4, 1),
+       .driver_info = (unsigned long) &rndis_info,
diff --git a/target/linux/generic/hack-6.12/800-GPIO-add-named-gpio-exports.patch b/target/linux/generic/hack-6.12/800-GPIO-add-named-gpio-exports.patch
new file mode 100644 (file)
index 0000000..f9b540f
--- /dev/null
@@ -0,0 +1,172 @@
+From cc809a441d8f2924f785eb863dfa6aef47a25b0b Mon Sep 17 00:00:00 2001
+From: John Crispin <blogic@openwrt.org>
+Date: Tue, 12 Aug 2014 20:49:27 +0200
+Subject: [PATCH 30/36] GPIO: add named gpio exports
+
+Signed-off-by: John Crispin <blogic@openwrt.org>
+--- a/drivers/gpio/gpiolib-of.c
++++ b/drivers/gpio/gpiolib-of.c
+@@ -21,6 +21,8 @@
+ #include <linux/gpio/consumer.h>
+ #include <linux/gpio/machine.h>
++#include <linux/init.h>
++#include <linux/platform_device.h>
+ #include "gpiolib.h"
+ #include "gpiolib-of.h"
+@@ -1198,3 +1200,73 @@ void of_gpiochip_remove(struct gpio_chip
+ {
+       of_node_put(dev_of_node(&chip->gpiodev->dev));
+ }
++
++#ifdef CONFIG_GPIO_SYSFS
++
++static const struct of_device_id gpio_export_ids[] = {
++      { .compatible = "gpio-export" },
++      { /* sentinel */ }
++};
++
++static int of_gpio_export_probe(struct platform_device *pdev)
++{
++      struct device_node *np = pdev->dev.of_node;
++      struct device_node *cnp;
++      u32 val;
++      int nb = 0;
++
++      for_each_child_of_node(np, cnp) {
++              const char *name = NULL;
++              int gpio;
++              bool dmc;
++              int max_gpio = 1;
++              int i;
++
++              of_property_read_string(cnp, "gpio-export,name", &name);
++
++              if (!name)
++                      max_gpio = of_gpio_named_count(cnp, "gpios");
++
++              for (i = 0; i < max_gpio; i++) {
++                      struct gpio_desc *desc;
++                      unsigned flags = 0;
++                      enum of_gpio_flags of_flags;
++
++                      desc = of_get_named_gpiod_flags(cnp, "gpios", i, &of_flags);
++                      if (IS_ERR(desc))
++                              return PTR_ERR(desc);
++                      gpio = desc_to_gpio(desc);
++
++                      if (of_flags & OF_GPIO_ACTIVE_LOW)
++                              flags |= GPIOF_ACTIVE_LOW;
++
++                      if (!of_property_read_u32(cnp, "gpio-export,output", &val))
++                              flags |= val ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW;
++                      else
++                              flags |= GPIOF_IN;
++
++                      if (devm_gpio_request_one(&pdev->dev, gpio, flags, name ? name : of_node_full_name(np)))
++                              continue;
++
++                      dmc = of_property_read_bool(cnp, "gpio-export,direction_may_change");
++                      gpio_export_with_name(gpio_to_desc(gpio), dmc, name);
++                      nb++;
++              }
++      }
++
++      dev_info(&pdev->dev, "%d gpio(s) exported\n", nb);
++
++      return 0;
++}
++
++static struct platform_driver gpio_export_driver = {
++      .driver         = {
++              .name           = "gpio-export",
++              .of_match_table = of_match_ptr(gpio_export_ids),
++      },
++      .probe          = of_gpio_export_probe,
++};
++
++module_platform_driver(gpio_export_driver);
++
++#endif
+--- a/include/linux/gpio/consumer.h
++++ b/include/linux/gpio/consumer.h
+@@ -628,7 +628,10 @@ static inline int devm_acpi_dev_add_driv
+ #if IS_ENABLED(CONFIG_GPIOLIB) && IS_ENABLED(CONFIG_GPIO_SYSFS)
++int __gpiod_export(struct gpio_desc *desc, bool direction_may_change, const char *name);
+ int gpiod_export(struct gpio_desc *desc, bool direction_may_change);
++int gpio_export_with_name(struct gpio_desc *desc, bool direction_may_change,
++                        const char *name);
+ int gpiod_export_link(struct device *dev, const char *name,
+                     struct gpio_desc *desc);
+ void gpiod_unexport(struct gpio_desc *desc);
+@@ -637,11 +640,25 @@ void gpiod_unexport(struct gpio_desc *de
+ #include <asm/errno.h>
++static inline int __gpiod_export(struct gpio_desc *desc,
++                             bool direction_may_change,
++                             const char *name)
++{
++      return -ENOSYS;
++}
++
+ static inline int gpiod_export(struct gpio_desc *desc,
+                              bool direction_may_change)
+ {
+       return -ENOSYS;
+ }
++
++static inline int gpio_export_with_name(struct gpio_desc *desc,
++                                      bool direction_may_change,
++                                      const char *name)
++{
++      return -ENOSYS;
++}
+ static inline int gpiod_export_link(struct device *dev, const char *name,
+                                   struct gpio_desc *desc)
+--- a/drivers/gpio/gpiolib-sysfs.c
++++ b/drivers/gpio/gpiolib-sysfs.c
+@@ -571,7 +571,7 @@ static struct class gpio_class = {
+  * Returns:
+  * 0 on success, or negative errno on failure.
+  */
+-int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
++int __gpiod_export(struct gpio_desc *desc, bool direction_may_change, const char *name)
+ {
+       const char *ioname = NULL;
+       struct gpio_device *gdev;
+@@ -629,6 +629,8 @@ int gpiod_export(struct gpio_desc *desc,
+       offset = gpio_chip_hwgpio(desc);
+       if (guard.gc->names && guard.gc->names[offset])
+               ioname = guard.gc->names[offset];
++      if (name)
++              ioname = name;
+       dev = device_create_with_groups(&gpio_class, &gdev->dev,
+                                       MKDEV(0, 0), data, gpio_groups,
+@@ -650,8 +652,21 @@ err_unlock:
+       gpiod_dbg(desc, "%s: status %d\n", __func__, status);
+       return status;
+ }
++EXPORT_SYMBOL_GPL(__gpiod_export);
++
++int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
++{
++      return __gpiod_export(desc, direction_may_change, NULL);
++}
+ EXPORT_SYMBOL_GPL(gpiod_export);
++int gpio_export_with_name(struct gpio_desc *desc, bool direction_may_change,
++                        const char *name)
++{
++      return __gpiod_export(desc, direction_may_change, name);
++}
++EXPORT_SYMBOL_GPL(gpio_export_with_name);
++
+ static int match_export(struct device *dev, const void *desc)
+ {
+       struct gpiod_data *data = dev_get_drvdata(dev);
diff --git a/target/linux/generic/hack-6.12/810-bcma-ssb-fallback-sprom.patch b/target/linux/generic/hack-6.12/810-bcma-ssb-fallback-sprom.patch
new file mode 100644 (file)
index 0000000..392c873
--- /dev/null
@@ -0,0 +1,182 @@
+From e4d708702e6c98f2111e33201a264d6788564cb2 Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Fri, 12 May 2023 11:08:43 +0200
+Subject: [PATCH] ssb_sprom: add generic kernel support for Broadcom  Fallback SPROMs
+
+---
+ drivers/bcma/Kconfig        |  4 ++++
+ drivers/bcma/Makefile       |  1 +
+ drivers/bcma/bcma_private.h |  1 +
+ drivers/bcma/main.c         |  8 ++++++++
+ drivers/bcma/sprom.c        | 23 ++++++++++++++---------
+ drivers/ssb/Kconfig         |  5 +++++
+ drivers/ssb/Makefile        |  1 +
+ drivers/ssb/main.c          |  8 ++++++++
+ drivers/ssb/sprom.c         | 12 +++++++++++-
+ drivers/ssb/ssb_private.h   |  2 +-
+ 10 files changed, 54 insertions(+), 11 deletions(-)
+
+--- a/drivers/bcma/Kconfig
++++ b/drivers/bcma/Kconfig
+@@ -18,6 +18,10 @@ config BCMA_BLOCKIO
+       bool
+       default y
++config BCMA_FALLBACK_SPROM
++      bool
++      default y
++
+ config BCMA_HOST_PCI_POSSIBLE
+       bool
+       depends on PCI = y
+--- a/drivers/bcma/Makefile
++++ b/drivers/bcma/Makefile
+@@ -11,6 +11,7 @@ bcma-$(CONFIG_BCMA_DRIVER_PCI_HOSTMODE)
+ bcma-$(CONFIG_BCMA_DRIVER_MIPS)               += driver_mips.o
+ bcma-$(CONFIG_BCMA_DRIVER_GMAC_CMN)   += driver_gmac_cmn.o
+ bcma-$(CONFIG_BCMA_DRIVER_GPIO)               += driver_gpio.o
++bcma-$(CONFIG_BCMA_FALLBACK_SPROM)    += fallback-sprom.o
+ bcma-$(CONFIG_BCMA_HOST_PCI)          += host_pci.o
+ bcma-$(CONFIG_BCMA_HOST_SOC)          += host_soc.o
+ obj-$(CONFIG_BCMA)                    += bcma.o
+--- a/drivers/bcma/bcma_private.h
++++ b/drivers/bcma/bcma_private.h
+@@ -8,6 +8,7 @@
+ #include <linux/bcma/bcma.h>
+ #include <linux/delay.h>
++#include "fallback-sprom.h"
+ #define bcma_err(bus, fmt, ...) \
+       dev_err((bus)->dev, "bus%d: " fmt, (bus)->num, ##__VA_ARGS__)
+--- a/drivers/bcma/main.c
++++ b/drivers/bcma/main.c
+@@ -677,6 +677,14 @@ static int __init bcma_modinit(void)
+ {
+       int err;
++#ifdef CONFIG_BCMA_FALLBACK_SPROM
++      err = bcma_fbs_register();
++      if (err) {
++              pr_err("Fallback SPROM initialization failed\n");
++              err = 0;
++      }
++#endif /* CONFIG_BCMA_FALLBACK_SPROM */
++
+       err = bcma_init_bus_register();
+       if (err)
+               return err;
+--- a/drivers/bcma/sprom.c
++++ b/drivers/bcma/sprom.c
+@@ -51,21 +51,26 @@ static int bcma_fill_sprom_with_fallback
+ {
+       int err;
+-      if (!get_fallback_sprom) {
++      if (get_fallback_sprom)
++              err = get_fallback_sprom(bus, out);
++
++#ifdef CONFIG_BCMA_FALLBACK_SPROM
++      if (!get_fallback_sprom || err)
++              err = bcma_get_fallback_sprom(bus, out);
++#else
++      if (!get_fallback_sprom)
+               err = -ENOENT;
+-              goto fail;
+-      }
++#endif /* CONFIG_BCMA_FALLBACK_SPROM */
+-      err = get_fallback_sprom(bus, out);
+-      if (err)
+-              goto fail;
++      if (err) {
++              bcma_warn(bus, "Using fallback SPROM failed (err %d)\n", err);
++              return err;
++      }
+       bcma_debug(bus, "Using SPROM revision %d provided by platform.\n",
+                  bus->sprom.revision);
++
+       return 0;
+-fail:
+-      bcma_warn(bus, "Using fallback SPROM failed (err %d)\n", err);
+-      return err;
+ }
+ /**************************************************
+--- a/drivers/ssb/Kconfig
++++ b/drivers/ssb/Kconfig
+@@ -25,6 +25,11 @@ if SSB
+ config SSB_SPROM
+       bool
++config SSB_FALLBACK_SPROM
++      bool
++      depends on SSB_PCIHOST
++      default y
++
+ # Support for Block-I/O. SELECT this from the driver that needs it.
+ config SSB_BLOCKIO
+       bool
+--- a/drivers/ssb/Makefile
++++ b/drivers/ssb/Makefile
+@@ -2,6 +2,7 @@
+ # core
+ ssb-y                                 += main.o scan.o
+ ssb-$(CONFIG_SSB_EMBEDDED)            += embedded.o
++ssb-$(CONFIG_SSB_FALLBACK_SPROM)      += fallback-sprom.o
+ ssb-$(CONFIG_SSB_SPROM)                       += sprom.o
+ # host support
+--- a/drivers/ssb/main.c
++++ b/drivers/ssb/main.c
+@@ -1289,6 +1289,14 @@ static int __init ssb_modinit(void)
+ {
+       int err;
++#ifdef CONFIG_SSB_FALLBACK_SPROM
++      err = ssb_fbs_register();
++      if (err) {
++              pr_err("Fallback SPROM initialization failed\n");
++              err = 0;
++      }
++#endif /* CONFIG_SSB_FALLBACK_SPROM */
++
+       /* See the comment at the ssb_is_early_boot definition */
+       ssb_is_early_boot = 0;
+       err = bus_register(&ssb_bustype);
+--- a/drivers/ssb/sprom.c
++++ b/drivers/ssb/sprom.c
+@@ -180,10 +180,20 @@ int ssb_arch_register_fallback_sprom(int
+ int ssb_fill_sprom_with_fallback(struct ssb_bus *bus, struct ssb_sprom *out)
+ {
++      int err;
++
++      if (get_fallback_sprom)
++              err = get_fallback_sprom(bus, out);
++
++#ifdef CONFIG_SSB_FALLBACK_SPROM
++      if (!get_fallback_sprom || err)
++              err = ssb_get_fallback_sprom(bus, out);
++#else
+       if (!get_fallback_sprom)
+               return -ENOENT;
++#endif /* CONFIG_SSB_FALLBACK_SPROM */
+-      return get_fallback_sprom(bus, out);
++      return err;
+ }
+ /* https://bcm-v4.sipsolutions.net/802.11/IsSpromAvailable */
+--- a/drivers/ssb/ssb_private.h
++++ b/drivers/ssb/ssb_private.h
+@@ -8,7 +8,7 @@
+ #include <linux/ssb/ssb.h>
+ #include <linux/types.h>
+ #include <linux/bcm47xx_wdt.h>
+-
++#include "fallback-sprom.h"
+ /* pci.c */
+ #ifdef CONFIG_SSB_PCIHOST
diff --git a/target/linux/generic/hack-6.12/901-debloat_sock_diag.patch b/target/linux/generic/hack-6.12/901-debloat_sock_diag.patch
new file mode 100644 (file)
index 0000000..17a22d0
--- /dev/null
@@ -0,0 +1,181 @@
+From 3b6115d6b57a263bdc8c9b1df273bd4a7955eead Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sat, 8 Jul 2017 08:16:31 +0200
+Subject: debloat: add some debloat patches, strip down procfs and make O_DIRECT support optional, saves ~15K after lzma on MIPS
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ net/Kconfig         | 3 +++
+ net/core/Makefile   | 3 ++-
+ net/core/sock.c     | 2 ++
+ net/ipv4/Kconfig    | 1 +
+ net/netlink/Kconfig | 1 +
+ net/packet/Kconfig  | 1 +
+ net/unix/Kconfig    | 1 +
+ 7 files changed, 11 insertions(+), 1 deletion(-)
+
+--- a/net/Kconfig
++++ b/net/Kconfig
+@@ -138,6 +138,9 @@ source "net/mptcp/Kconfig"
+ endif # if INET
++config SOCK_DIAG
++      bool
++
+ config NETWORK_SECMARK
+       bool "Security Marking"
+       help
+--- a/net/core/Makefile
++++ b/net/core/Makefile
+@@ -11,12 +11,13 @@ obj-$(CONFIG_SYSCTL) += sysctl_net_core.
+ obj-y              += dev.o dev_addr_lists.o dst.o netevent.o \
+                       neighbour.o rtnetlink.o utils.o link_watch.o filter.o \
+-                      sock_diag.o dev_ioctl.o tso.o sock_reuseport.o \
++                      dev_ioctl.o tso.o sock_reuseport.o \
+                       fib_notifier.o xdp.o flow_offload.o gro.o \
+                       netdev-genl.o netdev-genl-gen.o gso.o
+ obj-$(CONFIG_NETDEV_ADDR_LIST_TEST) += dev_addr_lists_test.o
++obj-$(CONFIG_SOCK_DIAG) += sock_diag.o
+ obj-y += net-sysfs.o
+ obj-y += hotdata.o
+ obj-y += netdev_rx_queue.o
+--- a/net/core/sock.c
++++ b/net/core/sock.c
+@@ -118,6 +118,7 @@
+ #include <linux/mroute.h>
+ #include <linux/mroute6.h>
+ #include <linux/icmpv6.h>
++#include <linux/cookie.h>
+ #include <linux/uaccess.h>
+@@ -152,6 +153,7 @@
+ static DEFINE_MUTEX(proto_list_mutex);
+ static LIST_HEAD(proto_list);
++DEFINE_COOKIE(sock_cookie);
+ static void sock_def_write_space_wfree(struct sock *sk);
+ static void sock_def_write_space(struct sock *sk);
+@@ -587,6 +589,21 @@ discard_and_relse:
+ }
+ EXPORT_SYMBOL(__sk_receive_skb);
++u64 __sock_gen_cookie(struct sock *sk)
++{
++      u64 res = atomic64_read(&sk->sk_cookie);
++
++      if (!res) {
++              u64 new = gen_cookie_next(&sock_cookie);
++
++              atomic64_cmpxchg(&sk->sk_cookie, res, new);
++
++              /* Another thread might have changed sk_cookie before us. */
++              res = atomic64_read(&sk->sk_cookie);
++      }
++      return res;
++}
++
+ INDIRECT_CALLABLE_DECLARE(struct dst_entry *ip6_dst_check(struct dst_entry *,
+                                                         u32));
+ INDIRECT_CALLABLE_DECLARE(struct dst_entry *ipv4_dst_check(struct dst_entry *,
+@@ -2342,9 +2359,11 @@ static void __sk_free(struct sock *sk)
+       if (likely(sk->sk_net_refcnt))
+               sock_inuse_add(sock_net(sk), -1);
++#ifdef CONFIG_SOCK_DIAG
+       if (unlikely(sk->sk_net_refcnt && sock_diag_has_destroy_listeners(sk)))
+               sock_diag_broadcast_destroy(sk);
+       else
++#endif
+               sk_destruct(sk);
+ }
+--- a/net/core/sock_diag.c
++++ b/net/core/sock_diag.c
+@@ -12,7 +12,6 @@
+ #include <linux/tcp.h>
+ #include <linux/workqueue.h>
+ #include <linux/nospec.h>
+-#include <linux/cookie.h>
+ #include <linux/inet_diag.h>
+ #include <linux/sock_diag.h>
+@@ -22,23 +21,6 @@ static const struct sock_diag_inet_compa
+ static struct workqueue_struct *broadcast_wq;
+-DEFINE_COOKIE(sock_cookie);
+-
+-u64 __sock_gen_cookie(struct sock *sk)
+-{
+-      u64 res = atomic64_read(&sk->sk_cookie);
+-
+-      if (!res) {
+-              u64 new = gen_cookie_next(&sock_cookie);
+-
+-              atomic64_cmpxchg(&sk->sk_cookie, res, new);
+-
+-              /* Another thread might have changed sk_cookie before us. */
+-              res = atomic64_read(&sk->sk_cookie);
+-      }
+-      return res;
+-}
+-
+ int sock_diag_check_cookie(struct sock *sk, const __u32 *cookie)
+ {
+       u64 res;
+--- a/net/ipv4/Kconfig
++++ b/net/ipv4/Kconfig
+@@ -423,6 +423,7 @@ config INET_TUNNEL
+ config INET_DIAG
+       tristate "INET: socket monitoring interface"
++      select SOCK_DIAG
+       default y
+       help
+         Support for INET (TCP, DCCP, etc) socket monitoring interface used by
+--- a/net/netlink/Kconfig
++++ b/net/netlink/Kconfig
+@@ -5,6 +5,7 @@
+ config NETLINK_DIAG
+       tristate "NETLINK: socket monitoring interface"
++      select SOCK_DIAG
+       default n
+       help
+         Support for NETLINK socket monitoring interface used by the ss tool.
+--- a/net/packet/Kconfig
++++ b/net/packet/Kconfig
+@@ -19,6 +19,7 @@ config PACKET
+ config PACKET_DIAG
+       tristate "Packet: sockets monitoring interface"
+       depends on PACKET
++      select SOCK_DIAG
+       default n
+       help
+         Support for PF_PACKET sockets monitoring interface used by the ss tool.
+--- a/net/unix/Kconfig
++++ b/net/unix/Kconfig
+@@ -24,6 +24,7 @@ config       AF_UNIX_OOB
+ config UNIX_DIAG
+       tristate "UNIX: socket monitoring interface"
+       depends on UNIX
++      select SOCK_DIAG
+       default n
+       help
+         Support for UNIX socket monitoring interface used by the ss tool.
+--- a/net/xdp/Kconfig
++++ b/net/xdp/Kconfig
+@@ -10,6 +10,7 @@ config XDP_SOCKETS
+ config XDP_SOCKETS_DIAG
+       tristate "XDP sockets: monitoring interface"
+       depends on XDP_SOCKETS
++      select SOCK_DIAG
+       default n
+       help
+         Support for PF_XDP sockets monitoring interface used by the ss tool.
diff --git a/target/linux/generic/hack-6.12/902-debloat_proc.patch b/target/linux/generic/hack-6.12/902-debloat_proc.patch
new file mode 100644 (file)
index 0000000..e3e2def
--- /dev/null
@@ -0,0 +1,420 @@
+From 9e3f1d0805b2d919904dd9a4ff0d956314cc3cba Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sat, 8 Jul 2017 08:20:09 +0200
+Subject: debloat: procfs
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ fs/locks.c               |  2 ++
+ fs/proc/Kconfig          |  5 +++++
+ fs/proc/consoles.c       |  3 +++
+ fs/proc/proc_tty.c       | 11 ++++++++++-
+ include/net/snmp.h       | 18 +++++++++++++++++-
+ ipc/msg.c                |  3 +++
+ ipc/sem.c                |  2 ++
+ ipc/shm.c                |  2 ++
+ ipc/util.c               |  3 +++
+ kernel/exec_domain.c     |  2 ++
+ kernel/irq/proc.c        |  9 +++++++++
+ kernel/time/timer_list.c |  2 ++
+ mm/vmalloc.c             |  2 ++
+ mm/vmstat.c              |  8 +++++---
+ net/8021q/vlanproc.c     |  6 ++++++
+ net/core/net-procfs.c    | 18 ++++++++++++------
+ net/core/sock.c          |  2 ++
+ net/ipv4/fib_trie.c      | 18 ++++++++++++------
+ net/ipv4/proc.c          |  3 +++
+ net/ipv4/route.c         |  3 +++
+ 20 files changed, 105 insertions(+), 17 deletions(-)
+
+--- a/fs/locks.c
++++ b/fs/locks.c
+@@ -2979,6 +2979,8 @@ static const struct seq_operations locks
+ static int __init proc_locks_init(void)
+ {
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return 0;
+       proc_create_seq_private("locks", 0, NULL, &locks_seq_operations,
+                       sizeof(struct locks_iterator), NULL);
+       return 0;
+--- a/fs/proc/Kconfig
++++ b/fs/proc/Kconfig
+@@ -101,6 +101,11 @@ config PROC_CHILDREN
+         Say Y if you are running any user-space software which takes benefit from
+         this interface. For example, rkt is such a piece of software.
++config PROC_STRIPPED
++      default n
++      depends on EXPERT
++      bool "Strip non-essential /proc functionality to reduce code size"
++
+ config PROC_PID_ARCH_STATUS
+       def_bool n
+       depends on PROC_FS
+--- a/fs/proc/consoles.c
++++ b/fs/proc/consoles.c
+@@ -110,6 +110,9 @@ static const struct seq_operations conso
+ static int __init proc_consoles_init(void)
+ {
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return 0;
++
+       proc_create_seq("consoles", 0, NULL, &consoles_op);
+       return 0;
+ }
+--- a/fs/proc/proc_tty.c
++++ b/fs/proc/proc_tty.c
+@@ -131,7 +131,10 @@ static const struct seq_operations tty_d
+ void proc_tty_register_driver(struct tty_driver *driver)
+ {
+       struct proc_dir_entry *ent;
+-              
++
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return;
++
+       if (!driver->driver_name || driver->proc_entry ||
+           !driver->ops->proc_show)
+               return;
+@@ -148,6 +151,9 @@ void proc_tty_unregister_driver(struct t
+ {
+       struct proc_dir_entry *ent;
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return;
++
+       ent = driver->proc_entry;
+       if (!ent)
+               return;
+@@ -162,6 +168,9 @@ void proc_tty_unregister_driver(struct t
+  */
+ void __init proc_tty_init(void)
+ {
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return;
++
+       if (!proc_mkdir("tty", NULL))
+               return;
+       proc_mkdir("tty/ldisc", NULL);  /* Preserved: it's userspace visible */
+--- a/include/net/snmp.h
++++ b/include/net/snmp.h
+@@ -124,6 +124,21 @@ struct linux_tls_mib {
+ #define DECLARE_SNMP_STAT(type, name) \
+       extern __typeof__(type) __percpu *name
++#ifdef CONFIG_PROC_STRIPPED
++#define __SNMP_STATS_DUMMY(mib)       \
++      do { (void) mib->mibs[0]; } while(0)
++
++#define __SNMP_INC_STATS(mib, field) __SNMP_STATS_DUMMY(mib)
++#define SNMP_INC_STATS_ATOMIC_LONG(mib, field) __SNMP_STATS_DUMMY(mib)
++#define SNMP_INC_STATS(mib, field) __SNMP_STATS_DUMMY(mib)
++#define SNMP_DEC_STATS(mib, field) __SNMP_STATS_DUMMY(mib)
++#define __SNMP_ADD_STATS(mib, field, addend) __SNMP_STATS_DUMMY(mib)
++#define SNMP_ADD_STATS(mib, field, addend) __SNMP_STATS_DUMMY(mib)
++#define SNMP_UPD_PO_STATS(mib, basefield, addend) __SNMP_STATS_DUMMY(mib)
++#define __SNMP_UPD_PO_STATS(mib, basefield, addend) __SNMP_STATS_DUMMY(mib)
++
++#else
++
+ #define __SNMP_INC_STATS(mib, field)  \
+                       __this_cpu_inc(mib->mibs[field])
+@@ -154,8 +169,9 @@ struct linux_tls_mib {
+               __this_cpu_add(ptr[basefield##OCTETS], addend); \
+       } while (0)
++#endif
+-#if BITS_PER_LONG==32
++#if (BITS_PER_LONG==32) && !defined(CONFIG_PROC_STRIPPED)
+ #define __SNMP_ADD_STATS64(mib, field, addend)                                \
+       do {                                                            \
+--- a/ipc/msg.c
++++ b/ipc/msg.c
+@@ -1370,6 +1370,9 @@ void __init msg_init(void)
+ {
+       msg_init_ns(&init_ipc_ns);
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return;
++
+       ipc_init_proc_interface("sysvipc/msg",
+                               "       key      msqid perms      cbytes       qnum lspid lrpid   uid   gid  cuid  cgid      stime      rtime      ctime\n",
+                               IPC_MSG_IDS, sysvipc_msg_proc_show);
+--- a/ipc/sem.c
++++ b/ipc/sem.c
+@@ -268,6 +268,8 @@ void sem_exit_ns(struct ipc_namespace *n
+ void __init sem_init(void)
+ {
+       sem_init_ns(&init_ipc_ns);
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return;
+       ipc_init_proc_interface("sysvipc/sem",
+                               "       key      semid perms      nsems   uid   gid  cuid  cgid      otime      ctime\n",
+                               IPC_SEM_IDS, sysvipc_sem_proc_show);
+--- a/ipc/shm.c
++++ b/ipc/shm.c
+@@ -155,6 +155,8 @@ pure_initcall(ipc_ns_init);
+ void __init shm_init(void)
+ {
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return;
+       ipc_init_proc_interface("sysvipc/shm",
+ #if BITS_PER_LONG <= 32
+                               "       key      shmid perms       size  cpid  lpid nattch   uid   gid  cuid  cgid      atime      dtime      ctime        rss       swap\n",
+--- a/ipc/util.c
++++ b/ipc/util.c
+@@ -141,6 +141,9 @@ void __init ipc_init_proc_interface(cons
+       struct proc_dir_entry *pde;
+       struct ipc_proc_iface *iface;
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return;
++
+       iface = kmalloc(sizeof(*iface), GFP_KERNEL);
+       if (!iface)
+               return;
+--- a/kernel/exec_domain.c
++++ b/kernel/exec_domain.c
+@@ -29,6 +29,8 @@ static int execdomains_proc_show(struct
+ static int __init proc_execdomains_init(void)
+ {
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return 0;
+       proc_create_single("execdomains", 0, NULL, execdomains_proc_show);
+       return 0;
+ }
+--- a/kernel/irq/proc.c
++++ b/kernel/irq/proc.c
+@@ -339,6 +339,9 @@ void register_irq_proc(unsigned int irq,
+       void __maybe_unused *irqp = (void *)(unsigned long) irq;
+       char name [MAX_NAMELEN];
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED) && !IS_ENABLED(CONFIG_SMP))
++              return;
++
+       if (!root_irq_dir || (desc->irq_data.chip == &no_irq_chip))
+               return;
+@@ -397,6 +400,9 @@ void unregister_irq_proc(unsigned int ir
+ {
+       char name [MAX_NAMELEN];
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED) && !IS_ENABLED(CONFIG_SMP))
++              return;
++
+       if (!root_irq_dir || !desc->dir)
+               return;
+ #ifdef CONFIG_SMP
+@@ -435,6 +441,9 @@ void init_irq_proc(void)
+       unsigned int irq;
+       struct irq_desc *desc;
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED) && !IS_ENABLED(CONFIG_SMP))
++              return;
++
+       /* create /proc/irq */
+       root_irq_dir = proc_mkdir("irq", NULL);
+       if (!root_irq_dir)
+--- a/kernel/time/timer_list.c
++++ b/kernel/time/timer_list.c
+@@ -354,6 +354,8 @@ static int __init init_timer_list_procfs
+ {
+       struct proc_dir_entry *pe;
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return 0;
+       pe = proc_create_seq_private("timer_list", 0400, NULL, &timer_list_sops,
+                       sizeof(struct timer_list_iter), NULL);
+       if (!pe)
+--- a/mm/vmalloc.c
++++ b/mm/vmalloc.c
+@@ -5079,6 +5079,9 @@ static int vmalloc_info_show(struct seq_
+ static int __init proc_vmalloc_init(void)
+ {
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return 0;
++
+       proc_create_single("vmallocinfo", 0400, NULL, vmalloc_info_show);
+       return 0;
+ }
+--- a/mm/vmstat.c
++++ b/mm/vmstat.c
+@@ -2197,10 +2197,12 @@ void __init init_mm_internals(void)
+       start_shepherd_timer();
+ #endif
+ #ifdef CONFIG_PROC_FS
+-      proc_create_seq("buddyinfo", 0444, NULL, &fragmentation_op);
+-      proc_create_seq("pagetypeinfo", 0400, NULL, &pagetypeinfo_op);
++      if (!IS_ENABLED(CONFIG_PROC_STRIPPED)) {
++              proc_create_seq("buddyinfo", 0444, NULL, &fragmentation_op);
++              proc_create_seq("pagetypeinfo", 0400, NULL, &pagetypeinfo_op);
++              proc_create_seq("zoneinfo", 0444, NULL, &zoneinfo_op);
++      }
+       proc_create_seq("vmstat", 0444, NULL, &vmstat_op);
+-      proc_create_seq("zoneinfo", 0444, NULL, &zoneinfo_op);
+ #endif
+ }
+--- a/net/8021q/vlanproc.c
++++ b/net/8021q/vlanproc.c
+@@ -93,6 +93,9 @@ void vlan_proc_cleanup(struct net *net)
+ {
+       struct vlan_net *vn = net_generic(net, vlan_net_id);
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return;
++
+       if (vn->proc_vlan_conf)
+               remove_proc_entry(name_conf, vn->proc_vlan_dir);
+@@ -112,6 +115,9 @@ int __net_init vlan_proc_init(struct net
+ {
+       struct vlan_net *vn = net_generic(net, vlan_net_id);
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return 0;
++
+       vn->proc_vlan_dir = proc_net_mkdir(net, name_root, net->proc_net);
+       if (!vn->proc_vlan_dir)
+               goto err;
+--- a/net/core/net-procfs.c
++++ b/net/core/net-procfs.c
+@@ -295,10 +295,12 @@ static int __net_init dev_proc_net_init(
+       if (!proc_create_net("dev", 0444, net->proc_net, &dev_seq_ops,
+                       sizeof(struct seq_net_private)))
+               goto out;
+-      if (!proc_create_seq("softnet_stat", 0444, net->proc_net,
++      if (!IS_ENABLED(CONFIG_PROC_STRIPPED) &&
++                      !proc_create_seq("softnet_stat", 0444, net->proc_net,
+                        &softnet_seq_ops))
+               goto out_dev;
+-      if (!proc_create_net("ptype", 0444, net->proc_net, &ptype_seq_ops,
++      if (!IS_ENABLED(CONFIG_PROC_STRIPPED) &&
++                      !proc_create_net("ptype", 0444, net->proc_net, &ptype_seq_ops,
+                       sizeof(struct seq_net_private)))
+               goto out_softnet;
+@@ -308,9 +310,11 @@ static int __net_init dev_proc_net_init(
+ out:
+       return rc;
+ out_ptype:
+-      remove_proc_entry("ptype", net->proc_net);
++      if (!IS_ENABLED(CONFIG_PROC_STRIPPED))
++              remove_proc_entry("ptype", net->proc_net);
+ out_softnet:
+-      remove_proc_entry("softnet_stat", net->proc_net);
++      if (!IS_ENABLED(CONFIG_PROC_STRIPPED))
++              remove_proc_entry("softnet_stat", net->proc_net);
+ out_dev:
+       remove_proc_entry("dev", net->proc_net);
+       goto out;
+@@ -320,8 +324,10 @@ static void __net_exit dev_proc_net_exit
+ {
+       wext_proc_exit(net);
+-      remove_proc_entry("ptype", net->proc_net);
+-      remove_proc_entry("softnet_stat", net->proc_net);
++      if (!IS_ENABLED(CONFIG_PROC_STRIPPED)) {
++              remove_proc_entry("ptype", net->proc_net);
++              remove_proc_entry("softnet_stat", net->proc_net);
++      }
+       remove_proc_entry("dev", net->proc_net);
+ }
+--- a/net/core/sock.c
++++ b/net/core/sock.c
+@@ -4285,6 +4285,8 @@ static __net_initdata struct pernet_oper
+ static int __init proto_init(void)
+ {
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return 0;
+       return register_pernet_subsys(&proto_net_ops);
+ }
+--- a/net/ipv4/fib_trie.c
++++ b/net/ipv4/fib_trie.c
+@@ -3016,11 +3016,13 @@ static const struct seq_operations fib_r
+ int __net_init fib_proc_init(struct net *net)
+ {
+-      if (!proc_create_net("fib_trie", 0444, net->proc_net, &fib_trie_seq_ops,
++      if (!IS_ENABLED(CONFIG_PROC_STRIPPED) &&
++                      !proc_create_net("fib_trie", 0444, net->proc_net, &fib_trie_seq_ops,
+                       sizeof(struct fib_trie_iter)))
+               goto out1;
+-      if (!proc_create_net_single("fib_triestat", 0444, net->proc_net,
++      if (!IS_ENABLED(CONFIG_PROC_STRIPPED) &&
++                      !proc_create_net_single("fib_triestat", 0444, net->proc_net,
+                       fib_triestat_seq_show, NULL))
+               goto out2;
+@@ -3031,17 +3033,21 @@ int __net_init fib_proc_init(struct net
+       return 0;
+ out3:
+-      remove_proc_entry("fib_triestat", net->proc_net);
++      if (!IS_ENABLED(CONFIG_PROC_STRIPPED))
++              remove_proc_entry("fib_triestat", net->proc_net);
+ out2:
+-      remove_proc_entry("fib_trie", net->proc_net);
++      if (!IS_ENABLED(CONFIG_PROC_STRIPPED))
++              remove_proc_entry("fib_trie", net->proc_net);
+ out1:
+       return -ENOMEM;
+ }
+ void __net_exit fib_proc_exit(struct net *net)
+ {
+-      remove_proc_entry("fib_trie", net->proc_net);
+-      remove_proc_entry("fib_triestat", net->proc_net);
++      if (!IS_ENABLED(CONFIG_PROC_STRIPPED)) {
++              remove_proc_entry("fib_trie", net->proc_net);
++              remove_proc_entry("fib_triestat", net->proc_net);
++      }
+       remove_proc_entry("route", net->proc_net);
+ }
+--- a/net/ipv4/proc.c
++++ b/net/ipv4/proc.c
+@@ -563,5 +563,8 @@ static __net_initdata struct pernet_oper
+ int __init ip_misc_proc_init(void)
+ {
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return 0;
++
+       return register_pernet_subsys(&ip_proc_ops);
+ }
+--- a/net/ipv4/route.c
++++ b/net/ipv4/route.c
+@@ -382,6 +382,9 @@ static struct pernet_operations ip_rt_pr
+ static int __init ip_rt_proc_init(void)
+ {
++      if (IS_ENABLED(CONFIG_PROC_STRIPPED))
++              return 0;
++
+       return register_pernet_subsys(&ip_rt_proc_ops);
+ }
+--- a/net/ipv4/inet_timewait_sock.c
++++ b/net/ipv4/inet_timewait_sock.c
+@@ -285,7 +285,7 @@ void __inet_twsk_schedule(struct inet_ti
+        */
+       if (!rearm) {
+-              bool kill = timeo <= 4*HZ;
++              bool __maybe_unused  kill = timeo <= 4*HZ;
+               __NET_INC_STATS(twsk_net(tw), kill ? LINUX_MIB_TIMEWAITKILLED :
+                                                    LINUX_MIB_TIMEWAITED);
diff --git a/target/linux/generic/hack-6.12/904-debloat_dma_buf.patch b/target/linux/generic/hack-6.12/904-debloat_dma_buf.patch
new file mode 100644 (file)
index 0000000..af566ec
--- /dev/null
@@ -0,0 +1,105 @@
+From e3692cb2fcd5ba1244512a0f43b8118f65f1c375 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sat, 8 Jul 2017 08:20:43 +0200
+Subject: debloat: dmabuf
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ drivers/base/Kconfig      |  2 +-
+ drivers/dma-buf/Makefile  | 10 +++++++---
+ drivers/dma-buf/dma-buf.c |  4 +++-
+ kernel/sched/core.c       |  1 +
+ net/Kconfig               |  2 +-
+ 5 files changed, 13 insertions(+), 6 deletions(-)
+
+--- a/drivers/base/Kconfig
++++ b/drivers/base/Kconfig
+@@ -198,7 +198,7 @@ config SOC_BUS
+ source "drivers/base/regmap/Kconfig"
+ config DMA_SHARED_BUFFER
+-      bool
++      tristate
+       default n
+       select IRQ_WORK
+       help
+--- a/drivers/dma-buf/heaps/Makefile
++++ b/drivers/dma-buf/heaps/Makefile
+@@ -1,3 +1,3 @@
+ # SPDX-License-Identifier: GPL-2.0
+-obj-$(CONFIG_DMABUF_HEAPS_SYSTEM)     += system_heap.o
+-obj-$(CONFIG_DMABUF_HEAPS_CMA)                += cma_heap.o
++dma-buf-objs-$(CONFIG_DMABUF_HEAPS_SYSTEM)    += system_heap.o
++dma-buf-objs-$(CONFIG_DMABUF_HEAPS_CMA)               += cma_heap.o
+--- a/drivers/dma-buf/Makefile
++++ b/drivers/dma-buf/Makefile
+@@ -1,12 +1,14 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+-obj-y := dma-buf.o dma-fence.o dma-fence-array.o dma-fence-chain.o \
++obj-$(CONFIG_DMA_SHARED_BUFFER) := dma-shared-buffer.o
++
++dma-buf-objs-y := dma-buf.o dma-fence.o dma-fence-array.o dma-fence-chain.o \
+        dma-fence-unwrap.o dma-resv.o
+-obj-$(CONFIG_DMABUF_HEAPS)    += dma-heap.o
+-obj-$(CONFIG_DMABUF_HEAPS)    += heaps/
+-obj-$(CONFIG_SYNC_FILE)               += sync_file.o
+-obj-$(CONFIG_SW_SYNC)         += sw_sync.o sync_debug.o
+-obj-$(CONFIG_UDMABUF)         += udmabuf.o
+-obj-$(CONFIG_DMABUF_SYSFS_STATS) += dma-buf-sysfs-stats.o
++dma-buf-objs-$(CONFIG_DMABUF_HEAPS)   += dma-heap.o
++obj-$(CONFIG_DMABUF_HEAPS)            += heaps/
++dma-buf-objs-$(CONFIG_SYNC_FILE)      += sync_file.o
++dma-buf-objs-$(CONFIG_SW_SYNC)                += sw_sync.o sync_debug.o
++dma-buf-objs-$(CONFIG_UDMABUF)                += udmabuf.o
++dma-buf-objs-$(CONFIG_DMABUF_SYSFS_STATS) += dma-buf-sysfs-stats.o
+ dmabuf_selftests-y := \
+       selftest.o \
+@@ -15,4 +17,6 @@ dmabuf_selftests-y := \
+       st-dma-fence-unwrap.o \
+       st-dma-resv.o
+-obj-$(CONFIG_DMABUF_SELFTESTS)        += dmabuf_selftests.o
++dma-buf-objs-$(CONFIG_DMABUF_SELFTESTS)       += dmabuf_selftests.o
++
++dma-shared-buffer-objs :=  $(dma-buf-objs-y)
+--- a/drivers/dma-buf/dma-buf.c
++++ b/drivers/dma-buf/dma-buf.c
+@@ -1743,4 +1743,5 @@ static void __exit dma_buf_deinit(void)
+       kern_unmount(dma_buf_mnt);
+       dma_buf_uninit_sysfs_statistics();
+ }
+-__exitcall(dma_buf_deinit);
++module_exit(dma_buf_deinit);
++MODULE_LICENSE("GPL");
+--- a/kernel/sched/core.c
++++ b/kernel/sched/core.c
+@@ -4434,6 +4434,7 @@ int wake_up_state(struct task_struct *p,
+ {
+       return try_to_wake_up(p, state, 0);
+ }
++EXPORT_SYMBOL_GPL(wake_up_state);
+ /*
+  * Perform scheduler related setup for a newly forked process p.
+--- a/fs/d_path.c
++++ b/fs/d_path.c
+@@ -314,6 +314,7 @@ char *dynamic_dname(char *buffer, int bu
+       buffer += buflen - sz;
+       return memcpy(buffer, temp, sz);
+ }
++EXPORT_SYMBOL_GPL(dynamic_dname);
+ char *simple_dname(struct dentry *dentry, char *buffer, int buflen)
+ {
+--- a/net/Kconfig
++++ b/net/Kconfig
+@@ -74,7 +74,7 @@ config SKB_EXTENSIONS
+ config NET_DEVMEM
+       def_bool y
+-      depends on DMA_SHARED_BUFFER
++      depends on DMA_SHARED_BUFFER=y
+       depends on GENERIC_ALLOCATOR
+       depends on PAGE_POOL
diff --git a/target/linux/generic/hack-6.12/910-kobject_uevent.patch b/target/linux/generic/hack-6.12/910-kobject_uevent.patch
new file mode 100644 (file)
index 0000000..99dd5a7
--- /dev/null
@@ -0,0 +1,35 @@
+From 0d37e6edc09c99e683dd91ca0e83bbc0df8477b3 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sun, 16 Jul 2017 16:56:10 +0200
+Subject: lib: add uevent_next_seqnum()
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ include/linux/kobject.h |  2 ++
+ lib/kobject_uevent.c    |  6 ++++++
+ 2 files changed, 8 insertions(+)
+
+--- a/include/linux/kobject.h
++++ b/include/linux/kobject.h
+@@ -219,4 +219,6 @@ int kobject_synth_uevent(struct kobject
+ __printf(2, 3)
+ int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...);
++u64 uevent_next_seqnum(void);
++
+ #endif /* _KOBJECT_H_ */
+--- a/lib/kobject_uevent.c
++++ b/lib/kobject_uevent.c
+@@ -178,6 +178,12 @@ out:
+       return r;
+ }
++u64 uevent_next_seqnum(void)
++{
++      return atomic64_inc_return(&uevent_seqnum);
++}
++EXPORT_SYMBOL_GPL(uevent_next_seqnum);
++
+ /**
+  * kobject_synth_uevent - send synthetic uevent with arguments
+  *
diff --git a/target/linux/generic/hack-6.12/911-kobject_add_broadcast_uevent.patch b/target/linux/generic/hack-6.12/911-kobject_add_broadcast_uevent.patch
new file mode 100644 (file)
index 0000000..df160ba
--- /dev/null
@@ -0,0 +1,76 @@
+From 0d37e6edc09c99e683dd91ca0e83bbc0df8477b3 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sun, 16 Jul 2017 16:56:10 +0200
+Subject: lib: add broadcast_uevent()
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ include/linux/kobject.h |  5 +++++
+ lib/kobject_uevent.c    | 37 +++++++++++++++++++++++++++++++++++++
+ 2 files changed, 42 insertions(+)
+
+--- a/include/linux/kobject.h
++++ b/include/linux/kobject.h
+@@ -32,6 +32,8 @@
+ #define UEVENT_NUM_ENVP                       64      /* number of env pointers */
+ #define UEVENT_BUFFER_SIZE            2048    /* buffer for the variables */
++struct sk_buff;
++
+ #ifdef CONFIG_UEVENT_HELPER
+ /* path to the userspace helper executed on an event */
+ extern char uevent_helper[];
+@@ -221,4 +223,7 @@ int add_uevent_var(struct kobj_uevent_en
+ u64 uevent_next_seqnum(void);
++int broadcast_uevent(struct sk_buff *skb, __u32 pid, __u32 group,
++                   gfp_t allocation);
++
+ #endif /* _KOBJECT_H_ */
+--- a/lib/kobject_uevent.c
++++ b/lib/kobject_uevent.c
+@@ -699,6 +699,43 @@ int add_uevent_var(struct kobj_uevent_en
+ EXPORT_SYMBOL_GPL(add_uevent_var);
+ #if defined(CONFIG_NET)
++int broadcast_uevent(struct sk_buff *skb, __u32 pid, __u32 group,
++                   gfp_t allocation)
++{
++      struct uevent_sock *ue_sk;
++      int err = 0;
++
++      /* send netlink message */
++      mutex_lock(&uevent_sock_mutex);
++      list_for_each_entry(ue_sk, &uevent_sock_list, list) {
++              struct sock *uevent_sock = ue_sk->sk;
++              struct sk_buff *skb2;
++
++              skb2 = skb_clone(skb, allocation);
++              if (!skb2)
++                      break;
++
++              err = netlink_broadcast(uevent_sock, skb2, pid, group,
++                                      allocation);
++              if (err)
++                      break;
++      }
++      mutex_unlock(&uevent_sock_mutex);
++
++      kfree_skb(skb);
++      return err;
++}
++#else
++int broadcast_uevent(struct sk_buff *skb, __u32 pid, __u32 group,
++                   gfp_t allocation)
++{
++      kfree_skb(skb);
++      return 0;
++}
++#endif
++EXPORT_SYMBOL_GPL(broadcast_uevent);
++
++#if defined(CONFIG_NET)
+ static int uevent_net_broadcast(struct sock *usk, struct sk_buff *skb,
+                               struct netlink_ext_ack *extack)
+ {
diff --git a/target/linux/generic/hack-6.12/920-device_tree_cmdline.patch b/target/linux/generic/hack-6.12/920-device_tree_cmdline.patch
new file mode 100644 (file)
index 0000000..4763e82
--- /dev/null
@@ -0,0 +1,21 @@
+From e08bcbbaa52fcc41f02743fd2e62a33255ce52da Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 13:52:28 +0200
+Subject: [PATCH] of/ftd: add device tree cmdline
+
+---
+ drivers/of/fdt.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/of/fdt.c
++++ b/drivers/of/fdt.c
+@@ -1049,6 +1049,9 @@ int __init early_init_dt_scan_chosen(cha
+       p = of_get_flat_dt_prop(node, "bootargs", &l);
+       if (p != NULL && l > 0)
+               strscpy(cmdline, p, min(l, COMMAND_LINE_SIZE));
++      p = of_get_flat_dt_prop(node, "bootargs-append", &l);
++      if (p != NULL && l > 0)
++              strlcat(cmdline, p, min_t(int, strlen(cmdline) + (int)l, COMMAND_LINE_SIZE));
+ handle_cmdline:
+       /*
diff --git a/target/linux/generic/hack-6.12/930-Revert-Revert-Revert-driver-core-Set-fw_devlink-on-b.patch b/target/linux/generic/hack-6.12/930-Revert-Revert-Revert-driver-core-Set-fw_devlink-on-b.patch
new file mode 100644 (file)
index 0000000..a7c8a8e
--- /dev/null
@@ -0,0 +1,30 @@
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Tue, 19 Jul 2022 06:17:48 +0200
+Subject: [PATCH] Revert "Revert "Revert "driver core: Set fw_devlink=on by
+ default"""
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This reverts commit ea718c699055c8566eb64432388a04974c43b2ea.
+
+With of_platform_populate() called for MTD partitions that commit breaks
+probing devices which reference MTD in device tree.
+
+Link: https://lore.kernel.org/all/696cb2da-20b9-b3dd-46d9-de4bf91a1506@gmail.com/T/#u
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+---
+ drivers/base/core.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/base/core.c
++++ b/drivers/base/core.c
+@@ -1653,7 +1653,7 @@ static void device_links_purge(struct de
+ #define FW_DEVLINK_FLAGS_RPM          (FW_DEVLINK_FLAGS_ON | \
+                                        DL_FLAG_PM_RUNTIME)
+-static u32 fw_devlink_flags = FW_DEVLINK_FLAGS_RPM;
++static u32 fw_devlink_flags = FW_DEVLINK_FLAGS_PERMISSIVE;
+ static int __init fw_devlink_setup(char *arg)
+ {
+       if (!arg)
diff --git a/target/linux/generic/pending-6.12/100-compiler.h-only-include-asm-rwonce.h-for-kernel-code.patch b/target/linux/generic/pending-6.12/100-compiler.h-only-include-asm-rwonce.h-for-kernel-code.patch
new file mode 100644 (file)
index 0000000..725e223
--- /dev/null
@@ -0,0 +1,29 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 22 Oct 2020 22:00:03 +0200
+Subject: [PATCH] compiler.h: only include asm/rwonce.h for kernel code
+
+This header file is not in uapi, which makes any user space code that includes
+linux/compiler.h to fail with the error 'asm/rwonce.h: No such file or directory'
+
+Fixes: e506ea451254 ("compiler.h: Split {READ,WRITE}_ONCE definitions out into rwonce.h")
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/include/linux/compiler.h
++++ b/include/linux/compiler.h
+@@ -191,6 +191,8 @@ void ftrace_likely_update(struct ftrace_
+       __v;                                                            \
+ })
++#include <asm/rwonce.h>
++
+ #endif /* __KERNEL__ */
+ /**
+@@ -298,6 +300,4 @@ static inline void *offset_to_ptr(const
+  */
+ #define prevent_tail_call_optimization()      mb()
+-#include <asm/rwonce.h>
+-
+ #endif /* __LINUX_COMPILER_H */
diff --git a/target/linux/generic/pending-6.12/102-MIPS-only-process-negative-stack-offsets-on-stack-tr.patch b/target/linux/generic/pending-6.12/102-MIPS-only-process-negative-stack-offsets-on-stack-tr.patch
new file mode 100644 (file)
index 0000000..d79d03d
--- /dev/null
@@ -0,0 +1,57 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Wed, 18 Apr 2018 10:50:05 +0200
+Subject: [PATCH] MIPS: only process negative stack offsets on stack traces
+
+Fixes endless back traces in cases where the compiler emits a stack
+pointer increase in a branch delay slot (probably for some form of
+function return).
+
+[    3.475442] BUG: MAX_STACK_TRACE_ENTRIES too low!
+[    3.480070] turning off the locking correctness validator.
+[    3.485521] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 4.14.34 #0
+[    3.491475] Stack : 00000000 00000000 00000000 00000000 80e0fce2 00000034 00000000 00000000
+[    3.499764]         87c3838c 80696377 8061047c 00000000 00000001 00000001 87c2d850 6534689f
+[    3.508059]         00000000 00000000 80e10000 00000000 00000000 000000cf 0000000f 00000000
+[    3.516353]         00000000 806a0000 00076891 00000000 00000000 00000000 ffffffff 00000000
+[    3.524648]         806c0000 00000004 80e10000 806a0000 00000003 80690000 00000000 80700000
+[    3.532942]         ...
+[    3.535362] Call Trace:
+[    3.537818] [<80010a48>] show_stack+0x58/0x100
+[    3.542207] [<804c2f78>] dump_stack+0xe8/0x170
+[    3.546613] [<80079f90>] save_trace+0xf0/0x110
+[    3.551010] [<8007b1ec>] mark_lock+0x33c/0x78c
+[    3.555413] [<8007bf48>] __lock_acquire+0x2ac/0x1a08
+[    3.560337] [<8007de60>] lock_acquire+0x64/0x8c
+[    3.564846] [<804e1570>] _raw_spin_lock_irqsave+0x54/0x78
+[    3.570186] [<801b618c>] kernfs_notify+0x94/0xac
+[    3.574770] [<801b7b10>] sysfs_notify+0x74/0xa0
+[    3.579257] [<801b618c>] kernfs_notify+0x94/0xac
+[    3.583839] [<801b7b10>] sysfs_notify+0x74/0xa0
+[    3.588329] [<801b618c>] kernfs_notify+0x94/0xac
+[    3.592911] [<801b7b10>] sysfs_notify+0x74/0xa0
+[    3.597401] [<801b618c>] kernfs_notify+0x94/0xac
+[    3.601983] [<801b7b10>] sysfs_notify+0x74/0xa0
+[    3.606473] [<801b618c>] kernfs_notify+0x94/0xac
+[    3.611055] [<801b7b10>] sysfs_notify+0x74/0xa0
+[    3.615545] [<801b618c>] kernfs_notify+0x94/0xac
+[    3.620125] [<801b7b10>] sysfs_notify+0x74/0xa0
+[    3.624619] [<801b618c>] kernfs_notify+0x94/0xac
+[    3.629197] [<801b7b10>] sysfs_notify+0x74/0xa0
+[    3.633691] [<801b618c>] kernfs_notify+0x94/0xac
+[    3.638269] [<801b7b10>] sysfs_notify+0x74/0xa0
+[    3.642763] [<801b618c>] kernfs_notify+0x94/0xac
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/arch/mips/kernel/process.c
++++ b/arch/mips/kernel/process.c
+@@ -395,6 +395,8 @@ static inline int is_sp_move_ins(union m
+       if (ip->i_format.opcode == addiu_op ||
+           ip->i_format.opcode == daddiu_op) {
++              if (ip->i_format.simmediate > 0)
++                      return 0;
+               *frame_size = -ip->i_format.simmediate;
+               return 1;
+       }
diff --git a/target/linux/generic/pending-6.12/103-kbuild-export-SUBARCH.patch b/target/linux/generic/pending-6.12/103-kbuild-export-SUBARCH.patch
new file mode 100644 (file)
index 0000000..0cedbec
--- /dev/null
@@ -0,0 +1,21 @@
+From 173019b66dcc9d68ad9333aa744dad1e369b5aa8 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sun, 9 Jul 2017 00:26:53 +0200
+Subject: [PATCH 34/34] kernel: add compile fix for linux 4.9 on x86
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ Makefile | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/Makefile
++++ b/Makefile
+@@ -589,7 +589,7 @@ export RUSTC_BOOTSTRAP := 1
+ # Allows finding `.clippy.toml` in out-of-srctree builds.
+ export CLIPPY_CONF_DIR := $(srctree)
+-export ARCH SRCARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC HOSTPKG_CONFIG
++export ARCH SRCARCH SUBARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC HOSTPKG_CONFIG
+ export RUSTC RUSTDOC RUSTFMT RUSTC_OR_CLIPPY_QUIET RUSTC_OR_CLIPPY BINDGEN
+ export HOSTRUSTC KBUILD_HOSTRUSTFLAGS
+ export CPP AR NM STRIP OBJCOPY OBJDUMP READELF PAHOLE RESOLVE_BTFIDS LEX YACC AWK INSTALLKERNEL
diff --git a/target/linux/generic/pending-6.12/111-watchdog-max63xx_wdt-Add-support-for-specifying-WDI-.patch b/target/linux/generic/pending-6.12/111-watchdog-max63xx_wdt-Add-support-for-specifying-WDI-.patch
new file mode 100644 (file)
index 0000000..9dd90ee
--- /dev/null
@@ -0,0 +1,75 @@
+From bd1b9f66d5134e518419f4c4dacf1884c1616983 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Thu, 28 Apr 2022 11:13:23 +0200
+Subject: [PATCH] watchdog: max63xx_wdt: Add support for specifying WDI logic
+ via GPIO
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+On some boards is WDI logic of max6370 chip connected via GPIO.
+So extend max63xx_wdt driver to allow specifying WDI logic via GPIO.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/watchdog/max63xx_wdt.c | 24 ++++++++++++++++++++++++
+ 1 file changed, 24 insertions(+)
+
+--- a/drivers/watchdog/max63xx_wdt.c
++++ b/drivers/watchdog/max63xx_wdt.c
+@@ -24,6 +24,7 @@
+ #include <linux/io.h>
+ #include <linux/slab.h>
+ #include <linux/property.h>
++#include <linux/gpio/consumer.h>
+ #define DEFAULT_HEARTBEAT 60
+ #define MAX_HEARTBEAT     60
+@@ -50,6 +51,9 @@ struct max63xx_wdt {
+       void __iomem *base;
+       spinlock_t lock;
++      /* GPIOs */
++      struct gpio_desc *gpio_wdi;
++
+       /* WDI and WSET bits write access routines */
+       void (*ping)(struct max63xx_wdt *wdt);
+       void (*set)(struct max63xx_wdt *wdt, u8 set);
+@@ -155,6 +159,17 @@ static const struct watchdog_info max63x
+       .identity = "max63xx Watchdog",
+ };
++static void max63xx_gpio_ping(struct max63xx_wdt *wdt)
++{
++      spin_lock(&wdt->lock);
++
++      gpiod_set_value(wdt->gpio_wdi, 1);
++      udelay(1);
++      gpiod_set_value(wdt->gpio_wdi, 0);
++
++      spin_unlock(&wdt->lock);
++}
++
+ static void max63xx_mmap_ping(struct max63xx_wdt *wdt)
+ {
+       u8 val;
+@@ -222,10 +237,19 @@ static int max63xx_wdt_probe(struct plat
+               return -EINVAL;
+       }
++      wdt->gpio_wdi = devm_gpiod_get(dev, NULL, GPIOD_FLAGS_BIT_DIR_OUT);
++      if (IS_ERR(wdt->gpio_wdi) && PTR_ERR(wdt->gpio_wdi) != -ENOENT)
++              return dev_err_probe(dev, PTR_ERR(wdt->gpio_wdi),
++                                   "unable to request gpio: %ld\n",
++                                   PTR_ERR(wdt->gpio_wdi));
++
+       err = max63xx_mmap_init(pdev, wdt);
+       if (err)
+               return err;
++      if (!IS_ERR(wdt->gpio_wdi))
++              wdt->ping = max63xx_gpio_ping;
++
+       platform_set_drvdata(pdev, &wdt->wdd);
+       watchdog_set_drvdata(&wdt->wdd, wdt);
diff --git a/target/linux/generic/pending-6.12/140-jffs2-use-.rename2-and-add-RENAME_WHITEOUT-support.patch b/target/linux/generic/pending-6.12/140-jffs2-use-.rename2-and-add-RENAME_WHITEOUT-support.patch
new file mode 100644 (file)
index 0000000..b5cc52d
--- /dev/null
@@ -0,0 +1,81 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: jffs2: use .rename2 and add RENAME_WHITEOUT support
+
+It is required for renames on overlayfs
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/fs/jffs2/dir.c
++++ b/fs/jffs2/dir.c
+@@ -620,8 +620,8 @@ static int jffs2_rmdir (struct inode *di
+       return ret;
+ }
+-static int jffs2_mknod (struct mnt_idmap *idmap, struct inode *dir_i,
+-                      struct dentry *dentry, umode_t mode, dev_t rdev)
++static int __jffs2_mknod (struct mnt_idmap *idmap, struct inode *dir_i,
++                        struct dentry *dentry, umode_t mode, dev_t rdev, bool whiteout)
+ {
+       struct jffs2_inode_info *f, *dir_f;
+       struct jffs2_sb_info *c;
+@@ -761,7 +761,11 @@ static int jffs2_mknod (struct mnt_idmap
+       mutex_unlock(&dir_f->sem);
+       jffs2_complete_reservation(c);
+-      d_instantiate_new(dentry, inode);
++      if (!whiteout)
++              d_instantiate_new(dentry, inode);
++      else
++              unlock_new_inode(inode);
++
+       return 0;
+  fail:
+@@ -769,6 +773,19 @@ static int jffs2_mknod (struct mnt_idmap
+       return ret;
+ }
++static int jffs2_mknod (struct mnt_idmap *idmap, struct inode *dir_i,
++                      struct dentry *dentry, umode_t mode, dev_t rdev)
++{
++      return __jffs2_mknod(idmap, dir_i, dentry, mode, rdev, false);
++}
++
++static int jffs2_whiteout (struct mnt_idmap *idmap, struct inode *old_dir,
++                         struct dentry *old_dentry)
++{
++      return __jffs2_mknod(idmap, old_dir, old_dentry, S_IFCHR | WHITEOUT_MODE,
++                           WHITEOUT_DEV, true);
++}
++
+ static int jffs2_rename (struct mnt_idmap *idmap,
+                        struct inode *old_dir_i, struct dentry *old_dentry,
+                        struct inode *new_dir_i, struct dentry *new_dentry,
+@@ -780,7 +797,7 @@ static int jffs2_rename (struct mnt_idma
+       uint8_t type;
+       uint32_t now;
+-      if (flags & ~RENAME_NOREPLACE)
++      if (flags & ~(RENAME_NOREPLACE|RENAME_WHITEOUT))
+               return -EINVAL;
+       /* The VFS will check for us and prevent trying to rename a
+@@ -846,9 +863,14 @@ static int jffs2_rename (struct mnt_idma
+       if (d_is_dir(old_dentry) && !victim_f)
+               inc_nlink(new_dir_i);
+-      /* Unlink the original */
+-      ret = jffs2_do_unlink(c, JFFS2_INODE_INFO(old_dir_i),
+-                            old_dentry->d_name.name, old_dentry->d_name.len, NULL, now);
++      if (flags & RENAME_WHITEOUT)
++              /* Replace with whiteout */
++              ret = jffs2_whiteout(idmap, old_dir_i, old_dentry);
++      else
++              /* Unlink the original */
++              ret = jffs2_do_unlink(c, JFFS2_INODE_INFO(old_dir_i),
++                                    old_dentry->d_name.name,
++                                    old_dentry->d_name.len, NULL, now);
+       /* We don't touch inode->i_nlink */
diff --git a/target/linux/generic/pending-6.12/141-jffs2-add-RENAME_EXCHANGE-support.patch b/target/linux/generic/pending-6.12/141-jffs2-add-RENAME_EXCHANGE-support.patch
new file mode 100644 (file)
index 0000000..44ba3bd
--- /dev/null
@@ -0,0 +1,73 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: jffs2: add RENAME_EXCHANGE support
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/fs/jffs2/dir.c
++++ b/fs/jffs2/dir.c
+@@ -794,18 +794,31 @@ static int jffs2_rename (struct mnt_idma
+       int ret;
+       struct jffs2_sb_info *c = JFFS2_SB_INFO(old_dir_i->i_sb);
+       struct jffs2_inode_info *victim_f = NULL;
++      struct inode *fst_inode = d_inode(old_dentry);
++      struct inode *snd_inode = d_inode(new_dentry);
+       uint8_t type;
+       uint32_t now;
+-      if (flags & ~(RENAME_NOREPLACE|RENAME_WHITEOUT))
++      if (flags & ~(RENAME_NOREPLACE|RENAME_WHITEOUT|RENAME_EXCHANGE))
+               return -EINVAL;
++      if ((flags & RENAME_EXCHANGE) && (old_dir_i != new_dir_i)) {
++              if (S_ISDIR(fst_inode->i_mode) && !S_ISDIR(snd_inode->i_mode)) {
++                      inc_nlink(new_dir_i);
++                      drop_nlink(old_dir_i);
++              }
++              else if (!S_ISDIR(fst_inode->i_mode) && S_ISDIR(snd_inode->i_mode)) {
++                      drop_nlink(new_dir_i);
++                      inc_nlink(old_dir_i);
++              }
++      }
++
+       /* The VFS will check for us and prevent trying to rename a
+        * file over a directory and vice versa, but if it's a directory,
+        * the VFS can't check whether the victim is empty. The filesystem
+        * needs to do that for itself.
+        */
+-      if (d_really_is_positive(new_dentry)) {
++      if (d_really_is_positive(new_dentry) && !(flags & RENAME_EXCHANGE)) {
+               victim_f = JFFS2_INODE_INFO(d_inode(new_dentry));
+               if (d_is_dir(new_dentry)) {
+                       struct jffs2_full_dirent *fd;
+@@ -840,7 +853,7 @@ static int jffs2_rename (struct mnt_idma
+       if (ret)
+               return ret;
+-      if (victim_f) {
++      if (victim_f && !(flags & RENAME_EXCHANGE)) {
+               /* There was a victim. Kill it off nicely */
+               if (d_is_dir(new_dentry))
+                       clear_nlink(d_inode(new_dentry));
+@@ -866,6 +879,12 @@ static int jffs2_rename (struct mnt_idma
+       if (flags & RENAME_WHITEOUT)
+               /* Replace with whiteout */
+               ret = jffs2_whiteout(idmap, old_dir_i, old_dentry);
++      else if (flags & RENAME_EXCHANGE)
++              /* Replace the original */
++              ret = jffs2_do_link(c, JFFS2_INODE_INFO(old_dir_i),
++                                  d_inode(new_dentry)->i_ino, type,
++                                  old_dentry->d_name.name, old_dentry->d_name.len,
++                                  now);
+       else
+               /* Unlink the original */
+               ret = jffs2_do_unlink(c, JFFS2_INODE_INFO(old_dir_i),
+@@ -898,7 +917,7 @@ static int jffs2_rename (struct mnt_idma
+               return ret;
+       }
+-      if (d_is_dir(old_dentry))
++      if (d_is_dir(old_dentry) && !(flags & RENAME_EXCHANGE))
+               drop_nlink(old_dir_i);
+       inode_set_mtime_to_ts(old_dir_i,
diff --git a/target/linux/generic/pending-6.12/142-jffs2-add-splice-ops.patch b/target/linux/generic/pending-6.12/142-jffs2-add-splice-ops.patch
new file mode 100644 (file)
index 0000000..ea57158
--- /dev/null
@@ -0,0 +1,20 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: jffs2: add splice ops
+
+Add splice_read using generic_file_splice_read.
+Add splice_write using iter_file_splice_write
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/fs/jffs2/file.c
++++ b/fs/jffs2/file.c
+@@ -53,6 +53,8 @@ const struct file_operations jffs2_file_
+       .open =         generic_file_open,
+       .read_iter =    generic_file_read_iter,
+       .write_iter =   generic_file_write_iter,
++      .splice_read =  filemap_splice_read,
++      .splice_write = iter_file_splice_write,
+       .unlocked_ioctl=jffs2_ioctl,
+       .mmap =         generic_file_readonly_mmap,
+       .fsync =        jffs2_fsync,
diff --git a/target/linux/generic/pending-6.12/150-bridge_allow_receiption_on_disabled_port.patch b/target/linux/generic/pending-6.12/150-bridge_allow_receiption_on_disabled_port.patch
new file mode 100644 (file)
index 0000000..530f2c3
--- /dev/null
@@ -0,0 +1,45 @@
+From: Stephen Hemminger <stephen@networkplumber.org>
+Subject: bridge: allow receiption on disabled port
+
+When an ethernet device is enslaved to a bridge, and the bridge STP
+detects loss of carrier (or operational state down), then normally
+packet receiption is blocked.
+
+This breaks control applications like WPA which maybe expecting to
+receive packets to negotiate to bring link up. The bridge needs to
+block forwarding packets from these disabled ports, but there is no
+hard requirement to not allow local packet delivery.
+
+Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+
+--- a/net/bridge/br_input.c
++++ b/net/bridge/br_input.c
+@@ -245,6 +245,9 @@ static void __br_handle_local_finish(str
+ /* note: already called with rcu_read_lock */
+ static int br_handle_local_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
+ {
++      struct net_bridge_port *p = br_port_get_rcu(skb->dev);
++
++      if (p->state != BR_STATE_DISABLED)
+       __br_handle_local_finish(skb);
+       /* return 1 to signal the okfn() was called so it's ok to use the skb */
+@@ -416,6 +419,17 @@ forward:
+               goto defer_stp_filtering;
+       switch (p->state) {
++      case BR_STATE_DISABLED:
++              if (ether_addr_equal(p->br->dev->dev_addr, dest))
++                      skb->pkt_type = PACKET_HOST;
++
++              if (NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING,
++                      dev_net(skb->dev), NULL, skb, skb->dev, NULL,
++                      br_handle_local_finish) == 1) {
++                      return RX_HANDLER_PASS;
++              }
++              break;
++
+       case BR_STATE_FORWARDING:
+       case BR_STATE_LEARNING:
+ defer_stp_filtering:
diff --git a/target/linux/generic/pending-6.12/151-net-bridge-do-not-send-arp-replies-if-src-and-target.patch b/target/linux/generic/pending-6.12/151-net-bridge-do-not-send-arp-replies-if-src-and-target.patch
new file mode 100644 (file)
index 0000000..3abeaca
--- /dev/null
@@ -0,0 +1,37 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 4 Jan 2024 15:21:21 +0100
+Subject: [PATCH] net: bridge: do not send arp replies if src and target hw
+ addr is the same
+
+There are broken devices in the wild that handle duplicate IP address
+detection by sending out ARP requests for the IP that they received from a
+DHCP server and refuse the address if they get a reply.
+When proxyarp is enabled, they would go into a loop of requesting an address
+and then NAKing it again.
+
+Link: https://github.com/openwrt/openwrt/issues/14309
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/bridge/br_arp_nd_proxy.c
++++ b/net/bridge/br_arp_nd_proxy.c
+@@ -204,7 +204,10 @@ void br_do_proxy_suppress_arp(struct sk_
+                       if ((p && (p->flags & BR_PROXYARP)) ||
+                           (f->dst && (f->dst->flags & BR_PROXYARP_WIFI)) ||
+                           br_is_neigh_suppress_enabled(f->dst, vid)) {
+-                              if (!vid)
++                              replied = true;
++                              if (!memcmp(n->ha, sha, dev->addr_len))
++                                      replied = false;
++                              else if (!vid)
+                                       br_arp_send(br, p, skb->dev, sip, tip,
+                                                   sha, n->ha, sha, 0, 0);
+                               else
+@@ -212,7 +215,6 @@ void br_do_proxy_suppress_arp(struct sk_
+                                                   sha, n->ha, sha,
+                                                   skb->vlan_proto,
+                                                   skb_vlan_tag_get(skb));
+-                              replied = true;
+                       }
+                       /* If we have replied or as long as we know the
diff --git a/target/linux/generic/pending-6.12/190-rtc-rs5c372-support_alarms_up_to_1_week.patch b/target/linux/generic/pending-6.12/190-rtc-rs5c372-support_alarms_up_to_1_week.patch
new file mode 100644 (file)
index 0000000..2f5c222
--- /dev/null
@@ -0,0 +1,94 @@
+From: Daniel González Cabanelas <dgcbueu@gmail.com>
+Subject: [PATCH 1/2] rtc: rs5c372: support alarms up to 1 week
+
+The Ricoh R2221x, R2223x, RS5C372, RV5C387A chips can handle 1 week
+alarms.
+
+Read the "wday" alarm register and convert it to a date to support up 1
+week in our driver.
+
+Signed-off-by: Daniel González Cabanelas <dgcbueu@gmail.com>
+---
+ drivers/rtc/rtc-rs5c372.c | 48 ++++++++++++++++++++++++++++++++++-----
+ 1 file changed, 42 insertions(+), 6 deletions(-)
+
+--- a/drivers/rtc/rtc-rs5c372.c
++++ b/drivers/rtc/rtc-rs5c372.c
+@@ -399,7 +399,9 @@ static int rs5c_read_alarm(struct device
+ {
+       struct i2c_client       *client = to_i2c_client(dev);
+       struct rs5c372          *rs5c = i2c_get_clientdata(client);
+-      int                     status;
++      int                     status, wday_offs;
++      struct rtc_time         rtc;
++      unsigned long           alarm_secs;
+       status = rs5c_get_regs(rs5c);
+       if (status < 0)
+@@ -409,6 +411,30 @@ static int rs5c_read_alarm(struct device
+       t->time.tm_sec = 0;
+       t->time.tm_min = bcd2bin(rs5c->regs[RS5C_REG_ALARM_A_MIN] & 0x7f);
+       t->time.tm_hour = rs5c_reg2hr(rs5c, rs5c->regs[RS5C_REG_ALARM_A_HOURS]);
++      t->time.tm_wday = ffs(rs5c->regs[RS5C_REG_ALARM_A_WDAY] & 0x7f) - 1;
++
++      /* determine the day, month and year based on alarm wday, taking as a
++       * reference the current time from the rtc
++       */
++      status = rs5c372_rtc_read_time(dev, &rtc);
++      if (status < 0)
++              return status;
++
++      wday_offs = t->time.tm_wday - rtc.tm_wday;
++      alarm_secs = mktime64(rtc.tm_year + 1900,
++                            rtc.tm_mon + 1,
++                            rtc.tm_mday + wday_offs,
++                            t->time.tm_hour,
++                            t->time.tm_min,
++                            t->time.tm_sec);
++
++      if (wday_offs < 0 || (wday_offs == 0 &&
++                            (t->time.tm_hour < rtc.tm_hour ||
++                             (t->time.tm_hour == rtc.tm_hour &&
++                              t->time.tm_min <= rtc.tm_min))))
++              alarm_secs += 7 * 86400;
++
++      rtc_time64_to_tm(alarm_secs, &t->time);
+       /* ... and status */
+       t->enabled = !!(rs5c->regs[RS5C_REG_CTRL1] & RS5C_CTRL1_AALE);
+@@ -423,12 +449,20 @@ static int rs5c_set_alarm(struct device
+       struct rs5c372          *rs5c = i2c_get_clientdata(client);
+       int                     status, addr, i;
+       unsigned char           buf[3];
++      struct rtc_time         rtc_tm;
++      unsigned long           rtc_secs, alarm_secs;
+-      /* only handle up to 24 hours in the future, like RTC_ALM_SET */
+-      if (t->time.tm_mday != -1
+-                      || t->time.tm_mon != -1
+-                      || t->time.tm_year != -1)
++      /* chip only can handle alarms up to one week in the future*/
++      status = rs5c372_rtc_read_time(dev, &rtc_tm);
++      if (status)
++              return status;
++      rtc_secs = rtc_tm_to_time64(&rtc_tm);
++      alarm_secs = rtc_tm_to_time64(&t->time);
++      if (alarm_secs >= rtc_secs + 7 * 86400) {
++              dev_err(dev, "%s: alarm maximum is one week in the future (%d)\n",
++                      __func__, status);
+               return -EINVAL;
++      }
+       /* REVISIT: round up tm_sec */
+@@ -449,7 +483,9 @@ static int rs5c_set_alarm(struct device
+       /* set alarm */
+       buf[0] = bin2bcd(t->time.tm_min);
+       buf[1] = rs5c_hr2reg(rs5c, t->time.tm_hour);
+-      buf[2] = 0x7f;  /* any/all days */
++      /* each bit is the day of the week, 0x7f means all days */
++      buf[2] = (t->time.tm_wday >= 0 && t->time.tm_wday < 7) ?
++                BIT(t->time.tm_wday) : 0x7f;
+       for (i = 0; i < sizeof(buf); i++) {
+               addr = RS5C_ADDR(RS5C_REG_ALARM_A_MIN + i);
diff --git a/target/linux/generic/pending-6.12/191-rtc-rs5c372-let_the_alarm_to_be_used_as_wakeup_source.patch b/target/linux/generic/pending-6.12/191-rtc-rs5c372-let_the_alarm_to_be_used_as_wakeup_source.patch
new file mode 100644 (file)
index 0000000..a29c548
--- /dev/null
@@ -0,0 +1,71 @@
+From: Daniel González Cabanelas <dgcbueu@gmail.com>
+Subject: [PATCH 2/2] rtc: rs5c372: let the alarm to be used as wakeup source
+
+Currently there is no use for the interrupts on the rs5c372 RTC and the
+wakealarm isn't enabled. There are some devices like NASes which use this
+RTC to wake up from the power off state when the INTR pin is activated by
+the alarm clock.
+
+Enable the alarm and let to be used as a wakeup source.
+
+Tested on a Buffalo LS421DE NAS.
+
+Signed-off-by: Daniel González Cabanelas <dgcbueu@gmail.com>
+---
+ drivers/rtc/rtc-rs5c372.c | 16 ++++++++++++++++
+ 1 file changed, 16 insertions(+)
+
+--- a/drivers/rtc/rtc-rs5c372.c
++++ b/drivers/rtc/rtc-rs5c372.c
+@@ -832,6 +832,7 @@ static int rs5c372_probe(struct i2c_clie
+       int err = 0;
+       int smbus_mode = 0;
+       struct rs5c372 *rs5c372;
++      bool rs5c372_can_wakeup_device = false;
+       dev_dbg(&client->dev, "%s\n", __func__);
+@@ -868,6 +869,12 @@ static int rs5c372_probe(struct i2c_clie
+               rs5c372->type = id->driver_data;
+       }
++#ifdef CONFIG_OF
++      if(of_property_read_bool(client->dev.of_node,
++                                            "wakeup-source"))
++              rs5c372_can_wakeup_device = true;
++#endif
++
+       /* we read registers 0x0f then 0x00-0x0f; skip the first one */
+       rs5c372->regs = &rs5c372->buf[1];
+       rs5c372->smbus = smbus_mode;
+@@ -901,6 +908,8 @@ static int rs5c372_probe(struct i2c_clie
+               goto exit;
+       }
++      rs5c372->has_irq = 1;
++
+       /* if the oscillator lost power and no other software (like
+        * the bootloader) set it up, do it here.
+        *
+@@ -927,6 +936,10 @@ static int rs5c372_probe(struct i2c_clie
+                       );
+       /* REVISIT use client->irq to register alarm irq ... */
++      if (rs5c372_can_wakeup_device) {
++              device_init_wakeup(&client->dev, true);
++      }
++
+       rs5c372->rtc = devm_rtc_device_register(&client->dev,
+                                       rs5c372_driver.driver.name,
+                                       &rs5c372_rtc_ops, THIS_MODULE);
+@@ -940,6 +953,10 @@ static int rs5c372_probe(struct i2c_clie
+       if (err)
+               goto exit;
++      /* the rs5c372 alarm only supports a minute accuracy */
++      set_bit(RTC_FEATURE_ALARM_RES_MINUTE, rs5c372->rtc->features);
++      clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rs5c372->rtc->features);
++
+       return 0;
+ exit:
diff --git a/target/linux/generic/pending-6.12/200-ARM-9404-1-arm32-fix-boot-hang-with-HAVE_LD_DEAD_COD.patch b/target/linux/generic/pending-6.12/200-ARM-9404-1-arm32-fix-boot-hang-with-HAVE_LD_DEAD_COD.patch
new file mode 100644 (file)
index 0000000..f36b118
--- /dev/null
@@ -0,0 +1,79 @@
+From cf3d39cfd29ab7bcbd6aa79d4a2f132817969e3d Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 15 Apr 2025 23:16:40 +0200
+Subject: [PATCH] ARM: 9404/1: arm32: fix boot hang with
+ HAVE_LD_DEAD_CODE_DATA_ELIMINATION
+
+It was reported that some SoC (mvebu based for example) hang on kernel
+loading. A variant of the feature was present in OpenWrt from long ago
+and adding the additional entry with KEEP, fix the problem.
+
+Fixes: ed0f94102251 ("ARM: 9404/1: arm32: enable HAVE_LD_DEAD_CODE_DATA_ELIMINATION")
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ arch/arm/include/asm/vmlinux.lds.h | 10 +++++-----
+ arch/arm/kernel/vmlinux.lds.S      |  4 ++--
+ 2 files changed, 7 insertions(+), 7 deletions(-)
+
+--- a/arch/arm/include/asm/vmlinux.lds.h
++++ b/arch/arm/include/asm/vmlinux.lds.h
+@@ -54,7 +54,7 @@
+ #define IDMAP_TEXT                                                    \
+               ALIGN_FUNCTION();                                       \
+               __idmap_text_start = .;                                 \
+-              *(.idmap.text)                                          \
++              KEEP(*(.idmap.text))                                    \
+               __idmap_text_end = .;                                   \
+ #define ARM_DISCARD                                                   \
+@@ -114,12 +114,12 @@
+       . = ALIGN(8);                                                   \
+       .ARM.unwind_idx : {                                             \
+               __start_unwind_idx = .;                                 \
+-              *(.ARM.exidx*)                                          \
++              KEEP(*(.ARM.exidx*))                                    \
+               __stop_unwind_idx = .;                                  \
+       }                                                               \
+       .ARM.unwind_tab : {                                             \
+               __start_unwind_tab = .;                                 \
+-              *(.ARM.extab*)                                          \
++              KEEP(*(.ARM.extab*))                                    \
+               __stop_unwind_tab = .;                                  \
+       }
+@@ -131,7 +131,7 @@
+       __vectors_lma = .;                                              \
+       OVERLAY 0xffff0000 : NOCROSSREFS AT(__vectors_lma) {            \
+               .vectors {                                              \
+-                      OVERLAY_KEEP(*(.vectors))                       \
++                      KEEP(*(.vectors))                               \
+               }                                                       \
+               .vectors.bhb.loop8 {                                    \
+                       OVERLAY_KEEP(*(.vectors.bhb.loop8))             \
+@@ -149,7 +149,7 @@
+                                                                       \
+       __stubs_lma = .;                                                \
+       .stubs ADDR(.vectors) + 0x1000 : AT(__stubs_lma) {              \
+-              *(.stubs)                                               \
++              KEEP(*(.stubs))                                         \
+       }                                                               \
+       ARM_LMA(__stubs, .stubs);                                       \
+       . = __stubs_lma + SIZEOF(.stubs);                               \
+--- a/arch/arm/kernel/vmlinux.lds.S
++++ b/arch/arm/kernel/vmlinux.lds.S
+@@ -104,13 +104,13 @@ SECTIONS
+       }
+       .init.tagtable : {
+               __tagtable_begin = .;
+-              *(.taglist.init)
++              KEEP(*(.taglist.init))
+               __tagtable_end = .;
+       }
+ #ifdef CONFIG_SMP_ON_UP
+       .init.smpalt : {
+               __smpalt_begin = .;
+-              *(.alt.smp.init)
++              KEEP(*(.alt.smp.init))
+               __smpalt_end = .;
+       }
+ #endif
diff --git a/target/linux/generic/pending-6.12/203-kallsyms_uncompressed.patch b/target/linux/generic/pending-6.12/203-kallsyms_uncompressed.patch
new file mode 100644 (file)
index 0000000..40de8f6
--- /dev/null
@@ -0,0 +1,183 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: kernel: add a config option for keeping the kallsyms table uncompressed, saving ~9kb kernel size after lzma on ar71xx
+
+[john@phrozen.org: added to my upstream queue 30.12.2016]
+lede-commit: e0e3509b5ce2ccf93d4d67ea907613f5f7ec2eed
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ init/Kconfig            | 11 +++++++++++
+ kernel/kallsyms.c       |  8 ++++++++
+ kernel/vmcore_info.c    |  2 ++
+ scripts/kallsyms.c      | 12 ++++++++++++
+ scripts/link-vmlinux.sh |  4 ++++
+ 5 files changed, 37 insertions(+)
+
+--- a/init/Kconfig
++++ b/init/Kconfig
+@@ -1534,6 +1534,17 @@ config SYSCTL_ARCH_UNALIGN_ALLOW
+         the unaligned access emulation.
+         see arch/parisc/kernel/unaligned.c for reference
++config KALLSYMS_UNCOMPRESSED
++      bool "Keep kallsyms uncompressed"
++      depends on KALLSYMS
++      help
++              Normally kallsyms contains compressed symbols (using a token table),
++              reducing the uncompressed kernel image size. Keeping the symbol table
++              uncompressed significantly improves the size of this part in compressed
++              kernel images.
++
++              Say N unless you need compressed kernel images to be small.
++
+ config HAVE_PCSPKR_PLATFORM
+       bool
+--- a/kernel/kallsyms.c
++++ b/kernel/kallsyms.c
+@@ -69,6 +69,11 @@ static unsigned int kallsyms_expand_symb
+        * For every byte on the compressed symbol data, copy the table
+        * entry for that byte.
+        */
++#ifdef CONFIG_KALLSYMS_UNCOMPRESSED
++      memcpy(result, data + 1, len - 1);
++      result += len - 1;
++      len = 0;
++#endif
+       while (len) {
+               tptr = &kallsyms_token_table[kallsyms_token_index[*data]];
+               data++;
+@@ -101,6 +106,9 @@ tail:
+  */
+ static char kallsyms_get_symbol_type(unsigned int off)
+ {
++#ifdef CONFIG_KALLSYMS_UNCOMPRESSED
++      return kallsyms_names[off + 1];
++#endif
+       /*
+        * Get just the first code, look it up in the token table,
+        * and return the first char from this token. If MSB of length
+--- a/kernel/vmcore_info.c
++++ b/kernel/vmcore_info.c
+@@ -214,8 +214,10 @@ static int __init crash_save_vmcoreinfo_
+ #ifdef CONFIG_KALLSYMS
+       VMCOREINFO_SYMBOL(kallsyms_names);
+       VMCOREINFO_SYMBOL(kallsyms_num_syms);
++#ifndef CONFIG_KALLSYMS_UNCOMPRESSED
+       VMCOREINFO_SYMBOL(kallsyms_token_table);
+       VMCOREINFO_SYMBOL(kallsyms_token_index);
++#endif
+       VMCOREINFO_SYMBOL(kallsyms_offsets);
+       VMCOREINFO_SYMBOL(kallsyms_relative_base);
+ #endif /* CONFIG_KALLSYMS */
+--- a/scripts/kallsyms.c
++++ b/scripts/kallsyms.c
+@@ -62,6 +62,7 @@ static struct addr_range percpu_range =
+ static struct sym_entry **table;
+ static unsigned int table_size, table_cnt;
+ static int all_symbols;
++static int uncompressed;
+ static int absolute_percpu;
+ static int token_profit[0x10000];
+@@ -412,13 +413,17 @@ static void write_src(void)
+               for (k = 0; k < table[i]->len; k++)
+                       printf(", 0x%02x", table[i]->sym[k]);
+-              /*
+-               * Now that we wrote out the compressed symbol name, restore the
+-               * original name and print it in the comment.
+-               */
+-              expand_symbol(table[i]->sym, table[i]->len, buf);
+-              strcpy((char *)table[i]->sym, buf);
+-              printf("\t/* %s */\n", table[i]->sym);
++              if (!uncompressed) {
++                      /*
++                       * Now that we wrote out the compressed symbol name, restore the
++                       * original name and print it in the comment.
++                       */
++                      expand_symbol(table[i]->sym, table[i]->len, buf);
++                      strcpy((char *)table[i]->sym, buf);
++                      printf("\t/* %s */\n", table[i]->sym);
++              } else {
++                      printf("\n");
++              }
+       }
+       printf("\n");
+@@ -429,20 +434,22 @@ static void write_src(void)
+       free(markers);
+-      output_label("kallsyms_token_table");
+-      off = 0;
+-      for (i = 0; i < 256; i++) {
+-              best_idx[i] = off;
+-              expand_symbol(best_table[i], best_table_len[i], buf);
+-              printf("\t.asciz\t\"%s\"\n", buf);
+-              off += strlen(buf) + 1;
++      if (!uncompressed) {
++              output_label("kallsyms_token_table");
++              off = 0;
++              for (i = 0; i < 256; i++) {
++                      best_idx[i] = off;
++                      expand_symbol(best_table[i], best_table_len[i], buf);
++                      printf("\t.asciz\t\"%s\"\n", buf);
++                      off += strlen(buf) + 1;
++              }
++              printf("\n");
++
++              output_label("kallsyms_token_index");
++              for (i = 0; i < 256; i++)
++                      printf("\t.short\t%d\n", best_idx[i]);
++              printf("\n");
+       }
+-      printf("\n");
+-
+-      output_label("kallsyms_token_index");
+-      for (i = 0; i < 256; i++)
+-              printf("\t.short\t%d\n", best_idx[i]);
+-      printf("\n");
+       output_label("kallsyms_offsets");
+@@ -532,6 +539,9 @@ static unsigned char *find_token(unsigne
+ {
+       int i;
++      if (uncompressed)
++              return NULL;
++
+       for (i = 0; i < len - 1; i++) {
+               if (str[i] == token[0] && str[i+1] == token[1])
+                       return &str[i];
+@@ -604,6 +614,9 @@ static void optimize_result(void)
+ {
+       int i, best;
++      if (uncompressed)
++              return;
++
+       /* using the '\0' symbol last allows compress_symbols to use standard
+        * fast string functions */
+       for (i = 255; i >= 0; i--) {
+@@ -763,6 +776,7 @@ int main(int argc, char **argv)
+               static const struct option long_options[] = {
+                       {"all-symbols",     no_argument, &all_symbols,     1},
+                       {"absolute-percpu", no_argument, &absolute_percpu, 1},
++                      {"uncompressed",   no_argument, &uncompressed,   1},
+                       {},
+               };
+--- a/scripts/link-vmlinux.sh
++++ b/scripts/link-vmlinux.sh
+@@ -144,6 +144,10 @@ kallsyms()
+               kallsymopt="${kallsymopt} --absolute-percpu"
+       fi
++      if is_enabled CONFIG_KALLSYMS_UNCOMPRESSED; then
++              kallsymopt="${kallsymopt} --uncompressed"
++      fi
++
+       info KSYMS "${2}.S"
+       scripts/kallsyms ${kallsymopt} "${1}" > "${2}.S"
diff --git a/target/linux/generic/pending-6.12/205-backtrace_module_info.patch b/target/linux/generic/pending-6.12/205-backtrace_module_info.patch
new file mode 100644 (file)
index 0000000..de9f860
--- /dev/null
@@ -0,0 +1,48 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: kernel: when KALLSYMS is disabled, print module address + size for matching backtrace entries
+
+[john@phrozen.org: felix will add this to his upstream queue]
+
+dmitrmax@gmail.com: fixed FTB when CONFIG_MODULES=n
+
+lede-commit 53827cdc824556cda910b23ce5030c363b8f1461
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+Signed-off-by: Maksim Dmitrichenko <dmitrmax@gmail.com>
+---
+ lib/vsprintf.c | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+--- a/lib/vsprintf.c
++++ b/lib/vsprintf.c
+@@ -983,8 +983,12 @@ char *symbol_string(char *buf, char *end
+                   struct printf_spec spec, const char *fmt)
+ {
+       unsigned long value;
+-#ifdef CONFIG_KALLSYMS
+       char sym[KSYM_SYMBOL_LEN];
++#ifndef CONFIG_KALLSYMS
++#ifdef CONFIG_MODULES
++      struct module *mod;
++#endif
++      int len;
+ #endif
+       if (fmt[1] == 'R')
+@@ -1005,8 +1009,16 @@ char *symbol_string(char *buf, char *end
+       return string_nocheck(buf, end, sym, spec);
+ #else
+-      return special_hex_number(buf, end, value, sizeof(void *));
++      len = snprintf(sym, sizeof(sym), "0x%lx", value);
++#ifdef CONFIG_MODULES
++      mod = __module_address(value);
++      if (mod)
++              snprintf(sym + len, sizeof(sym) - len, " [%s@%p+0x%x]",
++                       mod->name, mod->mem[MOD_TEXT].base,
++                       mod->mem[MOD_TEXT].size);
++#endif
+ #endif
++      return string(buf, end, sym, spec);
+ }
+ static const struct printf_spec default_str_spec = {
diff --git a/target/linux/generic/pending-6.12/240-remove-unsane-filenames-from-deps_initramfs-list.patch b/target/linux/generic/pending-6.12/240-remove-unsane-filenames-from-deps_initramfs-list.patch
new file mode 100644 (file)
index 0000000..9e78284
--- /dev/null
@@ -0,0 +1,30 @@
+From: Gabor Juhos <juhosg@openwrt.org>
+Subject: usr: sanitize deps_initramfs list
+
+If any filename in the intramfs dependency
+list contains a colon, that causes a kernel
+build error like this:
+
+/devel/openwrt/build_dir/linux-ar71xx_generic/linux-3.6.6/usr/Makefile:58: *** multiple target patterns.  Stop.
+make[5]: *** [usr] Error 2
+
+Fix it by removing such filenames from the
+deps_initramfs list.
+
+Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ usr/Makefile | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+--- a/usr/Makefile
++++ b/usr/Makefile
+@@ -56,6 +56,8 @@ hostprogs := gen_init_cpio
+ # The dependency list is generated by gen_initramfs.sh -l
+ -include $(obj)/.initramfs_data.cpio.d
++deps_initramfs := $(foreach v,$(deps_initramfs),$(if $(findstring :,$(v)),,$(v)))
++
+ # do not try to update files included in initramfs
+ $(deps_initramfs): ;
diff --git a/target/linux/generic/pending-6.12/250-kernel-fork-Increase-minimum-number-of-allowed-threa.patch b/target/linux/generic/pending-6.12/250-kernel-fork-Increase-minimum-number-of-allowed-threa.patch
new file mode 100644 (file)
index 0000000..5255a9c
--- /dev/null
@@ -0,0 +1,37 @@
+From a22c4c7bbfad3e93e717e16f6faf343259add27a Mon Sep 17 00:00:00 2001
+From: Hauke Mehrtens <hauke@hauke-m.de>
+Date: Fri, 27 Jun 2025 11:09:04 +0100
+Subject: [PATCH v2] kernel/fork: Increase minimum number of allowed threads
+
+A modern Linux system creates much more than 20 threads at bootup.
+When I booted up OpenWrt in qemu the system sometimes failed to boot up
+when it wanted to create the 419th thread. The VM had 128MB RAM and the
+calculation in set_max_threads() calculated that max_threads should be
+set to 419. When the system booted up it tried to notify the user space
+about every device it created because CONFIG_UEVENT_HELPER was set and
+used. I counted 1299 calls to call_usermodehelper_setup(), all of
+them try to create a new thread and call the userspace hotplug script in
+it.
+
+This fixes bootup of Linux on systems with low memory.
+
+I saw the problem with qemu 10.0.2 using these commands:
+qemu-system-aarch64 -machine virt -cpu cortex-a57 -nographic
+
+Cc: stable@vger.kernel.org
+Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
+---
+ kernel/fork.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/kernel/fork.c
++++ b/kernel/fork.c
+@@ -123,7 +123,7 @@
+ /*
+  * Minimum number of threads to boot the kernel
+  */
+-#define MIN_THREADS 20
++#define MIN_THREADS 600
+ /*
+  * Maximum number of threads
diff --git a/target/linux/generic/pending-6.12/270-platform-mikrotik-build-bits.patch b/target/linux/generic/pending-6.12/270-platform-mikrotik-build-bits.patch
new file mode 100644 (file)
index 0000000..772d9fe
--- /dev/null
@@ -0,0 +1,31 @@
+From c2deb5ef01a0ef09088832744cbace9e239a6ee0 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Thibaut=20VAR=C3=88NE?= <hacks@slashdirt.org>
+Date: Sat, 28 Mar 2020 12:11:50 +0100
+Subject: [PATCH] generic: platform/mikrotik build bits (5.4)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This patch adds platform/mikrotik kernel build bits
+
+Signed-off-by: Thibaut VARÈNE <hacks@slashdirt.org>
+---
+ drivers/platform/Kconfig  | 2 ++
+ drivers/platform/Makefile | 1 +
+ 2 files changed, 3 insertions(+)
+
+--- a/drivers/platform/Kconfig
++++ b/drivers/platform/Kconfig
+@@ -18,3 +18,5 @@ source "drivers/platform/surface/Kconfig
+ source "drivers/platform/x86/Kconfig"
+ source "drivers/platform/arm64/Kconfig"
++
++source "drivers/platform/mikrotik/Kconfig"
+--- a/drivers/platform/Makefile
++++ b/drivers/platform/Makefile
+@@ -13,3 +13,4 @@ obj-$(CONFIG_CHROME_PLATFORMS)       += chrome
+ obj-$(CONFIG_CZNIC_PLATFORMS) += cznic/
+ obj-$(CONFIG_SURFACE_PLATFORMS)       += surface/
+ obj-$(CONFIG_ARM64_PLATFORM_DEVICES)  += arm64/
++obj-$(CONFIG_MIKROTIK)                += mikrotik/
diff --git a/target/linux/generic/pending-6.12/300-mips_expose_boot_raw.patch b/target/linux/generic/pending-6.12/300-mips_expose_boot_raw.patch
new file mode 100644 (file)
index 0000000..ca8d010
--- /dev/null
@@ -0,0 +1,40 @@
+From: Mark Miller <mark@mirell.org>
+Subject: mips: expose CONFIG_BOOT_RAW
+
+This exposes the CONFIG_BOOT_RAW symbol in Kconfig. This is needed on
+certain Broadcom chipsets running CFE in order to load the kernel.
+
+Signed-off-by: Mark Miller <mark@mirell.org>
+Acked-by: Rob Landley <rob@landley.net>
+---
+--- a/arch/mips/Kconfig
++++ b/arch/mips/Kconfig
+@@ -1055,9 +1055,6 @@ config FW_ARC
+ config ARCH_MAY_HAVE_PC_FDC
+       bool
+-config BOOT_RAW
+-      bool
+-
+ config CEVT_BCM1480
+       bool
+@@ -2990,6 +2987,18 @@ choice
+               bool "Extend builtin kernel arguments with bootloader arguments"
+ endchoice
++config BOOT_RAW
++      bool "Enable the kernel to be executed from the load address"
++      default n
++      help
++       Allow the kernel to be executed from the load address for
++       bootloaders which cannot read the ELF format. This places
++       a jump to start_kernel at the load address.
++
++       If unsure, say N.
++
++
++
+ endmenu
+ config LOCKDEP_SUPPORT
diff --git a/target/linux/generic/pending-6.12/301-MIPS-Add-barriers-between-dcache-icache-flushes.patch b/target/linux/generic/pending-6.12/301-MIPS-Add-barriers-between-dcache-icache-flushes.patch
new file mode 100644 (file)
index 0000000..b3cb5f0
--- /dev/null
@@ -0,0 +1,71 @@
+From e6e6ef4275978823ec3a84133fc91f4ffbef5c84 Mon Sep 17 00:00:00 2001
+From: Paul Burton <paul.burton@imgtec.com>
+Date: Mon, 22 Feb 2016 18:09:44 +0000
+Subject: [PATCH] MIPS: Add barriers between dcache & icache flushes
+
+Index-based cache operations may be arbitrarily reordered by out of
+order CPUs. Thus code which writes back the dcache & then invalidates
+the icache using indexed cache ops must include a barrier between
+operating on the 2 caches in order to prevent the scenario in which:
+
+  - icache invalidation occurs.
+
+  - icache fetch occurs, due to speculation.
+
+  - dcache writeback occurs.
+
+If the above were allowed to happen then the icache would contain stale
+data. Forcing the dcache writeback to complete before the icache
+invalidation avoids this.
+
+Signed-off-by: Paul Burton <paul.burton@imgtec.com>
+Cc: James Hogan <james.hogan@imgtec.com>
+---
+ arch/mips/mm/c-r4k.c | 13 +++++++++++--
+ 1 file changed, 11 insertions(+), 2 deletions(-)
+
+--- a/arch/mips/mm/c-r4k.c
++++ b/arch/mips/mm/c-r4k.c
+@@ -403,6 +403,7 @@ static inline void local_r4k___flush_cac
+       default:
+               r4k_blast_dcache();
++              mb(); /* cache instructions may be reordered */
+               r4k_blast_icache();
+               break;
+       }
+@@ -483,8 +484,10 @@ static inline void local_r4k_flush_cache
+       if (cpu_has_dc_aliases || (exec && !cpu_has_ic_fills_f_dc))
+               r4k_blast_dcache();
+       /* If executable, blast stale lines from icache */
+-      if (exec)
++      if (exec) {
++              mb(); /* cache instructions may be reordered */
+               r4k_blast_icache();
++      }
+ }
+ static void r4k_flush_cache_range(struct vm_area_struct *vma,
+@@ -586,8 +589,13 @@ static inline void local_r4k_flush_cache
+       if (cpu_has_dc_aliases || (exec && !cpu_has_ic_fills_f_dc)) {
+               vaddr ? r4k_blast_dcache_page(addr) :
+                       r4k_blast_dcache_user_page(addr);
+-              if (exec && !cpu_icache_snoops_remote_store)
++              if (exec)
++                      mb(); /* cache instructions may be reordered */
++
++              if (exec && !cpu_icache_snoops_remote_store) {
+                       r4k_blast_scache_page(addr);
++                      mb(); /* cache instructions may be reordered */
++              }
+       }
+       if (exec) {
+               if (vaddr && cpu_has_vtag_icache && mm == current->active_mm) {
+@@ -654,6 +662,7 @@ static inline void __local_r4k_flush_ica
+                       else
+                               blast_dcache_range(start, end);
+               }
++              mb(); /* cache instructions may be reordered */
+       }
+       if (type == R4K_INDEX ||
diff --git a/target/linux/generic/pending-6.12/302-mips_no_branch_likely.patch b/target/linux/generic/pending-6.12/302-mips_no_branch_likely.patch
new file mode 100644 (file)
index 0000000..669aa81
--- /dev/null
@@ -0,0 +1,22 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: mips: use -mno-branch-likely for kernel and userspace
+
+saves ~11k kernel size after lzma and ~12k squashfs size in the
+
+lede-commit: 41a039f46450ffae9483d6216422098669da2900
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ arch/mips/Makefile | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/mips/Makefile
++++ b/arch/mips/Makefile
+@@ -94,7 +94,7 @@ all-$(CONFIG_SYS_SUPPORTS_ZBOOT)+= vmlin
+ # machines may also.  Since BFD is incredibly buggy with respect to
+ # crossformat linking we rely on the elf2ecoff tool for format conversion.
+ #
+-cflags-y                      += -G 0 -mno-abicalls -fno-pic -pipe
++cflags-y                      += -G 0 -mno-abicalls -fno-pic -pipe -mno-branch-likely
+ cflags-y                      += -msoft-float -Wa,-msoft-float
+ LDFLAGS_vmlinux                       += -G 0 -static -n -nostdlib
+ KBUILD_AFLAGS_MODULE          += -mlong-calls
diff --git a/target/linux/generic/pending-6.12/303-Revert-powerpc-dts-mpc85xx-remove-simple-bus-compatible-from-ifc-node.patch b/target/linux/generic/pending-6.12/303-Revert-powerpc-dts-mpc85xx-remove-simple-bus-compatible-from-ifc-node.patch
new file mode 100644 (file)
index 0000000..31f15a5
--- /dev/null
@@ -0,0 +1,125 @@
+From patchwork Wed Nov  5 20:55:24 2025
+From: Rosen Penev <rosenp@gmail.com>
+Subject: [PATCH] Revert "powerpc: dts: mpc85xx: remove "simple-bus" compatible
+ from ifc node"
+Date: Wed,  5 Nov 2025 12:55:24 -0800
+
+This reverts commit 0bf51cc9e9e57a751b4c5dacbfa499ba5cd8bd72.
+
+simple-bus is needed for legacy platforms such as P1010 so that nodes
+are populated properly.
+
+Fixes fsl,ifc-nand probing under at least P1010.
+
+Signed-off-by: Rosen Penev <rosenp@gmail.com>
+---
+ arch/powerpc/boot/dts/fsl/b4si-post.dtsi      | 2 +-
+ arch/powerpc/boot/dts/fsl/bsc9131si-post.dtsi | 2 +-
+ arch/powerpc/boot/dts/fsl/bsc9132si-post.dtsi | 2 +-
+ arch/powerpc/boot/dts/fsl/c293si-post.dtsi    | 2 +-
+ arch/powerpc/boot/dts/fsl/p1010si-post.dtsi   | 2 +-
+ arch/powerpc/boot/dts/fsl/t1023si-post.dtsi   | 2 +-
+ arch/powerpc/boot/dts/fsl/t1040si-post.dtsi   | 2 +-
+ arch/powerpc/boot/dts/fsl/t2081si-post.dtsi   | 2 +-
+ arch/powerpc/boot/dts/fsl/t4240si-post.dtsi   | 2 +-
+ 9 files changed, 9 insertions(+), 9 deletions(-)
+
+--- a/arch/powerpc/boot/dts/fsl/b4si-post.dtsi
++++ b/arch/powerpc/boot/dts/fsl/b4si-post.dtsi
+@@ -50,7 +50,7 @@
+ &ifc {
+       #address-cells = <2>;
+       #size-cells = <1>;
+-      compatible = "fsl,ifc";
++      compatible = "fsl,ifc", "simple-bus";
+       interrupts = <25 2 0 0>;
+ };
+--- a/arch/powerpc/boot/dts/fsl/bsc9131si-post.dtsi
++++ b/arch/powerpc/boot/dts/fsl/bsc9131si-post.dtsi
+@@ -35,7 +35,7 @@
+ &ifc {
+       #address-cells = <2>;
+       #size-cells = <1>;
+-      compatible = "fsl,ifc";
++      compatible = "fsl,ifc", "simple-bus";
+       interrupts = <16 2 0 0 20 2 0 0>;
+ };
+--- a/arch/powerpc/boot/dts/fsl/bsc9132si-post.dtsi
++++ b/arch/powerpc/boot/dts/fsl/bsc9132si-post.dtsi
+@@ -35,7 +35,7 @@
+ &ifc {
+       #address-cells = <2>;
+       #size-cells = <1>;
+-      compatible = "fsl,ifc";
++      compatible = "fsl,ifc", "simple-bus";
+       /* FIXME: Test whether interrupts are split */
+       interrupts = <16 2 0 0 20 2 0 0>;
+ };
+--- a/arch/powerpc/boot/dts/fsl/c293si-post.dtsi
++++ b/arch/powerpc/boot/dts/fsl/c293si-post.dtsi
+@@ -35,7 +35,7 @@
+ &ifc {
+       #address-cells = <2>;
+       #size-cells = <1>;
+-      compatible = "fsl,ifc";
++      compatible = "fsl,ifc", "simple-bus";
+       interrupts = <19 2 0 0>;
+ };
+--- a/arch/powerpc/boot/dts/fsl/p1010si-post.dtsi
++++ b/arch/powerpc/boot/dts/fsl/p1010si-post.dtsi
+@@ -35,7 +35,7 @@
+ &ifc {
+       #address-cells = <2>;
+       #size-cells = <1>;
+-      compatible = "fsl,ifc";
++      compatible = "fsl,ifc", "simple-bus";
+       interrupts = <16 2 0 0 19 2 0 0>;
+ };
+--- a/arch/powerpc/boot/dts/fsl/t1023si-post.dtsi
++++ b/arch/powerpc/boot/dts/fsl/t1023si-post.dtsi
+@@ -52,7 +52,7 @@
+ &ifc {
+       #address-cells = <2>;
+       #size-cells = <1>;
+-      compatible = "fsl,ifc";
++      compatible = "fsl,ifc", "simple-bus";
+       interrupts = <25 2 0 0>;
+ };
+--- a/arch/powerpc/boot/dts/fsl/t1040si-post.dtsi
++++ b/arch/powerpc/boot/dts/fsl/t1040si-post.dtsi
+@@ -52,7 +52,7 @@
+ &ifc {
+       #address-cells = <2>;
+       #size-cells = <1>;
+-      compatible = "fsl,ifc";
++      compatible = "fsl,ifc", "simple-bus";
+       interrupts = <25 2 0 0>;
+ };
+--- a/arch/powerpc/boot/dts/fsl/t2081si-post.dtsi
++++ b/arch/powerpc/boot/dts/fsl/t2081si-post.dtsi
+@@ -50,7 +50,7 @@
+ &ifc {
+       #address-cells = <2>;
+       #size-cells = <1>;
+-      compatible = "fsl,ifc";
++      compatible = "fsl,ifc", "simple-bus";
+       interrupts = <25 2 0 0>;
+ };
+--- a/arch/powerpc/boot/dts/fsl/t4240si-post.dtsi
++++ b/arch/powerpc/boot/dts/fsl/t4240si-post.dtsi
+@@ -50,7 +50,7 @@
+ &ifc {
+       #address-cells = <2>;
+       #size-cells = <1>;
+-      compatible = "fsl,ifc";
++      compatible = "fsl,ifc", "simple-bus";
+       interrupts = <25 2 0 0>;
+ };
diff --git a/target/linux/generic/pending-6.12/308-mips32r2_tune.patch b/target/linux/generic/pending-6.12/308-mips32r2_tune.patch
new file mode 100644 (file)
index 0000000..b9bd610
--- /dev/null
@@ -0,0 +1,22 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: kernel: add -mtune=34kc to MIPS CFLAGS when building for mips32r2
+
+This provides a good tradeoff across at least 24Kc-74Kc, while also
+producing smaller code.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ arch/mips/Makefile | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/mips/Makefile
++++ b/arch/mips/Makefile
+@@ -153,7 +153,7 @@ cflags-$(CONFIG_CPU_R4300) += $(call cc-
+ cflags-$(CONFIG_CPU_R4X00)    += $(call cc-option,-march=r4600,-march=mips3) -Wa,--trap
+ cflags-$(CONFIG_CPU_TX49XX)   += $(call cc-option,-march=r4600,-march=mips3) -Wa,--trap
+ cflags-$(CONFIG_CPU_MIPS32_R1)        += -march=mips32 -Wa,--trap
+-cflags-$(CONFIG_CPU_MIPS32_R2)        += -march=mips32r2 -Wa,--trap
++cflags-$(CONFIG_CPU_MIPS32_R2)        += -march=mips32r2 -mtune=34kc -Wa,--trap
+ cflags-$(CONFIG_CPU_MIPS32_R5)        += -march=mips32r5 -Wa,--trap -modd-spreg
+ cflags-$(CONFIG_CPU_MIPS32_R6)        += -march=mips32r6 -Wa,--trap -modd-spreg
+ cflags-$(CONFIG_CPU_MIPS64_R1)        += -march=mips64 -Wa,--trap
diff --git a/target/linux/generic/pending-6.12/310-arm_module_unresolved_weak_sym.patch b/target/linux/generic/pending-6.12/310-arm_module_unresolved_weak_sym.patch
new file mode 100644 (file)
index 0000000..c654f6b
--- /dev/null
@@ -0,0 +1,22 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: fix errors in unresolved weak symbols on arm
+
+lede-commit: 570699d4838a907c3ef9f2819bf19eb72997b32f
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ arch/arm/kernel/module.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/arch/arm/kernel/module.c
++++ b/arch/arm/kernel/module.c
+@@ -112,6 +112,10 @@ apply_relocate(Elf32_Shdr *sechdrs, cons
+                       return -ENOEXEC;
+               }
++              if ((IS_ERR_VALUE(sym->st_value) || !sym->st_value) &&
++                  ELF_ST_BIND(sym->st_info) == STB_WEAK)
++                      continue;
++
+               loc = dstsec->sh_addr + rel->r_offset;
+               switch (ELF32_R_TYPE(rel->r_info)) {
diff --git a/target/linux/generic/pending-6.12/330-MIPS-kexec-Accept-command-line-parameters-from-users.patch b/target/linux/generic/pending-6.12/330-MIPS-kexec-Accept-command-line-parameters-from-users.patch
new file mode 100644 (file)
index 0000000..a8c6b7a
--- /dev/null
@@ -0,0 +1,282 @@
+From: Yousong Zhou <yszhou4tech@gmail.com>
+Subject: MIPS: kexec: Accept command line parameters from userspace.
+
+Signed-off-by: Yousong Zhou <yszhou4tech@gmail.com>
+---
+ arch/mips/kernel/machine_kexec.c   |  153 +++++++++++++++++++++++++++++++-----
+ arch/mips/kernel/machine_kexec.h   |   20 +++++
+ arch/mips/kernel/relocate_kernel.S |   21 +++--
+ 3 files changed, 167 insertions(+), 27 deletions(-)
+ create mode 100644 arch/mips/kernel/machine_kexec.h
+
+--- a/arch/mips/kernel/machine_kexec.c
++++ b/arch/mips/kernel/machine_kexec.c
+@@ -10,14 +10,11 @@
+ #include <linux/libfdt.h>
+ #include <linux/reboot.h>
++#include <asm/bootinfo.h>
+ #include <asm/cacheflush.h>
+ #include <asm/page.h>
+-
+-extern const unsigned char relocate_new_kernel[];
+-extern const size_t relocate_new_kernel_size;
+-
+-extern unsigned long kexec_start_address;
+-extern unsigned long kexec_indirection_page;
++#include <linux/uaccess.h>
++#include "machine_kexec.h"
+ static unsigned long reboot_code_buffer;
+@@ -31,6 +28,101 @@ void (*_crash_smp_send_stop)(void) = NUL
+ void (*_machine_kexec_shutdown)(void) = NULL;
+ void (*_machine_crash_shutdown)(struct pt_regs *regs) = NULL;
++static void machine_kexec_print_args(void)
++{
++      unsigned long argc = (int)kexec_args[0];
++      int i;
++
++      pr_info("kexec_args[0] (argc): %lu\n", argc);
++      pr_info("kexec_args[1] (argv): %p\n", (void *)kexec_args[1]);
++      pr_info("kexec_args[2] (env ): %p\n", (void *)kexec_args[2]);
++      pr_info("kexec_args[3] (desc): %p\n", (void *)kexec_args[3]);
++
++      for (i = 0; i < argc; i++) {
++              pr_info("kexec_argv[%d] = %p, %s\n",
++                              i, kexec_argv[i], kexec_argv[i]);
++      }
++}
++
++static void machine_kexec_init_argv(struct kimage *image)
++{
++      void __user *buf = NULL;
++      size_t bufsz;
++      size_t size;
++      int i;
++
++      bufsz = 0;
++      for (i = 0; i < image->nr_segments; i++) {
++              struct kexec_segment *seg;
++
++              seg = &image->segment[i];
++              if (seg->bufsz < 6)
++                      continue;
++
++              if (strncmp((char *) seg->buf, "kexec ", 6))
++                      continue;
++
++              buf = seg->buf;
++              bufsz = seg->bufsz;
++              break;
++      }
++
++      if (!buf)
++              return;
++
++      size = KEXEC_COMMAND_LINE_SIZE;
++      size = min(size, bufsz);
++      if (size < bufsz)
++              pr_warn("kexec command line truncated to %zd bytes\n", size);
++
++      /* Copy to kernel space */
++      if (copy_from_user(kexec_argv_buf, buf, size))
++              pr_warn("kexec command line copy to kernel space failed\n");
++
++      kexec_argv_buf[size - 1] = 0;
++}
++
++static void machine_kexec_parse_argv(struct kimage *image)
++{
++      char *reboot_code_buffer;
++      int reloc_delta;
++      char *ptr;
++      int argc;
++      int i;
++
++      ptr = kexec_argv_buf;
++      argc = 0;
++
++      /*
++       * convert command line string to array of parameters
++       * (as bootloader does).
++       */
++      while (ptr && *ptr && (KEXEC_MAX_ARGC > argc)) {
++              if (*ptr == ' ') {
++                      *ptr++ = '\0';
++                      continue;
++              }
++
++              kexec_argv[argc++] = ptr;
++              ptr = strchr(ptr, ' ');
++      }
++
++      if (!argc)
++              return;
++
++      kexec_args[0] = argc;
++      kexec_args[1] = (unsigned long)kexec_argv;
++      kexec_args[2] = 0;
++      kexec_args[3] = 0;
++
++      reboot_code_buffer = page_address(image->control_code_page);
++      reloc_delta = reboot_code_buffer - (char *)kexec_relocate_new_kernel;
++
++      kexec_args[1] += reloc_delta;
++      for (i = 0; i < argc; i++)
++              kexec_argv[i] += reloc_delta;
++}
++
+ static void kexec_image_info(const struct kimage *kimage)
+ {
+       unsigned long i;
+@@ -100,6 +192,18 @@ machine_kexec_prepare(struct kimage *kim
+ #endif
+       kexec_image_info(kimage);
++      /*
++       * Whenever arguments passed from kexec-tools, Init the arguments as
++       * the original ones to try avoiding booting failure.
++       */
++
++      kexec_args[0] = fw_arg0;
++      kexec_args[1] = fw_arg1;
++      kexec_args[2] = fw_arg2;
++      kexec_args[3] = fw_arg3;
++
++      machine_kexec_init_argv(kimage);
++      machine_kexec_parse_argv(kimage);
+       if (_machine_kexec_prepare)
+               return _machine_kexec_prepare(kimage);
+@@ -162,7 +266,7 @@ machine_crash_shutdown(struct pt_regs *r
+ void kexec_nonboot_cpu_jump(void)
+ {
+       local_flush_icache_range((unsigned long)relocated_kexec_smp_wait,
+-                               reboot_code_buffer + relocate_new_kernel_size);
++                               reboot_code_buffer + KEXEC_RELOCATE_NEW_KERNEL_SIZE);
+       relocated_kexec_smp_wait(NULL);
+ }
+@@ -200,7 +304,7 @@ void kexec_reboot(void)
+        * machine_kexec() CPU.
+        */
+       local_flush_icache_range(reboot_code_buffer,
+-                               reboot_code_buffer + relocate_new_kernel_size);
++                               reboot_code_buffer + KEXEC_RELOCATE_NEW_KERNEL_SIZE);
+       do_kexec = (void *)reboot_code_buffer;
+       do_kexec();
+@@ -213,10 +317,12 @@ machine_kexec(struct kimage *image)
+       unsigned long *ptr;
+       reboot_code_buffer =
+-        (unsigned long)page_address(image->control_code_page);
++              (unsigned long)page_address(image->control_code_page);
++      pr_info("reboot_code_buffer = %p\n", (void *)reboot_code_buffer);
+       kexec_start_address =
+               (unsigned long) phys_to_virt(image->start);
++      pr_info("kexec_start_address = %p\n", (void *)kexec_start_address);
+       if (image->type == KEXEC_TYPE_DEFAULT) {
+               kexec_indirection_page =
+@@ -224,9 +330,19 @@ machine_kexec(struct kimage *image)
+       } else {
+               kexec_indirection_page = (unsigned long)&image->head;
+       }
++      pr_info("kexec_indirection_page = %p\n", (void *)kexec_indirection_page);
+-      memcpy((void*)reboot_code_buffer, relocate_new_kernel,
+-             relocate_new_kernel_size);
++      pr_info("Where is memcpy: %p\n", memcpy);
++      pr_info("kexec_relocate_new_kernel = %p, kexec_relocate_new_kernel_end = %p\n",
++              (void *)kexec_relocate_new_kernel, &kexec_relocate_new_kernel_end);
++      pr_info("Copy %lu bytes from %p to %p\n", KEXEC_RELOCATE_NEW_KERNEL_SIZE,
++              (void *)kexec_relocate_new_kernel, (void *)reboot_code_buffer);
++      memcpy((void*)reboot_code_buffer, kexec_relocate_new_kernel,
++             KEXEC_RELOCATE_NEW_KERNEL_SIZE);
++
++      pr_info("Before _print_args().\n");
++      machine_kexec_print_args();
++      pr_info("Before eval loop.\n");
+       /*
+        * The generic kexec code builds a page list with physical
+@@ -257,7 +373,7 @@ machine_kexec(struct kimage *image)
+ #ifdef CONFIG_SMP
+       /* All secondary cpus now may jump to kexec_wait cycle */
+       relocated_kexec_smp_wait = reboot_code_buffer +
+-              (void *)(kexec_smp_wait - relocate_new_kernel);
++              (void *)(kexec_smp_wait - kexec_relocate_new_kernel);
+       smp_wmb();
+       atomic_set(&kexec_ready_to_reboot, 1);
+ #endif
+--- /dev/null
++++ b/arch/mips/kernel/machine_kexec.h
+@@ -0,0 +1,20 @@
++#ifndef _MACHINE_KEXEC_H
++#define _MACHINE_KEXEC_H
++
++#ifndef __ASSEMBLY__
++extern const unsigned char kexec_relocate_new_kernel[];
++extern unsigned long kexec_relocate_new_kernel_end;
++extern unsigned long kexec_start_address;
++extern unsigned long kexec_indirection_page;
++
++extern char kexec_argv_buf[];
++extern char *kexec_argv[];
++
++#define KEXEC_RELOCATE_NEW_KERNEL_SIZE        ((unsigned long)&kexec_relocate_new_kernel_end - (unsigned long)kexec_relocate_new_kernel)
++#endif /* !__ASSEMBLY__ */
++
++#define KEXEC_COMMAND_LINE_SIZE               256
++#define KEXEC_ARGV_SIZE                       (KEXEC_COMMAND_LINE_SIZE / 16)
++#define KEXEC_MAX_ARGC                        (KEXEC_ARGV_SIZE / sizeof(long))
++
++#endif
+--- a/arch/mips/kernel/relocate_kernel.S
++++ b/arch/mips/kernel/relocate_kernel.S
+@@ -10,10 +10,11 @@
+ #include <asm/mipsregs.h>
+ #include <asm/stackframe.h>
+ #include <asm/addrspace.h>
++#include "machine_kexec.h"
+ #include <kernel-entry-init.h>
+-LEAF(relocate_new_kernel)
++LEAF(kexec_relocate_new_kernel)
+       PTR_L a0,       arg0
+       PTR_L a1,       arg1
+       PTR_L a2,       arg2
+@@ -97,7 +98,7 @@ done:
+ #endif
+       /* jump to kexec_start_address */
+       j               s1
+-      END(relocate_new_kernel)
++      END(kexec_relocate_new_kernel)
+ #ifdef CONFIG_SMP
+ /*
+@@ -176,8 +177,15 @@ EXPORT(kexec_indirection_page)
+       PTR_WD          0
+       .size           kexec_indirection_page, PTRSIZE
+-relocate_new_kernel_end:
++kexec_argv_buf:
++      EXPORT(kexec_argv_buf)
++      .skip           KEXEC_COMMAND_LINE_SIZE
++      .size           kexec_argv_buf, KEXEC_COMMAND_LINE_SIZE
++
++kexec_argv:
++      EXPORT(kexec_argv)
++      .skip           KEXEC_ARGV_SIZE
++      .size           kexec_argv, KEXEC_ARGV_SIZE
+-EXPORT(relocate_new_kernel_size)
+-      PTR_WD          relocate_new_kernel_end - relocate_new_kernel
+-      .size           relocate_new_kernel_size, PTRSIZE
++kexec_relocate_new_kernel_end:
++      EXPORT(kexec_relocate_new_kernel_end)
diff --git a/target/linux/generic/pending-6.12/332-arc-add-OWRTDTB-section.patch b/target/linux/generic/pending-6.12/332-arc-add-OWRTDTB-section.patch
new file mode 100644 (file)
index 0000000..5b943f3
--- /dev/null
@@ -0,0 +1,84 @@
+From bb0c3b0175240bf152fd7c644821a0cf9f77c37c Mon Sep 17 00:00:00 2001
+From: Evgeniy Didin <Evgeniy.Didin@synopsys.com>
+Date: Fri, 15 Mar 2019 18:53:38 +0300
+Subject: [PATCH] arc add OWRTDTB section
+
+This change allows OpenWRT to patch resulting kernel binary with
+external .dtb.
+
+That allows us to re-use exactky the same vmlinux on different boards
+given its ARC core configurations match (at least cache line sizes etc).
+
+""patch-dtb" searches for ASCII "OWRTDTB:" strign and copies external
+.dtb right after it, keeping the string in place.
+
+Signed-off-by: Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>
+Signed-off-by: Alexey Brodkin <abrodkin@synopsys.com>
+Signed-off-by: Evgeniy Didin <Evgeniy.Didin@synopsys.com>
+---
+ arch/arc/kernel/head.S        | 10 ++++++++++
+ arch/arc/kernel/setup.c       |  4 +++-
+ arch/arc/kernel/vmlinux.lds.S | 13 +++++++++++++
+ 3 files changed, 26 insertions(+), 1 deletion(-)
+
+--- a/arch/arc/kernel/head.S
++++ b/arch/arc/kernel/head.S
+@@ -88,6 +88,16 @@
+       DSP_EARLY_INIT
+ .endm
++      ; Here "patch-dtb" will embed external .dtb
++      ; Note "patch-dtb" searches for ASCII "OWRTDTB:" string
++      ; and pastes .dtb right after it, hense the string precedes
++      ; __image_dtb symbol.
++      .section .owrt, "aw",@progbits
++      .ascii  "OWRTDTB:"
++ENTRY(__image_dtb)
++      .fill   0x4000
++END(__image_dtb)
++
+       .section .init.text, "ax",@progbits
+ ;----------------------------------------------------------------
+--- a/arch/arc/kernel/setup.c
++++ b/arch/arc/kernel/setup.c
+@@ -450,6 +450,8 @@ static inline bool uboot_arg_invalid(uns
+ /* We always pass 0 as magic from U-boot */
+ #define UBOOT_MAGIC_VALUE     0
++extern struct boot_param_header __image_dtb;
++
+ void __init handle_uboot_args(void)
+ {
+       bool use_embedded_dtb = true;
+@@ -488,7 +490,7 @@ void __init handle_uboot_args(void)
+ ignore_uboot_args:
+       if (use_embedded_dtb) {
+-              machine_desc = setup_machine_fdt(__dtb_start);
++              machine_desc = setup_machine_fdt(&__image_dtb);
+               if (!machine_desc)
+                       panic("Embedded DT invalid\n");
+       }
+--- a/arch/arc/kernel/vmlinux.lds.S
++++ b/arch/arc/kernel/vmlinux.lds.S
+@@ -27,6 +27,19 @@ SECTIONS
+       . = CONFIG_LINUX_LINK_BASE;
++      /*
++      * In OpenWRT we want to patch built binary embedding .dtb of choice.
++      * This is implemented with "patch-dtb" utility which searches for
++      * "OWRTDTB:" string in first 16k of image and if it is found
++      * copies .dtb right after mentioned string.
++      *
++      * Note: "OWRTDTB:" won't be overwritten with .dtb, .dtb will follow it.
++      */
++      .owrt : {
++              *(.owrt)
++      . = ALIGN(PAGE_SIZE);
++      }
++
+       _int_vec_base_lds = .;
+       .vector : {
+               *(.vector)
diff --git a/target/linux/generic/pending-6.12/333-arc-enable-unaligned-access-in-kernel-mode.patch b/target/linux/generic/pending-6.12/333-arc-enable-unaligned-access-in-kernel-mode.patch
new file mode 100644 (file)
index 0000000..2c9808d
--- /dev/null
@@ -0,0 +1,24 @@
+From: Alexey Brodkin <abrodkin@synopsys.com>
+Subject: arc: enable unaligned access in kernel mode
+
+This enables misaligned access handling even in kernel mode.
+Some wireless drivers (ath9k-htc and mt7601u) use misaligned accesses
+here and there and to cope with that without fixing stuff in the drivers
+we're just gracefully handling it on ARC.
+
+Signed-off-by: Alexey Brodkin <abrodkin@synopsys.com>
+---
+ arch/arc/kernel/unaligned.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/arc/kernel/unaligned.c
++++ b/arch/arc/kernel/unaligned.c
+@@ -203,7 +203,7 @@ int misaligned_fixup(unsigned long addre
+       char buf[TASK_COMM_LEN];
+       /* handle user mode only and only if enabled by sysadmin */
+-      if (!user_mode(regs) || !unaligned_enabled)
++      if (!unaligned_enabled)
+               return 1;
+       if (no_unaligned_warning) {
diff --git a/target/linux/generic/pending-6.12/342-powerpc-Enable-kernel-XZ-compression-option-on-PPC_8.patch b/target/linux/generic/pending-6.12/342-powerpc-Enable-kernel-XZ-compression-option-on-PPC_8.patch
new file mode 100644 (file)
index 0000000..c812a08
--- /dev/null
@@ -0,0 +1,25 @@
+From 66770a004afe10df11d3902e16eaa0c2c39436bb Mon Sep 17 00:00:00 2001
+From: Pawel Dembicki <paweldembicki@gmail.com>
+Date: Fri, 24 May 2019 17:56:19 +0200
+Subject: [PATCH] powerpc: Enable kernel XZ compression option on PPC_85xx
+
+Enable kernel XZ compression option on PPC_85xx. Tested with
+simpleImage on TP-Link TL-WDR4900 (Freescale P1014 processor).
+
+Suggested-by: Christian Lamparter <chunkeey@gmail.com>
+Signed-off-by: Pawel Dembicki <paweldembicki@gmail.com>
+---
+ arch/powerpc/Kconfig | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/powerpc/Kconfig
++++ b/arch/powerpc/Kconfig
+@@ -254,7 +254,7 @@ config PPC
+       select HAVE_KERNEL_GZIP
+       select HAVE_KERNEL_LZMA                 if DEFAULT_UIMAGE
+       select HAVE_KERNEL_LZO                  if DEFAULT_UIMAGE
+-      select HAVE_KERNEL_XZ                   if PPC_BOOK3S || 44x
++      select HAVE_KERNEL_XZ                   if PPC_BOOK3S || 44x || PPC_85xx
+       select HAVE_KPROBES
+       select HAVE_KPROBES_ON_FTRACE
+       select HAVE_KRETPROBES
diff --git a/target/linux/generic/pending-6.12/350-mips-kernel-fix-detect_memory_region-function.patch b/target/linux/generic/pending-6.12/350-mips-kernel-fix-detect_memory_region-function.patch
new file mode 100644 (file)
index 0000000..a99eed4
--- /dev/null
@@ -0,0 +1,74 @@
+From: Shiji Yang <yangshiji66@outlook.com>
+Date: Wed, 13 Mar 2024 20:28:37 +0800
+Subject: [PATCH] mips: kernel: fix detect_memory_region() function
+
+1. Do not use memcmp() on unallocated memory, as the new introduced
+   fortify dynamic object size check[1] will report unexpected result.
+2. Use a fixed pattern instead of a random function pointer as the
+   magic value.
+3. Flip magic value and double check it.
+4. Enable this feature only for 32-bit CPUs. Currently, only ath79 and
+   ralink CPUs are using it.
+
+[1] 439a1bcac648 ("fortify: Use __builtin_dynamic_object_size() when available")
+Signed-off-by: Shiji Yang <yangshiji66@outlook.com>
+---
+ arch/mips/include/asm/bootinfo.h |  2 ++
+ arch/mips/kernel/setup.c         | 17 ++++++++++++-----
+ 2 files changed, 14 insertions(+), 5 deletions(-)
+
+--- a/arch/mips/include/asm/bootinfo.h
++++ b/arch/mips/include/asm/bootinfo.h
+@@ -93,7 +93,9 @@ const char *get_system_type(void);
+ extern unsigned long mips_machtype;
++#ifndef CONFIG_64BIT
+ extern void detect_memory_region(phys_addr_t start, phys_addr_t sz_min,  phys_addr_t sz_max);
++#endif
+ extern void prom_init(void);
+ extern void prom_free_prom_memory(void);
+--- a/arch/mips/kernel/setup.c
++++ b/arch/mips/kernel/setup.c
+@@ -86,21 +86,27 @@ static struct resource bss_resource = {
+ unsigned long __kaslr_offset __ro_after_init;
+ EXPORT_SYMBOL(__kaslr_offset);
+-static void *detect_magic __initdata = detect_memory_region;
+-
+ #ifdef CONFIG_MIPS_AUTO_PFN_OFFSET
+ unsigned long ARCH_PFN_OFFSET;
+ EXPORT_SYMBOL(ARCH_PFN_OFFSET);
+ #endif
++#ifndef CONFIG_64BIT
++static u32 detect_magic __initdata;
++#define MIPS_MEM_TEST_PATTERN         0xaa5555aa
++
+ void __init detect_memory_region(phys_addr_t start, phys_addr_t sz_min, phys_addr_t sz_max)
+ {
+-      void *dm = &detect_magic;
++      void *dm = (void *)KSEG1ADDR(&detect_magic);
+       phys_addr_t size;
+       for (size = sz_min; size < sz_max; size <<= 1) {
+-              if (!memcmp(dm, dm + size, sizeof(detect_magic)))
+-                      break;
++              __raw_writel(MIPS_MEM_TEST_PATTERN, dm);
++              if (__raw_readl(dm) == __raw_readl(dm + size)) {
++                      __raw_writel(~MIPS_MEM_TEST_PATTERN, dm);
++                      if (__raw_readl(dm) == __raw_readl(dm + size))
++                              break;
++              }
+       }
+       pr_debug("Memory: %lluMB of RAM detected at 0x%llx (min: %lluMB, max: %lluMB)\n",
+@@ -111,6 +117,7 @@ void __init detect_memory_region(phys_ad
+       memblock_add(start, size);
+ }
++#endif /* CONFIG_64BIT */
+ /*
+  * Manage initrd
diff --git a/target/linux/generic/pending-6.12/360-Revert-MIPS-mm-kmalloc-tlb_vpn-array-to-avoid-stack-.patch b/target/linux/generic/pending-6.12/360-Revert-MIPS-mm-kmalloc-tlb_vpn-array-to-avoid-stack-.patch
new file mode 100644 (file)
index 0000000..522fccd
--- /dev/null
@@ -0,0 +1,60 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Hauke Mehrtens <hauke@hauke-m.de>
+Date: Mon, 15 Dec 2025 01:45:07 +0100
+Subject: Revert "MIPS: mm: kmalloc tlb_vpn array to avoid stack overflow"
+
+This reverts commit 63a93d1cd6077d79735f804f5a4957bfb240280c.
+---
+ arch/mips/mm/tlb-r4k.c | 18 ++----------------
+ 1 file changed, 2 insertions(+), 16 deletions(-)
+
+--- a/arch/mips/mm/tlb-r4k.c
++++ b/arch/mips/mm/tlb-r4k.c
+@@ -12,7 +12,6 @@
+ #include <linux/init.h>
+ #include <linux/sched.h>
+ #include <linux/smp.h>
+-#include <linux/memblock.h>
+ #include <linux/mm.h>
+ #include <linux/hugetlb.h>
+ #include <linux/export.h>
+@@ -523,26 +522,17 @@ static int r4k_vpn_cmp(const void *a, co
+  * Initialise all TLB entries with unique values that do not clash with
+  * what we have been handed over and what we'll be using ourselves.
+  */
+-static void __ref r4k_tlb_uniquify(void)
++static void r4k_tlb_uniquify(void)
+ {
++      unsigned long tlb_vpns[1 << MIPS_CONF1_TLBS_SIZE];
+       int tlbsize = current_cpu_data.tlbsize;
+-      bool use_slab = slab_is_available();
+       int start = num_wired_entries();
+-      phys_addr_t tlb_vpn_size;
+-      unsigned long *tlb_vpns;
+       unsigned long vpn_mask;
+       int cnt, ent, idx, i;
+       vpn_mask = GENMASK(cpu_vmbits - 1, 13);
+       vpn_mask |= IS_ENABLED(CONFIG_64BIT) ? 3ULL << 62 : 1 << 31;
+-      tlb_vpn_size = tlbsize * sizeof(*tlb_vpns);
+-      tlb_vpns = (use_slab ?
+-                  kmalloc(tlb_vpn_size, GFP_KERNEL) :
+-                  memblock_alloc_raw(tlb_vpn_size, sizeof(*tlb_vpns)));
+-      if (WARN_ON(!tlb_vpns))
+-              return; /* Pray local_flush_tlb_all() is good enough. */
+-
+       htw_stop();
+       for (i = start, cnt = 0; i < tlbsize; i++, cnt++) {
+@@ -595,10 +585,6 @@ static void __ref r4k_tlb_uniquify(void)
+       tlbw_use_hazard();
+       htw_start();
+       flush_micro_tlb();
+-      if (use_slab)
+-              kfree(tlb_vpns);
+-      else
+-              memblock_free(tlb_vpns, tlb_vpn_size);
+ }
+ /*
diff --git a/target/linux/generic/pending-6.12/361-Revert-MIPS-mm-Prevent-a-TLB-shutdown-on-initial-uni.patch b/target/linux/generic/pending-6.12/361-Revert-MIPS-mm-Prevent-a-TLB-shutdown-on-initial-uni.patch
new file mode 100644 (file)
index 0000000..826ed8e
--- /dev/null
@@ -0,0 +1,146 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Hauke Mehrtens <hauke@hauke-m.de>
+Date: Mon, 15 Dec 2025 01:45:20 +0100
+Subject: Revert "MIPS: mm: Prevent a TLB shutdown on initial uniquification"
+
+This reverts commit 135178e90aa43ad949534e1d6e376c4034942caa.
+---
+ arch/mips/mm/tlb-r4k.c | 100 +++++++++++++++--------------------------
+ 1 file changed, 37 insertions(+), 63 deletions(-)
+
+--- a/arch/mips/mm/tlb-r4k.c
++++ b/arch/mips/mm/tlb-r4k.c
+@@ -15,7 +15,6 @@
+ #include <linux/mm.h>
+ #include <linux/hugetlb.h>
+ #include <linux/export.h>
+-#include <linux/sort.h>
+ #include <asm/cpu.h>
+ #include <asm/cpu-type.h>
+@@ -509,79 +508,55 @@ static int __init set_ntlb(char *str)
+ __setup("ntlb=", set_ntlb);
+-
+-/* Comparison function for EntryHi VPN fields.  */
+-static int r4k_vpn_cmp(const void *a, const void *b)
+-{
+-      long v = *(unsigned long *)a - *(unsigned long *)b;
+-      int s = sizeof(long) > sizeof(int) ? sizeof(long) * 8 - 1: 0;
+-      return s ? (v != 0) | v >> s : v;
+-}
+-
+-/*
+- * Initialise all TLB entries with unique values that do not clash with
+- * what we have been handed over and what we'll be using ourselves.
+- */
++/* Initialise all TLB entries with unique values */
+ static void r4k_tlb_uniquify(void)
+ {
+-      unsigned long tlb_vpns[1 << MIPS_CONF1_TLBS_SIZE];
+-      int tlbsize = current_cpu_data.tlbsize;
+-      int start = num_wired_entries();
+-      unsigned long vpn_mask;
+-      int cnt, ent, idx, i;
+-
+-      vpn_mask = GENMASK(cpu_vmbits - 1, 13);
+-      vpn_mask |= IS_ENABLED(CONFIG_64BIT) ? 3ULL << 62 : 1 << 31;
++      int entry = num_wired_entries();
+       htw_stop();
++      write_c0_entrylo0(0);
++      write_c0_entrylo1(0);
+-      for (i = start, cnt = 0; i < tlbsize; i++, cnt++) {
+-              unsigned long vpn;
++      while (entry < current_cpu_data.tlbsize) {
++              unsigned long asid_mask = cpu_asid_mask(&current_cpu_data);
++              unsigned long asid = 0;
++              int idx;
+-              write_c0_index(i);
+-              mtc0_tlbr_hazard();
+-              tlb_read();
+-              tlb_read_hazard();
+-              vpn = read_c0_entryhi();
+-              vpn &= vpn_mask & PAGE_MASK;
+-              tlb_vpns[cnt] = vpn;
++              /* Skip wired MMID to make ginvt_mmid work */
++              if (cpu_has_mmid)
++                      asid = MMID_KERNEL_WIRED + 1;
+-              /* Prevent any large pages from overlapping regular ones.  */
+-              write_c0_pagemask(read_c0_pagemask() & PM_DEFAULT_MASK);
++              /* Check for match before using UNIQUE_ENTRYHI */
++              do {
++                      if (cpu_has_mmid) {
++                              write_c0_memorymapid(asid);
++                              write_c0_entryhi(UNIQUE_ENTRYHI(entry));
++                      } else {
++                              write_c0_entryhi(UNIQUE_ENTRYHI(entry) | asid);
++                      }
++                      mtc0_tlbw_hazard();
++                      tlb_probe();
++                      tlb_probe_hazard();
++                      idx = read_c0_index();
++                      /* No match or match is on current entry */
++                      if (idx < 0 || idx == entry)
++                              break;
++                      /*
++                       * If we hit a match, we need to try again with
++                       * a different ASID.
++                       */
++                      asid++;
++              } while (asid < asid_mask);
++
++              if (idx >= 0 && idx != entry)
++                      panic("Unable to uniquify TLB entry %d", idx);
++
++              write_c0_index(entry);
+               mtc0_tlbw_hazard();
+               tlb_write_indexed();
+-              tlbw_use_hazard();
++              entry++;
+       }
+-      sort(tlb_vpns, cnt, sizeof(tlb_vpns[0]), r4k_vpn_cmp, NULL);
+-
+-      write_c0_pagemask(PM_DEFAULT_MASK);
+-      write_c0_entrylo0(0);
+-      write_c0_entrylo1(0);
+-
+-      idx = 0;
+-      ent = tlbsize;
+-      for (i = start; i < tlbsize; i++)
+-              while (1) {
+-                      unsigned long entryhi, vpn;
+-
+-                      entryhi = UNIQUE_ENTRYHI(ent);
+-                      vpn = entryhi & vpn_mask & PAGE_MASK;
+-
+-                      if (idx >= cnt || vpn < tlb_vpns[idx]) {
+-                              write_c0_entryhi(entryhi);
+-                              write_c0_index(i);
+-                              mtc0_tlbw_hazard();
+-                              tlb_write_indexed();
+-                              ent++;
+-                              break;
+-                      } else if (vpn == tlb_vpns[idx]) {
+-                              ent++;
+-                      } else {
+-                              idx++;
+-                      }
+-              }
+-
+       tlbw_use_hazard();
+       htw_start();
+       flush_micro_tlb();
+@@ -627,7 +602,6 @@ static void r4k_tlb_configure(void)
+       /* From this point on the ARC firmware is dead.  */
+       r4k_tlb_uniquify();
+-      local_flush_tlb_all();
+       /* Did I tell you that ARC SUCKS?  */
+ }
diff --git a/target/linux/generic/pending-6.12/400-mtd-mtdsplit-support.patch b/target/linux/generic/pending-6.12/400-mtd-mtdsplit-support.patch
new file mode 100644 (file)
index 0000000..2a3a285
--- /dev/null
@@ -0,0 +1,328 @@
+From 39717277d5c87bdb183cf2f258957b44ba99b4df Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 11:47:35 +0200
+Subject: [PATCH] mtd: mtdsplit support
+
+---
+ drivers/mtd/Kconfig            |  19 ++++
+ drivers/mtd/Makefile           |   2 +
+ drivers/mtd/mtdpart.c          | 169 ++++++++++++++++++++++++++++-----
+ include/linux/mtd/mtd.h        |  25 +++++
+ include/linux/mtd/partitions.h |   7 ++
+ 5 files changed, 197 insertions(+), 25 deletions(-)
+
+--- a/drivers/mtd/Kconfig
++++ b/drivers/mtd/Kconfig
+@@ -12,6 +12,25 @@ menuconfig MTD
+ if MTD
++menu "OpenWrt specific MTD options"
++
++config MTD_ROOTFS_ROOT_DEV
++      bool "Automatically set 'rootfs' partition to be root filesystem"
++      default y
++
++config MTD_SPLIT_FIRMWARE
++      bool "Automatically split firmware partition for kernel+rootfs"
++      default y
++
++config MTD_SPLIT_FIRMWARE_NAME
++      string "Firmware partition name"
++      depends on MTD_SPLIT_FIRMWARE
++      default "firmware"
++
++source "drivers/mtd/mtdsplit/Kconfig"
++
++endmenu
++
+ config MTD_TESTS
+       tristate "MTD tests support (DANGEROUS)"
+       depends on m
+--- a/drivers/mtd/Makefile
++++ b/drivers/mtd/Makefile
+@@ -9,6 +9,8 @@ mtd-y                          := mtdcore.o mtdsuper.o mtdconc
+ obj-y                         += parsers/
++obj-$(CONFIG_MTD_SPLIT)               += mtdsplit/
++
+ # 'Users' - code which presents functionality to userspace.
+ obj-$(CONFIG_MTD_BLKDEVS)     += mtd_blkdevs.o
+ obj-$(CONFIG_MTD_BLOCK)               += mtdblock.o
+--- a/drivers/mtd/mtdpart.c
++++ b/drivers/mtd/mtdpart.c
+@@ -15,11 +15,13 @@
+ #include <linux/kmod.h>
+ #include <linux/mtd/mtd.h>
+ #include <linux/mtd/partitions.h>
++#include <linux/magic.h>
+ #include <linux/err.h>
+ #include <linux/of.h>
+ #include <linux/of_platform.h>
+ #include "mtdcore.h"
++#include "mtdsplit/mtdsplit.h"
+ /*
+  * MTD methods which simply translate the effective address and pass through
+@@ -242,6 +244,147 @@ static int mtd_add_partition_attrs(struc
+       return ret;
+ }
++static DEFINE_SPINLOCK(part_parser_lock);
++static LIST_HEAD(part_parsers);
++
++static struct mtd_part_parser *mtd_part_parser_get(const char *name)
++{
++      struct mtd_part_parser *p, *ret = NULL;
++
++      spin_lock(&part_parser_lock);
++
++      list_for_each_entry(p, &part_parsers, list)
++              if (!strcmp(p->name, name) && try_module_get(p->owner)) {
++                      ret = p;
++                      break;
++              }
++
++      spin_unlock(&part_parser_lock);
++
++      return ret;
++}
++
++static inline void mtd_part_parser_put(const struct mtd_part_parser *p)
++{
++      module_put(p->owner);
++}
++
++static struct mtd_part_parser *
++get_partition_parser_by_type(enum mtd_parser_type type,
++                           struct mtd_part_parser *start)
++{
++      struct mtd_part_parser *p, *ret = NULL;
++
++      spin_lock(&part_parser_lock);
++
++      p = list_prepare_entry(start, &part_parsers, list);
++      if (start)
++              mtd_part_parser_put(start);
++
++      list_for_each_entry_continue(p, &part_parsers, list) {
++              if (p->type == type && try_module_get(p->owner)) {
++                      ret = p;
++                      break;
++              }
++      }
++
++      spin_unlock(&part_parser_lock);
++
++      return ret;
++}
++
++static int parse_mtd_partitions_by_type(struct mtd_info *master,
++                                      enum mtd_parser_type type,
++                                      const struct mtd_partition **pparts,
++                                      struct mtd_part_parser_data *data)
++{
++      struct mtd_part_parser *prev = NULL;
++      int ret = 0;
++
++      while (1) {
++              struct mtd_part_parser *parser;
++
++              parser = get_partition_parser_by_type(type, prev);
++              if (!parser)
++                      break;
++
++              ret = (*parser->parse_fn)(master, pparts, data);
++
++              if (ret > 0) {
++                      mtd_part_parser_put(parser);
++                      printk(KERN_NOTICE
++                             "%d %s partitions found on MTD device %s\n",
++                             ret, parser->name, master->name);
++                      break;
++              }
++
++              prev = parser;
++      }
++
++      return ret;
++}
++
++static int
++run_parsers_by_type(struct mtd_info *child, enum mtd_parser_type type)
++{
++      struct mtd_partition *parts;
++      int nr_parts;
++      int i;
++
++      nr_parts = parse_mtd_partitions_by_type(child, type, (const struct mtd_partition **)&parts,
++                                              NULL);
++      if (nr_parts <= 0)
++              return nr_parts;
++
++      if (WARN_ON(!parts))
++              return 0;
++
++      for (i = 0; i < nr_parts; i++) {
++              /* adjust partition offsets */
++              parts[i].offset += child->part.offset;
++
++              mtd_add_partition(child->parent,
++                                parts[i].name,
++                                parts[i].offset,
++                                parts[i].size);
++      }
++
++      kfree(parts);
++
++      return nr_parts;
++}
++
++#ifdef CONFIG_MTD_SPLIT_FIRMWARE_NAME
++#define SPLIT_FIRMWARE_NAME   CONFIG_MTD_SPLIT_FIRMWARE_NAME
++#else
++#define SPLIT_FIRMWARE_NAME   "unused"
++#endif
++
++static void split_firmware(struct mtd_info *master, struct mtd_info *part)
++{
++      run_parsers_by_type(part, MTD_PARSER_TYPE_FIRMWARE);
++}
++
++static void mtd_partition_split(struct mtd_info *master, struct mtd_info *part)
++{
++      static int rootfs_found = 0;
++
++      if (rootfs_found)
++              return;
++
++      if (of_property_present(mtd_get_of_node(part), "linux,rootfs") ||
++          !strcmp(part->name, "rootfs")) {
++              run_parsers_by_type(part, MTD_PARSER_TYPE_ROOTFS);
++
++              rootfs_found = 1;
++      }
++
++      if (IS_ENABLED(CONFIG_MTD_SPLIT_FIRMWARE) &&
++          !strcmp(part->name, SPLIT_FIRMWARE_NAME) &&
++          !of_property_present(mtd_get_of_node(part), "compatible"))
++              split_firmware(master, part);
++}
++
+ int mtd_add_partition(struct mtd_info *parent, const char *name,
+                     long long offset, long long length)
+ {
+@@ -280,6 +423,7 @@ int mtd_add_partition(struct mtd_info *p
+       if (ret)
+               goto err_remove_part;
++      mtd_partition_split(parent, child);
+       mtd_add_partition_attrs(child);
+       return 0;
+@@ -423,6 +567,7 @@ int add_mtd_partitions(struct mtd_info *
+                       goto err_del_partitions;
+               }
++              mtd_partition_split(master, child);
+               mtd_add_partition_attrs(child);
+               /* Look for subpartitions (skip if no maching parser found) */
+@@ -446,31 +591,6 @@ err_del_partitions:
+       return ret;
+ }
+-static DEFINE_SPINLOCK(part_parser_lock);
+-static LIST_HEAD(part_parsers);
+-
+-static struct mtd_part_parser *mtd_part_parser_get(const char *name)
+-{
+-      struct mtd_part_parser *p, *ret = NULL;
+-
+-      spin_lock(&part_parser_lock);
+-
+-      list_for_each_entry(p, &part_parsers, list)
+-              if (!strcmp(p->name, name) && try_module_get(p->owner)) {
+-                      ret = p;
+-                      break;
+-              }
+-
+-      spin_unlock(&part_parser_lock);
+-
+-      return ret;
+-}
+-
+-static inline void mtd_part_parser_put(const struct mtd_part_parser *p)
+-{
+-      module_put(p->owner);
+-}
+-
+ /*
+  * Many partition parsers just expected the core to kfree() all their data in
+  * one chunk. Do that by default.
+--- a/include/linux/mtd/mtd.h
++++ b/include/linux/mtd/mtd.h
+@@ -615,6 +615,24 @@ static inline void mtd_align_erase_req(s
+               req->len += mtd->erasesize - mod;
+ }
++static inline uint64_t mtd_roundup_to_eb(uint64_t sz, struct mtd_info *mtd)
++{
++      if (mtd_mod_by_eb(sz, mtd) == 0)
++              return sz;
++
++      /* Round up to next erase block */
++      return (mtd_div_by_eb(sz, mtd) + 1) * mtd->erasesize;
++}
++
++static inline uint64_t mtd_rounddown_to_eb(uint64_t sz, struct mtd_info *mtd)
++{
++      if (mtd_mod_by_eb(sz, mtd) == 0)
++              return sz;
++
++      /* Round down to the start of the current erase block */
++      return (mtd_div_by_eb(sz, mtd)) * mtd->erasesize;
++}
++
+ static inline uint32_t mtd_div_by_ws(uint64_t sz, struct mtd_info *mtd)
+ {
+       if (mtd->writesize_shift)
+@@ -688,6 +706,13 @@ extern struct mtd_info *of_get_mtd_devic
+ extern struct mtd_info *get_mtd_device_nm(const char *name);
+ extern void put_mtd_device(struct mtd_info *mtd);
++static inline uint64_t mtdpart_get_offset(const struct mtd_info *mtd)
++{
++      if (!mtd_is_partition(mtd))
++              return 0;
++
++      return mtd->part.offset;
++}
+ struct mtd_notifier {
+       void (*add)(struct mtd_info *mtd);
+--- a/include/linux/mtd/partitions.h
++++ b/include/linux/mtd/partitions.h
+@@ -75,6 +75,12 @@ struct mtd_part_parser_data {
+  * Functions dealing with the various ways of partitioning the space
+  */
++enum mtd_parser_type {
++      MTD_PARSER_TYPE_DEVICE = 0,
++      MTD_PARSER_TYPE_ROOTFS,
++      MTD_PARSER_TYPE_FIRMWARE,
++};
++
+ struct mtd_part_parser {
+       struct list_head list;
+       struct module *owner;
+@@ -83,6 +89,7 @@ struct mtd_part_parser {
+       int (*parse_fn)(struct mtd_info *, const struct mtd_partition **,
+                       struct mtd_part_parser_data *);
+       void (*cleanup)(const struct mtd_partition *pparts, int nr_parts);
++      enum mtd_parser_type type;
+ };
+ /* Container for passing around a set of parsed partitions */
diff --git a/target/linux/generic/pending-6.12/402-mtd-spi-nor-write-support-for-minor-aligned-partitions.patch b/target/linux/generic/pending-6.12/402-mtd-spi-nor-write-support-for-minor-aligned-partitions.patch
new file mode 100644 (file)
index 0000000..e8ad204
--- /dev/null
@@ -0,0 +1,245 @@
+From acacdac272927ae1d96e0bca51eb82899671eaea Mon Sep 17 00:00:00 2001
+From: John Thomson <git@johnthomson.fastmail.com.au>
+Date: Fri, 25 Dec 2020 18:50:08 +1000
+Subject: [PATCH] mtd: spi-nor: write support for minor aligned partitions
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Do not prevent writing to mtd partitions where a partition boundary sits
+on a minor erasesize boundary.
+This addresses a FIXME that has been present since the start of the
+linux git history:
+/* Doesn't start on a boundary of major erase size */
+/* FIXME: Let it be writable if it is on a boundary of
+ * _minor_ erase size though */
+
+Allow a uniform erase region spi-nor device to be configured
+to use the non-uniform erase regions code path for an erase with:
+CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE=y
+
+On supporting hardware (SECT_4K: majority of current SPI-NOR device)
+provide the facility for an erase to use the least number
+of SPI-NOR operations, as well as access to 4K erase without
+requiring CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
+
+Introduce erasesize_minor to the mtd struct,
+the smallest erasesize supported by the device
+
+On existing devices, this is useful where write support is wanted
+for data on a 4K partition, such as some u-boot-env partitions,
+or RouterBoot soft_config, while still netting the performance
+benefits of using 64K sectors
+
+Performance:
+time mtd erase firmware
+OpenWrt 5.10 ramips MT7621 w25q128jv 0xfc0000 partition length
+
+Without this patch
+MTD_SPI_NOR_USE_4K_SECTORS=y   |n
+real    2m 11.66s              |0m 50.86s
+user    0m 0.00s               |0m 0.00s
+sys     1m 56.20s              |0m 50.80s
+
+With this patch
+MTD_SPI_NOR_USE_VARIABLE_ERASE=n|y             |4K_SECTORS=y
+real    0m 51.68s              |0m 50.85s      |2m 12.89s
+user    0m 0.00s               |0m 0.00s       |0m 0.01s
+sys     0m 46.94s              |0m 50.38s      |2m 12.46s
+
+Signed-off-by: John Thomson <git@johnthomson.fastmail.com.au>
+Signed-off-by: Thibaut VARÈNE <hacks+kernel@slashdirt.org>
+
+---
+
+checkpatch does not like the printk(KERN_WARNING
+these should be changed separately beforehand?
+
+Changes v1 -> v2:
+Added mtdcore sysfs for erasesize_minor
+Removed finding minor erasesize for variable erase regions device,
+as untested and no responses regarding it.
+Moved IF_ENABLED for SPINOR variable erase to guard setting
+erasesize_minor in spi-nor/core.c
+Removed setting erasesize to minor where partition boundaries require
+minor erase to be writable
+Simplified minor boundary check by relying on minor being a factor of
+major
+
+Changes RFC -> v1:
+Fix uninitialized variable smatch warning
+Reported-by: kernel test robot <lkp@intel.com>
+Reported-by: Dan Carpenter <dan.carpenter@oracle.com>
+---
+ drivers/mtd/mtdcore.c       | 10 ++++++++++
+ drivers/mtd/mtdpart.c       | 35 +++++++++++++++++++++++++----------
+ drivers/mtd/spi-nor/Kconfig | 10 ++++++++++
+ drivers/mtd/spi-nor/core.c  | 11 +++++++++--
+ include/linux/mtd/mtd.h     |  2 ++
+ 5 files changed, 56 insertions(+), 12 deletions(-)
+
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -199,6 +199,15 @@ static ssize_t mtd_erasesize_show(struct
+ }
+ MTD_DEVICE_ATTR_RO(erasesize);
++static ssize_t mtd_erasesize_minor_show(struct device *dev,
++              struct device_attribute *attr, char *buf)
++{
++      struct mtd_info *mtd = dev_get_drvdata(dev);
++
++      return sysfs_emit(buf, "%lu\n", (unsigned long)mtd->erasesize_minor);
++}
++MTD_DEVICE_ATTR_RO(erasesize_minor);
++
+ static ssize_t mtd_writesize_show(struct device *dev,
+               struct device_attribute *attr, char *buf)
+ {
+@@ -344,6 +353,7 @@ static struct attribute *mtd_attrs[] = {
+       &dev_attr_flags.attr,
+       &dev_attr_size.attr,
+       &dev_attr_erasesize.attr,
++      &dev_attr_erasesize_minor.attr,
+       &dev_attr_writesize.attr,
+       &dev_attr_subpagesize.attr,
+       &dev_attr_oobsize.attr,
+--- a/drivers/mtd/mtdpart.c
++++ b/drivers/mtd/mtdpart.c
+@@ -47,6 +47,7 @@ static struct mtd_info *allocate_partiti
+       struct mtd_info *master = mtd_get_master(parent);
+       int wr_alignment = (parent->flags & MTD_NO_ERASE) ?
+                          master->writesize : master->erasesize;
++      int wr_alignment_minor = 0;
+       u64 parent_size = mtd_is_partition(parent) ?
+                         parent->part.size : parent->size;
+       struct mtd_info *child;
+@@ -171,6 +172,7 @@ static struct mtd_info *allocate_partiti
+       } else {
+               /* Single erase size */
+               child->erasesize = master->erasesize;
++              child->erasesize_minor = master->erasesize_minor;
+       }
+       /*
+@@ -178,26 +180,39 @@ static struct mtd_info *allocate_partiti
+        * exposes several regions with different erasesize. Adjust
+        * wr_alignment accordingly.
+        */
+-      if (!(child->flags & MTD_NO_ERASE))
++      if (!(child->flags & MTD_NO_ERASE)) {
+               wr_alignment = child->erasesize;
++              wr_alignment_minor = child->erasesize_minor;
++      }
+       tmp = mtd_get_master_ofs(child, 0);
+       remainder = do_div(tmp, wr_alignment);
+       if ((child->flags & MTD_WRITEABLE) && remainder) {
+-              /* Doesn't start on a boundary of major erase size */
+-              /* FIXME: Let it be writable if it is on a boundary of
+-               * _minor_ erase size though */
+-              child->flags &= ~MTD_WRITEABLE;
+-              printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase/write block boundary -- force read-only\n",
+-                      part->name);
++              if (wr_alignment_minor) {
++                      /* rely on minor being a factor of major erasesize */
++                      tmp = remainder;
++                      remainder = do_div(tmp, wr_alignment_minor);
++              }
++              if (remainder) {
++                      child->flags &= ~MTD_WRITEABLE;
++                      printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase/write block boundary -- force read-only\n",
++                              part->name);
++              }
+       }
+       tmp = mtd_get_master_ofs(child, 0) + child->part.size;
+       remainder = do_div(tmp, wr_alignment);
+       if ((child->flags & MTD_WRITEABLE) && remainder) {
+-              child->flags &= ~MTD_WRITEABLE;
+-              printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase/write block -- force read-only\n",
+-                      part->name);
++              if (wr_alignment_minor) {
++                      tmp = remainder;
++                      remainder = do_div(tmp, wr_alignment_minor);
++              }
++
++              if (remainder) {
++                      child->flags &= ~MTD_WRITEABLE;
++                      printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase/write block -- force read-only\n",
++                              part->name);
++              }
+       }
+       child->size = child->part.size;
+--- a/drivers/mtd/spi-nor/Kconfig
++++ b/drivers/mtd/spi-nor/Kconfig
+@@ -10,6 +10,16 @@ menuconfig MTD_SPI_NOR
+ if MTD_SPI_NOR
++config MTD_SPI_NOR_USE_VARIABLE_ERASE
++      bool "Disable uniform_erase to allow use of all hardware supported erasesizes"
++      depends on !MTD_SPI_NOR_USE_4K_SECTORS
++      default n
++      help
++        Allow mixed use of all hardware supported erasesizes,
++        by forcing spi_nor to use the multiple eraseregions code path.
++        For example: A 68K erase will use one 64K erase, and one 4K erase
++        on supporting hardware.
++
+ config MTD_SPI_NOR_USE_4K_SECTORS
+       bool "Use small 4096 B erase sectors"
+       default y
+--- a/drivers/mtd/spi-nor/core.c
++++ b/drivers/mtd/spi-nor/core.c
+@@ -1158,6 +1158,8 @@ static u8 spi_nor_convert_3to4_erase(u8
+ static bool spi_nor_has_uniform_erase(const struct spi_nor *nor)
+ {
++      if (IS_ENABLED(CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE))
++              return false;
+       return !!nor->params->erase_map.uniform_region.erase_mask;
+ }
+@@ -2516,6 +2518,7 @@ static int spi_nor_select_erase(struct s
+ {
+       struct spi_nor_erase_map *map = &nor->params->erase_map;
+       const struct spi_nor_erase_type *erase = NULL;
++      const struct spi_nor_erase_type *erase_minor = NULL;
+       struct mtd_info *mtd = &nor->mtd;
+       int i;
+@@ -2542,8 +2545,9 @@ static int spi_nor_select_erase(struct s
+        */
+       for (i = SNOR_ERASE_TYPE_MAX - 1; i >= 0; i--) {
+               if (map->erase_type[i].size) {
+-                      erase = &map->erase_type[i];
+-                      break;
++                      if (!erase)
++                              erase = &map->erase_type[i];
++                      erase_minor = &map->erase_type[i];
+               }
+       }
+@@ -2551,6 +2555,9 @@ static int spi_nor_select_erase(struct s
+               return -EINVAL;
+       mtd->erasesize = erase->size;
++      if (IS_ENABLED(CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE) &&
++                      erase_minor && erase_minor->size < erase->size)
++              mtd->erasesize_minor = erase_minor->size;
+       return 0;
+ }
+--- a/include/linux/mtd/mtd.h
++++ b/include/linux/mtd/mtd.h
+@@ -245,6 +245,8 @@ struct mtd_info {
+        * information below if they desire
+        */
+       uint32_t erasesize;
++      /* "Minor" (smallest) erase size supported by the whole device */
++      uint32_t erasesize_minor;
+       /* Minimal writable flash unit size. In case of NOR flash it is 1 (even
+        * though individual bits can be cleared), in case of NAND flash it is
+        * one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
diff --git a/target/linux/generic/pending-6.12/417-mtd-spi-nand-macronix-disable-continuous-read-for-MX.patch b/target/linux/generic/pending-6.12/417-mtd-spi-nand-macronix-disable-continuous-read-for-MX.patch
new file mode 100644 (file)
index 0000000..65531e0
--- /dev/null
@@ -0,0 +1,39 @@
+From 435afd182e8f5997033da1d69e56094ecb112cad Mon Sep 17 00:00:00 2001
+From: Chukun Pan <amadeus@jmu.edu.cn>
+Date: Thu, 10 Jun 2025 23:10:16 +0800
+Subject: [PATCH] mtd: spi-nand: macronix: disable continuous read for
+ MX35LFxGE4AD
+
+In on-die ECC mode, enabling continuous read will cause ubi_io_read error.
+[    7.852000] ubi0 warning: ubi_io_read: error -5 while reading ...
+
+So disable it first. Tested on GL.iNet GL-MT3000 with MX35LF2GE4AD flash.
+
+Fixes: 11813857864f ("mtd: spi-nand: macronix: Continuous read support")
+Signed-off-by: Chukun Pan <amadeus@jmu.edu.cn>
+---
+ drivers/mtd/nand/spi/macronix.c | 2 --
+ 1 file changed, 2 deletions(-)
+
+--- a/drivers/mtd/nand/spi/macronix.c
++++ b/drivers/mtd/nand/spi/macronix.c
+@@ -167,8 +167,7 @@ static const struct spinand_info macroni
+                                             &update_cache_variants),
+                    SPINAND_HAS_QE_BIT,
+                    SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
+-                                   macronix_ecc_get_status),
+-                   SPINAND_CONT_READ(macronix_set_cont_read)),
++                                   macronix_ecc_get_status)),
+       SPINAND_INFO("MX35LF4GE4AD",
+                    SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x37, 0x03),
+                    NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1),
+@@ -178,8 +177,7 @@ static const struct spinand_info macroni
+                                             &update_cache_variants),
+                    SPINAND_HAS_QE_BIT,
+                    SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
+-                                   macronix_ecc_get_status),
+-                   SPINAND_CONT_READ(macronix_set_cont_read)),
++                                   macronix_ecc_get_status)),
+       SPINAND_INFO("MX35LF1G24AD",
+                    SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14, 0x03),
+                    NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
diff --git a/target/linux/generic/pending-6.12/420-mtd-redboot_space.patch b/target/linux/generic/pending-6.12/420-mtd-redboot_space.patch
new file mode 100644 (file)
index 0000000..5518ea7
--- /dev/null
@@ -0,0 +1,41 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: add patch for including unpartitioned space in the rootfs partition for redboot devices (if applicable)
+
+[john@phrozen.org: used by ixp and others]
+
+lede-commit: 394918851f84e4d00fa16eb900e7700e95091f00
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ drivers/mtd/redboot.c | 19 +++++++++++++------
+ 1 file changed, 13 insertions(+), 6 deletions(-)
+
+--- a/drivers/mtd/parsers/redboot.c
++++ b/drivers/mtd/parsers/redboot.c
+@@ -278,14 +278,21 @@ nogood:
+ #endif
+               names += strlen(names) + 1;
+-#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
+               if (fl->next && fl->img->flash_base + fl->img->size + master->erasesize <= fl->next->img->flash_base) {
+-                      i++;
+-                      parts[i].offset = parts[i - 1].size + parts[i - 1].offset;
+-                      parts[i].size = fl->next->img->flash_base - parts[i].offset;
+-                      parts[i].name = nullname;
+-              }
++                      if (!strcmp(parts[i].name, "rootfs")) {
++                              parts[i].size = fl->next->img->flash_base;
++                              parts[i].size &= ~(master->erasesize - 1);
++                              parts[i].size -= parts[i].offset;
++#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
++                              nrparts--;
++                      } else {
++                              i++;
++                              parts[i].offset = parts[i-1].size + parts[i-1].offset;
++                              parts[i].size = fl->next->img->flash_base - parts[i].offset;
++                              parts[i].name = nullname;
+ #endif
++                      }
++              }
+               tmp_fl = fl;
+               fl = fl->next;
+               kfree(tmp_fl);
diff --git a/target/linux/generic/pending-6.12/430-mtd-add-myloader-partition-parser.patch b/target/linux/generic/pending-6.12/430-mtd-add-myloader-partition-parser.patch
new file mode 100644 (file)
index 0000000..8ce1123
--- /dev/null
@@ -0,0 +1,229 @@
+From: Florian Fainelli <f.fainelli@gmail.com>
+Subject: Add myloader partition table parser
+
+[john@phozen.org: shoud be upstreamable]
+
+lede-commit: d8bf22859b51faa09d22c056fe221a45d2f7a3b8
+Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
+[adjust for kernel 5.4, add myloader.c to patch]
+Signed-off-by: Adrian Schmutzler <freifunk@adrianschmutzler.de>
+
+--- a/drivers/mtd/parsers/Kconfig
++++ b/drivers/mtd/parsers/Kconfig
+@@ -62,6 +62,22 @@ config MTD_CMDLINE_PARTS
+         If unsure, say 'N'.
++config MTD_MYLOADER_PARTS
++      tristate "MyLoader partition parsing"
++      depends on ADM5120 || ATH79
++      help
++        MyLoader is a bootloader which allows the user to define partitions
++        in flash devices, by putting a table in the second erase block
++        on the device, similar to a partition table. This table gives the
++        offsets and lengths of the user defined partitions.
++
++        If you need code which can detect and parse these tables, and
++        register MTD 'partitions' corresponding to each image detected,
++        enable this option.
++
++        You will still need the parsing functions to be called by the driver
++        for your particular device. It won't happen automatically.
++
+ config MTD_OF_PARTS
+       tristate "OpenFirmware (device tree) partitioning parser"
+       default y
+--- a/drivers/mtd/parsers/Makefile
++++ b/drivers/mtd/parsers/Makefile
+@@ -3,6 +3,7 @@ obj-$(CONFIG_MTD_BCM47XX_PARTS)                += bcm4
+ obj-$(CONFIG_MTD_BCM63XX_PARTS)               += bcm63xxpart.o
+ obj-$(CONFIG_MTD_BRCM_U_BOOT)         += brcm_u-boot.o
+ obj-$(CONFIG_MTD_CMDLINE_PARTS)               += cmdlinepart.o
++obj-$(CONFIG_MTD_MYLOADER_PARTS)              += myloader.o
+ obj-$(CONFIG_MTD_OF_PARTS)            += ofpart.o
+ ofpart-y                              += ofpart_core.o
+ ofpart-$(CONFIG_MTD_OF_PARTS_BCM4908) += ofpart_bcm4908.o
+--- /dev/null
++++ b/drivers/mtd/parsers/myloader.c
+@@ -0,0 +1,181 @@
++/*
++ *  Parse MyLoader-style flash partition tables and produce a Linux partition
++ *  array to match.
++ *
++ *  Copyright (C) 2007-2009 Gabor Juhos <juhosg@openwrt.org>
++ *
++ *  This file was based on drivers/mtd/redboot.c
++ *  Author: Red Hat, Inc. - David Woodhouse <dwmw2@cambridge.redhat.com>
++ *
++ *  This program is free software; you can redistribute it and/or modify it
++ *  under the terms of the GNU General Public License version 2 as published
++ *  by the Free Software Foundation.
++ */
++
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/version.h>
++#include <linux/slab.h>
++#include <linux/init.h>
++#include <linux/vmalloc.h>
++#include <linux/mtd/mtd.h>
++#include <linux/mtd/partitions.h>
++#include <linux/byteorder/generic.h>
++#include <linux/myloader.h>
++
++#define BLOCK_LEN_MIN         0x10000
++#define PART_NAME_LEN         32
++
++struct part_data {
++      struct mylo_partition_table     tab;
++      char names[MYLO_MAX_PARTITIONS][PART_NAME_LEN];
++};
++
++static int myloader_parse_partitions(struct mtd_info *master,
++                                   const struct mtd_partition **pparts,
++                                   struct mtd_part_parser_data *data)
++{
++      struct part_data *buf;
++      struct mylo_partition_table *tab;
++      struct mylo_partition *part;
++      struct mtd_partition *mtd_parts;
++      struct mtd_partition *mtd_part;
++      int num_parts;
++      int ret, i;
++      size_t retlen;
++      char *names;
++      unsigned long offset;
++      unsigned long blocklen;
++
++      buf = vmalloc(sizeof(*buf));
++      if (!buf) {
++              return -ENOMEM;
++              goto out;
++      }
++      tab = &buf->tab;
++
++      blocklen = master->erasesize;
++      if (blocklen < BLOCK_LEN_MIN)
++              blocklen = BLOCK_LEN_MIN;
++
++      offset = blocklen;
++
++      /* Find the partition table */
++      for (i = 0; i < 4; i++, offset += blocklen) {
++              printk(KERN_DEBUG "%s: searching for MyLoader partition table"
++                              " at offset 0x%lx\n", master->name, offset);
++
++              ret = mtd_read(master, offset, sizeof(*buf), &retlen,
++                             (void *)buf);
++              if (ret)
++                      goto out_free_buf;
++
++              if (retlen != sizeof(*buf)) {
++                      ret = -EIO;
++                      goto out_free_buf;
++              }
++
++              /* Check for Partition Table magic number */
++              if (tab->magic == le32_to_cpu(MYLO_MAGIC_PARTITIONS))
++                      break;
++
++      }
++
++      if (tab->magic != le32_to_cpu(MYLO_MAGIC_PARTITIONS)) {
++              printk(KERN_DEBUG "%s: no MyLoader partition table found\n",
++                      master->name);
++              ret = 0;
++              goto out_free_buf;
++      }
++
++      /* The MyLoader and the Partition Table is always present */
++      num_parts = 2;
++
++      /* Detect number of used partitions */
++      for (i = 0; i < MYLO_MAX_PARTITIONS; i++) {
++              part = &tab->partitions[i];
++
++              if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE)
++                      continue;
++
++              num_parts++;
++      }
++
++      mtd_parts = kzalloc((num_parts * sizeof(*mtd_part) +
++                              num_parts * PART_NAME_LEN), GFP_KERNEL);
++
++      if (!mtd_parts) {
++              ret = -ENOMEM;
++              goto out_free_buf;
++      }
++
++      mtd_part = mtd_parts;
++      names = (char *)&mtd_parts[num_parts];
++
++      strncpy(names, "myloader", PART_NAME_LEN);
++      mtd_part->name = names;
++      mtd_part->offset = 0;
++      mtd_part->size = offset;
++      mtd_part->mask_flags = MTD_WRITEABLE;
++      mtd_part++;
++      names += PART_NAME_LEN;
++
++      strncpy(names, "partition_table", PART_NAME_LEN);
++      mtd_part->name = names;
++      mtd_part->offset = offset;
++      mtd_part->size = blocklen;
++      mtd_part->mask_flags = MTD_WRITEABLE;
++      mtd_part++;
++      names += PART_NAME_LEN;
++
++      for (i = 0; i < MYLO_MAX_PARTITIONS; i++) {
++              part = &tab->partitions[i];
++
++              if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE)
++                      continue;
++
++              if ((buf->names[i][0]) && (buf->names[i][0] != '\xff'))
++                      strncpy(names, buf->names[i], PART_NAME_LEN);
++              else
++                      snprintf(names, PART_NAME_LEN, "partition%d", i);
++
++              mtd_part->offset = le32_to_cpu(part->addr);
++              mtd_part->size = le32_to_cpu(part->size);
++              mtd_part->name = names;
++              mtd_part++;
++              names += PART_NAME_LEN;
++      }
++
++      *pparts = mtd_parts;
++      ret = num_parts;
++
++ out_free_buf:
++      vfree(buf);
++ out:
++      return ret;
++}
++
++static struct mtd_part_parser myloader_mtd_parser = {
++      .owner          = THIS_MODULE,
++      .parse_fn       = myloader_parse_partitions,
++      .name           = "MyLoader",
++};
++
++static int __init myloader_mtd_parser_init(void)
++{
++      register_mtd_parser(&myloader_mtd_parser);
++
++      return 0;
++}
++
++static void __exit myloader_mtd_parser_exit(void)
++{
++      deregister_mtd_parser(&myloader_mtd_parser);
++}
++
++module_init(myloader_mtd_parser_init);
++module_exit(myloader_mtd_parser_exit);
++
++MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");
++MODULE_DESCRIPTION("Parsing code for MyLoader partition tables");
++MODULE_LICENSE("GPL v2");
diff --git a/target/linux/generic/pending-6.12/431-mtd-bcm47xxpart-check-for-bad-blocks-when-calculatin.patch b/target/linux/generic/pending-6.12/431-mtd-bcm47xxpart-check-for-bad-blocks-when-calculatin.patch
new file mode 100644 (file)
index 0000000..bcea45d
--- /dev/null
@@ -0,0 +1,68 @@
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <zajec5@gmail.com>
+Subject: [PATCH] mtd: bcm47xxpart: check for bad blocks when calculating offsets
+
+Signed-off-by: Rafał Miłecki <zajec5@gmail.com>
+---
+
+--- a/drivers/mtd/parsers/parser_trx.c
++++ b/drivers/mtd/parsers/parser_trx.c
+@@ -25,6 +25,33 @@ struct trx_header {
+       uint32_t offset[3];
+ } __packed;
++/*
++ * Calculate real end offset (address) for a given amount of data. It checks
++ * all blocks skipping bad ones.
++ */
++static size_t parser_trx_real_offset(struct mtd_info *mtd, size_t bytes)
++{
++      size_t real_offset = 0;
++
++      if (mtd_block_isbad(mtd, real_offset))
++              pr_warn("Base offset shouldn't be at bad block");
++
++      while (bytes >= mtd->erasesize) {
++              bytes -= mtd->erasesize;
++              real_offset += mtd->erasesize;
++              while (mtd_block_isbad(mtd, real_offset)) {
++                      real_offset += mtd->erasesize;
++
++                      if (real_offset >= mtd->size)
++                              return real_offset - mtd->erasesize;
++              }
++      }
++
++      real_offset += bytes;
++
++      return real_offset;
++}
++
+ static const char *parser_trx_data_part_name(struct mtd_info *master,
+                                            size_t offset)
+ {
+@@ -86,21 +113,21 @@ static int parser_trx_parse(struct mtd_i
+       if (trx.offset[2]) {
+               part = &parts[curr_part++];
+               part->name = "loader";
+-              part->offset = trx.offset[i];
++              part->offset = parser_trx_real_offset(mtd, trx.offset[i]);
+               i++;
+       }
+       if (trx.offset[i]) {
+               part = &parts[curr_part++];
+               part->name = "linux";
+-              part->offset = trx.offset[i];
++              part->offset = parser_trx_real_offset(mtd, trx.offset[i]);
+               i++;
+       }
+       if (trx.offset[i]) {
+               part = &parts[curr_part++];
+-              part->name = parser_trx_data_part_name(mtd, trx.offset[i]);
+-              part->offset = trx.offset[i];
++              part->offset = parser_trx_real_offset(mtd, trx.offset[i]);
++              part->name = parser_trx_data_part_name(mtd, part->offset);
+               i++;
+       }
diff --git a/target/linux/generic/pending-6.12/432-mtd-bcm47xxpart-detect-T_Meter-partition.patch b/target/linux/generic/pending-6.12/432-mtd-bcm47xxpart-detect-T_Meter-partition.patch
new file mode 100644 (file)
index 0000000..852654d
--- /dev/null
@@ -0,0 +1,37 @@
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <zajec5@gmail.com>
+Subject: mtd: bcm47xxpart: detect T_Meter partition
+
+It can be found on many Netgear devices. It consists of many 0x30 blocks
+starting with 4D 54.
+
+Signed-off-by: Rafał Miłecki <zajec5@gmail.com>
+---
+ drivers/mtd/bcm47xxpart.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+--- a/drivers/mtd/parsers/bcm47xxpart.c
++++ b/drivers/mtd/parsers/bcm47xxpart.c
+@@ -35,6 +35,7 @@
+ #define NVRAM_HEADER                  0x48534C46      /* FLSH */
+ #define POT_MAGIC1                    0x54544f50      /* POTT */
+ #define POT_MAGIC2                    0x504f          /* OP */
++#define T_METER_MAGIC                 0x4D540000      /* MT */
+ #define ML_MAGIC1                     0x39685a42
+ #define ML_MAGIC2                     0x26594131
+ #define TRX_MAGIC                     0x30524448
+@@ -178,6 +179,15 @@ static int bcm47xxpart_parse(struct mtd_
+                                            MTD_WRITEABLE);
+                       continue;
+               }
++
++              /* T_Meter */
++              if ((le32_to_cpu(buf[0x000 / 4]) & 0xFFFF0000) == T_METER_MAGIC &&
++                  (le32_to_cpu(buf[0x030 / 4]) & 0xFFFF0000) == T_METER_MAGIC &&
++                  (le32_to_cpu(buf[0x060 / 4]) & 0xFFFF0000) == T_METER_MAGIC) {
++                      bcm47xxpart_add_part(&parts[curr_part++], "T_Meter", offset,
++                                           MTD_WRITEABLE);
++                      continue;
++              }
+               /* TRX */
+               if (buf[0x000 / 4] == TRX_MAGIC) {
diff --git a/target/linux/generic/pending-6.12/435-mtd-add-routerbootpart-parser-config.patch b/target/linux/generic/pending-6.12/435-mtd-add-routerbootpart-parser-config.patch
new file mode 100644 (file)
index 0000000..d516e1d
--- /dev/null
@@ -0,0 +1,38 @@
+From 4437e01fb6bca63fccdba5d6c44888b0935885c2 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Thibaut=20VAR=C3=88NE?= <hacks@slashdirt.org>
+Date: Tue, 24 Mar 2020 11:45:07 +0100
+Subject: [PATCH] generic: routerboot partition build bits (5.4)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This patch adds routerbootpart kernel build bits
+
+Signed-off-by: Thibaut VARÈNE <hacks@slashdirt.org>
+---
+ drivers/mtd/parsers/Kconfig  | 9 +++++++++
+ drivers/mtd/parsers/Makefile | 1 +
+ 2 files changed, 10 insertions(+)
+
+--- a/drivers/mtd/parsers/Kconfig
++++ b/drivers/mtd/parsers/Kconfig
+@@ -231,3 +231,12 @@ config MTD_SERCOMM_PARTS
+         partition map. This partition table contains real partition
+         offsets, which may differ from device to device depending on the
+         number and location of bad blocks on NAND.
++
++config MTD_ROUTERBOOT_PARTS
++      tristate "RouterBoot flash partition parser"
++      depends on MTD && OF
++      help
++       MikroTik RouterBoot is implemented as a multi segment system on the
++       flash, some of which are fixed and some of which are located at
++       variable offsets. This parser handles both cases via properly
++       formatted DTS.
+--- a/drivers/mtd/parsers/Makefile
++++ b/drivers/mtd/parsers/Makefile
+@@ -16,3 +16,4 @@ obj-$(CONFIG_MTD_SERCOMM_PARTS)              += scpa
+ obj-$(CONFIG_MTD_SHARPSL_PARTS)               += sharpslpart.o
+ obj-$(CONFIG_MTD_REDBOOT_PARTS)               += redboot.o
+ obj-$(CONFIG_MTD_QCOMSMEM_PARTS)      += qcomsmempart.o
++obj-$(CONFIG_MTD_ROUTERBOOT_PARTS)            += routerbootpart.o
diff --git a/target/linux/generic/pending-6.12/450-block-allow-setting-partition-of_node.patch b/target/linux/generic/pending-6.12/450-block-allow-setting-partition-of_node.patch
new file mode 100644 (file)
index 0000000..a3c8abc
--- /dev/null
@@ -0,0 +1,123 @@
+From decc6959a423c8617e87244fd269129fc4e7d0e6 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 7 Oct 2024 23:36:14 +0100
+Subject: [PATCH 1/8] block: allow setting partition of_node
+
+Allow partition parsers to set the Device Tree node for a partition by
+introducing of_put_partition() and extending struct parsed_partitions
+accordingly.
+
+As the partition information is preallocated independently of the actual
+number of partitions the additional pointer takes about 2 kiB of allocated
+memory which is worth avoiding in case CONFIG_OF is not set. This is
+achieved by only adding the corresponding field to the struct in case
+CONFIG_OF is set using #ifdef'ery.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ block/partitions/check.h | 16 +++++++++++++++-
+ block/partitions/core.c  | 14 +++++++++++---
+ 2 files changed, 26 insertions(+), 4 deletions(-)
+
+--- a/block/partitions/check.h
++++ b/block/partitions/check.h
+@@ -1,6 +1,7 @@
+ /* SPDX-License-Identifier: GPL-2.0 */
+ #include <linux/pagemap.h>
+ #include <linux/blkdev.h>
++#include <linux/of.h>
+ #include "../blk.h"
+ /*
+@@ -16,6 +17,9 @@ struct parsed_partitions {
+               int flags;
+               bool has_info;
+               struct partition_meta_info info;
++#ifdef CONFIG_OF
++              struct device_node *np;
++#endif
+       } *parts;
+       int next;
+       int limit;
+@@ -34,18 +38,28 @@ static inline void put_dev_sector(Sector
+ }
+ static inline void
+-put_partition(struct parsed_partitions *p, int n, sector_t from, sector_t size)
++of_put_partition(struct parsed_partitions *p, int n, sector_t from, sector_t size,
++               struct device_node *np)
+ {
+       if (n < p->limit) {
+               char tmp[1 + BDEVNAME_SIZE + 10 + 1];
+               p->parts[n].from = from;
+               p->parts[n].size = size;
++#ifdef CONFIG_OF
++              p->parts[n].np = np;
++#endif
+               snprintf(tmp, sizeof(tmp), " %s%d", p->name, n);
+               strlcat(p->pp_buf, tmp, PAGE_SIZE);
+       }
+ }
++static inline void
++put_partition(struct parsed_partitions *p, int n, sector_t from, sector_t size)
++{
++      of_put_partition(p, n, from, size, NULL);
++}
++
+ /* detection routines go here in alphabetical order: */
+ int adfspart_check_ADFS(struct parsed_partitions *state);
+ int adfspart_check_CUMANA(struct parsed_partitions *state);
+--- a/block/partitions/core.c
++++ b/block/partitions/core.c
+@@ -9,6 +9,7 @@
+ #include <linux/slab.h>
+ #include <linux/ctype.h>
+ #include <linux/vmalloc.h>
++#include <linux/device.h>
+ #include <linux/raid/detect.h>
+ #include "check.h"
+@@ -290,7 +291,8 @@ static const DEVICE_ATTR(whole_disk, 044
+  */
+ static struct block_device *add_partition(struct gendisk *disk, int partno,
+                               sector_t start, sector_t len, int flags,
+-                              struct partition_meta_info *info)
++                              struct partition_meta_info *info,
++                              struct device_node *np)
+ {
+       dev_t devt = MKDEV(0, 0);
+       struct device *ddev = disk_to_dev(disk);
+@@ -339,6 +341,7 @@ static struct block_device *add_partitio
+       pdev->class = &block_class;
+       pdev->type = &part_type;
+       pdev->parent = ddev;
++      device_set_node(pdev, of_fwnode_handle(np));
+       /* in consecutive minor range? */
+       if (bdev_partno(bdev) < disk->minors) {
+@@ -445,7 +448,7 @@ int bdev_add_partition(struct gendisk *d
+       }
+       part = add_partition(disk, partno, start, length,
+-                      ADDPART_FLAG_NONE, NULL);
++                      ADDPART_FLAG_NONE, NULL, NULL);
+       ret = PTR_ERR_OR_ZERO(part);
+ out:
+       mutex_unlock(&disk->open_mutex);
+@@ -559,8 +562,13 @@ static bool blk_add_partition(struct gen
+               size = get_capacity(disk) - from;
+       }
++#ifdef CONFIG_OF
+       part = add_partition(disk, p, from, size, state->parts[p].flags,
+-                           &state->parts[p].info);
++                           &state->parts[p].info, state->parts[p].np);
++#else
++      part = add_partition(disk, p, from, size, state->parts[p].flags,
++                           &state->parts[p].info, NULL);
++#endif
+       if (IS_ERR(part)) {
+               if (PTR_ERR(part) != -ENXIO) {
+                       printk(KERN_ERR " %s: p%d could not be added: %pe\n",
diff --git a/target/linux/generic/pending-6.12/451-block-partitions-of-assign-Device-Tree-node-to-parti.patch b/target/linux/generic/pending-6.12/451-block-partitions-of-assign-Device-Tree-node-to-parti.patch
new file mode 100644 (file)
index 0000000..f9e41d0
--- /dev/null
@@ -0,0 +1,25 @@
+From 5d265996597a6b0d654bbeda1bc3bae197061de7 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 7 Oct 2024 23:43:32 +0100
+Subject: [PATCH 2/8] block: partitions: of: assign Device Tree node to
+ partition
+
+Assign partition of_node so other drivers are able to identify a
+partition by its Device Tree node.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ block/partitions/of.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/block/partitions/of.c
++++ b/block/partitions/of.c
+@@ -48,7 +48,7 @@ static void add_of_partition(struct pars
+       u64 offset = of_read_number(reg, a_cells) / SECTOR_SIZE;
+       u64 size = of_read_number(reg + a_cells, s_cells) / SECTOR_SIZE;
+-      put_partition(state, slot, offset, size);
++      of_put_partition(state, slot, offset, size, np);
+       if (of_property_read_bool(np, "read-only"))
+               state->parts[slot].flags |= ADDPART_FLAG_READONLY;
diff --git a/target/linux/generic/pending-6.12/452-partitions-efi-apply-Linux-code-style.patch b/target/linux/generic/pending-6.12/452-partitions-efi-apply-Linux-code-style.patch
new file mode 100644 (file)
index 0000000..6e996ca
--- /dev/null
@@ -0,0 +1,498 @@
+From d4e82837c8b86ff2c21fa923271908988bc72faa Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 7 Oct 2024 23:22:53 +0100
+Subject: [PATCH 3/8] partitions/efi: apply Linux code style
+
+Fix (mostly white space related) coding style issues in
+block/partitions/efi.c by use of clang-format.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ block/partitions/efi.c | 235 +++++++++++++++++++++--------------------
+ 1 file changed, 121 insertions(+), 114 deletions(-)
+
+--- a/block/partitions/efi.c
++++ b/block/partitions/efi.c
+@@ -95,15 +95,13 @@
+  * the partition tables happens after init too.
+  */
+ static int force_gpt;
+-static int __init
+-force_gpt_fn(char *str)
++static int __init force_gpt_fn(char *str)
+ {
+       force_gpt = 1;
+       return 1;
+ }
+ __setup("gpt", force_gpt_fn);
+-
+ /**
+  * efi_crc32() - EFI version of crc32 function
+  * @buf: buffer to calculate crc32 of
+@@ -116,8 +114,7 @@ __setup("gpt", force_gpt_fn);
+  * Note, the EFI Specification, v1.02, has a reference to
+  * Dr. Dobbs Journal, May 1994 (actually it's in May 1992).
+  */
+-static inline u32
+-efi_crc32(const void *buf, unsigned long len)
++static inline u32 efi_crc32(const void *buf, unsigned long len)
+ {
+       return (crc32(~0L, buf, len) ^ ~0L);
+ }
+@@ -134,7 +131,8 @@ efi_crc32(const void *buf, unsigned long
+ static u64 last_lba(struct gendisk *disk)
+ {
+       return div_u64(bdev_nr_bytes(disk->part0),
+-                     queue_logical_block_size(disk->queue)) - 1ULL;
++                     queue_logical_block_size(disk->queue)) -
++             1ULL;
+ }
+ static inline int pmbr_part_valid(gpt_mbr_record *part)
+@@ -195,7 +193,7 @@ static int is_pmbr_valid(legacy_mbr *mbr
+ check_hybrid:
+       for (i = 0; i < 4; i++)
+               if ((mbr->partition_record[i].os_type !=
+-                      EFI_PMBR_OSTYPE_EFI_GPT) &&
++                   EFI_PMBR_OSTYPE_EFI_GPT) &&
+                   (mbr->partition_record[i].os_type != 0x00))
+                       ret = GPT_MBR_HYBRID;
+@@ -213,10 +211,11 @@ check_hybrid:
+        */
+       if (ret == GPT_MBR_PROTECTIVE) {
+               sz = le32_to_cpu(mbr->partition_record[part].size_in_lba);
+-              if (sz != (uint32_t) total_sectors - 1 && sz != 0xFFFFFFFF)
+-                      pr_debug("GPT: mbr size in lba (%u) different than whole disk (%u).\n",
+-                               sz, min_t(uint32_t,
+-                                         total_sectors - 1, 0xFFFFFFFF));
++              if (sz != (uint32_t)total_sectors - 1 && sz != 0xFFFFFFFF)
++                      pr_debug(
++                              "GPT: mbr size in lba (%u) different than whole disk (%u).\n",
++                              sz,
++                              min_t(uint32_t, total_sectors - 1, 0xFFFFFFFF));
+       }
+ done:
+       return ret;
+@@ -232,15 +231,14 @@ done:
+  * Description: Reads @count bytes from @state->disk into @buffer.
+  * Returns number of bytes read on success, 0 on error.
+  */
+-static size_t read_lba(struct parsed_partitions *state,
+-                     u64 lba, u8 *buffer, size_t count)
++static size_t read_lba(struct parsed_partitions *state, u64 lba, u8 *buffer,
++                     size_t count)
+ {
+       size_t totalreadcount = 0;
+-      sector_t n = lba *
+-              (queue_logical_block_size(state->disk->queue) / 512);
++      sector_t n = lba * (queue_logical_block_size(state->disk->queue) / 512);
+       if (!buffer || lba > last_lba(state->disk))
+-                return 0;
++              return 0;
+       while (count) {
+               int copied = 512;
+@@ -253,7 +251,7 @@ static size_t read_lba(struct parsed_par
+               memcpy(buffer, data, copied);
+               put_dev_sector(sect);
+               buffer += copied;
+-              totalreadcount +=copied;
++              totalreadcount += copied;
+               count -= copied;
+       }
+       return totalreadcount;
+@@ -278,17 +276,17 @@ static gpt_entry *alloc_read_gpt_entries
+               return NULL;
+       count = (size_t)le32_to_cpu(gpt->num_partition_entries) *
+-                le32_to_cpu(gpt->sizeof_partition_entry);
++              le32_to_cpu(gpt->sizeof_partition_entry);
+       if (!count)
+               return NULL;
+       pte = kmalloc(count, GFP_KERNEL);
+       if (!pte)
+               return NULL;
+-      if (read_lba(state, le64_to_cpu(gpt->partition_entry_lba),
+-                      (u8 *) pte, count) < count) {
++      if (read_lba(state, le64_to_cpu(gpt->partition_entry_lba), (u8 *)pte,
++                   count) < count) {
+               kfree(pte);
+-                pte=NULL;
++              pte = NULL;
+               return NULL;
+       }
+       return pte;
+@@ -313,9 +311,9 @@ static gpt_header *alloc_read_gpt_header
+       if (!gpt)
+               return NULL;
+-      if (read_lba(state, lba, (u8 *) gpt, ssz) < ssz) {
++      if (read_lba(state, lba, (u8 *)gpt, ssz) < ssz) {
+               kfree(gpt);
+-                gpt=NULL;
++              gpt = NULL;
+               return NULL;
+       }
+@@ -354,8 +352,9 @@ static int is_gpt_valid(struct parsed_pa
+       /* Check the GUID Partition Table header size is too big */
+       if (le32_to_cpu((*gpt)->header_size) >
+-                      queue_logical_block_size(state->disk->queue)) {
+-              pr_debug("GUID Partition Table Header size is too large: %u > %u\n",
++          queue_logical_block_size(state->disk->queue)) {
++              pr_debug(
++                      "GUID Partition Table Header size is too large: %u > %u\n",
+                       le32_to_cpu((*gpt)->header_size),
+                       queue_logical_block_size(state->disk->queue));
+               goto fail;
+@@ -363,16 +362,17 @@ static int is_gpt_valid(struct parsed_pa
+       /* Check the GUID Partition Table header size is too small */
+       if (le32_to_cpu((*gpt)->header_size) < sizeof(gpt_header)) {
+-              pr_debug("GUID Partition Table Header size is too small: %u < %zu\n",
+-                      le32_to_cpu((*gpt)->header_size),
+-                      sizeof(gpt_header));
++              pr_debug(
++                      "GUID Partition Table Header size is too small: %u < %zu\n",
++                      le32_to_cpu((*gpt)->header_size), sizeof(gpt_header));
+               goto fail;
+       }
+       /* Check the GUID Partition Table CRC */
+       origcrc = le32_to_cpu((*gpt)->header_crc32);
+       (*gpt)->header_crc32 = 0;
+-      crc = efi_crc32((const unsigned char *) (*gpt), le32_to_cpu((*gpt)->header_size));
++      crc = efi_crc32((const unsigned char *)(*gpt),
++                      le32_to_cpu((*gpt)->header_size));
+       if (crc != origcrc) {
+               pr_debug("GUID Partition Table Header CRC is wrong: %x != %x\n",
+@@ -396,20 +396,25 @@ static int is_gpt_valid(struct parsed_pa
+       lastlba = last_lba(state->disk);
+       if (le64_to_cpu((*gpt)->first_usable_lba) > lastlba) {
+               pr_debug("GPT: first_usable_lba incorrect: %lld > %lld\n",
+-                       (unsigned long long)le64_to_cpu((*gpt)->first_usable_lba),
++                       (unsigned long long)le64_to_cpu(
++                               (*gpt)->first_usable_lba),
+                        (unsigned long long)lastlba);
+               goto fail;
+       }
+       if (le64_to_cpu((*gpt)->last_usable_lba) > lastlba) {
+               pr_debug("GPT: last_usable_lba incorrect: %lld > %lld\n",
+-                       (unsigned long long)le64_to_cpu((*gpt)->last_usable_lba),
++                       (unsigned long long)le64_to_cpu(
++                               (*gpt)->last_usable_lba),
+                        (unsigned long long)lastlba);
+               goto fail;
+       }
+-      if (le64_to_cpu((*gpt)->last_usable_lba) < le64_to_cpu((*gpt)->first_usable_lba)) {
++      if (le64_to_cpu((*gpt)->last_usable_lba) <
++          le64_to_cpu((*gpt)->first_usable_lba)) {
+               pr_debug("GPT: last_usable_lba incorrect: %lld > %lld\n",
+-                       (unsigned long long)le64_to_cpu((*gpt)->last_usable_lba),
+-                       (unsigned long long)le64_to_cpu((*gpt)->first_usable_lba));
++                       (unsigned long long)le64_to_cpu(
++                               (*gpt)->last_usable_lba),
++                       (unsigned long long)le64_to_cpu(
++                               (*gpt)->first_usable_lba));
+               goto fail;
+       }
+       /* Check that sizeof_partition_entry has the correct value */
+@@ -420,10 +425,11 @@ static int is_gpt_valid(struct parsed_pa
+       /* Sanity check partition table size */
+       pt_size = (u64)le32_to_cpu((*gpt)->num_partition_entries) *
+-              le32_to_cpu((*gpt)->sizeof_partition_entry);
++                le32_to_cpu((*gpt)->sizeof_partition_entry);
+       if (pt_size > KMALLOC_MAX_SIZE) {
+-              pr_debug("GUID Partition Table is too large: %llu > %lu bytes\n",
+-                       (unsigned long long)pt_size, KMALLOC_MAX_SIZE);
++              pr_debug(
++                      "GUID Partition Table is too large: %llu > %lu bytes\n",
++                      (unsigned long long)pt_size, KMALLOC_MAX_SIZE);
+               goto fail;
+       }
+@@ -431,7 +437,7 @@ static int is_gpt_valid(struct parsed_pa
+               goto fail;
+       /* Check the GUID Partition Entry Array CRC */
+-      crc = efi_crc32((const unsigned char *) (*ptes), pt_size);
++      crc = efi_crc32((const unsigned char *)(*ptes), pt_size);
+       if (crc != le32_to_cpu((*gpt)->partition_entry_array_crc32)) {
+               pr_debug("GUID Partition Entry Array CRC check failed.\n");
+@@ -441,10 +447,10 @@ static int is_gpt_valid(struct parsed_pa
+       /* We're done, all's well */
+       return 1;
+- fail_ptes:
++fail_ptes:
+       kfree(*ptes);
+       *ptes = NULL;
+- fail:
++fail:
+       kfree(*gpt);
+       *gpt = NULL;
+       return 0;
+@@ -457,12 +463,11 @@ static int is_gpt_valid(struct parsed_pa
+  *
+  * Description: returns 1 if valid,  0 on error.
+  */
+-static inline int
+-is_pte_valid(const gpt_entry *pte, const u64 lastlba)
++static inline int is_pte_valid(const gpt_entry *pte, const u64 lastlba)
+ {
+       if ((!efi_guidcmp(pte->partition_type_guid, NULL_GUID)) ||
+-          le64_to_cpu(pte->starting_lba) > lastlba         ||
+-          le64_to_cpu(pte->ending_lba)   > lastlba)
++          le64_to_cpu(pte->starting_lba) > lastlba ||
++          le64_to_cpu(pte->ending_lba) > lastlba)
+               return 0;
+       return 1;
+ }
+@@ -477,8 +482,7 @@ is_pte_valid(const gpt_entry *pte, const
+  * and prints warnings on discrepancies.
+  * 
+  */
+-static void
+-compare_gpts(gpt_header *pgpt, gpt_header *agpt, u64 lastlba)
++static void compare_gpts(gpt_header *pgpt, gpt_header *agpt, u64 lastlba)
+ {
+       int error_found = 0;
+       if (!pgpt || !agpt)
+@@ -486,31 +490,32 @@ compare_gpts(gpt_header *pgpt, gpt_heade
+       if (le64_to_cpu(pgpt->my_lba) != le64_to_cpu(agpt->alternate_lba)) {
+               pr_warn("GPT:Primary header LBA != Alt. header alternate_lba\n");
+               pr_warn("GPT:%lld != %lld\n",
+-                     (unsigned long long)le64_to_cpu(pgpt->my_lba),
+-                       (unsigned long long)le64_to_cpu(agpt->alternate_lba));
++                      (unsigned long long)le64_to_cpu(pgpt->my_lba),
++                      (unsigned long long)le64_to_cpu(agpt->alternate_lba));
+               error_found++;
+       }
+       if (le64_to_cpu(pgpt->alternate_lba) != le64_to_cpu(agpt->my_lba)) {
+               pr_warn("GPT:Primary header alternate_lba != Alt. header my_lba\n");
+               pr_warn("GPT:%lld != %lld\n",
+-                     (unsigned long long)le64_to_cpu(pgpt->alternate_lba),
+-                       (unsigned long long)le64_to_cpu(agpt->my_lba));
++                      (unsigned long long)le64_to_cpu(pgpt->alternate_lba),
++                      (unsigned long long)le64_to_cpu(agpt->my_lba));
+               error_found++;
+       }
+       if (le64_to_cpu(pgpt->first_usable_lba) !=
+-            le64_to_cpu(agpt->first_usable_lba)) {
++          le64_to_cpu(agpt->first_usable_lba)) {
+               pr_warn("GPT:first_usable_lbas don't match.\n");
+               pr_warn("GPT:%lld != %lld\n",
+-                     (unsigned long long)le64_to_cpu(pgpt->first_usable_lba),
+-                       (unsigned long long)le64_to_cpu(agpt->first_usable_lba));
++                      (unsigned long long)le64_to_cpu(pgpt->first_usable_lba),
++                      (unsigned long long)le64_to_cpu(
++                              agpt->first_usable_lba));
+               error_found++;
+       }
+       if (le64_to_cpu(pgpt->last_usable_lba) !=
+-            le64_to_cpu(agpt->last_usable_lba)) {
++          le64_to_cpu(agpt->last_usable_lba)) {
+               pr_warn("GPT:last_usable_lbas don't match.\n");
+               pr_warn("GPT:%lld != %lld\n",
+-                     (unsigned long long)le64_to_cpu(pgpt->last_usable_lba),
+-                       (unsigned long long)le64_to_cpu(agpt->last_usable_lba));
++                      (unsigned long long)le64_to_cpu(pgpt->last_usable_lba),
++                      (unsigned long long)le64_to_cpu(agpt->last_usable_lba));
+               error_found++;
+       }
+       if (efi_guidcmp(pgpt->disk_guid, agpt->disk_guid)) {
+@@ -518,27 +523,27 @@ compare_gpts(gpt_header *pgpt, gpt_heade
+               error_found++;
+       }
+       if (le32_to_cpu(pgpt->num_partition_entries) !=
+-            le32_to_cpu(agpt->num_partition_entries)) {
++          le32_to_cpu(agpt->num_partition_entries)) {
+               pr_warn("GPT:num_partition_entries don't match: "
+-                     "0x%x != 0x%x\n",
+-                     le32_to_cpu(pgpt->num_partition_entries),
+-                     le32_to_cpu(agpt->num_partition_entries));
++                      "0x%x != 0x%x\n",
++                      le32_to_cpu(pgpt->num_partition_entries),
++                      le32_to_cpu(agpt->num_partition_entries));
+               error_found++;
+       }
+       if (le32_to_cpu(pgpt->sizeof_partition_entry) !=
+-            le32_to_cpu(agpt->sizeof_partition_entry)) {
++          le32_to_cpu(agpt->sizeof_partition_entry)) {
+               pr_warn("GPT:sizeof_partition_entry values don't match: "
+-                     "0x%x != 0x%x\n",
+-                       le32_to_cpu(pgpt->sizeof_partition_entry),
+-                     le32_to_cpu(agpt->sizeof_partition_entry));
++                      "0x%x != 0x%x\n",
++                      le32_to_cpu(pgpt->sizeof_partition_entry),
++                      le32_to_cpu(agpt->sizeof_partition_entry));
+               error_found++;
+       }
+       if (le32_to_cpu(pgpt->partition_entry_array_crc32) !=
+-            le32_to_cpu(agpt->partition_entry_array_crc32)) {
++          le32_to_cpu(agpt->partition_entry_array_crc32)) {
+               pr_warn("GPT:partition_entry_array_crc32 values don't match: "
+-                     "0x%x != 0x%x\n",
+-                       le32_to_cpu(pgpt->partition_entry_array_crc32),
+-                     le32_to_cpu(agpt->partition_entry_array_crc32));
++                      "0x%x != 0x%x\n",
++                      le32_to_cpu(pgpt->partition_entry_array_crc32),
++                      le32_to_cpu(agpt->partition_entry_array_crc32));
+               error_found++;
+       }
+       if (le64_to_cpu(pgpt->alternate_lba) != lastlba) {
+@@ -594,7 +599,7 @@ static int find_valid_gpt(struct parsed_
+               return 0;
+       lastlba = last_lba(state->disk);
+-        if (!force_gpt) {
++      if (!force_gpt) {
+               /* This will be added to the EFI Spec. per Intel after v1.02. */
+               legacymbr = kzalloc(sizeof(*legacymbr), GFP_KERNEL);
+               if (!legacymbr)
+@@ -608,18 +613,17 @@ static int find_valid_gpt(struct parsed_
+                       goto fail;
+               pr_debug("Device has a %s MBR\n",
+-                       good_pmbr == GPT_MBR_PROTECTIVE ?
+-                                              "protective" : "hybrid");
++                       good_pmbr == GPT_MBR_PROTECTIVE ? "protective" :
++                                                         "hybrid");
+       }
+-      good_pgpt = is_gpt_valid(state, GPT_PRIMARY_PARTITION_TABLE_LBA,
+-                               &pgpt, &pptes);
+-        if (good_pgpt)
+-              good_agpt = is_gpt_valid(state,
+-                                       le64_to_cpu(pgpt->alternate_lba),
+-                                       &agpt, &aptes);
+-        if (!good_agpt && force_gpt)
+-                good_agpt = is_gpt_valid(state, lastlba, &agpt, &aptes);
++      good_pgpt = is_gpt_valid(state, GPT_PRIMARY_PARTITION_TABLE_LBA, &pgpt,
++                               &pptes);
++      if (good_pgpt)
++              good_agpt = is_gpt_valid(
++                      state, le64_to_cpu(pgpt->alternate_lba), &agpt, &aptes);
++      if (!good_agpt && force_gpt)
++              good_agpt = is_gpt_valid(state, lastlba, &agpt, &aptes);
+       if (!good_agpt && force_gpt && fops->alternative_gpt_sector) {
+               sector_t agpt_sector;
+@@ -627,43 +631,42 @@ static int find_valid_gpt(struct parsed_
+               err = fops->alternative_gpt_sector(disk, &agpt_sector);
+               if (!err)
+-                      good_agpt = is_gpt_valid(state, agpt_sector,
+-                                               &agpt, &aptes);
++                      good_agpt =
++                              is_gpt_valid(state, agpt_sector, &agpt, &aptes);
+       }
+-        /* The obviously unsuccessful case */
+-        if (!good_pgpt && !good_agpt)
+-                goto fail;
+-
+-        compare_gpts(pgpt, agpt, lastlba);
+-
+-        /* The good cases */
+-        if (good_pgpt) {
+-                *gpt  = pgpt;
+-                *ptes = pptes;
+-                kfree(agpt);
+-                kfree(aptes);
++      /* The obviously unsuccessful case */
++      if (!good_pgpt && !good_agpt)
++              goto fail;
++
++      compare_gpts(pgpt, agpt, lastlba);
++
++      /* The good cases */
++      if (good_pgpt) {
++              *gpt = pgpt;
++              *ptes = pptes;
++              kfree(agpt);
++              kfree(aptes);
+               if (!good_agpt)
+-                        pr_warn("Alternate GPT is invalid, using primary GPT.\n");
+-                return 1;
+-        }
+-        else if (good_agpt) {
+-                *gpt  = agpt;
+-                *ptes = aptes;
+-                kfree(pgpt);
+-                kfree(pptes);
++                      pr_warn("Alternate GPT is invalid, using primary GPT.\n");
++              return 1;
++      } else if (good_agpt) {
++              *gpt = agpt;
++              *ptes = aptes;
++              kfree(pgpt);
++              kfree(pptes);
+               pr_warn("Primary GPT is invalid, using alternate GPT.\n");
+-                return 1;
+-        }
++              return 1;
++      }
+- fail:
+-        kfree(pgpt);
+-        kfree(agpt);
+-        kfree(pptes);
+-        kfree(aptes);
+-        *gpt = NULL;
+-        *ptes = NULL;
+-        return 0;
++fail:
++      kfree(pgpt);
++      kfree(agpt);
++      kfree(pptes);
++      kfree(aptes);
++      *gpt = NULL;
++      *ptes = NULL;
++      return 0;
+ }
+ /**
+@@ -725,7 +728,9 @@ int efi_partition(struct parsed_partitio
+       pr_debug("GUID Partition Table is valid!  Yea!\n");
+-      for (i = 0; i < le32_to_cpu(gpt->num_partition_entries) && i < state->limit-1; i++) {
++      for (i = 0; i < le32_to_cpu(gpt->num_partition_entries) &&
++                  i < state->limit - 1;
++           i++) {
+               struct partition_meta_info *info;
+               unsigned label_max;
+               u64 start = le64_to_cpu(ptes[i].starting_lba);
+@@ -735,10 +740,11 @@ int efi_partition(struct parsed_partitio
+               if (!is_pte_valid(&ptes[i], last_lba(state->disk)))
+                       continue;
+-              put_partition(state, i+1, start * ssz, size * ssz);
++              put_partition(state, i + 1, start * ssz, size * ssz);
+               /* If this is a RAID volume, tell md */
+-              if (!efi_guidcmp(ptes[i].partition_type_guid, PARTITION_LINUX_RAID_GUID))
++              if (!efi_guidcmp(ptes[i].partition_type_guid,
++                               PARTITION_LINUX_RAID_GUID))
+                       state->parts[i + 1].flags = ADDPART_FLAG_RAID;
+               info = &state->parts[i + 1].info;
+@@ -747,7 +753,8 @@ int efi_partition(struct parsed_partitio
+               /* Naively convert UTF16-LE to 7 bits. */
+               label_max = min(ARRAY_SIZE(info->volname) - 1,
+                               ARRAY_SIZE(ptes[i].partition_name));
+-              utf16_le_to_7bit(ptes[i].partition_name, label_max, info->volname);
++              utf16_le_to_7bit(ptes[i].partition_name, label_max,
++                               info->volname);
+               state->parts[i + 1].has_info = true;
+       }
+       kfree(ptes);
diff --git a/target/linux/generic/pending-6.12/453-partitions-efi-allow-assigning-partition-Device-Tree.patch b/target/linux/generic/pending-6.12/453-partitions-efi-allow-assigning-partition-Device-Tree.patch
new file mode 100644 (file)
index 0000000..cc2c203
--- /dev/null
@@ -0,0 +1,75 @@
+From f28e36c19b927671d93ccd2b5de3d518fccf721e Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 8 Oct 2024 00:25:12 +0100
+Subject: [PATCH 4/8] partitions/efi: allow assigning partition Device Tree
+ node
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ block/partitions/efi.c | 34 +++++++++++++++++++++++++++++++++-
+ 1 file changed, 33 insertions(+), 1 deletion(-)
+
+--- a/block/partitions/efi.c
++++ b/block/partitions/efi.c
+@@ -86,6 +86,7 @@
+ #include <linux/crc32.h>
+ #include <linux/ctype.h>
+ #include <linux/math64.h>
++#include <linux/of.h>
+ #include <linux/slab.h>
+ #include "check.h"
+ #include "efi.h"
+@@ -694,6 +695,34 @@ static void utf16_le_to_7bit(const __le1
+       }
+ }
++static struct device_node *find_partition_of_node(struct device_node *partitions_np,
++                                                gpt_entry *ptes)
++{
++      char volname[64], partuuid[UUID_STRING_LEN + 1];
++      const char *uuid, *name;
++
++      if (!partitions_np ||
++          !of_device_is_compatible(partitions_np, "gpt-partitions"))
++              return NULL;
++
++      efi_guid_to_str(&ptes->unique_partition_guid, partuuid);
++      utf16_le_to_7bit(ptes->partition_name, ARRAY_SIZE(volname) - 1, volname);
++
++      for_each_available_child_of_node_scoped(partitions_np, np) {
++              if (!of_property_read_string(np, "uuid", &uuid) &&
++                  strncmp(uuid, partuuid, ARRAY_SIZE(partuuid)))
++                      continue;
++
++              if (!of_property_read_string(np, "partname", &name) &&
++                  strncmp(name, volname, ARRAY_SIZE(volname)))
++                      continue;
++
++              return np;
++      }
++
++      return NULL;
++}
++
+ /**
+  * efi_partition - scan for GPT partitions
+  * @state: disk parsed partitions
+@@ -719,6 +748,8 @@ int efi_partition(struct parsed_partitio
+       gpt_entry *ptes = NULL;
+       u32 i;
+       unsigned ssz = queue_logical_block_size(state->disk->queue) / 512;
++      struct device *ddev = disk_to_dev(state->disk);
++      struct device_node *partitions_np = of_node_get(ddev->of_node);
+       if (!find_valid_gpt(state, &gpt, &ptes) || !gpt || !ptes) {
+               kfree(gpt);
+@@ -740,7 +771,8 @@ int efi_partition(struct parsed_partitio
+               if (!is_pte_valid(&ptes[i], last_lba(state->disk)))
+                       continue;
+-              put_partition(state, i + 1, start * ssz, size * ssz);
++              of_put_partition(state, i + 1, start * ssz, size * ssz,
++                               find_partition_of_node(partitions_np, &ptes[i]));
+               /* If this is a RAID volume, tell md */
+               if (!efi_guidcmp(ptes[i].partition_type_guid,
diff --git a/target/linux/generic/pending-6.12/454-block-add-support-for-notifications.patch b/target/linux/generic/pending-6.12/454-block-add-support-for-notifications.patch
new file mode 100644 (file)
index 0000000..6a5abef
--- /dev/null
@@ -0,0 +1,164 @@
+From 858df9db80c1568db255659baca7602504f823af Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 15 May 2024 08:19:51 +0100
+Subject: [PATCH 5/8] block: add support for notifications
+
+Add notifier block to notify other subsystems about the addition or
+removal of block devices.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ block/Kconfig          |   6 +++
+ block/Makefile         |   1 +
+ block/blk-notify.c     | 107 +++++++++++++++++++++++++++++++++++++++++
+ include/linux/blkdev.h |   8 +++
+ 4 files changed, 122 insertions(+)
+ create mode 100644 block/blk-notify.c
+
+--- a/block/Kconfig
++++ b/block/Kconfig
+@@ -209,6 +209,12 @@ config BLK_INLINE_ENCRYPTION_FALLBACK
+         by falling back to the kernel crypto API when inline
+         encryption hardware is not present.
++config BLOCK_NOTIFIERS
++      bool "Enable support for notifications in block layer"
++      help
++        Enable this option to provide notifiers for other subsystems
++        upon addition or removal of block devices.
++
+ source "block/partitions/Kconfig"
+ config BLK_MQ_PCI
+--- a/block/Makefile
++++ b/block/Makefile
+@@ -38,3 +38,4 @@ obj-$(CONFIG_BLK_INLINE_ENCRYPTION)  += b
+                                          blk-crypto-sysfs.o
+ obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK)  += blk-crypto-fallback.o
+ obj-$(CONFIG_BLOCK_HOLDER_DEPRECATED) += holder.o
++obj-$(CONFIG_BLOCK_NOTIFIERS) += blk-notify.o
+--- /dev/null
++++ b/block/blk-notify.c
+@@ -0,0 +1,107 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Notifiers for addition and removal of block devices
++ *
++ * Copyright (c) 2024 Daniel Golle <daniel@makrotopia.org>
++ */
++
++#include <linux/list.h>
++#include <linux/mutex.h>
++#include <linux/notifier.h>
++#include <linux/workqueue.h>
++
++#include "blk.h"
++
++struct blk_device_list {
++      struct device *dev;
++      struct list_head list;
++};
++
++static RAW_NOTIFIER_HEAD(blk_notifier_list);
++static DEFINE_MUTEX(blk_notifier_lock);
++static LIST_HEAD(blk_devices);
++
++struct blk_notify_event {
++      struct delayed_work work;
++      struct device *dev;
++};
++
++void blk_register_notify(struct notifier_block *nb)
++{
++      struct blk_device_list *existing_blkdev;
++
++      mutex_lock(&blk_notifier_lock);
++      raw_notifier_chain_register(&blk_notifier_list, nb);
++
++      list_for_each_entry(existing_blkdev, &blk_devices, list)
++              nb->notifier_call(nb, BLK_DEVICE_ADD, existing_blkdev->dev);
++
++      mutex_unlock(&blk_notifier_lock);
++}
++EXPORT_SYMBOL_GPL(blk_register_notify);
++
++void blk_unregister_notify(struct notifier_block *nb)
++{
++      mutex_lock(&blk_notifier_lock);
++      raw_notifier_chain_unregister(&blk_notifier_list, nb);
++      mutex_unlock(&blk_notifier_lock);
++}
++EXPORT_SYMBOL_GPL(blk_unregister_notify);
++
++static void blk_notify_work(struct work_struct *work)
++{
++      struct blk_notify_event *ev =
++              container_of(work, struct blk_notify_event, work.work);
++
++      raw_notifier_call_chain(&blk_notifier_list, BLK_DEVICE_ADD, ev->dev);
++      kfree(ev);
++}
++
++static int blk_call_notifier_add(struct device *dev)
++{
++      struct blk_device_list *new_blkdev;
++      struct blk_notify_event *ev;
++
++      new_blkdev = kmalloc(sizeof (*new_blkdev), GFP_KERNEL);
++      if (!new_blkdev)
++              return -ENOMEM;
++
++      ev = kmalloc(sizeof(*ev), GFP_KERNEL);
++      INIT_DEFERRABLE_WORK(&ev->work, blk_notify_work);
++      ev->dev = dev;
++      new_blkdev->dev = dev;
++      mutex_lock(&blk_notifier_lock);
++      list_add_tail(&new_blkdev->list, &blk_devices);
++      schedule_delayed_work(&ev->work, msecs_to_jiffies(500));
++      mutex_unlock(&blk_notifier_lock);
++
++      return 0;
++}
++
++static void blk_call_notifier_remove(struct device *dev)
++{
++      struct blk_device_list *old_blkdev, *tmp;
++
++      mutex_lock(&blk_notifier_lock);
++      list_for_each_entry_safe(old_blkdev, tmp, &blk_devices, list) {
++              if (old_blkdev->dev != dev)
++                      continue;
++
++              list_del(&old_blkdev->list);
++              kfree(old_blkdev);
++      }
++      raw_notifier_call_chain(&blk_notifier_list, BLK_DEVICE_REMOVE, dev);
++      mutex_unlock(&blk_notifier_lock);
++}
++
++static struct class_interface blk_notifications_bus_interface __refdata = {
++      .class = &block_class,
++      .add_dev = &blk_call_notifier_add,
++      .remove_dev = &blk_call_notifier_remove,
++};
++
++static int __init blk_notifications_init(void)
++{
++      return class_interface_register(&blk_notifications_bus_interface);
++}
++device_initcall(blk_notifications_init);
+--- a/include/linux/blkdev.h
++++ b/include/linux/blkdev.h
+@@ -1753,4 +1753,12 @@ static inline bool bdev_can_atomic_write
+ #define DEFINE_IO_COMP_BATCH(name)    struct io_comp_batch name = { }
++
++#ifdef CONFIG_BLOCK_NOTIFIERS
++#define BLK_DEVICE_ADD                1
++#define BLK_DEVICE_REMOVE     2
++extern void blk_register_notify(struct notifier_block *nb);
++extern void blk_unregister_notify(struct notifier_block *nb);
++#endif
++
+ #endif /* _LINUX_BLKDEV_H */
diff --git a/target/linux/generic/pending-6.12/455-block-add-new-genhd-flag-GENHD_FL_NVMEM.patch b/target/linux/generic/pending-6.12/455-block-add-new-genhd-flag-GENHD_FL_NVMEM.patch
new file mode 100644 (file)
index 0000000..4f9a26f
--- /dev/null
@@ -0,0 +1,29 @@
+From 2b28bf485b283991eea9f00c5831c1c39c73991f Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 21 Mar 2024 19:33:49 +0000
+Subject: [PATCH 6/8] block: add new genhd flag GENHD_FL_NVMEM
+
+Add new flag to destinguish block devices which may act as an NVMEM
+provider.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ include/linux/blkdev.h | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/include/linux/blkdev.h
++++ b/include/linux/blkdev.h
+@@ -82,11 +82,13 @@ struct partition_meta_info {
+  * ``GENHD_FL_NO_PART``: partition support is disabled.  The kernel will not
+  * scan for partitions from add_disk, and users can't add partitions manually.
+  *
++ * ``GENHD_FL_NVMEM``: the block device should be considered as NVMEM provider.
+  */
+ enum {
+       GENHD_FL_REMOVABLE                      = 1 << 0,
+       GENHD_FL_HIDDEN                         = 1 << 1,
+       GENHD_FL_NO_PART                        = 1 << 2,
++      GENHD_FL_NVMEM                          = 1 << 3,
+ };
+ enum {
diff --git a/target/linux/generic/pending-6.12/456-nvmem-implement-block-NVMEM-provider.patch b/target/linux/generic/pending-6.12/456-nvmem-implement-block-NVMEM-provider.patch
new file mode 100644 (file)
index 0000000..563b9f1
--- /dev/null
@@ -0,0 +1,250 @@
+From 26ae98bf77fdbd60536fe5c2175e528469b9ac9a Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 15 May 2024 18:18:37 +0300
+Subject: [PATCH 7/8] nvmem: implement block NVMEM provider
+
+On embedded devices using an eMMC it is common that one or more partitions
+on the eMMC are used to store MAC addresses and Wi-Fi calibration EEPROM
+data. Allow referencing any block device or partition in Device Tree to
+allow e.g. Ethernet and Wi-Fi drivers accessing them via the NVMEM layer.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/nvmem/Kconfig  |  11 +++
+ drivers/nvmem/Makefile |   2 +
+ drivers/nvmem/block.c  | 198 +++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 211 insertions(+)
+ create mode 100644 drivers/nvmem/block.c
+
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -40,6 +40,17 @@ config NVMEM_APPLE_EFUSES
+         This driver can also be built as a module. If so, the module will
+         be called nvmem-apple-efuses.
++config NVMEM_BLOCK
++      tristate "Block device NVMEM provider"
++      depends on BLOCK
++      depends on OF
++      depends on NVMEM
++      select BLOCK_NOTIFIERS
++      help
++        Allow block devices (or partitions) to act as NVMEM prodivers,
++        typically used with eMMC to store MAC addresses or Wi-Fi
++        calibration data on embedded devices.
++
+ config NVMEM_BCM_OCOTP
+       tristate "Broadcom On-Chip OTP Controller support"
+       depends on ARCH_BCM_IPROC || COMPILE_TEST
+--- a/drivers/nvmem/Makefile
++++ b/drivers/nvmem/Makefile
+@@ -14,6 +14,8 @@ obj-$(CONFIG_NVMEM_APPLE_EFUSES)     += nvme
+ nvmem-apple-efuses-y                  := apple-efuses.o
+ obj-$(CONFIG_NVMEM_BCM_OCOTP)         += nvmem-bcm-ocotp.o
+ nvmem-bcm-ocotp-y                     := bcm-ocotp.o
++obj-$(CONFIG_NVMEM_BLOCK)             += nvmem-block.o
++nvmem-block-y                         := block.o
+ obj-$(CONFIG_NVMEM_BRCM_NVRAM)                += nvmem_brcm_nvram.o
+ nvmem_brcm_nvram-y                    := brcm_nvram.o
+ obj-$(CONFIG_NVMEM_IMX_IIM)           += nvmem-imx-iim.o
+--- /dev/null
++++ b/drivers/nvmem/block.c
+@@ -0,0 +1,198 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * block device NVMEM provider
++ *
++ * Copyright (c) 2024 Daniel Golle <daniel@makrotopia.org>
++ *
++ * Useful on devices using a partition on an eMMC for MAC addresses or
++ * Wi-Fi calibration EEPROM data.
++ */
++
++#include <linux/blkdev.h>
++#include <linux/nvmem-provider.h>
++#include <linux/of.h>
++#include <linux/pagemap.h>
++#include <linux/property.h>
++
++/* List of all NVMEM devices */
++static LIST_HEAD(nvmem_devices);
++static DEFINE_MUTEX(devices_mutex);
++
++struct blk_nvmem {
++      struct nvmem_device     *nvmem;
++      struct device           *dev;
++      struct list_head        list;
++};
++
++static int blk_nvmem_reg_read(void *priv, unsigned int from,
++                            void *val, size_t bytes)
++{
++      blk_mode_t mode = BLK_OPEN_READ | BLK_OPEN_RESTRICT_WRITES;
++      unsigned long offs = from & ~PAGE_MASK, to_read;
++      pgoff_t f_index = from >> PAGE_SHIFT;
++      struct blk_nvmem *bnv = priv;
++      size_t bytes_left = bytes;
++      struct file *bdev_file;
++      struct folio *folio;
++      void *p;
++      int ret = 0;
++
++      bdev_file = bdev_file_open_by_dev(bnv->dev->devt, mode, priv, NULL);
++      if (!bdev_file)
++              return -ENODEV;
++
++      if (IS_ERR(bdev_file))
++              return PTR_ERR(bdev_file);
++
++      while (bytes_left) {
++              folio = read_mapping_folio(bdev_file->f_mapping, f_index++, NULL);
++              if (IS_ERR(folio)) {
++                      ret = PTR_ERR(folio);
++                      goto err_release_bdev;
++              }
++              to_read = min_t(unsigned long, bytes_left, PAGE_SIZE - offs);
++              p = folio_address(folio) + offset_in_folio(folio, offs);
++              memcpy(val, p, to_read);
++              offs = 0;
++              bytes_left -= to_read;
++              val += to_read;
++              folio_put(folio);
++      }
++
++err_release_bdev:
++      fput(bdev_file);
++
++      return ret;
++}
++
++static int blk_nvmem_register(struct device *dev)
++{
++      struct block_device *bdev = dev_to_bdev(dev);
++      struct device_node *np = dev_of_node(dev);
++      struct nvmem_config config = {};
++      struct blk_nvmem *bnv;
++
++      /* skip devices which do not have a device tree node */
++      if (!np)
++              return 0;
++
++      /* skip devices without an nvmem layout defined */
++      if (!of_get_child_by_name(np, "nvmem-layout"))
++              return 0;
++
++      /*
++       * skip devices which don't have GENHD_FL_NVMEM set
++       *
++       * This flag is used for mtdblock and ubiblock devices because
++       * both, MTD and UBI already implement their own NVMEM provider.
++       * To avoid registering multiple NVMEM providers for the same
++       * device node, don't register the block NVMEM provider for them.
++       */
++      if (!(bdev->bd_disk->flags & GENHD_FL_NVMEM))
++              return 0;
++
++      /*
++       * skip block device too large to be represented as NVMEM devices
++       * which are using an 'int' as address
++       */
++      if (bdev_nr_bytes(bdev) > INT_MAX)
++              return -EFBIG;
++
++      bnv = kzalloc(sizeof(struct blk_nvmem), GFP_KERNEL);
++      if (!bnv)
++              return -ENOMEM;
++
++      config.id = NVMEM_DEVID_NONE;
++      config.dev = &bdev->bd_device;
++      config.name = dev_name(&bdev->bd_device);
++      config.owner = THIS_MODULE;
++      config.priv = bnv;
++      config.reg_read = blk_nvmem_reg_read;
++      config.size = bdev_nr_bytes(bdev);
++      config.word_size = 1;
++      config.stride = 1;
++      config.read_only = true;
++      config.root_only = true;
++      config.ignore_wp = true;
++      config.of_node = to_of_node(dev->fwnode);
++
++      bnv->dev = &bdev->bd_device;
++      bnv->nvmem = nvmem_register(&config);
++      if (IS_ERR(bnv->nvmem)) {
++              dev_err_probe(&bdev->bd_device, PTR_ERR(bnv->nvmem),
++                            "Failed to register NVMEM device\n");
++
++              kfree(bnv);
++              return PTR_ERR(bnv->nvmem);
++      }
++
++      mutex_lock(&devices_mutex);
++      list_add_tail(&bnv->list, &nvmem_devices);
++      mutex_unlock(&devices_mutex);
++
++      return 0;
++}
++
++static void blk_nvmem_unregister(struct device *dev)
++{
++      struct blk_nvmem *bnv_c, *bnv = NULL;
++
++      mutex_lock(&devices_mutex);
++      list_for_each_entry(bnv_c, &nvmem_devices, list) {
++              if (bnv_c->dev == dev) {
++                      bnv = bnv_c;
++                      break;
++              }
++      }
++
++      if (!bnv) {
++              mutex_unlock(&devices_mutex);
++              return;
++      }
++
++      list_del(&bnv->list);
++      mutex_unlock(&devices_mutex);
++      nvmem_unregister(bnv->nvmem);
++      kfree(bnv);
++}
++
++static int blk_nvmem_handler(struct notifier_block *this, unsigned long code, void *obj)
++{
++      struct device *dev = (struct device *)obj;
++
++      switch (code) {
++      case BLK_DEVICE_ADD:
++              return blk_nvmem_register(dev);
++              break;
++      case BLK_DEVICE_REMOVE:
++              blk_nvmem_unregister(dev);
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      return 0;
++}
++
++static struct notifier_block blk_nvmem_notifier = {
++      .notifier_call = blk_nvmem_handler,
++};
++
++static int __init blk_nvmem_init(void)
++{
++      blk_register_notify(&blk_nvmem_notifier);
++
++      return 0;
++}
++
++static void __exit blk_nvmem_exit(void)
++{
++      blk_unregister_notify(&blk_nvmem_notifier);
++}
++
++module_init(blk_nvmem_init);
++module_exit(blk_nvmem_exit);
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
++MODULE_DESCRIPTION("block device NVMEM provider");
diff --git a/target/linux/generic/pending-6.12/457-mmc-block-set-GENHD_FL_NVMEM.patch b/target/linux/generic/pending-6.12/457-mmc-block-set-GENHD_FL_NVMEM.patch
new file mode 100644 (file)
index 0000000..955fa66
--- /dev/null
@@ -0,0 +1,22 @@
+From caac69565748349a3e532130a891e18dd5c36c5e Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 20 Jul 2023 17:36:44 +0100
+Subject: [PATCH 8/8] mmc: block: set GENHD_FL_NVMEM
+
+Set flag to consider MMC block devices as NVMEM providers.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/mmc/core/block.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/mmc/core/block.c
++++ b/drivers/mmc/core/block.c
+@@ -2644,6 +2644,7 @@ static struct mmc_blk_data *mmc_blk_allo
+       md->disk->major = MMC_BLOCK_MAJOR;
+       md->disk->minors = perdev_minors;
+       md->disk->first_minor = devidx * perdev_minors;
++      md->disk->flags = GENHD_FL_NVMEM;
+       md->disk->fops = &mmc_bdops;
+       md->disk->private_data = md;
+       md->parent = parent;
diff --git a/target/linux/generic/pending-6.12/460-mtd-cfi_cmdset_0002-no-erase_suspend.patch b/target/linux/generic/pending-6.12/460-mtd-cfi_cmdset_0002-no-erase_suspend.patch
new file mode 100644 (file)
index 0000000..2435133
--- /dev/null
@@ -0,0 +1,25 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: kernel: disable cfi cmdset 0002 erase suspend
+
+on some platforms, erase suspend leads to data corruption and lockups when write
+ops collide with erase ops. this has been observed on the buffalo wzr-hp-g300nh.
+rather than play whack-a-mole with a hard to reproduce issue on a variety of devices,
+simply disable erase suspend, as it will usually not produce any useful gain on
+the small filesystems used on embedded hardware.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ drivers/mtd/chips/cfi_cmdset_0002.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/mtd/chips/cfi_cmdset_0002.c
++++ b/drivers/mtd/chips/cfi_cmdset_0002.c
+@@ -906,7 +906,7 @@ static int get_chip(struct map_info *map
+               return 0;
+       case FL_ERASING:
+-              if (!cfip || !(cfip->EraseSuspend & (0x1|0x2)) ||
++              if (1 /* no suspend */ || !cfip || !(cfip->EraseSuspend & (0x1|0x2)) ||
+                   !(mode == FL_READY || mode == FL_POINT ||
+                   (mode == FL_WRITING && (cfip->EraseSuspend & 0x2))))
+                       goto sleep;
diff --git a/target/linux/generic/pending-6.12/461-mtd-cfi_cmdset_0002-add-buffer-write-cmd-timeout.patch b/target/linux/generic/pending-6.12/461-mtd-cfi_cmdset_0002-add-buffer-write-cmd-timeout.patch
new file mode 100644 (file)
index 0000000..059d967
--- /dev/null
@@ -0,0 +1,17 @@
+From: George Kashperko <george@znau.edu.ua>
+Subject: Issue map read after Write Buffer Load command to ensure chip is ready to receive data.
+
+Signed-off-by: George Kashperko <george@znau.edu.ua>
+---
+ drivers/mtd/chips/cfi_cmdset_0002.c |    1 +
+ 1 file changed, 1 insertion(+)
+--- a/drivers/mtd/chips/cfi_cmdset_0002.c
++++ b/drivers/mtd/chips/cfi_cmdset_0002.c
+@@ -2050,6 +2050,7 @@ static int __xipram do_write_buffer(stru
+       /* Write Buffer Load */
+       map_write(map, CMD(0x25), cmd_adr);
++      (void) map_read(map, cmd_adr);
+       chip->state = FL_WRITING_TO_BUFFER;
diff --git a/target/linux/generic/pending-6.12/476-mtd-spi-nor-add-eon-en25q128.patch b/target/linux/generic/pending-6.12/476-mtd-spi-nor-add-eon-en25q128.patch
new file mode 100644 (file)
index 0000000..afc5e48
--- /dev/null
@@ -0,0 +1,22 @@
+From: Piotr Dymacz <pepe2k@gmail.com>
+Subject: kernel/mtd: add support for EON EN25Q128
+
+Signed-off-by: Piotr Dymacz <pepe2k@gmail.com>
+---
+ drivers/mtd/spi-nor/spi-nor.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/mtd/spi-nor/eon.c
++++ b/drivers/mtd/spi-nor/eon.c
+@@ -18,6 +18,11 @@ static const struct flash_info eon_nor_p
+               .name = "en25p64",
+               .size = SZ_8M,
+       }, {
++              .id = SNOR_ID(0x1c, 0x30, 0x18),
++              .name = "en25q128",
++              .size = SZ_16M,
++              .no_sfdp_flags = SECT_4K,
++      }, {
+               .id = SNOR_ID(0x1c, 0x30, 0x14),
+               .name = "en25q80a",
+               .size = SZ_1M,
diff --git a/target/linux/generic/pending-6.12/477-mtd-spi-nor-add-eon-en25qx128a.patch b/target/linux/generic/pending-6.12/477-mtd-spi-nor-add-eon-en25qx128a.patch
new file mode 100644 (file)
index 0000000..4dd229b
--- /dev/null
@@ -0,0 +1,24 @@
+From: Christian Marangi <ansuelsmth@gmail.com>
+Subject: kernel/mtd: add support for EON EN25QX128A
+
+Add support for EON EN25QX128A with no flags as it does
+support SFDP parsing.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/mtd/spi-nor/spi-nor.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/mtd/spi-nor/eon.c
++++ b/drivers/mtd/spi-nor/eon.c
+@@ -23,6 +23,10 @@ static const struct flash_info eon_nor_p
+               .size = SZ_16M,
+               .no_sfdp_flags = SECT_4K,
+       }, {
++              .id = SNOR_ID(0x1c, 0x71, 0x18),
++              .name = "en25qx128a",
++              .size = SZ_16M,
++      }, {
+               .id = SNOR_ID(0x1c, 0x30, 0x14),
+               .name = "en25q80a",
+               .size = SZ_1M,
diff --git a/target/linux/generic/pending-6.12/479-mtd-spi-nor-add-xtx-xt25f128b.patch b/target/linux/generic/pending-6.12/479-mtd-spi-nor-add-xtx-xt25f128b.patch
new file mode 100644 (file)
index 0000000..be8bafa
--- /dev/null
@@ -0,0 +1,84 @@
+From patchwork Thu Feb  6 17:19:41 2020
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+X-Patchwork-Submitter: Daniel Golle <daniel@makrotopia.org>
+X-Patchwork-Id: 1234465
+Date: Thu, 6 Feb 2020 19:19:41 +0200
+From: Daniel Golle <daniel@makrotopia.org>
+To: linux-mtd@lists.infradead.org
+Subject: [PATCH v2] mtd: spi-nor: Add support for xt25f128b chip
+Message-ID: <20200206171941.GA2398@makrotopia.org>
+MIME-Version: 1.0
+Content-Disposition: inline
+List-Subscribe: <http://lists.infradead.org/mailman/listinfo/linux-mtd>,
+ <mailto:linux-mtd-request@lists.infradead.org?subject=subscribe>
+Cc: Eitan Cohen <eitan@neot-semadar.com>, Piotr Dymacz <pepe2k@gmail.com>,
+ Tudor Ambarus <tudor.ambarus@microchip.com>
+Sender: "linux-mtd" <linux-mtd-bounces@lists.infradead.org>
+Errors-To: linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org
+
+Add XT25F128B made by XTX Technology (Shenzhen) Limited.
+This chip supports dual and quad read and uniform 4K-byte erase.
+Verified on Teltonika RUT955 which comes with XT25F128B in recent
+versions of the device.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ drivers/mtd/spi-nor/spi-nor.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/mtd/spi-nor/Makefile
++++ b/drivers/mtd/spi-nor/Makefile
+@@ -14,6 +14,7 @@ spi-nor-objs                 += spansion.o
+ spi-nor-objs                  += sst.o
+ spi-nor-objs                  += winbond.o
+ spi-nor-objs                  += xmc.o
++spi-nor-objs                  += xtx.o
+ spi-nor-$(CONFIG_DEBUG_FS)    += debugfs.o
+ obj-$(CONFIG_MTD_SPI_NOR)     += spi-nor.o
+--- /dev/null
++++ b/drivers/mtd/spi-nor/xtx.c
+@@ -0,0 +1,20 @@
++// SPDX-License-Identifier: GPL-2.0
++#include <linux/mtd/spi-nor.h>
++
++#include "core.h"
++
++static const struct flash_info xtx_parts[] = {
++      /* XTX Technology (Shenzhen) Limited */
++      {
++              .id = SNOR_ID(0x0B, 0x40, 0x18),
++              .name = "xt25f128b",
++              .size = SZ_16M,
++              .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
++      },
++};
++
++const struct spi_nor_manufacturer spi_nor_xtx = {
++      .name = "xtx",
++      .parts = xtx_parts,
++      .nparts = ARRAY_SIZE(xtx_parts),
++};
+--- a/drivers/mtd/spi-nor/core.c
++++ b/drivers/mtd/spi-nor/core.c
+@@ -1979,6 +1979,7 @@ static const struct spi_nor_manufacturer
+       &spi_nor_sst,
+       &spi_nor_winbond,
+       &spi_nor_xmc,
++      &spi_nor_xtx,
+ };
+ static const struct flash_info spi_nor_generic_flash = {
+--- a/drivers/mtd/spi-nor/core.h
++++ b/drivers/mtd/spi-nor/core.h
+@@ -593,6 +593,7 @@ extern const struct spi_nor_manufacturer
+ extern const struct spi_nor_manufacturer spi_nor_sst;
+ extern const struct spi_nor_manufacturer spi_nor_winbond;
+ extern const struct spi_nor_manufacturer spi_nor_xmc;
++extern const struct spi_nor_manufacturer spi_nor_xtx;
+ extern const struct attribute_group *spi_nor_sysfs_groups[];
diff --git a/target/linux/generic/pending-6.12/481-mtd-spi-nor-add-support-for-Gigadevice-GD25D05.patch b/target/linux/generic/pending-6.12/481-mtd-spi-nor-add-support-for-Gigadevice-GD25D05.patch
new file mode 100644 (file)
index 0000000..c4caf73
--- /dev/null
@@ -0,0 +1,25 @@
+From d68b4aa22e8c625685bfad642dd7337948dc0ad1 Mon Sep 17 00:00:00 2001
+From: Koen Vandeputte <koen.vandeputte@ncentric.com>
+Date: Mon, 6 Jan 2020 13:07:56 +0100
+Subject: [PATCH] mtd: spi-nor: add support for Gigadevice GD25D05
+
+Signed-off-by: Koen Vandeputte <koen.vandeputte@ncentric.com>
+---
+ drivers/mtd/spi-nor/spi-nor.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+--- a/drivers/mtd/spi-nor/gigadevice.c
++++ b/drivers/mtd/spi-nor/gigadevice.c
+@@ -35,6 +35,12 @@ static const struct spi_nor_fixups gd25q
+ static const struct flash_info gigadevice_nor_parts[] = {
+       {
++              .id = SNOR_ID(0xc8, 0x40, 0x10),
++              .name = "gd25q05",
++              .size = SZ_64K,
++              .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
++              .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
++      }, {
+               .id = SNOR_ID(0xc8, 0x40, 0x15),
+               .name = "gd25q16",
+               .size = SZ_2M,
diff --git a/target/linux/generic/pending-6.12/482-mtd-spi-nor-add-gd25q512.patch b/target/linux/generic/pending-6.12/482-mtd-spi-nor-add-gd25q512.patch
new file mode 100644 (file)
index 0000000..2b34d14
--- /dev/null
@@ -0,0 +1,25 @@
+From f8943df3beb0d3f9754bb35320c3a378727175a8 Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Thu, 14 Jul 2022 08:38:07 +0200
+Subject: [PATCH] spi-nor/gigadevic: add gd25q512
+
+---
+ drivers/mtd/spi-nor/gigadevice.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/mtd/spi-nor/gigadevice.c
++++ b/drivers/mtd/spi-nor/gigadevice.c
+@@ -71,6 +71,13 @@ static const struct flash_info gigadevic
+               .fixups = &gd25q256_fixups,
+               .fixup_flags = SPI_NOR_4B_OPCODES,
+       }, {
++              .id = SNOR_ID(0xc8, 0x40, 0x20),
++              .name = "gd25q512",
++              .size = SZ_64M,
++              .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
++              .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
++              .fixup_flags = SPI_NOR_4B_OPCODES,
++      }, {
+               .id = SNOR_ID(0xc8, 0x60, 0x16),
+               .name = "gd25lq32",
+               .size = SZ_4M,
diff --git a/target/linux/generic/pending-6.12/484-mtd-spi-nor-add-esmt-f25l16pa.patch b/target/linux/generic/pending-6.12/484-mtd-spi-nor-add-esmt-f25l16pa.patch
new file mode 100644 (file)
index 0000000..1e296ee
--- /dev/null
@@ -0,0 +1,27 @@
+From 87363cc0e522de3294ea6ae10fb468d2a8d6fb2f Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 12:17:21 +0200
+Subject: [PATCH] spi-nor/esmt.c: add esmt f25l16pa
+
+This fixes support for Dongwon T&I DW02-412H which uses F25L16PA(2S)
+flash.
+
+---
+ drivers/mtd/spi-nor/esmt.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/mtd/spi-nor/esmt.c
++++ b/drivers/mtd/spi-nor/esmt.c
+@@ -10,6 +10,12 @@
+ static const struct flash_info esmt_nor_parts[] = {
+       {
++              .id = SNOR_ID(0x8c, 0x21, 0x15),
++              .name = "f25l16pa-2s",
++              .size = SZ_2M,
++              .flags = SPI_NOR_HAS_LOCK,
++              .no_sfdp_flags = SECT_4K,
++      }, {
+               .id = SNOR_ID(0x8c, 0x20, 0x16),
+               .name = "f25l32pa",
+               .size = SZ_4M,
diff --git a/target/linux/generic/pending-6.12/485-mtd-spi-nor-add-xmc-xm25qh128c.patch b/target/linux/generic/pending-6.12/485-mtd-spi-nor-add-xmc-xm25qh128c.patch
new file mode 100644 (file)
index 0000000..886d755
--- /dev/null
@@ -0,0 +1,27 @@
+From f6b33d850f7f12555df2fa0e3349b33427bf5890 Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 12:19:01 +0200
+Subject: [PATCH] spi-nor/xmc.c: add xm25qh128c
+
+The XMC XM25QH128C is a 16MB SPI NOR chip. The patch is verified on
+Ruijie RG-EW3200GX PRO.
+Datasheet available at https://www.xmcwh.com/uploads/435/XM25QH128C.pdf
+
+---
+ drivers/mtd/spi-nor/xmc.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/mtd/spi-nor/xmc.c
++++ b/drivers/mtd/spi-nor/xmc.c
+@@ -19,6 +19,11 @@ static const struct flash_info xmc_nor_p
+               .name = "XM25QH128A",
+               .size = SZ_16M,
+               .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
++      }, {
++              .id = SNOR_ID(0x20, 0x40, 0x18),
++              .name = "XM25QH128C",
++              .size = SZ_16M,
++              .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+       },
+ };
diff --git a/target/linux/generic/pending-6.12/487-mtd-spinand-Add-support-for-Etron-EM73D044VCx.patch b/target/linux/generic/pending-6.12/487-mtd-spinand-Add-support-for-Etron-EM73D044VCx.patch
new file mode 100644 (file)
index 0000000..65d8384
--- /dev/null
@@ -0,0 +1,170 @@
+From f32085fc0b87049491b07e198d924d738a1a2834 Mon Sep 17 00:00:00 2001
+From: Daniel Danzberger <daniel@dd-wrt.com>
+Date: Wed, 3 Aug 2022 17:31:03 +0200
+Subject: [PATCH] mtd: spinand: Add support for Etron EM73D044VCx
+
+Airoha is a new ARM platform based on Cortex-A53 which has recently been
+merged into linux-next.
+
+Due to BootROM limitations on this platform, the Cortex-A53 can't run in
+Aarch64 mode and code must be compiled for 32-Bit ARM.
+
+This support is based mostly on those linux-next commits backported
+for kernel 5.15.
+
+Patches:
+1 - platform support = linux-next
+2 - clock driver = linux-next
+3 - gpio driver = linux-next
+4 - linux,usable-memory-range dts support = linux-next
+5 - mtd spinand driver
+6 - spi driver
+7 - pci driver (kconfig only, uses mediatek PCI) = linux-next
+
+Still missing:
+- Ethernet driver
+- Sysupgrade support
+
+A.t.m there exists one subtarget EN7523 with only one evaluation
+board.
+
+The initramfs can be run with the following commands from u-boot:
+-
+u-boot> setenv bootfile \
+       openwrt-airoha-airoha_en7523-evb-initramfs-kernel.bin
+u-boot> tftpboot
+u-boot> bootm 0x81800000
+-
+
+Submitted-by: Daniel Danzberger <daniel@dd-wrt.com>
+
+--- a/drivers/mtd/nand/spi/Makefile
++++ b/drivers/mtd/nand/spi/Makefile
+@@ -1,4 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0
+-spinand-objs := core.o alliancememory.o ato.o esmt.o fmsh.o foresee.o gigadevice.o macronix.o
+-spinand-objs += micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
++spinand-objs := core.o alliancememory.o ato.o esmt.o etron.o fmsh.o foresee.o gigadevice.o
++spinand-objs += macronix.o micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
+ obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -1186,6 +1186,7 @@ static const struct spinand_manufacturer
+       &ato_spinand_manufacturer,
+       &esmt_8c_spinand_manufacturer,
+       &esmt_c8_spinand_manufacturer,
++      &etron_spinand_manufacturer,
+       &fmsh_spinand_manufacturer,
+       &foresee_spinand_manufacturer,
+       &gigadevice_spinand_manufacturer,
+--- /dev/null
++++ b/drivers/mtd/nand/spi/etron.c
+@@ -0,0 +1,98 @@
++// SPDX-License-Identifier: GPL-2.0
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include <linux/mtd/spinand.h>
++
++#define SPINAND_MFR_ETRON                     0xd5
++
++
++static SPINAND_OP_VARIANTS(read_cache_variants,
++              SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
++              SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
++
++static SPINAND_OP_VARIANTS(write_cache_variants,
++              SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
++              SPINAND_PROG_LOAD(true, 0, NULL, 0));
++
++static SPINAND_OP_VARIANTS(update_cache_variants,
++              SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
++              SPINAND_PROG_LOAD(false, 0, NULL, 0));
++
++static int etron_ooblayout_ecc(struct mtd_info *mtd, int section,
++                                      struct mtd_oob_region *oobregion)
++{
++      if (section)
++              return -ERANGE;
++
++      oobregion->offset = 72;
++      oobregion->length = 56;
++
++      return 0;
++}
++
++static int etron_ooblayout_free(struct mtd_info *mtd, int section,
++                         struct mtd_oob_region *oobregion)
++{
++      if (section)
++              return -ERANGE;
++
++      oobregion->offset = 1;
++      oobregion->length = 71;
++
++      return 0;
++}
++
++static int etron_ecc_get_status(struct spinand_device *spinand, u8 status)
++{
++      switch (status & STATUS_ECC_MASK) {
++      case STATUS_ECC_NO_BITFLIPS:
++              return 0;
++
++      case STATUS_ECC_HAS_BITFLIPS:
++              /* Between 1-7 bitflips were corrected */
++              return 7;
++
++      case STATUS_ECC_MASK:
++              /* Maximum bitflips were corrected */
++              return 8;
++
++      case STATUS_ECC_UNCOR_ERROR:
++              return -EBADMSG;
++      }
++
++      return -EINVAL;
++}
++
++static const struct mtd_ooblayout_ops etron_ooblayout = {
++      .ecc = etron_ooblayout_ecc,
++      .free = etron_ooblayout_free,
++};
++
++static const struct spinand_info etron_spinand_table[] = {
++      SPINAND_INFO("EM73D044VCx",
++                   SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x1f),
++                   // bpc, pagesize, oobsize, pagesperblock, bperlun, maxbadplun, ppl, lpt, #t
++                   NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
++                   NAND_ECCREQ(8, 512),
++                   SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++                                            &write_cache_variants,
++                                            &update_cache_variants),
++                   SPINAND_HAS_QE_BIT,
++                   SPINAND_ECCINFO(&etron_ooblayout, etron_ecc_get_status)),
++};
++
++static const struct spinand_manufacturer_ops etron_spinand_manuf_ops = {
++};
++
++const struct spinand_manufacturer etron_spinand_manufacturer = {
++      .id = SPINAND_MFR_ETRON,
++      .name = "Etron",
++      .chips = etron_spinand_table,
++      .nchips = ARRAY_SIZE(etron_spinand_table),
++      .ops = &etron_spinand_manuf_ops,
++};
+--- a/include/linux/mtd/spinand.h
++++ b/include/linux/mtd/spinand.h
+@@ -264,6 +264,7 @@ extern const struct spinand_manufacturer
+ extern const struct spinand_manufacturer ato_spinand_manufacturer;
+ extern const struct spinand_manufacturer esmt_8c_spinand_manufacturer;
+ extern const struct spinand_manufacturer esmt_c8_spinand_manufacturer;
++extern const struct spinand_manufacturer etron_spinand_manufacturer;
+ extern const struct spinand_manufacturer fmsh_spinand_manufacturer;
+ extern const struct spinand_manufacturer foresee_spinand_manufacturer;
+ extern const struct spinand_manufacturer gigadevice_spinand_manufacturer;
diff --git a/target/linux/generic/pending-6.12/488-mtd-spi-nor-add-xmc-xm25qh64c.patch b/target/linux/generic/pending-6.12/488-mtd-spi-nor-add-xmc-xm25qh64c.patch
new file mode 100644 (file)
index 0000000..6e65c55
--- /dev/null
@@ -0,0 +1,25 @@
+From: Joe Mullally <jwmullally@gmail.com>
+Subject: mtd/spi-nor/xmc: add support for XMC XM25QH64C
+
+The XMC XM25QH64C is a 8MB SPI NOR chip. The patch is verified on TL-WPA8631P v3.
+Datasheet available at https://www.xmcwh.com/uploads/442/XM25QH64C.pdf
+
+Signed-off-by: Joe Mullally <jwmullally@gmail.com>
+---
+ drivers/mtd/spi-nor/xmc.c                             | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/mtd/spi-nor/xmc.c
++++ b/drivers/mtd/spi-nor/xmc.c
+@@ -15,6 +15,11 @@ static const struct flash_info xmc_nor_p
+               .size = SZ_8M,
+               .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+       }, {
++              .id = SNOR_ID(0x20, 0x40, 0x17),
++              .name = "XM25QH64C",
++              .size = SZ_8M,
++              .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
++      }, {
+               .id = SNOR_ID(0x20, 0x70, 0x18),
+               .name = "XM25QH128A",
+               .size = SZ_16M,
diff --git a/target/linux/generic/pending-6.12/490-ubi-auto-attach-mtd-device-named-ubi-or-data-on-boot.patch b/target/linux/generic/pending-6.12/490-ubi-auto-attach-mtd-device-named-ubi-or-data-on-boot.patch
new file mode 100644 (file)
index 0000000..27b8611
--- /dev/null
@@ -0,0 +1,104 @@
+From: Daniel Golle <daniel@makrotopia.org>
+Subject: ubi: auto-attach mtd device named "ubi" or "data" on boot
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/mtd/ubi/build.c | 36 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 36 insertions(+)
+
+--- a/drivers/mtd/ubi/build.c
++++ b/drivers/mtd/ubi/build.c
+@@ -1263,6 +1263,80 @@ static struct mtd_notifier ubi_mtd_notif
+       .remove = ubi_notify_remove,
+ };
++
++/*
++ * This function tries attaching mtd partitions named either "ubi" or "data"
++ * during boot.
++ */
++static void __init ubi_auto_attach(void)
++{
++      int err;
++      struct mtd_info *mtd;
++      struct device_node *np;
++      loff_t offset = 0;
++      size_t len;
++      char magic[4];
++
++      /* try attaching mtd device named "ubi" or "data" */
++      mtd = open_mtd_device("ubi");
++      if (IS_ERR(mtd))
++              mtd = open_mtd_device("data");
++
++      if (IS_ERR(mtd))
++              return;
++
++      /* skip "linux,ubi" mtd as it has already been attached */
++      np = mtd_get_of_node(mtd);
++      if (of_device_is_compatible(np, "linux,ubi"))
++              goto cleanup;
++
++      /* get the first not bad block */
++      if (mtd_can_have_bb(mtd))
++              while (mtd_block_isbad(mtd, offset)) {
++                      offset += mtd->erasesize;
++
++                      if (offset > mtd->size) {
++                              pr_err("UBI error: Failed to find a non-bad "
++                                     "block on mtd%d\n", mtd->index);
++                              goto cleanup;
++                      }
++              }
++
++      /* check if the read from flash was successful */
++      err = mtd_read(mtd, offset, 4, &len, (void *) magic);
++      if ((err && !mtd_is_bitflip(err)) || len != 4) {
++              pr_err("UBI error: unable to read from mtd%d\n", mtd->index);
++              goto cleanup;
++      }
++
++      /* check for a valid ubi magic */
++      if (strncmp(magic, "UBI#", 4)) {
++              pr_err("UBI error: no valid UBI magic found inside mtd%d\n", mtd->index);
++              goto cleanup;
++      }
++
++      /* don't auto-add media types where UBI doesn't makes sense */
++      if (mtd->type != MTD_NANDFLASH &&
++          mtd->type != MTD_NORFLASH &&
++          mtd->type != MTD_DATAFLASH &&
++          mtd->type != MTD_MLCNANDFLASH)
++              goto cleanup;
++
++      mutex_lock(&ubi_devices_mutex);
++      pr_notice("UBI: auto-attach mtd%d\n", mtd->index);
++      err = ubi_attach_mtd_dev(mtd, UBI_DEV_NUM_AUTO, 0, 0, false, false);
++      mutex_unlock(&ubi_devices_mutex);
++      if (err < 0) {
++              pr_err("UBI error: cannot attach mtd%d\n", mtd->index);
++              goto cleanup;
++      }
++
++      return;
++
++cleanup:
++      put_mtd_device(mtd);
++}
++
+ static int __init ubi_init_attach(void)
+ {
+       int err, i, k;
+@@ -1314,6 +1388,12 @@ static int __init ubi_init_attach(void)
+               }
+       }
++      /* auto-attach mtd devices only if built-in to the kernel and no ubi.mtd
++       * parameter was given */
++      if (IS_ENABLED(CONFIG_MTD_ROOTFS_ROOT_DEV) &&
++          !ubi_is_module() && !mtd_devs)
++              ubi_auto_attach();
++
+       return 0;
+ out_detach:
diff --git a/target/linux/generic/pending-6.12/491-ubi-auto-create-ubiblock-device-for-rootfs.patch b/target/linux/generic/pending-6.12/491-ubi-auto-create-ubiblock-device-for-rootfs.patch
new file mode 100644 (file)
index 0000000..44c3bf3
--- /dev/null
@@ -0,0 +1,77 @@
+From: Daniel Golle <daniel@makrotopia.org>
+Subject: ubi: auto-create ubiblock device for rootfs
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/mtd/ubi/block.c | 42 ++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 42 insertions(+)
+
+--- a/drivers/mtd/ubi/block.c
++++ b/drivers/mtd/ubi/block.c
+@@ -575,10 +575,47 @@ match_volume_desc(struct ubi_volume_info
+       return true;
+ }
++#define UBIFS_NODE_MAGIC  0x06101831
++static inline int ubi_vol_is_ubifs(struct ubi_volume_desc *desc)
++{
++      int ret;
++      uint32_t magic_of, magic;
++      ret = ubi_read(desc, 0, (char *)&magic_of, 0, 4);
++      if (ret)
++              return 0;
++      magic = le32_to_cpu(magic_of);
++      return magic == UBIFS_NODE_MAGIC;
++}
++
++static void ubiblock_create_auto_rootfs(struct ubi_volume_info *vi)
++{
++      int ret, is_ubifs;
++      struct ubi_volume_desc *desc;
++
++      if (strcmp(vi->name, "rootfs") &&
++          strcmp(vi->name, "fit"))
++              return;
++
++      desc = ubi_open_volume(vi->ubi_num, vi->vol_id, UBI_READONLY);
++      if (IS_ERR(desc))
++              return;
++
++      is_ubifs = ubi_vol_is_ubifs(desc);
++      ubi_close_volume(desc);
++      if (is_ubifs)
++              return;
++
++      ret = ubiblock_create(vi);
++      if (ret)
++              pr_err("UBI error: block: can't add '%s' volume, err=%d\n",
++                      vi->name, ret);
++}
++
+ static void
+ ubiblock_create_from_param(struct ubi_volume_info *vi)
+ {
+       int i, ret = 0;
++      bool got_param = false;
+       struct ubiblock_param *p;
+       /*
+@@ -591,6 +628,7 @@ ubiblock_create_from_param(struct ubi_vo
+               if (!match_volume_desc(vi, p->name, p->ubi_num, p->vol_id))
+                       continue;
++              got_param = true;
+               ret = ubiblock_create(vi);
+               if (ret) {
+                       pr_err(
+@@ -599,6 +637,10 @@ ubiblock_create_from_param(struct ubi_vo
+               }
+               break;
+       }
++
++      /* auto-attach "rootfs" volume if existing and non-ubifs */
++      if (!got_param && IS_ENABLED(CONFIG_MTD_ROOTFS_ROOT_DEV))
++              ubiblock_create_auto_rootfs(vi);
+ }
+ static int ubiblock_notify(struct notifier_block *nb,
diff --git a/target/linux/generic/pending-6.12/492-try-auto-mounting-ubi0-rootfs-in-init-do_mounts.c.patch b/target/linux/generic/pending-6.12/492-try-auto-mounting-ubi0-rootfs-in-init-do_mounts.c.patch
new file mode 100644 (file)
index 0000000..7a4c633
--- /dev/null
@@ -0,0 +1,54 @@
+From: Daniel Golle <daniel@makrotopia.org>
+Subject: try auto-mounting ubi0:rootfs in init/do_mounts.c
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ init/do_mounts.c | 26 +++++++++++++++++++++++++-
+ 1 file changed, 25 insertions(+), 1 deletion(-)
+
+--- a/init/do_mounts.c
++++ b/init/do_mounts.c
+@@ -250,7 +250,30 @@ retry:
+ out:
+       put_page(page);
+ }
+- 
++
++#ifdef CONFIG_MTD_ROOTFS_ROOT_DEV
++static int __init mount_ubi_rootfs(void)
++{
++      int flags = MS_SILENT;
++      int err, tried = 0;
++
++      while (tried < 2) {
++              err = do_mount_root("ubi0:rootfs", "ubifs", flags, \
++                                      root_mount_data);
++              switch (err) {
++                      case -EACCES:
++                              flags |= MS_RDONLY;
++                              tried++;
++                              break;
++                      default:
++                              return err;
++              }
++      }
++
++      return -EINVAL;
++}
++#endif
++
+ #ifdef CONFIG_ROOT_NFS
+ #define NFSROOT_TIMEOUT_MIN   5
+@@ -387,6 +410,11 @@ static inline void mount_block_root(char
+ void __init mount_root(char *root_device_name)
+ {
++#ifdef CONFIG_MTD_ROOTFS_ROOT_DEV
++      if (!mount_ubi_rootfs())
++              return;
++#endif
++
+       switch (ROOT_DEV) {
+       case Root_NFS:
+               mount_nfs_root();
diff --git a/target/linux/generic/pending-6.12/493-ubi-set-ROOT_DEV-to-ubiblock-rootfs-if-unset.patch b/target/linux/generic/pending-6.12/493-ubi-set-ROOT_DEV-to-ubiblock-rootfs-if-unset.patch
new file mode 100644 (file)
index 0000000..4007c8a
--- /dev/null
@@ -0,0 +1,34 @@
+From: Daniel Golle <daniel@makrotopia.org>
+Subject: ubi: set ROOT_DEV to ubiblock "rootfs" if unset
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/mtd/ubi/block.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+--- a/drivers/mtd/ubi/block.c
++++ b/drivers/mtd/ubi/block.c
+@@ -41,6 +41,7 @@
+ #include <linux/scatterlist.h>
+ #include <linux/idr.h>
+ #include <asm/div64.h>
++#include <linux/root_dev.h>
+ #include "ubi-media.h"
+ #include "ubi.h"
+@@ -431,6 +432,15 @@ int ubiblock_create(struct ubi_volume_in
+       dev_info(disk_to_dev(dev->gd), "created from ubi%d:%d(%s)",
+                dev->ubi_num, dev->vol_id, vi->name);
+       mutex_unlock(&devices_mutex);
++
++      if (!strcmp(vi->name, "rootfs") &&
++          IS_ENABLED(CONFIG_MTD_ROOTFS_ROOT_DEV) &&
++          ROOT_DEV == 0) {
++              pr_notice("ubiblock: device ubiblock%d_%d (%s) set to be root filesystem\n",
++                        dev->ubi_num, dev->vol_id, vi->name);
++              ROOT_DEV = MKDEV(gd->major, gd->first_minor);
++      }
++
+       return 0;
+ out_remove_minor:
diff --git a/target/linux/generic/pending-6.12/494-mtd-ubi-add-EOF-marker-support.patch b/target/linux/generic/pending-6.12/494-mtd-ubi-add-EOF-marker-support.patch
new file mode 100644 (file)
index 0000000..4134317
--- /dev/null
@@ -0,0 +1,60 @@
+From: Gabor Juhos <juhosg@openwrt.org>
+Subject: mtd: add EOF marker support to the UBI layer
+
+Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
+---
+ drivers/mtd/ubi/attach.c | 25 ++++++++++++++++++++++---
+ drivers/mtd/ubi/ubi.h    |  1 +
+ 2 files changed, 23 insertions(+), 3 deletions(-)
+
+--- a/drivers/mtd/ubi/attach.c
++++ b/drivers/mtd/ubi/attach.c
+@@ -926,6 +926,13 @@ static bool vol_ignored(int vol_id)
+ #endif
+ }
++static bool ec_hdr_has_eof(struct ubi_ec_hdr *ech)
++{
++      return ech->padding1[0] == 'E' &&
++             ech->padding1[1] == 'O' &&
++             ech->padding1[2] == 'F';
++}
++
+ /**
+  * scan_peb - scan and process UBI headers of a PEB.
+  * @ubi: UBI device description object
+@@ -958,9 +965,21 @@ static int scan_peb(struct ubi_device *u
+               return 0;
+       }
+-      err = ubi_io_read_ec_hdr(ubi, pnum, ech, 0);
+-      if (err < 0)
+-              return err;
++      if (!ai->eof_found) {
++              err = ubi_io_read_ec_hdr(ubi, pnum, ech, 0);
++              if (err < 0)
++                      return err;
++
++              if (ec_hdr_has_eof(ech)) {
++                      pr_notice("UBI: EOF marker found, PEBs from %d will be erased\n",
++                              pnum);
++                      ai->eof_found = true;
++              }
++      }
++
++      if (ai->eof_found)
++              err = UBI_IO_FF_BITFLIPS;
++
+       switch (err) {
+       case 0:
+               break;
+--- a/drivers/mtd/ubi/ubi.h
++++ b/drivers/mtd/ubi/ubi.h
+@@ -778,6 +778,7 @@ struct ubi_attach_info {
+       int mean_ec;
+       uint64_t ec_sum;
+       int ec_count;
++      bool eof_found;
+       struct kmem_cache *aeb_slab_cache;
+       struct ubi_ec_hdr *ech;
+       struct ubi_vid_io_buf *vidb;
diff --git a/target/linux/generic/pending-6.12/496-dt-bindings-add-bindings-for-mtd-concat-devices.patch b/target/linux/generic/pending-6.12/496-dt-bindings-add-bindings-for-mtd-concat-devices.patch
new file mode 100644 (file)
index 0000000..01f3b9e
--- /dev/null
@@ -0,0 +1,52 @@
+From 5734c6669fba7ddb5ef491ccff7159d15dba0b59 Mon Sep 17 00:00:00 2001
+From: Bernhard Frauendienst <kernel@nospam.obeliks.de>
+Date: Wed, 5 Sep 2018 01:32:51 +0200
+Subject: [PATCH 496/497] dt-bindings: add bindings for mtd-concat devices
+
+Document virtual mtd-concat device bindings.
+
+Signed-off-by: Bernhard Frauendienst <kernel@nospam.obeliks.de>
+---
+ .../devicetree/bindings/mtd/mtd-concat.txt    | 36 +++++++++++++++++++
+ 1 file changed, 36 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/mtd/mtd-concat.txt
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/mtd/mtd-concat.txt
+@@ -0,0 +1,36 @@
++Virtual MTD concat device
++
++Requires properties:
++- devices: list of phandles to mtd nodes that should be concatenated
++
++Example:
++
++&spi {
++      flash0: flash@0 {
++              ...
++      };
++      flash1: flash@1 {
++              ...
++      };
++};
++
++flash {
++      compatible = "mtd-concat";
++
++      devices = <&flash0 &flash1>;
++
++      partitions {
++              compatible = "fixed-partitions";
++
++              partition@0 {
++                      label = "boot";
++                      reg = <0x0000000 0x0040000>;
++                      read-only;
++              };
++
++              partition@40000 {
++                      label = "firmware";
++                      reg = <0x0040000 0x1fc0000>;
++              };
++      }
++}
diff --git a/target/linux/generic/pending-6.12/497-mtd-mtdconcat-add-dt-driver-for-concat-devices.patch b/target/linux/generic/pending-6.12/497-mtd-mtdconcat-add-dt-driver-for-concat-devices.patch
new file mode 100644 (file)
index 0000000..cda5e10
--- /dev/null
@@ -0,0 +1,215 @@
+From e53f712d8eac71f54399b61038ccf87d2cee99d7 Mon Sep 17 00:00:00 2001
+From: Bernhard Frauendienst <kernel@nospam.obeliks.de>
+Date: Sat, 25 Aug 2018 12:35:22 +0200
+Subject: [PATCH 497/497] mtd: mtdconcat: add dt driver for concat devices
+
+Some mtd drivers like physmap variants have support for concatenating
+multiple mtd devices, but there is no generic way to define such a
+concat device from within the device tree.
+
+This is useful for some SoC boards that use multiple flash chips as
+memory banks of a single mtd device, with partitions spanning chip
+borders.
+
+This commit adds a driver for creating virtual mtd-concat devices. They
+must have a compatible = "mtd-concat" line, and define a list of devices
+to concat in the 'devices' property, for example:
+
+flash {
+  compatible = "mtd-concat";
+
+  devices = <&flash0 &flash1>;
+
+  partitions {
+    ...
+  };
+};
+
+The driver is added to the very end of the mtd Makefile to increase the
+likelyhood of all child devices already being loaded at the time of
+probing, preventing unnecessary deferred probes.
+
+Signed-off-by: Bernhard Frauendienst <kernel@nospam.obeliks.de>
+---
+ drivers/mtd/Kconfig                 |   2 +
+ drivers/mtd/Makefile                |   3 +
+ drivers/mtd/composite/Kconfig       |  12 +++
+ drivers/mtd/composite/Makefile      |   6 ++
+ drivers/mtd/composite/virt_concat.c | 128 ++++++++++++++++++++++++++++
+ 5 files changed, 151 insertions(+)
+ create mode 100644 drivers/mtd/composite/Kconfig
+ create mode 100644 drivers/mtd/composite/Makefile
+ create mode 100644 drivers/mtd/composite/virt_concat.c
+
+--- a/drivers/mtd/Kconfig
++++ b/drivers/mtd/Kconfig
+@@ -241,4 +241,6 @@ source "drivers/mtd/ubi/Kconfig"
+ source "drivers/mtd/hyperbus/Kconfig"
++source "drivers/mtd/composite/Kconfig"
++
+ endif # MTD
+--- a/drivers/mtd/Makefile
++++ b/drivers/mtd/Makefile
+@@ -33,3 +33,6 @@ obj-y                += chips/ lpddr/ maps/ devices/ n
+ obj-$(CONFIG_MTD_SPI_NOR)     += spi-nor/
+ obj-$(CONFIG_MTD_UBI)         += ubi/
+ obj-$(CONFIG_MTD_HYPERBUS)    += hyperbus/
++
++# Composite drivers must be loaded last
++obj-y                         += composite/
+--- /dev/null
++++ b/drivers/mtd/composite/Kconfig
+@@ -0,0 +1,12 @@
++menu "Composite MTD device drivers"
++      depends on MTD!=n
++
++config MTD_VIRT_CONCAT
++      tristate "Virtual concat MTD device"
++      help
++        This driver allows creation of a virtual MTD concat device, which
++        concatenates multiple underlying MTD devices to a single device.
++        This is required by some SoC boards where multiple memory banks are
++        used as one device with partitions spanning across device boundaries.
++
++endmenu
+--- /dev/null
++++ b/drivers/mtd/composite/Makefile
+@@ -0,0 +1,6 @@
++# SPDX-License-Identifier: GPL-2.0
++#
++# linux/drivers/mtd/composite/Makefile
++#
++
++obj-$(CONFIG_MTD_VIRT_CONCAT)   += virt_concat.o
+--- /dev/null
++++ b/drivers/mtd/composite/virt_concat.c
+@@ -0,0 +1,127 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Virtual concat MTD device driver
++ *
++ * Copyright (C) 2018 Bernhard Frauendienst
++ * Author: Bernhard Frauendienst, kernel@nospam.obeliks.de
++ */
++
++#include <linux/platform_device.h>
++#include <linux/module.h>
++#include <linux/device.h>
++#include <linux/mtd/concat.h>
++#include <linux/mtd/mtd.h>
++#include <linux/mtd/partitions.h>
++#include <linux/of.h>
++#include <linux/mod_devicetable.h>
++#include <linux/slab.h>
++
++/*
++ * struct of_virt_concat - platform device driver data.
++ * @cmtd the final mtd_concat device
++ * @num_devices the number of devices in @devices
++ * @devices points to an array of devices already loaded
++ */
++struct of_virt_concat {
++      struct mtd_info *cmtd;
++      int num_devices;
++      struct mtd_info **devices;
++};
++
++static void virt_concat_remove(struct platform_device *pdev)
++{
++      struct of_virt_concat *info;
++      int i;
++
++      info = platform_get_drvdata(pdev);
++      if (!info)
++              return;
++
++      // unset data for when this is called after a probe error
++      platform_set_drvdata(pdev, NULL);
++
++      if (info->cmtd) {
++              mtd_device_unregister(info->cmtd);
++              mtd_concat_destroy(info->cmtd);
++      }
++
++      if (info->devices) {
++              for (i = 0; i < info->num_devices; i++)
++                      put_mtd_device(info->devices[i]);
++      }
++}
++
++static int virt_concat_probe(struct platform_device *pdev)
++{
++      struct device_node *node = pdev->dev.of_node;
++      struct of_phandle_iterator it;
++      struct of_virt_concat *info;
++      struct mtd_info *mtd;
++      int err = 0, count;
++
++      count = of_count_phandle_with_args(node, "devices", NULL);
++      if (count <= 0)
++              return -EINVAL;
++
++      info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
++      if (!info)
++              return -ENOMEM;
++      info->devices = devm_kcalloc(&pdev->dev, count,
++                                   sizeof(*(info->devices)), GFP_KERNEL);
++      if (!info->devices) {
++              err = -ENOMEM;
++              goto err_remove;
++      }
++
++      platform_set_drvdata(pdev, info);
++
++      of_for_each_phandle(&it, err, node, "devices", NULL, 0) {
++              mtd = of_get_mtd_device_by_node(it.node);
++              if (IS_ERR(mtd)) {
++                      of_node_put(it.node);
++                      err = -EPROBE_DEFER;
++                      goto err_remove;
++              }
++
++              info->devices[info->num_devices++] = mtd;
++      }
++
++      info->cmtd = mtd_concat_create(info->devices, info->num_devices,
++                                     dev_name(&pdev->dev));
++      if (!info->cmtd) {
++              err = -ENXIO;
++              goto err_remove;
++      }
++
++      info->cmtd->dev.parent = &pdev->dev;
++      mtd_set_of_node(info->cmtd, node);
++      mtd_device_register(info->cmtd, NULL, 0);
++
++      return 0;
++
++err_remove:
++      virt_concat_remove(pdev);
++
++      return err;
++}
++
++static const struct of_device_id virt_concat_of_match[] = {
++      { .compatible = "mtd-concat", },
++      { /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, virt_concat_of_match);
++
++static struct platform_driver virt_concat_driver = {
++      .probe = virt_concat_probe,
++      .remove = virt_concat_remove,
++      .driver  = {
++              .name   = "virt-mtdconcat",
++              .of_match_table = virt_concat_of_match,
++      },
++};
++
++module_platform_driver(virt_concat_driver);
++
++MODULE_LICENSE("GPL v2");
++MODULE_AUTHOR("Bernhard Frauendienst <kernel@nospam.obeliks.de>");
++MODULE_DESCRIPTION("Virtual concat MTD device driver");
diff --git a/target/linux/generic/pending-6.12/500-fs_cdrom_dependencies.patch b/target/linux/generic/pending-6.12/500-fs_cdrom_dependencies.patch
new file mode 100644 (file)
index 0000000..7c14358
--- /dev/null
@@ -0,0 +1,52 @@
+From af7b91bcecce0eae24e90acd35d96ecee73e1407 Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 12:21:15 +0200
+Subject: [PATCH] fs: add cdrom dependency
+
+---
+ fs/hfs/Kconfig     | 1 +
+ fs/hfsplus/Kconfig | 1 +
+ fs/isofs/Kconfig   | 1 +
+ fs/udf/Kconfig     | 1 +
+ 4 files changed, 4 insertions(+)
+
+--- a/fs/hfs/Kconfig
++++ b/fs/hfs/Kconfig
+@@ -2,6 +2,7 @@
+ config HFS_FS
+       tristate "Apple Macintosh file system support"
+       depends on BLOCK
++      select CDROM
+       select BUFFER_HEAD
+       select NLS
+       select LEGACY_DIRECT_IO
+--- a/fs/hfsplus/Kconfig
++++ b/fs/hfsplus/Kconfig
+@@ -2,6 +2,7 @@
+ config HFSPLUS_FS
+       tristate "Apple Extended HFS file system support"
+       depends on BLOCK
++      select CDROM
+       select BUFFER_HEAD
+       select NLS
+       select NLS_UTF8
+--- a/fs/isofs/Kconfig
++++ b/fs/isofs/Kconfig
+@@ -1,6 +1,7 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+ config ISO9660_FS
+       tristate "ISO 9660 CDROM file system support"
++      select CDROM
+       select BUFFER_HEAD
+       help
+         This is the standard file system used on CD-ROMs.  It was previously
+--- a/fs/udf/Kconfig
++++ b/fs/udf/Kconfig
+@@ -1,6 +1,7 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+ config UDF_FS
+       tristate "UDF file system support"
++      select CDROM
+       select BUFFER_HEAD
+       select CRC_ITU_T
+       select NLS
diff --git a/target/linux/generic/pending-6.12/510-block-add-uImage.FIT-subimage-block-driver.patch b/target/linux/generic/pending-6.12/510-block-add-uImage.FIT-subimage-block-driver.patch
new file mode 100644 (file)
index 0000000..74b079a
--- /dev/null
@@ -0,0 +1,757 @@
+From 3a200922ac4cbf4bfffdf86c0957b1401474e839 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 16 Nov 2022 12:49:52 +0000
+Subject: [PATCH 1/2] block: add uImage.FIT subimage block driver
+
+Add a small block driver which exposes filesystem sub-images contained
+in U-Boot uImage.FIT images as block devices.
+
+The uImage.FIT image has to be stored directly on a block device or
+partition, MTD device or partition, or UBI volume.
+
+The driver is intended for systems using the U-Boot bootloader and
+uses the root device hint left by the bootloader (or the user) in
+the 'chosen' section of the device-tree.
+
+Example:
+/dts-v1/;
+/ {
+        chosen {
+                rootdisk = <&mmc0_part3>;
+        };
+};
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ MAINTAINERS                 |   6 +
+ drivers/block/Kconfig       |  12 +
+ drivers/block/Makefile      |   2 +
+ drivers/block/fitblk.c      | 660 ++++++++++++++++++++++++++++++++++++
+ drivers/block/open          |   4 +
+ include/uapi/linux/fitblk.h |  10 +
+ 6 files changed, 694 insertions(+)
+ create mode 100644 drivers/block/fitblk.c
+ create mode 100644 drivers/block/open
+ create mode 100644 include/uapi/linux/fitblk.h
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -23677,6 +23677,12 @@ F:    Documentation/filesystems/ubifs-authe
+ F:    Documentation/filesystems/ubifs.rst
+ F:    fs/ubifs/
++U-BOOT UIMAGE.FIT PARSER
++M:    Daniel Golle <daniel@makrotopia.org>
++L:    linux-block@vger.kernel.org
++S:    Maintained
++F:    drivers/block/fitblk.c
++
+ UBLK USERSPACE BLOCK DRIVER
+ M:    Ming Lei <ming.lei@redhat.com>
+ L:    linux-block@vger.kernel.org
+--- a/drivers/block/Kconfig
++++ b/drivers/block/Kconfig
+@@ -363,6 +363,18 @@ config BLK_DEV_RUST_NULL
+         If unsure, say N.
++config UIMAGE_FIT_BLK
++      bool "uImage.FIT block driver"
++      help
++        This driver allows using filesystems contained in uImage.FIT images
++        by mapping them as block devices.
++
++        It can currently not be built as a module due to libfdt symbols not
++        being exported.
++
++        Say Y if you want to mount filesystems sub-images of a uImage.FIT
++        stored in a block device partition, mtdblock or ubiblock device.
++
+ config BLK_DEV_RBD
+       tristate "Rados block device (RBD)"
+       depends on INET && BLOCK
+--- a/drivers/block/Makefile
++++ b/drivers/block/Makefile
+@@ -42,4 +42,6 @@ obj-$(CONFIG_BLK_DEV_NULL_BLK)       += null_b
+ obj-$(CONFIG_BLK_DEV_UBLK)                    += ublk_drv.o
++obj-$(CONFIG_UIMAGE_FIT_BLK)  += fitblk.o
++
+ swim_mod-y    := swim.o swim_asm.o
+--- /dev/null
++++ b/drivers/block/fitblk.c
+@@ -0,0 +1,660 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * uImage.FIT virtual block device driver.
++ *
++ * Copyright (C) 2023 Daniel Golle
++ * Copyright (C) 2007 Nick Piggin
++ * Copyright (C) 2007 Novell Inc.
++ *
++ * Initially derived from drivers/block/brd.c which is in parts derived from
++ * drivers/block/rd.c, and drivers/block/loop.c, copyright of their respective
++ * owners.
++ *
++ * uImage.FIT headers extracted from Das U-Boot
++ *  (C) Copyright 2008 Semihalf
++ *  (C) Copyright 2000-2005
++ *  Wolfgang Denk, DENX Software Engineering, wd@denx.de.
++ */
++
++#include <linux/init.h>
++#include <linux/initrd.h>
++#include <linux/module.h>
++#include <linux/moduleparam.h>
++#include <linux/major.h>
++#include <linux/blkdev.h>
++#include <linux/blkpg.h>
++#include <linux/blk-mq.h>
++#include <linux/ctype.h>
++#include <linux/hdreg.h>
++#include <linux/list.h>
++#include <linux/mutex.h>
++#include <linux/of.h>
++#include <linux/of_device.h>
++#include <linux/of_fdt.h>
++#include <linux/pagemap.h>
++#include <linux/platform_device.h>
++#include <linux/property.h>
++#include <linux/refcount.h>
++#include <linux/task_work.h>
++#include <linux/types.h>
++#include <linux/libfdt.h>
++#include <linux/mtd/mtd.h>
++#include <linux/root_dev.h>
++#include <uapi/linux/fitblk.h>
++
++#define FIT_DEVICE_PREFIX     "fit"
++
++/* maximum number of pages used for the uImage.FIT index structure */
++#define FIT_MAX_PAGES         1024
++
++/* minimum free sectors to map as read-write "remainder" volume */
++#define MIN_FREE_SECT         16
++
++/* maximum number of mapped loadables */
++#define MAX_FIT_LOADABLES     16
++
++/* constants for uImage.FIT structrure traversal */
++#define FIT_IMAGES_PATH               "/images"
++#define FIT_CONFS_PATH                "/configurations"
++
++/* hash/signature/key node */
++#define FIT_HASH_NODENAME     "hash"
++#define FIT_ALGO_PROP         "algo"
++#define FIT_VALUE_PROP                "value"
++#define FIT_IGNORE_PROP               "uboot-ignore"
++#define FIT_SIG_NODENAME      "signature"
++#define FIT_KEY_REQUIRED      "required"
++#define FIT_KEY_HINT          "key-name-hint"
++
++/* cipher node */
++#define FIT_CIPHER_NODENAME   "cipher"
++#define FIT_ALGO_PROP         "algo"
++
++/* image node */
++#define FIT_DATA_PROP         "data"
++#define FIT_DATA_POSITION_PROP        "data-position"
++#define FIT_DATA_OFFSET_PROP  "data-offset"
++#define FIT_DATA_SIZE_PROP    "data-size"
++#define FIT_TIMESTAMP_PROP    "timestamp"
++#define FIT_DESC_PROP         "description"
++#define FIT_ARCH_PROP         "arch"
++#define FIT_TYPE_PROP         "type"
++#define FIT_OS_PROP           "os"
++#define FIT_COMP_PROP         "compression"
++#define FIT_ENTRY_PROP                "entry"
++#define FIT_LOAD_PROP         "load"
++
++/* configuration node */
++#define FIT_KERNEL_PROP               "kernel"
++#define FIT_FILESYSTEM_PROP   "filesystem"
++#define FIT_RAMDISK_PROP      "ramdisk"
++#define FIT_FDT_PROP          "fdt"
++#define FIT_LOADABLE_PROP     "loadables"
++#define FIT_DEFAULT_PROP      "default"
++#define FIT_SETUP_PROP                "setup"
++#define FIT_FPGA_PROP         "fpga"
++#define FIT_FIRMWARE_PROP     "firmware"
++#define FIT_STANDALONE_PROP   "standalone"
++
++/* fitblk driver data */
++static const char *_fitblk_claim_ptr = "I belong to fitblk";
++static const char *ubootver;
++struct device_node *rootdisk;
++static struct platform_device *pdev;
++static LIST_HEAD(fitblk_devices);
++static DEFINE_MUTEX(devices_mutex);
++refcount_t num_devs;
++
++struct fitblk {
++      struct platform_device  *pdev;
++      struct file             *bdev_file;
++      sector_t                start_sect;
++      struct gendisk          *disk;
++      struct work_struct      remove_work;
++      struct list_head        list;
++      bool                    dead;
++};
++
++static int fitblk_open(struct gendisk *disk, fmode_t mode)
++{
++      struct fitblk *fitblk = disk->private_data;
++
++      if (fitblk->dead)
++              return -ENOENT;
++
++      return 0;
++}
++
++static void fitblk_release(struct gendisk *disk)
++{
++      return;
++}
++
++static void fitblk_submit_bio(struct bio *orig_bio)
++{
++      struct bio *bio = orig_bio;
++      struct fitblk *fitblk = bio->bi_bdev->bd_disk->private_data;
++
++      if (fitblk->dead)
++              return;
++
++      /* mangle bio and re-submit */
++      while (bio) {
++              bio->bi_iter.bi_sector += fitblk->start_sect;
++              bio->bi_bdev = file_bdev(fitblk->bdev_file);
++              bio = bio->bi_next;
++      }
++      submit_bio(orig_bio);
++}
++
++static void fitblk_remove(struct fitblk *fitblk)
++{
++      blk_mark_disk_dead(fitblk->disk);
++      mutex_lock(&devices_mutex);
++      fitblk->dead = true;
++      list_del(&fitblk->list);
++      mutex_unlock(&devices_mutex);
++
++      schedule_work(&fitblk->remove_work);
++}
++
++static int fitblk_ioctl(struct block_device *bdev, fmode_t mode,
++                      unsigned int cmd, unsigned long arg)
++{
++      struct fitblk *fitblk = bdev->bd_disk->private_data;
++
++      if (!capable(CAP_SYS_ADMIN))
++              return -EACCES;
++
++      if (fitblk->dead)
++              return -ENOENT;
++
++      switch (cmd) {
++      case FITBLK_RELEASE:
++              fitblk_remove(fitblk);
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      return 0;
++}
++
++static const struct block_device_operations fitblk_fops = {
++      .owner          = THIS_MODULE,
++      .ioctl          = fitblk_ioctl,
++      .open           = fitblk_open,
++      .release        = fitblk_release,
++      .submit_bio     = fitblk_submit_bio,
++};
++
++static void fitblk_purge(struct work_struct *work)
++{
++      struct fitblk *fitblk = container_of(work, struct fitblk, remove_work);
++
++      del_gendisk(fitblk->disk);
++      refcount_dec(&num_devs);
++      platform_device_del(fitblk->pdev);
++      platform_device_put(fitblk->pdev);
++
++      if (refcount_dec_if_one(&num_devs)) {
++              sysfs_remove_link(&pdev->dev.kobj, "lower_dev");
++              fput(fitblk->bdev_file);
++      }
++
++      kfree(fitblk);
++}
++
++static int add_fit_subimage_device(struct file *bdev_file,
++                                 unsigned int slot, sector_t start_sect,
++                                 sector_t nr_sect, bool readonly)
++{
++      struct block_device *bdev = file_bdev(bdev_file);
++      struct fitblk *fitblk;
++      struct gendisk *disk;
++      int err;
++
++      mutex_lock(&devices_mutex);
++      if (!refcount_inc_not_zero(&num_devs))
++              return -EBADF;
++
++      fitblk = kzalloc(sizeof(struct fitblk), GFP_KERNEL);
++      if (!fitblk) {
++              err = -ENOMEM;
++              goto out_unlock;
++      }
++
++      fitblk->bdev_file = bdev_file;
++      fitblk->start_sect = start_sect;
++      INIT_WORK(&fitblk->remove_work, fitblk_purge);
++
++      disk = blk_alloc_disk(&bdev->bd_disk->queue->limits, NUMA_NO_NODE);
++      if (!disk) {
++              err = -ENOMEM;
++              goto out_free_fitblk;
++      }
++
++      disk->first_minor = 0;
++      disk->flags = bdev->bd_disk->flags | GENHD_FL_NO_PART;
++      disk->fops = &fitblk_fops;
++      disk->private_data = fitblk;
++      if (readonly) {
++              set_disk_ro(disk, 1);
++              snprintf(disk->disk_name, sizeof(disk->disk_name), FIT_DEVICE_PREFIX "%u", slot);
++      } else {
++              strcpy(disk->disk_name, FIT_DEVICE_PREFIX "rw");
++      }
++
++      set_capacity(disk, nr_sect);
++      disk->queue->queue_flags = bdev->bd_disk->queue->queue_flags;
++
++      fitblk->disk = disk;
++      fitblk->pdev = platform_device_alloc(disk->disk_name, PLATFORM_DEVID_NONE);
++      if (!fitblk->pdev) {
++              err = -ENOMEM;
++              goto out_cleanup_disk;
++      }
++
++      fitblk->pdev->dev.parent = &pdev->dev;
++      err = platform_device_add(fitblk->pdev);
++      if (err)
++              goto out_put_pdev;
++
++      err = device_add_disk(&fitblk->pdev->dev, disk, NULL);
++      if (err)
++              goto out_del_pdev;
++
++      if (!ROOT_DEV)
++              ROOT_DEV = disk->part0->bd_dev;
++
++      list_add_tail(&fitblk->list, &fitblk_devices);
++
++      mutex_unlock(&devices_mutex);
++
++      return 0;
++
++out_del_pdev:
++      platform_device_del(fitblk->pdev);
++out_put_pdev:
++      platform_device_put(fitblk->pdev);
++out_cleanup_disk:
++      put_disk(disk);
++out_free_fitblk:
++      kfree(fitblk);
++out_unlock:
++      refcount_dec(&num_devs);
++      mutex_unlock(&devices_mutex);
++      return err;
++}
++
++static void fitblk_mark_dead(struct block_device *bdev, bool surprise)
++{
++      struct list_head *n, *tmp;
++      struct fitblk *fitblk;
++
++      mutex_lock(&devices_mutex);
++      list_for_each_safe(n, tmp, &fitblk_devices) {
++              fitblk = list_entry(n, struct fitblk, list);
++              if (file_bdev(fitblk->bdev_file) != bdev)
++                      continue;
++
++              fitblk->dead = true;
++              list_del(&fitblk->list);
++              /* removal needs to be deferred to avoid deadlock */
++              schedule_work(&fitblk->remove_work);
++      }
++      mutex_unlock(&devices_mutex);
++}
++
++static const struct blk_holder_ops fitblk_hops = {
++      .mark_dead = fitblk_mark_dead,
++};
++
++static int parse_fit_on_dev(struct device *dev)
++{
++      struct file *bdev_file;
++      struct block_device *bdev;
++      struct address_space *mapping;
++      struct folio *folio;
++      pgoff_t f_index = 0;
++      size_t bytes_left, bytes_to_copy;
++      void *pre_fit, *fit, *fit_c;
++      u64 dsize, dsectors, imgmaxsect = 0;
++      u32 size, image_pos, image_len;
++      const __be32 *image_offset_be, *image_len_be, *image_pos_be;
++      int ret = 0, node, images, config;
++      const char *image_name, *image_type, *image_description,
++              *config_default, *config_description, *config_loadables;
++      u32 image_name_len, image_type_len, image_description_len,
++              bootconf_len, config_default_len, config_description_len,
++              config_loadables_len;
++      sector_t start_sect, nr_sects;
++      struct device_node *np = NULL;
++      const char *bootconf_c;
++      const char *loadable;
++      char *bootconf = NULL, *bootconf_term;
++      bool found;
++      int loadables_rem_len, loadable_len;
++      u16 loadcnt;
++      unsigned int slot = 0;
++
++      /* Exclusive open the block device to receive holder notifications */
++      bdev_file = bdev_file_open_by_dev(dev->devt,
++                                        BLK_OPEN_READ | BLK_OPEN_RESTRICT_WRITES,
++                                        &_fitblk_claim_ptr, &fitblk_hops);
++      if (!bdev_file)
++              return -ENODEV;
++
++      if (IS_ERR(bdev_file))
++              return PTR_ERR(bdev_file);
++
++      bdev = file_bdev(bdev_file);
++      mapping = bdev_file->f_mapping;
++
++      /* map first page */
++      folio = read_mapping_folio(mapping, f_index++, NULL);
++      if (IS_ERR(folio)) {
++              ret = PTR_ERR(folio);
++              goto out_blkdev;
++      }
++      pre_fit = folio_address(folio) + offset_in_folio(folio, 0);
++
++      /* uImage.FIT is based on flattened device tree structure */
++      if (fdt_check_header(pre_fit)) {
++              ret = -EINVAL;
++              folio_put(folio);
++              goto out_blkdev;
++      }
++
++      size = fdt_totalsize(pre_fit);
++
++      if (size > PAGE_SIZE * FIT_MAX_PAGES) {
++              ret = -EOPNOTSUPP;
++              folio_put(folio);
++              goto out_blkdev;
++      }
++
++      /* acquire disk size */
++      dsectors = bdev_nr_sectors(bdev);
++      dsize = dsectors << SECTOR_SHIFT;
++
++      /* abort if FIT structure is larger than disk or partition size */
++      if (size >= dsize) {
++              ret = -EFBIG;
++              folio_put(folio);
++              goto out_blkdev;
++      }
++
++      fit = kmalloc(size, GFP_KERNEL);
++      if (!fit) {
++              ret = -ENOMEM;
++              folio_put(folio);
++              goto out_blkdev;
++      }
++
++      bytes_left = size;
++      fit_c = fit;
++      while (bytes_left > 0) {
++              bytes_to_copy = min_t(size_t, bytes_left,
++                                    folio_size(folio) - offset_in_folio(folio, 0));
++              memcpy(fit_c, pre_fit, bytes_to_copy);
++              fit_c += bytes_to_copy;
++              bytes_left -= bytes_to_copy;
++              if (bytes_left) {
++                      folio_put(folio);
++                      folio = read_mapping_folio(mapping, f_index++, NULL);
++                      if (IS_ERR(folio)) {
++                              ret = PTR_ERR(folio);
++                              goto out_blkdev;
++                      };
++                      pre_fit = folio_address(folio) + offset_in_folio(folio, 0);
++              }
++      }
++      folio_put(folio);
++
++      /* set boot config node name U-Boot may have added to the device tree */
++      np = of_find_node_by_path("/chosen");
++      if (np) {
++              bootconf_c = of_get_property(np, "u-boot,bootconf", &bootconf_len);
++              if (bootconf_c && bootconf_len)
++                      bootconf = kmemdup_nul(bootconf_c, bootconf_len, GFP_KERNEL);
++      }
++
++      if (bootconf) {
++              bootconf_term = strchr(bootconf, '#');
++              if (bootconf_term)
++                      *bootconf_term = '\0';
++      }
++
++      /* find configuration path in uImage.FIT */
++      config = fdt_path_offset(fit, FIT_CONFS_PATH);
++      if (config < 0) {
++              pr_err("FIT: Cannot find %s node: %d\n",
++                      FIT_CONFS_PATH, config);
++              ret = -ENOENT;
++              goto out_bootconf;
++      }
++
++      /* get default configuration node name */
++      config_default =
++              fdt_getprop(fit, config, FIT_DEFAULT_PROP, &config_default_len);
++
++      /* make sure we got either default or selected boot config node name */
++      if (!config_default && !bootconf) {
++              pr_err("FIT: Cannot find default configuration\n");
++              ret = -ENOENT;
++              goto out_bootconf;
++      }
++
++      /* find selected boot config node, fallback on default config node */
++      node = fdt_subnode_offset(fit, config, bootconf ?: config_default);
++      if (node < 0) {
++              pr_err("FIT: Cannot find %s node: %d\n",
++                      bootconf ?: config_default, node);
++              ret = -ENOENT;
++              goto out_bootconf;
++      }
++
++      pr_info("FIT: Detected U-Boot %s\n", ubootver);
++
++      /* get selected configuration data */
++      config_description =
++              fdt_getprop(fit, node, FIT_DESC_PROP, &config_description_len);
++      config_loadables = fdt_getprop(fit, node, FIT_LOADABLE_PROP,
++                                     &config_loadables_len);
++
++      pr_info("FIT: %s configuration: \"%.*s\"%s%.*s%s\n",
++              bootconf ? "Selected" : "Default",
++              bootconf ? bootconf_len : config_default_len,
++              bootconf ?: config_default,
++              config_description ? " (" : "",
++              config_description ? config_description_len : 0,
++              config_description ?: "",
++              config_description ? ")" : "");
++
++      if (!config_loadables || !config_loadables_len) {
++              pr_err("FIT: No loadables configured in \"%s\"\n",
++                      bootconf ?: config_default);
++              ret = -ENOENT;
++              goto out_bootconf;
++      }
++
++      /* get images path in uImage.FIT */
++      images = fdt_path_offset(fit, FIT_IMAGES_PATH);
++      if (images < 0) {
++              pr_err("FIT: Cannot find %s node: %d\n", FIT_IMAGES_PATH, images);
++              ret = -EINVAL;
++              goto out_bootconf;
++      }
++
++      /* iterate over images in uImage.FIT */
++      fdt_for_each_subnode(node, fit, images) {
++              image_name = fdt_get_name(fit, node, &image_name_len);
++              image_type = fdt_getprop(fit, node, FIT_TYPE_PROP, &image_type_len);
++              image_offset_be = fdt_getprop(fit, node, FIT_DATA_OFFSET_PROP, NULL);
++              image_pos_be = fdt_getprop(fit, node, FIT_DATA_POSITION_PROP, NULL);
++              image_len_be = fdt_getprop(fit, node, FIT_DATA_SIZE_PROP, NULL);
++
++              if (!image_name || !image_type || !image_len_be ||
++                  !image_name_len || !image_type_len)
++                      continue;
++
++              image_len = be32_to_cpu(*image_len_be);
++              if (!image_len)
++                      continue;
++
++              if (image_offset_be)
++                      image_pos = be32_to_cpu(*image_offset_be) + size;
++              else if (image_pos_be)
++                      image_pos = be32_to_cpu(*image_pos_be);
++              else
++                      continue;
++
++              image_description = fdt_getprop(fit, node, FIT_DESC_PROP,
++                                              &image_description_len);
++
++              pr_info("FIT: %16s sub-image 0x%08x..0x%08x \"%.*s\"%s%.*s%s\n",
++                      image_type, image_pos, image_pos + image_len - 1,
++                      image_name_len, image_name, image_description ? " (" : "",
++                      image_description ? image_description_len : 0,
++                      image_description ?: "", image_description ? ") " : "");
++
++              /* only 'filesystem' images should be mapped as partitions */
++              if (strncmp(image_type, FIT_FILESYSTEM_PROP, image_type_len))
++                      continue;
++
++              /* check if sub-image is part of configured loadables */
++              found = false;
++              loadable = config_loadables;
++              loadables_rem_len = config_loadables_len;
++              for (loadcnt = 0; loadables_rem_len > 1 &&
++                                loadcnt < MAX_FIT_LOADABLES; ++loadcnt) {
++                      loadable_len =
++                              strnlen(loadable, loadables_rem_len - 1) + 1;
++                      loadables_rem_len -= loadable_len;
++                      if (!strncmp(image_name, loadable, loadable_len)) {
++                              found = true;
++                              break;
++                      }
++                      loadable += loadable_len;
++              }
++              if (!found)
++                      continue;
++
++              if (image_pos % (1 << PAGE_SHIFT)) {
++                      dev_err(dev, "FIT: image %.*s start not aligned to page boundaries, skipping\n",
++                              image_name_len, image_name);
++                      continue;
++              }
++
++              if (image_len % (1 << PAGE_SHIFT)) {
++                      dev_err(dev, "FIT: sub-image %.*s end not aligned to page boundaries, skipping\n",
++                              image_name_len, image_name);
++                      continue;
++              }
++
++              start_sect = image_pos >> SECTOR_SHIFT;
++              nr_sects = image_len >> SECTOR_SHIFT;
++              imgmaxsect = max_t(sector_t, imgmaxsect, start_sect + nr_sects);
++
++              if (start_sect + nr_sects > dsectors) {
++                      dev_err(dev, "FIT: sub-image %.*s disk access beyond EOD\n",
++                              image_name_len, image_name);
++                      continue;
++              }
++
++              if (!slot) {
++                      ret = sysfs_create_link_nowarn(&pdev->dev.kobj, bdev_kobj(bdev), "lower_dev");
++                      if (ret && ret != -EEXIST)
++                              goto out_bootconf;
++
++                      ret = 0;
++              }
++
++              add_fit_subimage_device(bdev_file, slot++, start_sect, nr_sects, true);
++      }
++
++      if (!slot)
++              goto out_bootconf;
++
++      dev_info(dev, "mapped %u uImage.FIT filesystem sub-image%s as /dev/fit%s%u%s\n",
++               slot, (slot > 1)?"s":"", (slot > 1)?"[0...":"", slot - 1,
++               (slot > 1)?"]":"");
++
++      /* in case uImage.FIT is stored in a partition, map the remaining space */
++      if (!bdev_read_only(bdev) && bdev_is_partition(bdev) &&
++          (imgmaxsect + MIN_FREE_SECT) < dsectors) {
++              add_fit_subimage_device(bdev_file, slot++, imgmaxsect,
++                                      dsectors - imgmaxsect, false);
++              dev_info(dev, "mapped remaining space as /dev/fitrw\n");
++      }
++
++out_bootconf:
++      kfree(bootconf);
++      kfree(fit);
++out_blkdev:
++      if (!slot)
++              fput(bdev_file);
++
++      return ret;
++}
++
++static int fitblk_match_of_node(struct device *dev, const void *np)
++{
++      int ret;
++
++      ret = device_match_of_node(dev, np);
++      if (ret)
++              return ret;
++
++      /*
++       * To match ubiblock and mtdblock devices by their parent ubi
++       * or mtd device, also consider block device parent
++       */
++      if (!dev->parent)
++              return 0;
++
++      return device_match_of_node(dev->parent, np);
++}
++
++static int fitblk_probe(struct platform_device *pdev)
++{
++      struct device *dev;
++
++      dev = class_find_device(&block_class, NULL, rootdisk, fitblk_match_of_node);
++      if (!dev)
++              return -EPROBE_DEFER;
++
++      return parse_fit_on_dev(dev);
++}
++
++static struct platform_driver fitblk_driver = {
++      .probe          = fitblk_probe,
++      .driver         = {
++              .name   = "fitblk",
++      },
++};
++
++static int __init fitblk_init(void)
++{
++      /* detect U-Boot firmware */
++      ubootver = of_get_property(of_chosen, "u-boot,version", NULL);
++      if (!ubootver)
++              return 0;
++
++      /* parse 'rootdisk' property phandle */
++      rootdisk = of_parse_phandle(of_chosen, "rootdisk", 0);
++      if (!rootdisk)
++              return 0;
++
++      if (platform_driver_register(&fitblk_driver))
++              return -ENODEV;
++
++      refcount_set(&num_devs, 1);
++      pdev = platform_device_register_simple("fitblk", -1, NULL, 0);
++      if (IS_ERR(pdev))
++              return PTR_ERR(pdev);
++
++      return 0;
++}
++device_initcall(fitblk_init);
+--- /dev/null
++++ b/include/uapi/linux/fitblk.h
+@@ -0,0 +1,10 @@
++/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
++#ifndef _UAPI_LINUX_FITBLK_H
++#define _UAPI_LINUX_FITBLK_H
++
++/*
++ * IOCTL commands --- we will commandeer 0x46 ('F')
++ */
++#define FITBLK_RELEASE             0x4600
++
++#endif /* _UAPI_LINUX_FITBLK_H */
diff --git a/target/linux/generic/pending-6.12/511-init-bypass-device-lookup-for-dev-fit-rootfs.patch b/target/linux/generic/pending-6.12/511-init-bypass-device-lookup-for-dev-fit-rootfs.patch
new file mode 100644 (file)
index 0000000..b3d8d7d
--- /dev/null
@@ -0,0 +1,25 @@
+From 5ede3f8aed9a1a579bf7304142600d1f3500add9 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 12 Jun 2023 03:58:42 +0100
+Subject: [PATCH 2/2] init: bypass device lookup for /dev/fit* rootfs
+
+Allow 'rootwait' as /dev/fit* can show up late if the underlaying
+device is probed late.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ init/do_mounts.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/init/do_mounts.c
++++ b/init/do_mounts.c
+@@ -465,7 +465,8 @@ static dev_t __init parse_root_device(ch
+       int error;
+       dev_t dev;
+-      if (!strncmp(root_device_name, "mtd", 3) ||
++      if (!strncmp(root_device_name, "fit", 3) ||
++          !strncmp(root_device_name, "mtd", 3) ||
+           !strncmp(root_device_name, "ubi", 3))
+               return Root_Generic;
+       if (strcmp(root_device_name, "/dev/nfs") == 0)
diff --git a/target/linux/generic/pending-6.12/530-jffs2_make_lzma_available.patch b/target/linux/generic/pending-6.12/530-jffs2_make_lzma_available.patch
new file mode 100644 (file)
index 0000000..213e6f1
--- /dev/null
@@ -0,0 +1,5190 @@
+From: Alexandros C. Couloumbis <alex@ozo.com>
+Subject: fs: add jffs2/lzma support (not activated by default yet)
+
+lede-commit: c2c88d315fa0e881f8b19da07b62859b915b11b2
+Signed-off-by: Alexandros C. Couloumbis <alex@ozo.com>
+---
+ fs/jffs2/Kconfig             |    9 +
+ fs/jffs2/Makefile            |    3 +
+ fs/jffs2/compr.c             |    6 +
+ fs/jffs2/compr.h             |   10 +-
+ fs/jffs2/compr_lzma.c        |  128 +++
+ fs/jffs2/super.c             |   33 +-
+ include/linux/lzma.h         |   62 ++
+ include/linux/lzma/LzFind.h  |  115 +++
+ include/linux/lzma/LzHash.h  |   54 +
+ include/linux/lzma/LzmaDec.h |  231 +++++
+ include/linux/lzma/LzmaEnc.h |   80 ++
+ include/linux/lzma/Types.h   |  226 +++++
+ include/uapi/linux/jffs2.h   |    1 +
+ lib/Kconfig                  |    6 +
+ lib/Makefile                 |   12 +
+ lib/lzma/LzFind.c            |  761 ++++++++++++++
+ lib/lzma/LzmaDec.c           |  999 +++++++++++++++++++
+ lib/lzma/LzmaEnc.c           | 2271 ++++++++++++++++++++++++++++++++++++++++++
+ lib/lzma/Makefile            |    7 +
+ 19 files changed, 5008 insertions(+), 6 deletions(-)
+ create mode 100644 fs/jffs2/compr_lzma.c
+ create mode 100644 include/linux/lzma.h
+ create mode 100644 include/linux/lzma/LzFind.h
+ create mode 100644 include/linux/lzma/LzHash.h
+ create mode 100644 include/linux/lzma/LzmaDec.h
+ create mode 100644 include/linux/lzma/LzmaEnc.h
+ create mode 100644 include/linux/lzma/Types.h
+ create mode 100644 lib/lzma/LzFind.c
+ create mode 100644 lib/lzma/LzmaDec.c
+ create mode 100644 lib/lzma/LzmaEnc.c
+ create mode 100644 lib/lzma/Makefile
+
+--- a/fs/jffs2/Kconfig
++++ b/fs/jffs2/Kconfig
+@@ -136,6 +136,15 @@ config JFFS2_LZO
+         This feature was added in July, 2007. Say 'N' if you need
+         compatibility with older bootloaders or kernels.
++config JFFS2_LZMA
++      bool "JFFS2 LZMA compression support" if JFFS2_COMPRESSION_OPTIONS
++      select LZMA_COMPRESS
++      select LZMA_DECOMPRESS
++      depends on JFFS2_FS
++      default n
++      help
++        JFFS2 wrapper to the LZMA C SDK
++
+ config JFFS2_RTIME
+       bool "JFFS2 RTIME compression support" if JFFS2_COMPRESSION_OPTIONS
+       depends on JFFS2_FS
+--- a/fs/jffs2/Makefile
++++ b/fs/jffs2/Makefile
+@@ -19,4 +19,7 @@ jffs2-$(CONFIG_JFFS2_RUBIN)  += compr_rub
+ jffs2-$(CONFIG_JFFS2_RTIME)   += compr_rtime.o
+ jffs2-$(CONFIG_JFFS2_ZLIB)    += compr_zlib.o
+ jffs2-$(CONFIG_JFFS2_LZO)     += compr_lzo.o
++jffs2-$(CONFIG_JFFS2_LZMA)    += compr_lzma.o
+ jffs2-$(CONFIG_JFFS2_SUMMARY)   += summary.o
++
++CFLAGS_compr_lzma.o += -Iinclude/linux -Ilib/lzma
+--- a/fs/jffs2/compr.c
++++ b/fs/jffs2/compr.c
+@@ -381,6 +381,9 @@ int __init jffs2_compressors_init(void)
+       ret = jffs2_lzo_init();
+       if (ret)
+               goto exit_dynrubin;
++      ret = jffs2_lzma_init();
++      if (ret)
++              goto exit_lzo;
+ /* Setting default compression mode */
+@@ -402,6 +405,8 @@ int __init jffs2_compressors_init(void)
+ #endif
+       return 0;
++exit_lzo:
++      jffs2_lzo_exit();
+ exit_dynrubin:
+       jffs2_dynrubin_exit();
+ exit_runinmips:
+@@ -417,6 +422,7 @@ exit:
+ int jffs2_compressors_exit(void)
+ {
+ /* Unregistering compressors */
++      jffs2_lzma_exit();
+       jffs2_lzo_exit();
+       jffs2_dynrubin_exit();
+       jffs2_rubinmips_exit();
+--- a/fs/jffs2/compr.h
++++ b/fs/jffs2/compr.h
+@@ -29,9 +29,9 @@
+ #define JFFS2_DYNRUBIN_PRIORITY  20
+ #define JFFS2_LZARI_PRIORITY     30
+ #define JFFS2_RTIME_PRIORITY     50
+-#define JFFS2_ZLIB_PRIORITY      60
+-#define JFFS2_LZO_PRIORITY       80
+-
++#define JFFS2_LZMA_PRIORITY      70
++#define JFFS2_ZLIB_PRIORITY      80
++#define JFFS2_LZO_PRIORITY       90
+ #define JFFS2_RUBINMIPS_DISABLED /* RUBINs will be used only */
+ #define JFFS2_DYNRUBIN_DISABLED  /*      for decompression */
+@@ -115,5 +115,12 @@ extern void jffs2_lzo_exit(void);
+ static inline int jffs2_lzo_init(void) { return 0; }
+ static inline void jffs2_lzo_exit(void) {}
+ #endif
++#ifdef CONFIG_JFFS2_LZMA
++extern int jffs2_lzma_init(void);
++extern void jffs2_lzma_exit(void);
++#else
++static inline int jffs2_lzma_init(void) { return 0; }
++static inline void jffs2_lzma_exit(void) {}
++#endif
+ #endif /* __JFFS2_COMPR_H__ */
+--- /dev/null
++++ b/fs/jffs2/compr_lzma.c
+@@ -0,0 +1,128 @@
++/*
++ * JFFS2 -- Journalling Flash File System, Version 2.
++ *
++ * For licensing information, see the file 'LICENCE' in this directory.
++ *
++ * JFFS2 wrapper to the LZMA C SDK
++ *
++ */
++
++#include <linux/lzma.h>
++#include "compr.h"
++
++#ifdef __KERNEL__
++      static DEFINE_MUTEX(deflate_mutex);
++#endif
++
++CLzmaEncHandle *p;
++Byte propsEncoded[LZMA_PROPS_SIZE];
++SizeT propsSize = sizeof(propsEncoded);
++
++STATIC void lzma_free_workspace(void)
++{
++      LzmaEnc_Destroy(p, &lzma_alloc, &lzma_alloc);
++}
++
++STATIC int INIT lzma_alloc_workspace(CLzmaEncProps *props)
++{
++      if ((p = (CLzmaEncHandle *)LzmaEnc_Create(&lzma_alloc)) == NULL)
++      {
++              PRINT_ERROR("Failed to allocate lzma deflate workspace\n");
++              return -ENOMEM;
++      }
++
++      if (LzmaEnc_SetProps(p, props) != SZ_OK)
++      {
++              lzma_free_workspace();
++              return -1;
++      }
++
++      if (LzmaEnc_WriteProperties(p, propsEncoded, &propsSize) != SZ_OK)
++      {
++              lzma_free_workspace();
++              return -1;
++      }
++
++      return 0;
++}
++
++STATIC int jffs2_lzma_compress(unsigned char *data_in, unsigned char *cpage_out,
++                            uint32_t *sourcelen, uint32_t *dstlen)
++{
++      SizeT compress_size = (SizeT)(*dstlen);
++      int ret;
++
++      #ifdef __KERNEL__
++              mutex_lock(&deflate_mutex);
++      #endif
++
++      ret = LzmaEnc_MemEncode(p, cpage_out, &compress_size, data_in, *sourcelen,
++              0, NULL, &lzma_alloc, &lzma_alloc);
++
++      #ifdef __KERNEL__
++              mutex_unlock(&deflate_mutex);
++      #endif
++
++      if (ret != SZ_OK)
++              return -1;
++
++      *dstlen = (uint32_t)compress_size;
++
++      return 0;
++}
++
++STATIC int jffs2_lzma_decompress(unsigned char *data_in, unsigned char *cpage_out,
++                               uint32_t srclen, uint32_t destlen)
++{
++      int ret;
++      SizeT dl = (SizeT)destlen;
++      SizeT sl = (SizeT)srclen;
++      ELzmaStatus status;
++
++      ret = LzmaDecode(cpage_out, &dl, data_in, &sl, propsEncoded,
++              propsSize, LZMA_FINISH_ANY, &status, &lzma_alloc);
++
++      if (ret != SZ_OK || status == LZMA_STATUS_NOT_FINISHED || dl != (SizeT)destlen)
++              return -1;
++
++      return 0;
++}
++
++static struct jffs2_compressor jffs2_lzma_comp = {
++      .priority = JFFS2_LZMA_PRIORITY,
++      .name = "lzma",
++      .compr = JFFS2_COMPR_LZMA,
++      .compress = &jffs2_lzma_compress,
++      .decompress = &jffs2_lzma_decompress,
++      .disabled = 0,
++};
++
++int INIT jffs2_lzma_init(void)
++{
++      int ret;
++      CLzmaEncProps props;
++      LzmaEncProps_Init(&props);
++
++      props.dictSize = LZMA_BEST_DICT(0x2000);
++      props.level = LZMA_BEST_LEVEL;
++      props.lc = LZMA_BEST_LC;
++      props.lp = LZMA_BEST_LP;
++      props.pb = LZMA_BEST_PB;
++      props.fb = LZMA_BEST_FB;
++
++      ret = lzma_alloc_workspace(&props);
++      if (ret < 0)
++              return ret;
++
++      ret = jffs2_register_compressor(&jffs2_lzma_comp);
++      if (ret)
++              lzma_free_workspace();
++
++      return ret;
++}
++
++void jffs2_lzma_exit(void)
++{
++      jffs2_unregister_compressor(&jffs2_lzma_comp);
++      lzma_free_workspace();
++}
+--- a/fs/jffs2/super.c
++++ b/fs/jffs2/super.c
+@@ -376,14 +376,41 @@ static int __init init_jffs2_fs(void)
+       BUILD_BUG_ON(sizeof(struct jffs2_raw_inode) != 68);
+       BUILD_BUG_ON(sizeof(struct jffs2_raw_summary) != 32);
+-      pr_info("version 2.2."
++      pr_info("version 2.2"
+ #ifdef CONFIG_JFFS2_FS_WRITEBUFFER
+              " (NAND)"
+ #endif
+ #ifdef CONFIG_JFFS2_SUMMARY
+-             " (SUMMARY) "
++             " (SUMMARY)"
+ #endif
+-             " © 2001-2006 Red Hat, Inc.\n");
++#ifdef CONFIG_JFFS2_ZLIB
++             " (ZLIB)"
++#endif
++#ifdef CONFIG_JFFS2_LZO
++             " (LZO)"
++#endif
++#ifdef CONFIG_JFFS2_LZMA
++             " (LZMA)"
++#endif
++#ifdef CONFIG_JFFS2_RTIME
++             " (RTIME)"
++#endif
++#ifdef CONFIG_JFFS2_RUBIN
++             " (RUBIN)"
++#endif
++#ifdef  CONFIG_JFFS2_CMODE_NONE
++             " (CMODE_NONE)"
++#endif
++#ifdef CONFIG_JFFS2_CMODE_PRIORITY
++             " (CMODE_PRIORITY)"
++#endif
++#ifdef CONFIG_JFFS2_CMODE_SIZE
++             " (CMODE_SIZE)"
++#endif
++#ifdef CONFIG_JFFS2_CMODE_FAVOURLZO
++             " (CMODE_FAVOURLZO)"
++#endif
++             " (c) 2001-2006 Red Hat, Inc.\n");
+       jffs2_inode_cachep = kmem_cache_create("jffs2_i",
+                                            sizeof(struct jffs2_inode_info),
+--- /dev/null
++++ b/include/linux/lzma.h
+@@ -0,0 +1,62 @@
++#ifndef __LZMA_H__
++#define __LZMA_H__
++
++#ifdef __KERNEL__
++      #include <linux/kernel.h>
++      #include <linux/sched.h>
++      #include <linux/slab.h>
++      #include <linux/vmalloc.h>
++      #include <linux/init.h>
++      #define LZMA_MALLOC vmalloc
++      #define LZMA_FREE vfree
++      #define PRINT_ERROR(msg) printk(KERN_WARNING #msg)
++      #define INIT __init
++      #define STATIC static
++#else
++      #include <stdint.h>
++      #include <stdlib.h>
++      #include <stdio.h>
++      #include <unistd.h>
++      #include <string.h>
++      #include <asm/types.h>
++      #include <errno.h>
++      #include <linux/jffs2.h>
++      #ifndef PAGE_SIZE
++              extern int page_size;
++              #define PAGE_SIZE page_size
++      #endif
++      #define LZMA_MALLOC malloc
++      #define LZMA_FREE free
++      #define PRINT_ERROR(msg) fprintf(stderr, msg)
++      #define INIT
++      #define STATIC
++#endif
++
++#include "lzma/LzmaDec.h"
++#include "lzma/LzmaEnc.h"
++
++#define LZMA_BEST_LEVEL (9)
++#define LZMA_BEST_LC    (0)
++#define LZMA_BEST_LP    (0)
++#define LZMA_BEST_PB    (0)
++#define LZMA_BEST_FB  (273)
++
++#define LZMA_BEST_DICT(n) (((int)((n) / 2)) * 2)
++
++static void *p_lzma_malloc(void *p, size_t size)
++{
++      if (size == 0)
++              return NULL;
++
++      return LZMA_MALLOC(size);
++}
++
++static void p_lzma_free(void *p, void *address)
++{
++      if (address != NULL)
++              LZMA_FREE(address);
++}
++
++static ISzAlloc lzma_alloc = { .Alloc = p_lzma_malloc, .Free = p_lzma_free };
++
++#endif
+--- /dev/null
++++ b/include/linux/lzma/LzFind.h
+@@ -0,0 +1,115 @@
++/* LzFind.h -- Match finder for LZ algorithms
++2009-04-22 : Igor Pavlov : Public domain */
++
++#ifndef __LZ_FIND_H
++#define __LZ_FIND_H
++
++#include "Types.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++typedef UInt32 CLzRef;
++
++typedef struct _CMatchFinder
++{
++  Byte *buffer;
++  UInt32 pos;
++  UInt32 posLimit;
++  UInt32 streamPos;
++  UInt32 lenLimit;
++
++  UInt32 cyclicBufferPos;
++  UInt32 cyclicBufferSize; /* it must be = (historySize + 1) */
++
++  UInt32 matchMaxLen;
++  CLzRef *hash;
++  CLzRef *son;
++  UInt32 hashMask;
++  UInt32 cutValue;
++
++  Byte *bufferBase;
++  ISeqInStream *stream;
++  int streamEndWasReached;
++
++  UInt32 blockSize;
++  UInt32 keepSizeBefore;
++  UInt32 keepSizeAfter;
++
++  UInt32 numHashBytes;
++  int directInput;
++  size_t directInputRem;
++  int btMode;
++  int bigHash;
++  UInt32 historySize;
++  UInt32 fixedHashSize;
++  UInt32 hashSizeSum;
++  UInt32 numSons;
++  SRes result;
++  UInt32 crc[256];
++} CMatchFinder;
++
++#define Inline_MatchFinder_GetPointerToCurrentPos(p) ((p)->buffer)
++#define Inline_MatchFinder_GetIndexByte(p, index) ((p)->buffer[(Int32)(index)])
++
++#define Inline_MatchFinder_GetNumAvailableBytes(p) ((p)->streamPos - (p)->pos)
++
++int MatchFinder_NeedMove(CMatchFinder *p);
++Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p);
++void MatchFinder_MoveBlock(CMatchFinder *p);
++void MatchFinder_ReadIfRequired(CMatchFinder *p);
++
++void MatchFinder_Construct(CMatchFinder *p);
++
++/* Conditions:
++     historySize <= 3 GB
++     keepAddBufferBefore + matchMaxLen + keepAddBufferAfter < 511MB
++*/
++int MatchFinder_Create(CMatchFinder *p, UInt32 historySize,
++    UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter,
++    ISzAlloc *alloc);
++void MatchFinder_Free(CMatchFinder *p, ISzAlloc *alloc);
++void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, UInt32 numItems);
++void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue);
++
++UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *buffer, CLzRef *son,
++    UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue,
++    UInt32 *distances, UInt32 maxLen);
++
++/*
++Conditions:
++  Mf_GetNumAvailableBytes_Func must be called before each Mf_GetMatchLen_Func.
++  Mf_GetPointerToCurrentPos_Func's result must be used only before any other function
++*/
++
++typedef void (*Mf_Init_Func)(void *object);
++typedef Byte (*Mf_GetIndexByte_Func)(void *object, Int32 index);
++typedef UInt32 (*Mf_GetNumAvailableBytes_Func)(void *object);
++typedef const Byte * (*Mf_GetPointerToCurrentPos_Func)(void *object);
++typedef UInt32 (*Mf_GetMatches_Func)(void *object, UInt32 *distances);
++typedef void (*Mf_Skip_Func)(void *object, UInt32);
++
++typedef struct _IMatchFinder
++{
++  Mf_Init_Func Init;
++  Mf_GetIndexByte_Func GetIndexByte;
++  Mf_GetNumAvailableBytes_Func GetNumAvailableBytes;
++  Mf_GetPointerToCurrentPos_Func GetPointerToCurrentPos;
++  Mf_GetMatches_Func GetMatches;
++  Mf_Skip_Func Skip;
++} IMatchFinder;
++
++void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable);
++
++void MatchFinder_Init(CMatchFinder *p);
++UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances);
++UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances);
++void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num);
++void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+--- /dev/null
++++ b/include/linux/lzma/LzHash.h
+@@ -0,0 +1,54 @@
++/* LzHash.h -- HASH functions for LZ algorithms
++2009-02-07 : Igor Pavlov : Public domain */
++
++#ifndef __LZ_HASH_H
++#define __LZ_HASH_H
++
++#define kHash2Size (1 << 10)
++#define kHash3Size (1 << 16)
++#define kHash4Size (1 << 20)
++
++#define kFix3HashSize (kHash2Size)
++#define kFix4HashSize (kHash2Size + kHash3Size)
++#define kFix5HashSize (kHash2Size + kHash3Size + kHash4Size)
++
++#define HASH2_CALC hashValue = cur[0] | ((UInt32)cur[1] << 8);
++
++#define HASH3_CALC { \
++  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
++  hash2Value = temp & (kHash2Size - 1); \
++  hashValue = (temp ^ ((UInt32)cur[2] << 8)) & p->hashMask; }
++
++#define HASH4_CALC { \
++  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
++  hash2Value = temp & (kHash2Size - 1); \
++  hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); \
++  hashValue = (temp ^ ((UInt32)cur[2] << 8) ^ (p->crc[cur[3]] << 5)) & p->hashMask; }
++
++#define HASH5_CALC { \
++  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
++  hash2Value = temp & (kHash2Size - 1); \
++  hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); \
++  hash4Value = (temp ^ ((UInt32)cur[2] << 8) ^ (p->crc[cur[3]] << 5)); \
++  hashValue = (hash4Value ^ (p->crc[cur[4]] << 3)) & p->hashMask; \
++  hash4Value &= (kHash4Size - 1); }
++
++/* #define HASH_ZIP_CALC hashValue = ((cur[0] | ((UInt32)cur[1] << 8)) ^ p->crc[cur[2]]) & 0xFFFF; */
++#define HASH_ZIP_CALC hashValue = ((cur[2] | ((UInt32)cur[0] << 8)) ^ p->crc[cur[1]]) & 0xFFFF;
++
++
++#define MT_HASH2_CALC \
++  hash2Value = (p->crc[cur[0]] ^ cur[1]) & (kHash2Size - 1);
++
++#define MT_HASH3_CALC { \
++  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
++  hash2Value = temp & (kHash2Size - 1); \
++  hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); }
++
++#define MT_HASH4_CALC { \
++  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
++  hash2Value = temp & (kHash2Size - 1); \
++  hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); \
++  hash4Value = (temp ^ ((UInt32)cur[2] << 8) ^ (p->crc[cur[3]] << 5)) & (kHash4Size - 1); }
++
++#endif
+--- /dev/null
++++ b/include/linux/lzma/LzmaDec.h
+@@ -0,0 +1,231 @@
++/* LzmaDec.h -- LZMA Decoder
++2009-02-07 : Igor Pavlov : Public domain */
++
++#ifndef __LZMA_DEC_H
++#define __LZMA_DEC_H
++
++#include "Types.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++/* #define _LZMA_PROB32 */
++/* _LZMA_PROB32 can increase the speed on some CPUs,
++   but memory usage for CLzmaDec::probs will be doubled in that case */
++
++#ifdef _LZMA_PROB32
++#define CLzmaProb UInt32
++#else
++#define CLzmaProb UInt16
++#endif
++
++
++/* ---------- LZMA Properties ---------- */
++
++#define LZMA_PROPS_SIZE 5
++
++typedef struct _CLzmaProps
++{
++  unsigned lc, lp, pb;
++  UInt32 dicSize;
++} CLzmaProps;
++
++/* LzmaProps_Decode - decodes properties
++Returns:
++  SZ_OK
++  SZ_ERROR_UNSUPPORTED - Unsupported properties
++*/
++
++SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size);
++
++
++/* ---------- LZMA Decoder state ---------- */
++
++/* LZMA_REQUIRED_INPUT_MAX = number of required input bytes for worst case.
++   Num bits = log2((2^11 / 31) ^ 22) + 26 < 134 + 26 = 160; */
++
++#define LZMA_REQUIRED_INPUT_MAX 20
++
++typedef struct
++{
++  CLzmaProps prop;
++  CLzmaProb *probs;
++  Byte *dic;
++  const Byte *buf;
++  UInt32 range, code;
++  SizeT dicPos;
++  SizeT dicBufSize;
++  UInt32 processedPos;
++  UInt32 checkDicSize;
++  unsigned state;
++  UInt32 reps[4];
++  unsigned remainLen;
++  int needFlush;
++  int needInitState;
++  UInt32 numProbs;
++  unsigned tempBufSize;
++  Byte tempBuf[LZMA_REQUIRED_INPUT_MAX];
++} CLzmaDec;
++
++#define LzmaDec_Construct(p) { (p)->dic = 0; (p)->probs = 0; }
++
++void LzmaDec_Init(CLzmaDec *p);
++
++/* There are two types of LZMA streams:
++     0) Stream with end mark. That end mark adds about 6 bytes to compressed size.
++     1) Stream without end mark. You must know exact uncompressed size to decompress such stream. */
++
++typedef enum
++{
++  LZMA_FINISH_ANY,   /* finish at any point */
++  LZMA_FINISH_END    /* block must be finished at the end */
++} ELzmaFinishMode;
++
++/* ELzmaFinishMode has meaning only if the decoding reaches output limit !!!
++
++   You must use LZMA_FINISH_END, when you know that current output buffer
++   covers last bytes of block. In other cases you must use LZMA_FINISH_ANY.
++
++   If LZMA decoder sees end marker before reaching output limit, it returns SZ_OK,
++   and output value of destLen will be less than output buffer size limit.
++   You can check status result also.
++
++   You can use multiple checks to test data integrity after full decompression:
++     1) Check Result and "status" variable.
++     2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize.
++     3) Check that output(srcLen) = compressedSize, if you know real compressedSize.
++        You must use correct finish mode in that case. */
++
++typedef enum
++{
++  LZMA_STATUS_NOT_SPECIFIED,               /* use main error code instead */
++  LZMA_STATUS_FINISHED_WITH_MARK,          /* stream was finished with end mark. */
++  LZMA_STATUS_NOT_FINISHED,                /* stream was not finished */
++  LZMA_STATUS_NEEDS_MORE_INPUT,            /* you must provide more input bytes */
++  LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK  /* there is probability that stream was finished without end mark */
++} ELzmaStatus;
++
++/* ELzmaStatus is used only as output value for function call */
++
++
++/* ---------- Interfaces ---------- */
++
++/* There are 3 levels of interfaces:
++     1) Dictionary Interface
++     2) Buffer Interface
++     3) One Call Interface
++   You can select any of these interfaces, but don't mix functions from different
++   groups for same object. */
++
++
++/* There are two variants to allocate state for Dictionary Interface:
++     1) LzmaDec_Allocate / LzmaDec_Free
++     2) LzmaDec_AllocateProbs / LzmaDec_FreeProbs
++   You can use variant 2, if you set dictionary buffer manually.
++   For Buffer Interface you must always use variant 1.
++
++LzmaDec_Allocate* can return:
++  SZ_OK
++  SZ_ERROR_MEM         - Memory allocation error
++  SZ_ERROR_UNSUPPORTED - Unsupported properties
++*/
++
++SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc);
++void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc);
++
++SRes LzmaDec_Allocate(CLzmaDec *state, const Byte *prop, unsigned propsSize, ISzAlloc *alloc);
++void LzmaDec_Free(CLzmaDec *state, ISzAlloc *alloc);
++
++/* ---------- Dictionary Interface ---------- */
++
++/* You can use it, if you want to eliminate the overhead for data copying from
++   dictionary to some other external buffer.
++   You must work with CLzmaDec variables directly in this interface.
++
++   STEPS:
++     LzmaDec_Constr()
++     LzmaDec_Allocate()
++     for (each new stream)
++     {
++       LzmaDec_Init()
++       while (it needs more decompression)
++       {
++         LzmaDec_DecodeToDic()
++         use data from CLzmaDec::dic and update CLzmaDec::dicPos
++       }
++     }
++     LzmaDec_Free()
++*/
++
++/* LzmaDec_DecodeToDic
++
++   The decoding to internal dictionary buffer (CLzmaDec::dic).
++   You must manually update CLzmaDec::dicPos, if it reaches CLzmaDec::dicBufSize !!!
++
++finishMode:
++  It has meaning only if the decoding reaches output limit (dicLimit).
++  LZMA_FINISH_ANY - Decode just dicLimit bytes.
++  LZMA_FINISH_END - Stream must be finished after dicLimit.
++
++Returns:
++  SZ_OK
++    status:
++      LZMA_STATUS_FINISHED_WITH_MARK
++      LZMA_STATUS_NOT_FINISHED
++      LZMA_STATUS_NEEDS_MORE_INPUT
++      LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
++  SZ_ERROR_DATA - Data error
++*/
++
++SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit,
++    const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status);
++
++
++/* ---------- Buffer Interface ---------- */
++
++/* It's zlib-like interface.
++   See LzmaDec_DecodeToDic description for information about STEPS and return results,
++   but you must use LzmaDec_DecodeToBuf instead of LzmaDec_DecodeToDic and you don't need
++   to work with CLzmaDec variables manually.
++
++finishMode:
++  It has meaning only if the decoding reaches output limit (*destLen).
++  LZMA_FINISH_ANY - Decode just destLen bytes.
++  LZMA_FINISH_END - Stream must be finished after (*destLen).
++*/
++
++SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen,
++    const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status);
++
++
++/* ---------- One Call Interface ---------- */
++
++/* LzmaDecode
++
++finishMode:
++  It has meaning only if the decoding reaches output limit (*destLen).
++  LZMA_FINISH_ANY - Decode just destLen bytes.
++  LZMA_FINISH_END - Stream must be finished after (*destLen).
++
++Returns:
++  SZ_OK
++    status:
++      LZMA_STATUS_FINISHED_WITH_MARK
++      LZMA_STATUS_NOT_FINISHED
++      LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
++  SZ_ERROR_DATA - Data error
++  SZ_ERROR_MEM  - Memory allocation error
++  SZ_ERROR_UNSUPPORTED - Unsupported properties
++  SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src).
++*/
++
++SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen,
++    const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode,
++    ELzmaStatus *status, ISzAlloc *alloc);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+--- /dev/null
++++ b/include/linux/lzma/LzmaEnc.h
+@@ -0,0 +1,80 @@
++/*  LzmaEnc.h -- LZMA Encoder
++2009-02-07 : Igor Pavlov : Public domain */
++
++#ifndef __LZMA_ENC_H
++#define __LZMA_ENC_H
++
++#include "Types.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#define LZMA_PROPS_SIZE 5
++
++typedef struct _CLzmaEncProps
++{
++  int level;       /*  0 <= level <= 9 */
++  UInt32 dictSize; /* (1 << 12) <= dictSize <= (1 << 27) for 32-bit version
++                      (1 << 12) <= dictSize <= (1 << 30) for 64-bit version
++                       default = (1 << 24) */
++  int lc;          /* 0 <= lc <= 8, default = 3 */
++  int lp;          /* 0 <= lp <= 4, default = 0 */
++  int pb;          /* 0 <= pb <= 4, default = 2 */
++  int algo;        /* 0 - fast, 1 - normal, default = 1 */
++  int fb;          /* 5 <= fb <= 273, default = 32 */
++  int btMode;      /* 0 - hashChain Mode, 1 - binTree mode - normal, default = 1 */
++  int numHashBytes; /* 2, 3 or 4, default = 4 */
++  UInt32 mc;        /* 1 <= mc <= (1 << 30), default = 32 */
++  unsigned writeEndMark;  /* 0 - do not write EOPM, 1 - write EOPM, default = 0 */
++  int numThreads;  /* 1 or 2, default = 2 */
++} CLzmaEncProps;
++
++void LzmaEncProps_Init(CLzmaEncProps *p);
++void LzmaEncProps_Normalize(CLzmaEncProps *p);
++UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2);
++
++
++/* ---------- CLzmaEncHandle Interface ---------- */
++
++/* LzmaEnc_* functions can return the following exit codes:
++Returns:
++  SZ_OK           - OK
++  SZ_ERROR_MEM    - Memory allocation error
++  SZ_ERROR_PARAM  - Incorrect paramater in props
++  SZ_ERROR_WRITE  - Write callback error.
++  SZ_ERROR_PROGRESS - some break from progress callback
++  SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version)
++*/
++
++typedef void * CLzmaEncHandle;
++
++CLzmaEncHandle LzmaEnc_Create(ISzAlloc *alloc);
++void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAlloc *alloc, ISzAlloc *allocBig);
++SRes LzmaEnc_SetProps(CLzmaEncHandle p, const CLzmaEncProps *props);
++SRes LzmaEnc_WriteProperties(CLzmaEncHandle p, Byte *properties, SizeT *size);
++SRes LzmaEnc_Encode(CLzmaEncHandle p, ISeqOutStream *outStream, ISeqInStream *inStream,
++    ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig);
++SRes LzmaEnc_MemEncode(CLzmaEncHandle p, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen,
++    int writeEndMark, ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig);
++
++/* ---------- One Call Interface ---------- */
++
++/* LzmaEncode
++Return code:
++  SZ_OK               - OK
++  SZ_ERROR_MEM        - Memory allocation error
++  SZ_ERROR_PARAM      - Incorrect paramater
++  SZ_ERROR_OUTPUT_EOF - output buffer overflow
++  SZ_ERROR_THREAD     - errors in multithreading functions (only for Mt version)
++*/
++
++SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen,
++    const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark,
++    ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+--- /dev/null
++++ b/include/linux/lzma/Types.h
+@@ -0,0 +1,226 @@
++/* Types.h -- Basic types
++2009-11-23 : Igor Pavlov : Public domain */
++
++#ifndef __7Z_TYPES_H
++#define __7Z_TYPES_H
++
++#include <stddef.h>
++
++#ifdef _WIN32
++#include <windows.h>
++#endif
++
++#ifndef EXTERN_C_BEGIN
++#ifdef __cplusplus
++#define EXTERN_C_BEGIN extern "C" {
++#define EXTERN_C_END }
++#else
++#define EXTERN_C_BEGIN
++#define EXTERN_C_END
++#endif
++#endif
++
++EXTERN_C_BEGIN
++
++#define SZ_OK 0
++
++#define SZ_ERROR_DATA 1
++#define SZ_ERROR_MEM 2
++#define SZ_ERROR_CRC 3
++#define SZ_ERROR_UNSUPPORTED 4
++#define SZ_ERROR_PARAM 5
++#define SZ_ERROR_INPUT_EOF 6
++#define SZ_ERROR_OUTPUT_EOF 7
++#define SZ_ERROR_READ 8
++#define SZ_ERROR_WRITE 9
++#define SZ_ERROR_PROGRESS 10
++#define SZ_ERROR_FAIL 11
++#define SZ_ERROR_THREAD 12
++
++#define SZ_ERROR_ARCHIVE 16
++#define SZ_ERROR_NO_ARCHIVE 17
++
++typedef int SRes;
++
++#ifdef _WIN32
++typedef DWORD WRes;
++#else
++typedef int WRes;
++#endif
++
++#ifndef RINOK
++#define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; }
++#endif
++
++typedef unsigned char Byte;
++typedef short Int16;
++typedef unsigned short UInt16;
++
++#ifdef _LZMA_UINT32_IS_ULONG
++typedef long Int32;
++typedef unsigned long UInt32;
++#else
++typedef int Int32;
++typedef unsigned int UInt32;
++#endif
++
++#ifdef _SZ_NO_INT_64
++
++/* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers.
++   NOTES: Some code will work incorrectly in that case! */
++
++typedef long Int64;
++typedef unsigned long UInt64;
++
++#else
++
++#if defined(_MSC_VER) || defined(__BORLANDC__)
++typedef __int64 Int64;
++typedef unsigned __int64 UInt64;
++#else
++typedef long long int Int64;
++typedef unsigned long long int UInt64;
++#endif
++
++#endif
++
++#ifdef _LZMA_NO_SYSTEM_SIZE_T
++typedef UInt32 SizeT;
++#else
++typedef size_t SizeT;
++#endif
++
++typedef int Bool;
++#define True 1
++#define False 0
++
++
++#ifdef _WIN32
++#define MY_STD_CALL __stdcall
++#else
++#define MY_STD_CALL
++#endif
++
++#ifdef _MSC_VER
++
++#if _MSC_VER >= 1300
++#define MY_NO_INLINE __declspec(noinline)
++#else
++#define MY_NO_INLINE
++#endif
++
++#define MY_CDECL __cdecl
++#define MY_FAST_CALL __fastcall
++
++#else
++
++#define MY_CDECL
++#define MY_FAST_CALL
++
++#endif
++
++
++/* The following interfaces use first parameter as pointer to structure */
++
++typedef struct
++{
++  SRes (*Read)(void *p, void *buf, size_t *size);
++    /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream.
++       (output(*size) < input(*size)) is allowed */
++} ISeqInStream;
++
++/* it can return SZ_ERROR_INPUT_EOF */
++SRes SeqInStream_Read(ISeqInStream *stream, void *buf, size_t size);
++SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorType);
++SRes SeqInStream_ReadByte(ISeqInStream *stream, Byte *buf);
++
++typedef struct
++{
++  size_t (*Write)(void *p, const void *buf, size_t size);
++    /* Returns: result - the number of actually written bytes.
++       (result < size) means error */
++} ISeqOutStream;
++
++typedef enum
++{
++  SZ_SEEK_SET = 0,
++  SZ_SEEK_CUR = 1,
++  SZ_SEEK_END = 2
++} ESzSeek;
++
++typedef struct
++{
++  SRes (*Read)(void *p, void *buf, size_t *size);  /* same as ISeqInStream::Read */
++  SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin);
++} ISeekInStream;
++
++typedef struct
++{
++  SRes (*Look)(void *p, void **buf, size_t *size);
++    /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream.
++       (output(*size) > input(*size)) is not allowed
++       (output(*size) < input(*size)) is allowed */
++  SRes (*Skip)(void *p, size_t offset);
++    /* offset must be <= output(*size) of Look */
++
++  SRes (*Read)(void *p, void *buf, size_t *size);
++    /* reads directly (without buffer). It's same as ISeqInStream::Read */
++  SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin);
++} ILookInStream;
++
++SRes LookInStream_LookRead(ILookInStream *stream, void *buf, size_t *size);
++SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset);
++
++/* reads via ILookInStream::Read */
++SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType);
++SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size);
++
++#define LookToRead_BUF_SIZE (1 << 14)
++
++typedef struct
++{
++  ILookInStream s;
++  ISeekInStream *realStream;
++  size_t pos;
++  size_t size;
++  Byte buf[LookToRead_BUF_SIZE];
++} CLookToRead;
++
++void LookToRead_CreateVTable(CLookToRead *p, int lookahead);
++void LookToRead_Init(CLookToRead *p);
++
++typedef struct
++{
++  ISeqInStream s;
++  ILookInStream *realStream;
++} CSecToLook;
++
++void SecToLook_CreateVTable(CSecToLook *p);
++
++typedef struct
++{
++  ISeqInStream s;
++  ILookInStream *realStream;
++} CSecToRead;
++
++void SecToRead_CreateVTable(CSecToRead *p);
++
++typedef struct
++{
++  SRes (*Progress)(void *p, UInt64 inSize, UInt64 outSize);
++    /* Returns: result. (result != SZ_OK) means break.
++       Value (UInt64)(Int64)-1 for size means unknown value. */
++} ICompressProgress;
++
++typedef struct
++{
++  void *(*Alloc)(void *p, size_t size);
++  void (*Free)(void *p, void *address); /* address can be 0 */
++} ISzAlloc;
++
++#define IAlloc_Alloc(p, size) (p)->Alloc((p), size)
++#define IAlloc_Free(p, a) (p)->Free((p), a)
++
++EXTERN_C_END
++
++#endif
+--- a/include/uapi/linux/jffs2.h
++++ b/include/uapi/linux/jffs2.h
+@@ -46,6 +46,7 @@
+ #define JFFS2_COMPR_DYNRUBIN  0x05
+ #define JFFS2_COMPR_ZLIB      0x06
+ #define JFFS2_COMPR_LZO               0x07
++#define JFFS2_COMPR_LZMA      0x08
+ /* Compatibility flags. */
+ #define JFFS2_COMPAT_MASK 0xc000      /* What do to if an unknown nodetype is found */
+ #define JFFS2_NODE_ACCURATE 0x2000
+--- a/lib/Kconfig
++++ b/lib/Kconfig
+@@ -353,6 +353,12 @@ config ZSTD_DECOMPRESS
+ source "lib/xz/Kconfig"
++config LZMA_COMPRESS
++    tristate
++
++config LZMA_DECOMPRESS
++    tristate
++
+ #
+ # These all provide a common interface (hence the apparent duplication with
+ # ZLIB_INFLATE; DECOMPRESS_GZIP is just a wrapper.)
+--- a/lib/Makefile
++++ b/lib/Makefile
+@@ -126,6 +126,16 @@ CFLAGS_kobject.o += -DDEBUG
+ CFLAGS_kobject_uevent.o += -DDEBUG
+ endif
++ifdef CONFIG_JFFS2_ZLIB
++  CONFIG_ZLIB_INFLATE:=y
++  CONFIG_ZLIB_DEFLATE:=y
++endif
++
++ifdef CONFIG_JFFS2_LZMA
++  CONFIG_LZMA_DECOMPRESS:=y
++  CONFIG_LZMA_COMPRESS:=y
++endif
++
+ obj-$(CONFIG_DEBUG_INFO_REDUCED) += debug_info.o
+ CFLAGS_debug_info.o += $(call cc-option, -femit-struct-debug-detailed=any)
+@@ -185,6 +195,8 @@ obj-$(CONFIG_ZSTD_COMPRESS) += zstd/
+ obj-$(CONFIG_ZSTD_DECOMPRESS) += zstd/
+ obj-$(CONFIG_XZ_DEC) += xz/
+ obj-$(CONFIG_RAID6_PQ) += raid6/
++obj-$(CONFIG_LZMA_COMPRESS) += lzma/
++obj-$(CONFIG_LZMA_DECOMPRESS) += lzma/
+ lib-$(CONFIG_DECOMPRESS_GZIP) += decompress_inflate.o
+ lib-$(CONFIG_DECOMPRESS_BZIP2) += decompress_bunzip2.o
+--- /dev/null
++++ b/lib/lzma/LzFind.c
+@@ -0,0 +1,761 @@
++/* LzFind.c -- Match finder for LZ algorithms
++2009-04-22 : Igor Pavlov : Public domain */
++
++#include <string.h>
++
++#include "LzFind.h"
++#include "LzHash.h"
++
++#define kEmptyHashValue 0
++#define kMaxValForNormalize ((UInt32)0xFFFFFFFF)
++#define kNormalizeStepMin (1 << 10) /* it must be power of 2 */
++#define kNormalizeMask (~(kNormalizeStepMin - 1))
++#define kMaxHistorySize ((UInt32)3 << 30)
++
++#define kStartMaxLen 3
++
++static void LzInWindow_Free(CMatchFinder *p, ISzAlloc *alloc)
++{
++  if (!p->directInput)
++  {
++    alloc->Free(alloc, p->bufferBase);
++    p->bufferBase = 0;
++  }
++}
++
++/* keepSizeBefore + keepSizeAfter + keepSizeReserv must be < 4G) */
++
++static int LzInWindow_Create(CMatchFinder *p, UInt32 keepSizeReserv, ISzAlloc *alloc)
++{
++  UInt32 blockSize = p->keepSizeBefore + p->keepSizeAfter + keepSizeReserv;
++  if (p->directInput)
++  {
++    p->blockSize = blockSize;
++    return 1;
++  }
++  if (p->bufferBase == 0 || p->blockSize != blockSize)
++  {
++    LzInWindow_Free(p, alloc);
++    p->blockSize = blockSize;
++    p->bufferBase = (Byte *)alloc->Alloc(alloc, (size_t)blockSize);
++  }
++  return (p->bufferBase != 0);
++}
++
++Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p) { return p->buffer; }
++static Byte MatchFinder_GetIndexByte(CMatchFinder *p, Int32 index) { return p->buffer[index]; }
++
++static UInt32 MatchFinder_GetNumAvailableBytes(CMatchFinder *p) { return p->streamPos - p->pos; }
++
++void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue)
++{
++  p->posLimit -= subValue;
++  p->pos -= subValue;
++  p->streamPos -= subValue;
++}
++
++static void MatchFinder_ReadBlock(CMatchFinder *p)
++{
++  if (p->streamEndWasReached || p->result != SZ_OK)
++    return;
++  if (p->directInput)
++  {
++    UInt32 curSize = 0xFFFFFFFF - p->streamPos;
++    if (curSize > p->directInputRem)
++      curSize = (UInt32)p->directInputRem;
++    p->directInputRem -= curSize;
++    p->streamPos += curSize;
++    if (p->directInputRem == 0)
++      p->streamEndWasReached = 1;
++    return;
++  }
++  for (;;)
++  {
++    Byte *dest = p->buffer + (p->streamPos - p->pos);
++    size_t size = (p->bufferBase + p->blockSize - dest);
++    if (size == 0)
++      return;
++    p->result = p->stream->Read(p->stream, dest, &size);
++    if (p->result != SZ_OK)
++      return;
++    if (size == 0)
++    {
++      p->streamEndWasReached = 1;
++      return;
++    }
++    p->streamPos += (UInt32)size;
++    if (p->streamPos - p->pos > p->keepSizeAfter)
++      return;
++  }
++}
++
++void MatchFinder_MoveBlock(CMatchFinder *p)
++{
++  memmove(p->bufferBase,
++    p->buffer - p->keepSizeBefore,
++    (size_t)(p->streamPos - p->pos + p->keepSizeBefore));
++  p->buffer = p->bufferBase + p->keepSizeBefore;
++}
++
++int MatchFinder_NeedMove(CMatchFinder *p)
++{
++  if (p->directInput)
++    return 0;
++  /* if (p->streamEndWasReached) return 0; */
++  return ((size_t)(p->bufferBase + p->blockSize - p->buffer) <= p->keepSizeAfter);
++}
++
++void MatchFinder_ReadIfRequired(CMatchFinder *p)
++{
++  if (p->streamEndWasReached)
++    return;
++  if (p->keepSizeAfter >= p->streamPos - p->pos)
++    MatchFinder_ReadBlock(p);
++}
++
++static void MatchFinder_CheckAndMoveAndRead(CMatchFinder *p)
++{
++  if (MatchFinder_NeedMove(p))
++    MatchFinder_MoveBlock(p);
++  MatchFinder_ReadBlock(p);
++}
++
++static void MatchFinder_SetDefaultSettings(CMatchFinder *p)
++{
++  p->cutValue = 32;
++  p->btMode = 1;
++  p->numHashBytes = 4;
++  p->bigHash = 0;
++}
++
++#define kCrcPoly 0xEDB88320
++
++void MatchFinder_Construct(CMatchFinder *p)
++{
++  UInt32 i;
++  p->bufferBase = 0;
++  p->directInput = 0;
++  p->hash = 0;
++  MatchFinder_SetDefaultSettings(p);
++
++  for (i = 0; i < 256; i++)
++  {
++    UInt32 r = i;
++    int j;
++    for (j = 0; j < 8; j++)
++      r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1));
++    p->crc[i] = r;
++  }
++}
++
++static void MatchFinder_FreeThisClassMemory(CMatchFinder *p, ISzAlloc *alloc)
++{
++  alloc->Free(alloc, p->hash);
++  p->hash = 0;
++}
++
++void MatchFinder_Free(CMatchFinder *p, ISzAlloc *alloc)
++{
++  MatchFinder_FreeThisClassMemory(p, alloc);
++  LzInWindow_Free(p, alloc);
++}
++
++static CLzRef* AllocRefs(UInt32 num, ISzAlloc *alloc)
++{
++  size_t sizeInBytes = (size_t)num * sizeof(CLzRef);
++  if (sizeInBytes / sizeof(CLzRef) != num)
++    return 0;
++  return (CLzRef *)alloc->Alloc(alloc, sizeInBytes);
++}
++
++int MatchFinder_Create(CMatchFinder *p, UInt32 historySize,
++    UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter,
++    ISzAlloc *alloc)
++{
++  UInt32 sizeReserv;
++  if (historySize > kMaxHistorySize)
++  {
++    MatchFinder_Free(p, alloc);
++    return 0;
++  }
++  sizeReserv = historySize >> 1;
++  if (historySize > ((UInt32)2 << 30))
++    sizeReserv = historySize >> 2;
++  sizeReserv += (keepAddBufferBefore + matchMaxLen + keepAddBufferAfter) / 2 + (1 << 19);
++
++  p->keepSizeBefore = historySize + keepAddBufferBefore + 1;
++  p->keepSizeAfter = matchMaxLen + keepAddBufferAfter;
++  /* we need one additional byte, since we use MoveBlock after pos++ and before dictionary using */
++  if (LzInWindow_Create(p, sizeReserv, alloc))
++  {
++    UInt32 newCyclicBufferSize = historySize + 1;
++    UInt32 hs;
++    p->matchMaxLen = matchMaxLen;
++    {
++      p->fixedHashSize = 0;
++      if (p->numHashBytes == 2)
++        hs = (1 << 16) - 1;
++      else
++      {
++        hs = historySize - 1;
++        hs |= (hs >> 1);
++        hs |= (hs >> 2);
++        hs |= (hs >> 4);
++        hs |= (hs >> 8);
++        hs >>= 1;
++        hs |= 0xFFFF; /* don't change it! It's required for Deflate */
++        if (hs > (1 << 24))
++        {
++          if (p->numHashBytes == 3)
++            hs = (1 << 24) - 1;
++          else
++            hs >>= 1;
++        }
++      }
++      p->hashMask = hs;
++      hs++;
++      if (p->numHashBytes > 2) p->fixedHashSize += kHash2Size;
++      if (p->numHashBytes > 3) p->fixedHashSize += kHash3Size;
++      if (p->numHashBytes > 4) p->fixedHashSize += kHash4Size;
++      hs += p->fixedHashSize;
++    }
++
++    {
++      UInt32 prevSize = p->hashSizeSum + p->numSons;
++      UInt32 newSize;
++      p->historySize = historySize;
++      p->hashSizeSum = hs;
++      p->cyclicBufferSize = newCyclicBufferSize;
++      p->numSons = (p->btMode ? newCyclicBufferSize * 2 : newCyclicBufferSize);
++      newSize = p->hashSizeSum + p->numSons;
++      if (p->hash != 0 && prevSize == newSize)
++        return 1;
++      MatchFinder_FreeThisClassMemory(p, alloc);
++      p->hash = AllocRefs(newSize, alloc);
++      if (p->hash != 0)
++      {
++        p->son = p->hash + p->hashSizeSum;
++        return 1;
++      }
++    }
++  }
++  MatchFinder_Free(p, alloc);
++  return 0;
++}
++
++static void MatchFinder_SetLimits(CMatchFinder *p)
++{
++  UInt32 limit = kMaxValForNormalize - p->pos;
++  UInt32 limit2 = p->cyclicBufferSize - p->cyclicBufferPos;
++  if (limit2 < limit)
++    limit = limit2;
++  limit2 = p->streamPos - p->pos;
++  if (limit2 <= p->keepSizeAfter)
++  {
++    if (limit2 > 0)
++      limit2 = 1;
++  }
++  else
++    limit2 -= p->keepSizeAfter;
++  if (limit2 < limit)
++    limit = limit2;
++  {
++    UInt32 lenLimit = p->streamPos - p->pos;
++    if (lenLimit > p->matchMaxLen)
++      lenLimit = p->matchMaxLen;
++    p->lenLimit = lenLimit;
++  }
++  p->posLimit = p->pos + limit;
++}
++
++void MatchFinder_Init(CMatchFinder *p)
++{
++  UInt32 i;
++  for (i = 0; i < p->hashSizeSum; i++)
++    p->hash[i] = kEmptyHashValue;
++  p->cyclicBufferPos = 0;
++  p->buffer = p->bufferBase;
++  p->pos = p->streamPos = p->cyclicBufferSize;
++  p->result = SZ_OK;
++  p->streamEndWasReached = 0;
++  MatchFinder_ReadBlock(p);
++  MatchFinder_SetLimits(p);
++}
++
++static UInt32 MatchFinder_GetSubValue(CMatchFinder *p)
++{
++  return (p->pos - p->historySize - 1) & kNormalizeMask;
++}
++
++void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, UInt32 numItems)
++{
++  UInt32 i;
++  for (i = 0; i < numItems; i++)
++  {
++    UInt32 value = items[i];
++    if (value <= subValue)
++      value = kEmptyHashValue;
++    else
++      value -= subValue;
++    items[i] = value;
++  }
++}
++
++static void MatchFinder_Normalize(CMatchFinder *p)
++{
++  UInt32 subValue = MatchFinder_GetSubValue(p);
++  MatchFinder_Normalize3(subValue, p->hash, p->hashSizeSum + p->numSons);
++  MatchFinder_ReduceOffsets(p, subValue);
++}
++
++static void MatchFinder_CheckLimits(CMatchFinder *p)
++{
++  if (p->pos == kMaxValForNormalize)
++    MatchFinder_Normalize(p);
++  if (!p->streamEndWasReached && p->keepSizeAfter == p->streamPos - p->pos)
++    MatchFinder_CheckAndMoveAndRead(p);
++  if (p->cyclicBufferPos == p->cyclicBufferSize)
++    p->cyclicBufferPos = 0;
++  MatchFinder_SetLimits(p);
++}
++
++static UInt32 * Hc_GetMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son,
++    UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue,
++    UInt32 *distances, UInt32 maxLen)
++{
++  son[_cyclicBufferPos] = curMatch;
++  for (;;)
++  {
++    UInt32 delta = pos - curMatch;
++    if (cutValue-- == 0 || delta >= _cyclicBufferSize)
++      return distances;
++    {
++      const Byte *pb = cur - delta;
++      curMatch = son[_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)];
++      if (pb[maxLen] == cur[maxLen] && *pb == *cur)
++      {
++        UInt32 len = 0;
++        while (++len != lenLimit)
++          if (pb[len] != cur[len])
++            break;
++        if (maxLen < len)
++        {
++          *distances++ = maxLen = len;
++          *distances++ = delta - 1;
++          if (len == lenLimit)
++            return distances;
++        }
++      }
++    }
++  }
++}
++
++UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son,
++    UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue,
++    UInt32 *distances, UInt32 maxLen)
++{
++  CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1;
++  CLzRef *ptr1 = son + (_cyclicBufferPos << 1);
++  UInt32 len0 = 0, len1 = 0;
++  for (;;)
++  {
++    UInt32 delta = pos - curMatch;
++    if (cutValue-- == 0 || delta >= _cyclicBufferSize)
++    {
++      *ptr0 = *ptr1 = kEmptyHashValue;
++      return distances;
++    }
++    {
++      CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1);
++      const Byte *pb = cur - delta;
++      UInt32 len = (len0 < len1 ? len0 : len1);
++      if (pb[len] == cur[len])
++      {
++        if (++len != lenLimit && pb[len] == cur[len])
++          while (++len != lenLimit)
++            if (pb[len] != cur[len])
++              break;
++        if (maxLen < len)
++        {
++          *distances++ = maxLen = len;
++          *distances++ = delta - 1;
++          if (len == lenLimit)
++          {
++            *ptr1 = pair[0];
++            *ptr0 = pair[1];
++            return distances;
++          }
++        }
++      }
++      if (pb[len] < cur[len])
++      {
++        *ptr1 = curMatch;
++        ptr1 = pair + 1;
++        curMatch = *ptr1;
++        len1 = len;
++      }
++      else
++      {
++        *ptr0 = curMatch;
++        ptr0 = pair;
++        curMatch = *ptr0;
++        len0 = len;
++      }
++    }
++  }
++}
++
++static void SkipMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son,
++    UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue)
++{
++  CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1;
++  CLzRef *ptr1 = son + (_cyclicBufferPos << 1);
++  UInt32 len0 = 0, len1 = 0;
++  for (;;)
++  {
++    UInt32 delta = pos - curMatch;
++    if (cutValue-- == 0 || delta >= _cyclicBufferSize)
++    {
++      *ptr0 = *ptr1 = kEmptyHashValue;
++      return;
++    }
++    {
++      CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1);
++      const Byte *pb = cur - delta;
++      UInt32 len = (len0 < len1 ? len0 : len1);
++      if (pb[len] == cur[len])
++      {
++        while (++len != lenLimit)
++          if (pb[len] != cur[len])
++            break;
++        {
++          if (len == lenLimit)
++          {
++            *ptr1 = pair[0];
++            *ptr0 = pair[1];
++            return;
++          }
++        }
++      }
++      if (pb[len] < cur[len])
++      {
++        *ptr1 = curMatch;
++        ptr1 = pair + 1;
++        curMatch = *ptr1;
++        len1 = len;
++      }
++      else
++      {
++        *ptr0 = curMatch;
++        ptr0 = pair;
++        curMatch = *ptr0;
++        len0 = len;
++      }
++    }
++  }
++}
++
++#define MOVE_POS \
++  ++p->cyclicBufferPos; \
++  p->buffer++; \
++  if (++p->pos == p->posLimit) MatchFinder_CheckLimits(p);
++
++#define MOVE_POS_RET MOVE_POS return offset;
++
++static void MatchFinder_MovePos(CMatchFinder *p) { MOVE_POS; }
++
++#define GET_MATCHES_HEADER2(minLen, ret_op) \
++  UInt32 lenLimit; UInt32 hashValue; const Byte *cur; UInt32 curMatch; \
++  lenLimit = p->lenLimit; { if (lenLimit < minLen) { MatchFinder_MovePos(p); ret_op; }} \
++  cur = p->buffer;
++
++#define GET_MATCHES_HEADER(minLen) GET_MATCHES_HEADER2(minLen, return 0)
++#define SKIP_HEADER(minLen)        GET_MATCHES_HEADER2(minLen, continue)
++
++#define MF_PARAMS(p) p->pos, p->buffer, p->son, p->cyclicBufferPos, p->cyclicBufferSize, p->cutValue
++
++#define GET_MATCHES_FOOTER(offset, maxLen) \
++  offset = (UInt32)(GetMatchesSpec1(lenLimit, curMatch, MF_PARAMS(p), \
++  distances + offset, maxLen) - distances); MOVE_POS_RET;
++
++#define SKIP_FOOTER \
++  SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); MOVE_POS;
++
++static UInt32 Bt2_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
++{
++  UInt32 offset;
++  GET_MATCHES_HEADER(2)
++  HASH2_CALC;
++  curMatch = p->hash[hashValue];
++  p->hash[hashValue] = p->pos;
++  offset = 0;
++  GET_MATCHES_FOOTER(offset, 1)
++}
++
++UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
++{
++  UInt32 offset;
++  GET_MATCHES_HEADER(3)
++  HASH_ZIP_CALC;
++  curMatch = p->hash[hashValue];
++  p->hash[hashValue] = p->pos;
++  offset = 0;
++  GET_MATCHES_FOOTER(offset, 2)
++}
++
++static UInt32 Bt3_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
++{
++  UInt32 hash2Value, delta2, maxLen, offset;
++  GET_MATCHES_HEADER(3)
++
++  HASH3_CALC;
++
++  delta2 = p->pos - p->hash[hash2Value];
++  curMatch = p->hash[kFix3HashSize + hashValue];
++
++  p->hash[hash2Value] =
++  p->hash[kFix3HashSize + hashValue] = p->pos;
++
++
++  maxLen = 2;
++  offset = 0;
++  if (delta2 < p->cyclicBufferSize && *(cur - delta2) == *cur)
++  {
++    for (; maxLen != lenLimit; maxLen++)
++      if (cur[(ptrdiff_t)maxLen - delta2] != cur[maxLen])
++        break;
++    distances[0] = maxLen;
++    distances[1] = delta2 - 1;
++    offset = 2;
++    if (maxLen == lenLimit)
++    {
++      SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p));
++      MOVE_POS_RET;
++    }
++  }
++  GET_MATCHES_FOOTER(offset, maxLen)
++}
++
++static UInt32 Bt4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
++{
++  UInt32 hash2Value, hash3Value, delta2, delta3, maxLen, offset;
++  GET_MATCHES_HEADER(4)
++
++  HASH4_CALC;
++
++  delta2 = p->pos - p->hash[                hash2Value];
++  delta3 = p->pos - p->hash[kFix3HashSize + hash3Value];
++  curMatch = p->hash[kFix4HashSize + hashValue];
++
++  p->hash[                hash2Value] =
++  p->hash[kFix3HashSize + hash3Value] =
++  p->hash[kFix4HashSize + hashValue] = p->pos;
++
++  maxLen = 1;
++  offset = 0;
++  if (delta2 < p->cyclicBufferSize && *(cur - delta2) == *cur)
++  {
++    distances[0] = maxLen = 2;
++    distances[1] = delta2 - 1;
++    offset = 2;
++  }
++  if (delta2 != delta3 && delta3 < p->cyclicBufferSize && *(cur - delta3) == *cur)
++  {
++    maxLen = 3;
++    distances[offset + 1] = delta3 - 1;
++    offset += 2;
++    delta2 = delta3;
++  }
++  if (offset != 0)
++  {
++    for (; maxLen != lenLimit; maxLen++)
++      if (cur[(ptrdiff_t)maxLen - delta2] != cur[maxLen])
++        break;
++    distances[offset - 2] = maxLen;
++    if (maxLen == lenLimit)
++    {
++      SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p));
++      MOVE_POS_RET;
++    }
++  }
++  if (maxLen < 3)
++    maxLen = 3;
++  GET_MATCHES_FOOTER(offset, maxLen)
++}
++
++static UInt32 Hc4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
++{
++  UInt32 hash2Value, hash3Value, delta2, delta3, maxLen, offset;
++  GET_MATCHES_HEADER(4)
++
++  HASH4_CALC;
++
++  delta2 = p->pos - p->hash[                hash2Value];
++  delta3 = p->pos - p->hash[kFix3HashSize + hash3Value];
++  curMatch = p->hash[kFix4HashSize + hashValue];
++
++  p->hash[                hash2Value] =
++  p->hash[kFix3HashSize + hash3Value] =
++  p->hash[kFix4HashSize + hashValue] = p->pos;
++
++  maxLen = 1;
++  offset = 0;
++  if (delta2 < p->cyclicBufferSize && *(cur - delta2) == *cur)
++  {
++    distances[0] = maxLen = 2;
++    distances[1] = delta2 - 1;
++    offset = 2;
++  }
++  if (delta2 != delta3 && delta3 < p->cyclicBufferSize && *(cur - delta3) == *cur)
++  {
++    maxLen = 3;
++    distances[offset + 1] = delta3 - 1;
++    offset += 2;
++    delta2 = delta3;
++  }
++  if (offset != 0)
++  {
++    for (; maxLen != lenLimit; maxLen++)
++      if (cur[(ptrdiff_t)maxLen - delta2] != cur[maxLen])
++        break;
++    distances[offset - 2] = maxLen;
++    if (maxLen == lenLimit)
++    {
++      p->son[p->cyclicBufferPos] = curMatch;
++      MOVE_POS_RET;
++    }
++  }
++  if (maxLen < 3)
++    maxLen = 3;
++  offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p),
++    distances + offset, maxLen) - (distances));
++  MOVE_POS_RET
++}
++
++UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
++{
++  UInt32 offset;
++  GET_MATCHES_HEADER(3)
++  HASH_ZIP_CALC;
++  curMatch = p->hash[hashValue];
++  p->hash[hashValue] = p->pos;
++  offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p),
++    distances, 2) - (distances));
++  MOVE_POS_RET
++}
++
++static void Bt2_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
++{
++  do
++  {
++    SKIP_HEADER(2)
++    HASH2_CALC;
++    curMatch = p->hash[hashValue];
++    p->hash[hashValue] = p->pos;
++    SKIP_FOOTER
++  }
++  while (--num != 0);
++}
++
++void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
++{
++  do
++  {
++    SKIP_HEADER(3)
++    HASH_ZIP_CALC;
++    curMatch = p->hash[hashValue];
++    p->hash[hashValue] = p->pos;
++    SKIP_FOOTER
++  }
++  while (--num != 0);
++}
++
++static void Bt3_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
++{
++  do
++  {
++    UInt32 hash2Value;
++    SKIP_HEADER(3)
++    HASH3_CALC;
++    curMatch = p->hash[kFix3HashSize + hashValue];
++    p->hash[hash2Value] =
++    p->hash[kFix3HashSize + hashValue] = p->pos;
++    SKIP_FOOTER
++  }
++  while (--num != 0);
++}
++
++static void Bt4_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
++{
++  do
++  {
++    UInt32 hash2Value, hash3Value;
++    SKIP_HEADER(4)
++    HASH4_CALC;
++    curMatch = p->hash[kFix4HashSize + hashValue];
++    p->hash[                hash2Value] =
++    p->hash[kFix3HashSize + hash3Value] = p->pos;
++    p->hash[kFix4HashSize + hashValue] = p->pos;
++    SKIP_FOOTER
++  }
++  while (--num != 0);
++}
++
++static void Hc4_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
++{
++  do
++  {
++    UInt32 hash2Value, hash3Value;
++    SKIP_HEADER(4)
++    HASH4_CALC;
++    curMatch = p->hash[kFix4HashSize + hashValue];
++    p->hash[                hash2Value] =
++    p->hash[kFix3HashSize + hash3Value] =
++    p->hash[kFix4HashSize + hashValue] = p->pos;
++    p->son[p->cyclicBufferPos] = curMatch;
++    MOVE_POS
++  }
++  while (--num != 0);
++}
++
++void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
++{
++  do
++  {
++    SKIP_HEADER(3)
++    HASH_ZIP_CALC;
++    curMatch = p->hash[hashValue];
++    p->hash[hashValue] = p->pos;
++    p->son[p->cyclicBufferPos] = curMatch;
++    MOVE_POS
++  }
++  while (--num != 0);
++}
++
++void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable)
++{
++  vTable->Init = (Mf_Init_Func)MatchFinder_Init;
++  vTable->GetIndexByte = (Mf_GetIndexByte_Func)MatchFinder_GetIndexByte;
++  vTable->GetNumAvailableBytes = (Mf_GetNumAvailableBytes_Func)MatchFinder_GetNumAvailableBytes;
++  vTable->GetPointerToCurrentPos = (Mf_GetPointerToCurrentPos_Func)MatchFinder_GetPointerToCurrentPos;
++  if (!p->btMode)
++  {
++    vTable->GetMatches = (Mf_GetMatches_Func)Hc4_MatchFinder_GetMatches;
++    vTable->Skip = (Mf_Skip_Func)Hc4_MatchFinder_Skip;
++  }
++  else if (p->numHashBytes == 2)
++  {
++    vTable->GetMatches = (Mf_GetMatches_Func)Bt2_MatchFinder_GetMatches;
++    vTable->Skip = (Mf_Skip_Func)Bt2_MatchFinder_Skip;
++  }
++  else if (p->numHashBytes == 3)
++  {
++    vTable->GetMatches = (Mf_GetMatches_Func)Bt3_MatchFinder_GetMatches;
++    vTable->Skip = (Mf_Skip_Func)Bt3_MatchFinder_Skip;
++  }
++  else
++  {
++    vTable->GetMatches = (Mf_GetMatches_Func)Bt4_MatchFinder_GetMatches;
++    vTable->Skip = (Mf_Skip_Func)Bt4_MatchFinder_Skip;
++  }
++}
+--- /dev/null
++++ b/lib/lzma/LzmaDec.c
+@@ -0,0 +1,999 @@
++/* LzmaDec.c -- LZMA Decoder
++2009-09-20 : Igor Pavlov : Public domain */
++
++#include "LzmaDec.h"
++
++#include <string.h>
++
++#define kNumTopBits 24
++#define kTopValue ((UInt32)1 << kNumTopBits)
++
++#define kNumBitModelTotalBits 11
++#define kBitModelTotal (1 << kNumBitModelTotalBits)
++#define kNumMoveBits 5
++
++#define RC_INIT_SIZE 5
++
++#define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); }
++
++#define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound)
++#define UPDATE_0(p) range = bound; *(p) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits));
++#define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits));
++#define GET_BIT2(p, i, A0, A1) IF_BIT_0(p) \
++  { UPDATE_0(p); i = (i + i); A0; } else \
++  { UPDATE_1(p); i = (i + i) + 1; A1; }
++#define GET_BIT(p, i) GET_BIT2(p, i, ; , ;)
++
++#define TREE_GET_BIT(probs, i) { GET_BIT((probs + i), i); }
++#define TREE_DECODE(probs, limit, i) \
++  { i = 1; do { TREE_GET_BIT(probs, i); } while (i < limit); i -= limit; }
++
++/* #define _LZMA_SIZE_OPT */
++
++#ifdef _LZMA_SIZE_OPT
++#define TREE_6_DECODE(probs, i) TREE_DECODE(probs, (1 << 6), i)
++#else
++#define TREE_6_DECODE(probs, i) \
++  { i = 1; \
++  TREE_GET_BIT(probs, i); \
++  TREE_GET_BIT(probs, i); \
++  TREE_GET_BIT(probs, i); \
++  TREE_GET_BIT(probs, i); \
++  TREE_GET_BIT(probs, i); \
++  TREE_GET_BIT(probs, i); \
++  i -= 0x40; }
++#endif
++
++#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_ERROR; range <<= 8; code = (code << 8) | (*buf++); }
++
++#define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound)
++#define UPDATE_0_CHECK range = bound;
++#define UPDATE_1_CHECK range -= bound; code -= bound;
++#define GET_BIT2_CHECK(p, i, A0, A1) IF_BIT_0_CHECK(p) \
++  { UPDATE_0_CHECK; i = (i + i); A0; } else \
++  { UPDATE_1_CHECK; i = (i + i) + 1; A1; }
++#define GET_BIT_CHECK(p, i) GET_BIT2_CHECK(p, i, ; , ;)
++#define TREE_DECODE_CHECK(probs, limit, i) \
++  { i = 1; do { GET_BIT_CHECK(probs + i, i) } while (i < limit); i -= limit; }
++
++
++#define kNumPosBitsMax 4
++#define kNumPosStatesMax (1 << kNumPosBitsMax)
++
++#define kLenNumLowBits 3
++#define kLenNumLowSymbols (1 << kLenNumLowBits)
++#define kLenNumMidBits 3
++#define kLenNumMidSymbols (1 << kLenNumMidBits)
++#define kLenNumHighBits 8
++#define kLenNumHighSymbols (1 << kLenNumHighBits)
++
++#define LenChoice 0
++#define LenChoice2 (LenChoice + 1)
++#define LenLow (LenChoice2 + 1)
++#define LenMid (LenLow + (kNumPosStatesMax << kLenNumLowBits))
++#define LenHigh (LenMid + (kNumPosStatesMax << kLenNumMidBits))
++#define kNumLenProbs (LenHigh + kLenNumHighSymbols)
++
++
++#define kNumStates 12
++#define kNumLitStates 7
++
++#define kStartPosModelIndex 4
++#define kEndPosModelIndex 14
++#define kNumFullDistances (1 << (kEndPosModelIndex >> 1))
++
++#define kNumPosSlotBits 6
++#define kNumLenToPosStates 4
++
++#define kNumAlignBits 4
++#define kAlignTableSize (1 << kNumAlignBits)
++
++#define kMatchMinLen 2
++#define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols)
++
++#define IsMatch 0
++#define IsRep (IsMatch + (kNumStates << kNumPosBitsMax))
++#define IsRepG0 (IsRep + kNumStates)
++#define IsRepG1 (IsRepG0 + kNumStates)
++#define IsRepG2 (IsRepG1 + kNumStates)
++#define IsRep0Long (IsRepG2 + kNumStates)
++#define PosSlot (IsRep0Long + (kNumStates << kNumPosBitsMax))
++#define SpecPos (PosSlot + (kNumLenToPosStates << kNumPosSlotBits))
++#define Align (SpecPos + kNumFullDistances - kEndPosModelIndex)
++#define LenCoder (Align + kAlignTableSize)
++#define RepLenCoder (LenCoder + kNumLenProbs)
++#define Literal (RepLenCoder + kNumLenProbs)
++
++#define LZMA_BASE_SIZE 1846
++#define LZMA_LIT_SIZE 768
++
++#define LzmaProps_GetNumProbs(p) ((UInt32)LZMA_BASE_SIZE + (LZMA_LIT_SIZE << ((p)->lc + (p)->lp)))
++
++#if Literal != LZMA_BASE_SIZE
++StopCompilingDueBUG
++#endif
++
++#define LZMA_DIC_MIN (1 << 12)
++
++/* First LZMA-symbol is always decoded.
++And it decodes new LZMA-symbols while (buf < bufLimit), but "buf" is without last normalization
++Out:
++  Result:
++    SZ_OK - OK
++    SZ_ERROR_DATA - Error
++  p->remainLen:
++    < kMatchSpecLenStart : normal remain
++    = kMatchSpecLenStart : finished
++    = kMatchSpecLenStart + 1 : Flush marker
++    = kMatchSpecLenStart + 2 : State Init Marker
++*/
++
++static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte *bufLimit)
++{
++  CLzmaProb *probs = p->probs;
++
++  unsigned state = p->state;
++  UInt32 rep0 = p->reps[0], rep1 = p->reps[1], rep2 = p->reps[2], rep3 = p->reps[3];
++  unsigned pbMask = ((unsigned)1 << (p->prop.pb)) - 1;
++  unsigned lpMask = ((unsigned)1 << (p->prop.lp)) - 1;
++  unsigned lc = p->prop.lc;
++
++  Byte *dic = p->dic;
++  SizeT dicBufSize = p->dicBufSize;
++  SizeT dicPos = p->dicPos;
++
++  UInt32 processedPos = p->processedPos;
++  UInt32 checkDicSize = p->checkDicSize;
++  unsigned len = 0;
++
++  const Byte *buf = p->buf;
++  UInt32 range = p->range;
++  UInt32 code = p->code;
++
++  do
++  {
++    CLzmaProb *prob;
++    UInt32 bound;
++    unsigned ttt;
++    unsigned posState = processedPos & pbMask;
++
++    prob = probs + IsMatch + (state << kNumPosBitsMax) + posState;
++    IF_BIT_0(prob)
++    {
++      unsigned symbol;
++      UPDATE_0(prob);
++      prob = probs + Literal;
++      if (checkDicSize != 0 || processedPos != 0)
++        prob += (LZMA_LIT_SIZE * (((processedPos & lpMask) << lc) +
++        (dic[(dicPos == 0 ? dicBufSize : dicPos) - 1] >> (8 - lc))));
++
++      if (state < kNumLitStates)
++      {
++        state -= (state < 4) ? state : 3;
++        symbol = 1;
++        do { GET_BIT(prob + symbol, symbol) } while (symbol < 0x100);
++      }
++      else
++      {
++        unsigned matchByte = p->dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)];
++        unsigned offs = 0x100;
++        state -= (state < 10) ? 3 : 6;
++        symbol = 1;
++        do
++        {
++          unsigned bit;
++          CLzmaProb *probLit;
++          matchByte <<= 1;
++          bit = (matchByte & offs);
++          probLit = prob + offs + bit + symbol;
++          GET_BIT2(probLit, symbol, offs &= ~bit, offs &= bit)
++        }
++        while (symbol < 0x100);
++      }
++      dic[dicPos++] = (Byte)symbol;
++      processedPos++;
++      continue;
++    }
++    else
++    {
++      UPDATE_1(prob);
++      prob = probs + IsRep + state;
++      IF_BIT_0(prob)
++      {
++        UPDATE_0(prob);
++        state += kNumStates;
++        prob = probs + LenCoder;
++      }
++      else
++      {
++        UPDATE_1(prob);
++        if (checkDicSize == 0 && processedPos == 0)
++          return SZ_ERROR_DATA;
++        prob = probs + IsRepG0 + state;
++        IF_BIT_0(prob)
++        {
++          UPDATE_0(prob);
++          prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState;
++          IF_BIT_0(prob)
++          {
++            UPDATE_0(prob);
++            dic[dicPos] = dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)];
++            dicPos++;
++            processedPos++;
++            state = state < kNumLitStates ? 9 : 11;
++            continue;
++          }
++          UPDATE_1(prob);
++        }
++        else
++        {
++          UInt32 distance;
++          UPDATE_1(prob);
++          prob = probs + IsRepG1 + state;
++          IF_BIT_0(prob)
++          {
++            UPDATE_0(prob);
++            distance = rep1;
++          }
++          else
++          {
++            UPDATE_1(prob);
++            prob = probs + IsRepG2 + state;
++            IF_BIT_0(prob)
++            {
++              UPDATE_0(prob);
++              distance = rep2;
++            }
++            else
++            {
++              UPDATE_1(prob);
++              distance = rep3;
++              rep3 = rep2;
++            }
++            rep2 = rep1;
++          }
++          rep1 = rep0;
++          rep0 = distance;
++        }
++        state = state < kNumLitStates ? 8 : 11;
++        prob = probs + RepLenCoder;
++      }
++      {
++        unsigned limit, offset;
++        CLzmaProb *probLen = prob + LenChoice;
++        IF_BIT_0(probLen)
++        {
++          UPDATE_0(probLen);
++          probLen = prob + LenLow + (posState << kLenNumLowBits);
++          offset = 0;
++          limit = (1 << kLenNumLowBits);
++        }
++        else
++        {
++          UPDATE_1(probLen);
++          probLen = prob + LenChoice2;
++          IF_BIT_0(probLen)
++          {
++            UPDATE_0(probLen);
++            probLen = prob + LenMid + (posState << kLenNumMidBits);
++            offset = kLenNumLowSymbols;
++            limit = (1 << kLenNumMidBits);
++          }
++          else
++          {
++            UPDATE_1(probLen);
++            probLen = prob + LenHigh;
++            offset = kLenNumLowSymbols + kLenNumMidSymbols;
++            limit = (1 << kLenNumHighBits);
++          }
++        }
++        TREE_DECODE(probLen, limit, len);
++        len += offset;
++      }
++
++      if (state >= kNumStates)
++      {
++        UInt32 distance;
++        prob = probs + PosSlot +
++            ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << kNumPosSlotBits);
++        TREE_6_DECODE(prob, distance);
++        if (distance >= kStartPosModelIndex)
++        {
++          unsigned posSlot = (unsigned)distance;
++          int numDirectBits = (int)(((distance >> 1) - 1));
++          distance = (2 | (distance & 1));
++          if (posSlot < kEndPosModelIndex)
++          {
++            distance <<= numDirectBits;
++            prob = probs + SpecPos + distance - posSlot - 1;
++            {
++              UInt32 mask = 1;
++              unsigned i = 1;
++              do
++              {
++                GET_BIT2(prob + i, i, ; , distance |= mask);
++                mask <<= 1;
++              }
++              while (--numDirectBits != 0);
++            }
++          }
++          else
++          {
++            numDirectBits -= kNumAlignBits;
++            do
++            {
++              NORMALIZE
++              range >>= 1;
++
++              {
++                UInt32 t;
++                code -= range;
++                t = (0 - ((UInt32)code >> 31)); /* (UInt32)((Int32)code >> 31) */
++                distance = (distance << 1) + (t + 1);
++                code += range & t;
++              }
++              /*
++              distance <<= 1;
++              if (code >= range)
++              {
++                code -= range;
++                distance |= 1;
++              }
++              */
++            }
++            while (--numDirectBits != 0);
++            prob = probs + Align;
++            distance <<= kNumAlignBits;
++            {
++              unsigned i = 1;
++              GET_BIT2(prob + i, i, ; , distance |= 1);
++              GET_BIT2(prob + i, i, ; , distance |= 2);
++              GET_BIT2(prob + i, i, ; , distance |= 4);
++              GET_BIT2(prob + i, i, ; , distance |= 8);
++            }
++            if (distance == (UInt32)0xFFFFFFFF)
++            {
++              len += kMatchSpecLenStart;
++              state -= kNumStates;
++              break;
++            }
++          }
++        }
++        rep3 = rep2;
++        rep2 = rep1;
++        rep1 = rep0;
++        rep0 = distance + 1;
++        if (checkDicSize == 0)
++        {
++          if (distance >= processedPos)
++            return SZ_ERROR_DATA;
++        }
++        else if (distance >= checkDicSize)
++          return SZ_ERROR_DATA;
++        state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3;
++      }
++
++      len += kMatchMinLen;
++
++      if (limit == dicPos)
++        return SZ_ERROR_DATA;
++      {
++        SizeT rem = limit - dicPos;
++        unsigned curLen = ((rem < len) ? (unsigned)rem : len);
++        SizeT pos = (dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0);
++
++        processedPos += curLen;
++
++        len -= curLen;
++        if (pos + curLen <= dicBufSize)
++        {
++          Byte *dest = dic + dicPos;
++          ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dicPos;
++          const Byte *lim = dest + curLen;
++          dicPos += curLen;
++          do
++            *(dest) = (Byte)*(dest + src);
++          while (++dest != lim);
++        }
++        else
++        {
++          do
++          {
++            dic[dicPos++] = dic[pos];
++            if (++pos == dicBufSize)
++              pos = 0;
++          }
++          while (--curLen != 0);
++        }
++      }
++    }
++  }
++  while (dicPos < limit && buf < bufLimit);
++  NORMALIZE;
++  p->buf = buf;
++  p->range = range;
++  p->code = code;
++  p->remainLen = len;
++  p->dicPos = dicPos;
++  p->processedPos = processedPos;
++  p->reps[0] = rep0;
++  p->reps[1] = rep1;
++  p->reps[2] = rep2;
++  p->reps[3] = rep3;
++  p->state = state;
++
++  return SZ_OK;
++}
++
++static void MY_FAST_CALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit)
++{
++  if (p->remainLen != 0 && p->remainLen < kMatchSpecLenStart)
++  {
++    Byte *dic = p->dic;
++    SizeT dicPos = p->dicPos;
++    SizeT dicBufSize = p->dicBufSize;
++    unsigned len = p->remainLen;
++    UInt32 rep0 = p->reps[0];
++    if (limit - dicPos < len)
++      len = (unsigned)(limit - dicPos);
++
++    if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len)
++      p->checkDicSize = p->prop.dicSize;
++
++    p->processedPos += len;
++    p->remainLen -= len;
++    while (len-- != 0)
++    {
++      dic[dicPos] = dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)];
++      dicPos++;
++    }
++    p->dicPos = dicPos;
++  }
++}
++
++static int MY_FAST_CALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit)
++{
++  do
++  {
++    SizeT limit2 = limit;
++    if (p->checkDicSize == 0)
++    {
++      UInt32 rem = p->prop.dicSize - p->processedPos;
++      if (limit - p->dicPos > rem)
++        limit2 = p->dicPos + rem;
++    }
++    RINOK(LzmaDec_DecodeReal(p, limit2, bufLimit));
++    if (p->processedPos >= p->prop.dicSize)
++      p->checkDicSize = p->prop.dicSize;
++    LzmaDec_WriteRem(p, limit);
++  }
++  while (p->dicPos < limit && p->buf < bufLimit && p->remainLen < kMatchSpecLenStart);
++
++  if (p->remainLen > kMatchSpecLenStart)
++  {
++    p->remainLen = kMatchSpecLenStart;
++  }
++  return 0;
++}
++
++typedef enum
++{
++  DUMMY_ERROR, /* unexpected end of input stream */
++  DUMMY_LIT,
++  DUMMY_MATCH,
++  DUMMY_REP
++} ELzmaDummy;
++
++static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inSize)
++{
++  UInt32 range = p->range;
++  UInt32 code = p->code;
++  const Byte *bufLimit = buf + inSize;
++  CLzmaProb *probs = p->probs;
++  unsigned state = p->state;
++  ELzmaDummy res;
++
++  {
++    CLzmaProb *prob;
++    UInt32 bound;
++    unsigned ttt;
++    unsigned posState = (p->processedPos) & ((1 << p->prop.pb) - 1);
++
++    prob = probs + IsMatch + (state << kNumPosBitsMax) + posState;
++    IF_BIT_0_CHECK(prob)
++    {
++      UPDATE_0_CHECK
++
++      /* if (bufLimit - buf >= 7) return DUMMY_LIT; */
++
++      prob = probs + Literal;
++      if (p->checkDicSize != 0 || p->processedPos != 0)
++        prob += (LZMA_LIT_SIZE *
++          ((((p->processedPos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) +
++          (p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc))));
++
++      if (state < kNumLitStates)
++      {
++        unsigned symbol = 1;
++        do { GET_BIT_CHECK(prob + symbol, symbol) } while (symbol < 0x100);
++      }
++      else
++      {
++        unsigned matchByte = p->dic[p->dicPos - p->reps[0] +
++            ((p->dicPos < p->reps[0]) ? p->dicBufSize : 0)];
++        unsigned offs = 0x100;
++        unsigned symbol = 1;
++        do
++        {
++          unsigned bit;
++          CLzmaProb *probLit;
++          matchByte <<= 1;
++          bit = (matchByte & offs);
++          probLit = prob + offs + bit + symbol;
++          GET_BIT2_CHECK(probLit, symbol, offs &= ~bit, offs &= bit)
++        }
++        while (symbol < 0x100);
++      }
++      res = DUMMY_LIT;
++    }
++    else
++    {
++      unsigned len;
++      UPDATE_1_CHECK;
++
++      prob = probs + IsRep + state;
++      IF_BIT_0_CHECK(prob)
++      {
++        UPDATE_0_CHECK;
++        state = 0;
++        prob = probs + LenCoder;
++        res = DUMMY_MATCH;
++      }
++      else
++      {
++        UPDATE_1_CHECK;
++        res = DUMMY_REP;
++        prob = probs + IsRepG0 + state;
++        IF_BIT_0_CHECK(prob)
++        {
++          UPDATE_0_CHECK;
++          prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState;
++          IF_BIT_0_CHECK(prob)
++          {
++            UPDATE_0_CHECK;
++            NORMALIZE_CHECK;
++            return DUMMY_REP;
++          }
++          else
++          {
++            UPDATE_1_CHECK;
++          }
++        }
++        else
++        {
++          UPDATE_1_CHECK;
++          prob = probs + IsRepG1 + state;
++          IF_BIT_0_CHECK(prob)
++          {
++            UPDATE_0_CHECK;
++          }
++          else
++          {
++            UPDATE_1_CHECK;
++            prob = probs + IsRepG2 + state;
++            IF_BIT_0_CHECK(prob)
++            {
++              UPDATE_0_CHECK;
++            }
++            else
++            {
++              UPDATE_1_CHECK;
++            }
++          }
++        }
++        state = kNumStates;
++        prob = probs + RepLenCoder;
++      }
++      {
++        unsigned limit, offset;
++        CLzmaProb *probLen = prob + LenChoice;
++        IF_BIT_0_CHECK(probLen)
++        {
++          UPDATE_0_CHECK;
++          probLen = prob + LenLow + (posState << kLenNumLowBits);
++          offset = 0;
++          limit = 1 << kLenNumLowBits;
++        }
++        else
++        {
++          UPDATE_1_CHECK;
++          probLen = prob + LenChoice2;
++          IF_BIT_0_CHECK(probLen)
++          {
++            UPDATE_0_CHECK;
++            probLen = prob + LenMid + (posState << kLenNumMidBits);
++            offset = kLenNumLowSymbols;
++            limit = 1 << kLenNumMidBits;
++          }
++          else
++          {
++            UPDATE_1_CHECK;
++            probLen = prob + LenHigh;
++            offset = kLenNumLowSymbols + kLenNumMidSymbols;
++            limit = 1 << kLenNumHighBits;
++          }
++        }
++        TREE_DECODE_CHECK(probLen, limit, len);
++        len += offset;
++      }
++
++      if (state < 4)
++      {
++        unsigned posSlot;
++        prob = probs + PosSlot +
++            ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) <<
++            kNumPosSlotBits);
++        TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot);
++        if (posSlot >= kStartPosModelIndex)
++        {
++          int numDirectBits = ((posSlot >> 1) - 1);
++
++          /* if (bufLimit - buf >= 8) return DUMMY_MATCH; */
++
++          if (posSlot < kEndPosModelIndex)
++          {
++            prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits) - posSlot - 1;
++          }
++          else
++          {
++            numDirectBits -= kNumAlignBits;
++            do
++            {
++              NORMALIZE_CHECK
++              range >>= 1;
++              code -= range & (((code - range) >> 31) - 1);
++              /* if (code >= range) code -= range; */
++            }
++            while (--numDirectBits != 0);
++            prob = probs + Align;
++            numDirectBits = kNumAlignBits;
++          }
++          {
++            unsigned i = 1;
++            do
++            {
++              GET_BIT_CHECK(prob + i, i);
++            }
++            while (--numDirectBits != 0);
++          }
++        }
++      }
++    }
++  }
++  NORMALIZE_CHECK;
++  return res;
++}
++
++
++static void LzmaDec_InitRc(CLzmaDec *p, const Byte *data)
++{
++  p->code = ((UInt32)data[1] << 24) | ((UInt32)data[2] << 16) | ((UInt32)data[3] << 8) | ((UInt32)data[4]);
++  p->range = 0xFFFFFFFF;
++  p->needFlush = 0;
++}
++
++static void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState)
++{
++  p->needFlush = 1;
++  p->remainLen = 0;
++  p->tempBufSize = 0;
++
++  if (initDic)
++  {
++    p->processedPos = 0;
++    p->checkDicSize = 0;
++    p->needInitState = 1;
++  }
++  if (initState)
++    p->needInitState = 1;
++}
++
++void LzmaDec_Init(CLzmaDec *p)
++{
++  p->dicPos = 0;
++  LzmaDec_InitDicAndState(p, True, True);
++}
++
++static void LzmaDec_InitStateReal(CLzmaDec *p)
++{
++  UInt32 numProbs = Literal + ((UInt32)LZMA_LIT_SIZE << (p->prop.lc + p->prop.lp));
++  UInt32 i;
++  CLzmaProb *probs = p->probs;
++  for (i = 0; i < numProbs; i++)
++    probs[i] = kBitModelTotal >> 1;
++  p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1;
++  p->state = 0;
++  p->needInitState = 0;
++}
++
++SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen,
++    ELzmaFinishMode finishMode, ELzmaStatus *status)
++{
++  SizeT inSize = *srcLen;
++  (*srcLen) = 0;
++  LzmaDec_WriteRem(p, dicLimit);
++
++  *status = LZMA_STATUS_NOT_SPECIFIED;
++
++  while (p->remainLen != kMatchSpecLenStart)
++  {
++      int checkEndMarkNow;
++
++      if (p->needFlush != 0)
++      {
++        for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--)
++          p->tempBuf[p->tempBufSize++] = *src++;
++        if (p->tempBufSize < RC_INIT_SIZE)
++        {
++          *status = LZMA_STATUS_NEEDS_MORE_INPUT;
++          return SZ_OK;
++        }
++        if (p->tempBuf[0] != 0)
++          return SZ_ERROR_DATA;
++
++        LzmaDec_InitRc(p, p->tempBuf);
++        p->tempBufSize = 0;
++      }
++
++      checkEndMarkNow = 0;
++      if (p->dicPos >= dicLimit)
++      {
++        if (p->remainLen == 0 && p->code == 0)
++        {
++          *status = LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK;
++          return SZ_OK;
++        }
++        if (finishMode == LZMA_FINISH_ANY)
++        {
++          *status = LZMA_STATUS_NOT_FINISHED;
++          return SZ_OK;
++        }
++        if (p->remainLen != 0)
++        {
++          *status = LZMA_STATUS_NOT_FINISHED;
++          return SZ_ERROR_DATA;
++        }
++        checkEndMarkNow = 1;
++      }
++
++      if (p->needInitState)
++        LzmaDec_InitStateReal(p);
++
++      if (p->tempBufSize == 0)
++      {
++        SizeT processed;
++        const Byte *bufLimit;
++        if (inSize < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow)
++        {
++          int dummyRes = LzmaDec_TryDummy(p, src, inSize);
++          if (dummyRes == DUMMY_ERROR)
++          {
++            memcpy(p->tempBuf, src, inSize);
++            p->tempBufSize = (unsigned)inSize;
++            (*srcLen) += inSize;
++            *status = LZMA_STATUS_NEEDS_MORE_INPUT;
++            return SZ_OK;
++          }
++          if (checkEndMarkNow && dummyRes != DUMMY_MATCH)
++          {
++            *status = LZMA_STATUS_NOT_FINISHED;
++            return SZ_ERROR_DATA;
++          }
++          bufLimit = src;
++        }
++        else
++          bufLimit = src + inSize - LZMA_REQUIRED_INPUT_MAX;
++        p->buf = src;
++        if (LzmaDec_DecodeReal2(p, dicLimit, bufLimit) != 0)
++          return SZ_ERROR_DATA;
++        processed = (SizeT)(p->buf - src);
++        (*srcLen) += processed;
++        src += processed;
++        inSize -= processed;
++      }
++      else
++      {
++        unsigned rem = p->tempBufSize, lookAhead = 0;
++        while (rem < LZMA_REQUIRED_INPUT_MAX && lookAhead < inSize)
++          p->tempBuf[rem++] = src[lookAhead++];
++        p->tempBufSize = rem;
++        if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow)
++        {
++          int dummyRes = LzmaDec_TryDummy(p, p->tempBuf, rem);
++          if (dummyRes == DUMMY_ERROR)
++          {
++            (*srcLen) += lookAhead;
++            *status = LZMA_STATUS_NEEDS_MORE_INPUT;
++            return SZ_OK;
++          }
++          if (checkEndMarkNow && dummyRes != DUMMY_MATCH)
++          {
++            *status = LZMA_STATUS_NOT_FINISHED;
++            return SZ_ERROR_DATA;
++          }
++        }
++        p->buf = p->tempBuf;
++        if (LzmaDec_DecodeReal2(p, dicLimit, p->buf) != 0)
++          return SZ_ERROR_DATA;
++        lookAhead -= (rem - (unsigned)(p->buf - p->tempBuf));
++        (*srcLen) += lookAhead;
++        src += lookAhead;
++        inSize -= lookAhead;
++        p->tempBufSize = 0;
++      }
++  }
++  if (p->code == 0)
++    *status = LZMA_STATUS_FINISHED_WITH_MARK;
++  return (p->code == 0) ? SZ_OK : SZ_ERROR_DATA;
++}
++
++SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status)
++{
++  SizeT outSize = *destLen;
++  SizeT inSize = *srcLen;
++  *srcLen = *destLen = 0;
++  for (;;)
++  {
++    SizeT inSizeCur = inSize, outSizeCur, dicPos;
++    ELzmaFinishMode curFinishMode;
++    SRes res;
++    if (p->dicPos == p->dicBufSize)
++      p->dicPos = 0;
++    dicPos = p->dicPos;
++    if (outSize > p->dicBufSize - dicPos)
++    {
++      outSizeCur = p->dicBufSize;
++      curFinishMode = LZMA_FINISH_ANY;
++    }
++    else
++    {
++      outSizeCur = dicPos + outSize;
++      curFinishMode = finishMode;
++    }
++
++    res = LzmaDec_DecodeToDic(p, outSizeCur, src, &inSizeCur, curFinishMode, status);
++    src += inSizeCur;
++    inSize -= inSizeCur;
++    *srcLen += inSizeCur;
++    outSizeCur = p->dicPos - dicPos;
++    memcpy(dest, p->dic + dicPos, outSizeCur);
++    dest += outSizeCur;
++    outSize -= outSizeCur;
++    *destLen += outSizeCur;
++    if (res != 0)
++      return res;
++    if (outSizeCur == 0 || outSize == 0)
++      return SZ_OK;
++  }
++}
++
++void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc)
++{
++  alloc->Free(alloc, p->probs);
++  p->probs = 0;
++}
++
++static void LzmaDec_FreeDict(CLzmaDec *p, ISzAlloc *alloc)
++{
++  alloc->Free(alloc, p->dic);
++  p->dic = 0;
++}
++
++void LzmaDec_Free(CLzmaDec *p, ISzAlloc *alloc)
++{
++  LzmaDec_FreeProbs(p, alloc);
++  LzmaDec_FreeDict(p, alloc);
++}
++
++SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size)
++{
++  UInt32 dicSize;
++  Byte d;
++
++  if (size < LZMA_PROPS_SIZE)
++    return SZ_ERROR_UNSUPPORTED;
++  else
++    dicSize = data[1] | ((UInt32)data[2] << 8) | ((UInt32)data[3] << 16) | ((UInt32)data[4] << 24);
++
++  if (dicSize < LZMA_DIC_MIN)
++    dicSize = LZMA_DIC_MIN;
++  p->dicSize = dicSize;
++
++  d = data[0];
++  if (d >= (9 * 5 * 5))
++    return SZ_ERROR_UNSUPPORTED;
++
++  p->lc = d % 9;
++  d /= 9;
++  p->pb = d / 5;
++  p->lp = d % 5;
++
++  return SZ_OK;
++}
++
++static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAlloc *alloc)
++{
++  UInt32 numProbs = LzmaProps_GetNumProbs(propNew);
++  if (p->probs == 0 || numProbs != p->numProbs)
++  {
++    LzmaDec_FreeProbs(p, alloc);
++    p->probs = (CLzmaProb *)alloc->Alloc(alloc, numProbs * sizeof(CLzmaProb));
++    p->numProbs = numProbs;
++    if (p->probs == 0)
++      return SZ_ERROR_MEM;
++  }
++  return SZ_OK;
++}
++
++SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc)
++{
++  CLzmaProps propNew;
++  RINOK(LzmaProps_Decode(&propNew, props, propsSize));
++  RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc));
++  p->prop = propNew;
++  return SZ_OK;
++}
++
++SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc)
++{
++  CLzmaProps propNew;
++  SizeT dicBufSize;
++  RINOK(LzmaProps_Decode(&propNew, props, propsSize));
++  RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc));
++  dicBufSize = propNew.dicSize;
++  if (p->dic == 0 || dicBufSize != p->dicBufSize)
++  {
++    LzmaDec_FreeDict(p, alloc);
++    p->dic = (Byte *)alloc->Alloc(alloc, dicBufSize);
++    if (p->dic == 0)
++    {
++      LzmaDec_FreeProbs(p, alloc);
++      return SZ_ERROR_MEM;
++    }
++  }
++  p->dicBufSize = dicBufSize;
++  p->prop = propNew;
++  return SZ_OK;
++}
++
++SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen,
++    const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode,
++    ELzmaStatus *status, ISzAlloc *alloc)
++{
++  CLzmaDec p;
++  SRes res;
++  SizeT inSize = *srcLen;
++  SizeT outSize = *destLen;
++  *srcLen = *destLen = 0;
++  if (inSize < RC_INIT_SIZE)
++    return SZ_ERROR_INPUT_EOF;
++
++  LzmaDec_Construct(&p);
++  res = LzmaDec_AllocateProbs(&p, propData, propSize, alloc);
++  if (res != 0)
++    return res;
++  p.dic = dest;
++  p.dicBufSize = outSize;
++
++  LzmaDec_Init(&p);
++
++  *srcLen = inSize;
++  res = LzmaDec_DecodeToDic(&p, outSize, src, srcLen, finishMode, status);
++
++  if (res == SZ_OK && *status == LZMA_STATUS_NEEDS_MORE_INPUT)
++    res = SZ_ERROR_INPUT_EOF;
++
++  (*destLen) = p.dicPos;
++  LzmaDec_FreeProbs(&p, alloc);
++  return res;
++}
+--- /dev/null
++++ b/lib/lzma/LzmaEnc.c
+@@ -0,0 +1,2271 @@
++/* LzmaEnc.c -- LZMA Encoder
++2009-11-24 : Igor Pavlov : Public domain */
++
++#include <string.h>
++
++/* #define SHOW_STAT */
++/* #define SHOW_STAT2 */
++
++#if defined(SHOW_STAT) || defined(SHOW_STAT2)
++#include <stdio.h>
++#endif
++
++#include "LzmaEnc.h"
++
++/* disable MT */
++#define _7ZIP_ST
++
++#include "LzFind.h"
++#ifndef _7ZIP_ST
++#include "LzFindMt.h"
++#endif
++
++#ifdef SHOW_STAT
++static int ttt = 0;
++#endif
++
++#define kBlockSizeMax ((1 << LZMA_NUM_BLOCK_SIZE_BITS) - 1)
++
++#define kBlockSize (9 << 10)
++#define kUnpackBlockSize (1 << 18)
++#define kMatchArraySize (1 << 21)
++#define kMatchRecordMaxSize ((LZMA_MATCH_LEN_MAX * 2 + 3) * LZMA_MATCH_LEN_MAX)
++
++#define kNumMaxDirectBits (31)
++
++#define kNumTopBits 24
++#define kTopValue ((UInt32)1 << kNumTopBits)
++
++#define kNumBitModelTotalBits 11
++#define kBitModelTotal (1 << kNumBitModelTotalBits)
++#define kNumMoveBits 5
++#define kProbInitValue (kBitModelTotal >> 1)
++
++#define kNumMoveReducingBits 4
++#define kNumBitPriceShiftBits 4
++#define kBitPrice (1 << kNumBitPriceShiftBits)
++
++void LzmaEncProps_Init(CLzmaEncProps *p)
++{
++  p->level = 5;
++  p->dictSize = p->mc = 0;
++  p->lc = p->lp = p->pb = p->algo = p->fb = p->btMode = p->numHashBytes = p->numThreads = -1;
++  p->writeEndMark = 0;
++}
++
++void LzmaEncProps_Normalize(CLzmaEncProps *p)
++{
++  int level = p->level;
++  if (level < 0) level = 5;
++  p->level = level;
++  if (p->dictSize == 0) p->dictSize = (level <= 5 ? (1 << (level * 2 + 14)) : (level == 6 ? (1 << 25) : (1 << 26)));
++  if (p->lc < 0) p->lc = 3;
++  if (p->lp < 0) p->lp = 0;
++  if (p->pb < 0) p->pb = 2;
++  if (p->algo < 0) p->algo = (level < 5 ? 0 : 1);
++  if (p->fb < 0) p->fb = (level < 7 ? 32 : 64);
++  if (p->btMode < 0) p->btMode = (p->algo == 0 ? 0 : 1);
++  if (p->numHashBytes < 0) p->numHashBytes = 4;
++  if (p->mc == 0)  p->mc = (16 + (p->fb >> 1)) >> (p->btMode ? 0 : 1);
++  if (p->numThreads < 0)
++    p->numThreads =
++      #ifndef _7ZIP_ST
++      ((p->btMode && p->algo) ? 2 : 1);
++      #else
++      1;
++      #endif
++}
++
++UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2)
++{
++  CLzmaEncProps props = *props2;
++  LzmaEncProps_Normalize(&props);
++  return props.dictSize;
++}
++
++/* #define LZMA_LOG_BSR */
++/* Define it for Intel's CPU */
++
++
++#ifdef LZMA_LOG_BSR
++
++#define kDicLogSizeMaxCompress 30
++
++#define BSR2_RET(pos, res) { unsigned long i; _BitScanReverse(&i, (pos)); res = (i + i) + ((pos >> (i - 1)) & 1); }
++
++UInt32 GetPosSlot1(UInt32 pos)
++{
++  UInt32 res;
++  BSR2_RET(pos, res);
++  return res;
++}
++#define GetPosSlot2(pos, res) { BSR2_RET(pos, res); }
++#define GetPosSlot(pos, res) { if (pos < 2) res = pos; else BSR2_RET(pos, res); }
++
++#else
++
++#define kNumLogBits (9 + (int)sizeof(size_t) / 2)
++#define kDicLogSizeMaxCompress ((kNumLogBits - 1) * 2 + 7)
++
++static void LzmaEnc_FastPosInit(Byte *g_FastPos)
++{
++  int c = 2, slotFast;
++  g_FastPos[0] = 0;
++  g_FastPos[1] = 1;
++
++  for (slotFast = 2; slotFast < kNumLogBits * 2; slotFast++)
++  {
++    UInt32 k = (1 << ((slotFast >> 1) - 1));
++    UInt32 j;
++    for (j = 0; j < k; j++, c++)
++      g_FastPos[c] = (Byte)slotFast;
++  }
++}
++
++#define BSR2_RET(pos, res) { UInt32 i = 6 + ((kNumLogBits - 1) & \
++  (0 - (((((UInt32)1 << (kNumLogBits + 6)) - 1) - pos) >> 31))); \
++  res = p->g_FastPos[pos >> i] + (i * 2); }
++/*
++#define BSR2_RET(pos, res) { res = (pos < (1 << (kNumLogBits + 6))) ? \
++  p->g_FastPos[pos >> 6] + 12 : \
++  p->g_FastPos[pos >> (6 + kNumLogBits - 1)] + (6 + (kNumLogBits - 1)) * 2; }
++*/
++
++#define GetPosSlot1(pos) p->g_FastPos[pos]
++#define GetPosSlot2(pos, res) { BSR2_RET(pos, res); }
++#define GetPosSlot(pos, res) { if (pos < kNumFullDistances) res = p->g_FastPos[pos]; else BSR2_RET(pos, res); }
++
++#endif
++
++
++#define LZMA_NUM_REPS 4
++
++typedef unsigned CState;
++
++typedef struct
++{
++  UInt32 price;
++
++  CState state;
++  int prev1IsChar;
++  int prev2;
++
++  UInt32 posPrev2;
++  UInt32 backPrev2;
++
++  UInt32 posPrev;
++  UInt32 backPrev;
++  UInt32 backs[LZMA_NUM_REPS];
++} COptimal;
++
++#define kNumOpts (1 << 12)
++
++#define kNumLenToPosStates 4
++#define kNumPosSlotBits 6
++#define kDicLogSizeMin 0
++#define kDicLogSizeMax 32
++#define kDistTableSizeMax (kDicLogSizeMax * 2)
++
++
++#define kNumAlignBits 4
++#define kAlignTableSize (1 << kNumAlignBits)
++#define kAlignMask (kAlignTableSize - 1)
++
++#define kStartPosModelIndex 4
++#define kEndPosModelIndex 14
++#define kNumPosModels (kEndPosModelIndex - kStartPosModelIndex)
++
++#define kNumFullDistances (1 << (kEndPosModelIndex >> 1))
++
++#ifdef _LZMA_PROB32
++#define CLzmaProb UInt32
++#else
++#define CLzmaProb UInt16
++#endif
++
++#define LZMA_PB_MAX 4
++#define LZMA_LC_MAX 8
++#define LZMA_LP_MAX 4
++
++#define LZMA_NUM_PB_STATES_MAX (1 << LZMA_PB_MAX)
++
++
++#define kLenNumLowBits 3
++#define kLenNumLowSymbols (1 << kLenNumLowBits)
++#define kLenNumMidBits 3
++#define kLenNumMidSymbols (1 << kLenNumMidBits)
++#define kLenNumHighBits 8
++#define kLenNumHighSymbols (1 << kLenNumHighBits)
++
++#define kLenNumSymbolsTotal (kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols)
++
++#define LZMA_MATCH_LEN_MIN 2
++#define LZMA_MATCH_LEN_MAX (LZMA_MATCH_LEN_MIN + kLenNumSymbolsTotal - 1)
++
++#define kNumStates 12
++
++typedef struct
++{
++  CLzmaProb choice;
++  CLzmaProb choice2;
++  CLzmaProb low[LZMA_NUM_PB_STATES_MAX << kLenNumLowBits];
++  CLzmaProb mid[LZMA_NUM_PB_STATES_MAX << kLenNumMidBits];
++  CLzmaProb high[kLenNumHighSymbols];
++} CLenEnc;
++
++typedef struct
++{
++  CLenEnc p;
++  UInt32 prices[LZMA_NUM_PB_STATES_MAX][kLenNumSymbolsTotal];
++  UInt32 tableSize;
++  UInt32 counters[LZMA_NUM_PB_STATES_MAX];
++} CLenPriceEnc;
++
++typedef struct
++{
++  UInt32 range;
++  Byte cache;
++  UInt64 low;
++  UInt64 cacheSize;
++  Byte *buf;
++  Byte *bufLim;
++  Byte *bufBase;
++  ISeqOutStream *outStream;
++  UInt64 processed;
++  SRes res;
++} CRangeEnc;
++
++typedef struct
++{
++  CLzmaProb *litProbs;
++
++  CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX];
++  CLzmaProb isRep[kNumStates];
++  CLzmaProb isRepG0[kNumStates];
++  CLzmaProb isRepG1[kNumStates];
++  CLzmaProb isRepG2[kNumStates];
++  CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX];
++
++  CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits];
++  CLzmaProb posEncoders[kNumFullDistances - kEndPosModelIndex];
++  CLzmaProb posAlignEncoder[1 << kNumAlignBits];
++
++  CLenPriceEnc lenEnc;
++  CLenPriceEnc repLenEnc;
++
++  UInt32 reps[LZMA_NUM_REPS];
++  UInt32 state;
++} CSaveState;
++
++typedef struct
++{
++  IMatchFinder matchFinder;
++  void *matchFinderObj;
++
++  #ifndef _7ZIP_ST
++  Bool mtMode;
++  CMatchFinderMt matchFinderMt;
++  #endif
++
++  CMatchFinder matchFinderBase;
++
++  #ifndef _7ZIP_ST
++  Byte pad[128];
++  #endif
++
++  UInt32 optimumEndIndex;
++  UInt32 optimumCurrentIndex;
++
++  UInt32 longestMatchLength;
++  UInt32 numPairs;
++  UInt32 numAvail;
++  COptimal opt[kNumOpts];
++
++  #ifndef LZMA_LOG_BSR
++  Byte g_FastPos[1 << kNumLogBits];
++  #endif
++
++  UInt32 ProbPrices[kBitModelTotal >> kNumMoveReducingBits];
++  UInt32 matches[LZMA_MATCH_LEN_MAX * 2 + 2 + 1];
++  UInt32 numFastBytes;
++  UInt32 additionalOffset;
++  UInt32 reps[LZMA_NUM_REPS];
++  UInt32 state;
++
++  UInt32 posSlotPrices[kNumLenToPosStates][kDistTableSizeMax];
++  UInt32 distancesPrices[kNumLenToPosStates][kNumFullDistances];
++  UInt32 alignPrices[kAlignTableSize];
++  UInt32 alignPriceCount;
++
++  UInt32 distTableSize;
++
++  unsigned lc, lp, pb;
++  unsigned lpMask, pbMask;
++
++  CLzmaProb *litProbs;
++
++  CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX];
++  CLzmaProb isRep[kNumStates];
++  CLzmaProb isRepG0[kNumStates];
++  CLzmaProb isRepG1[kNumStates];
++  CLzmaProb isRepG2[kNumStates];
++  CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX];
++
++  CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits];
++  CLzmaProb posEncoders[kNumFullDistances - kEndPosModelIndex];
++  CLzmaProb posAlignEncoder[1 << kNumAlignBits];
++
++  CLenPriceEnc lenEnc;
++  CLenPriceEnc repLenEnc;
++
++  unsigned lclp;
++
++  Bool fastMode;
++
++  CRangeEnc rc;
++
++  Bool writeEndMark;
++  UInt64 nowPos64;
++  UInt32 matchPriceCount;
++  Bool finished;
++  Bool multiThread;
++
++  SRes result;
++  UInt32 dictSize;
++  UInt32 matchFinderCycles;
++
++  int needInit;
++
++  CSaveState saveState;
++} CLzmaEnc;
++
++/*void LzmaEnc_SaveState(CLzmaEncHandle pp)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  CSaveState *dest = &p->saveState;
++  int i;
++  dest->lenEnc = p->lenEnc;
++  dest->repLenEnc = p->repLenEnc;
++  dest->state = p->state;
++
++  for (i = 0; i < kNumStates; i++)
++  {
++    memcpy(dest->isMatch[i], p->isMatch[i], sizeof(p->isMatch[i]));
++    memcpy(dest->isRep0Long[i], p->isRep0Long[i], sizeof(p->isRep0Long[i]));
++  }
++  for (i = 0; i < kNumLenToPosStates; i++)
++    memcpy(dest->posSlotEncoder[i], p->posSlotEncoder[i], sizeof(p->posSlotEncoder[i]));
++  memcpy(dest->isRep, p->isRep, sizeof(p->isRep));
++  memcpy(dest->isRepG0, p->isRepG0, sizeof(p->isRepG0));
++  memcpy(dest->isRepG1, p->isRepG1, sizeof(p->isRepG1));
++  memcpy(dest->isRepG2, p->isRepG2, sizeof(p->isRepG2));
++  memcpy(dest->posEncoders, p->posEncoders, sizeof(p->posEncoders));
++  memcpy(dest->posAlignEncoder, p->posAlignEncoder, sizeof(p->posAlignEncoder));
++  memcpy(dest->reps, p->reps, sizeof(p->reps));
++  memcpy(dest->litProbs, p->litProbs, (0x300 << p->lclp) * sizeof(CLzmaProb));
++}*/
++
++/*void LzmaEnc_RestoreState(CLzmaEncHandle pp)
++{
++  CLzmaEnc *dest = (CLzmaEnc *)pp;
++  const CSaveState *p = &dest->saveState;
++  int i;
++  dest->lenEnc = p->lenEnc;
++  dest->repLenEnc = p->repLenEnc;
++  dest->state = p->state;
++
++  for (i = 0; i < kNumStates; i++)
++  {
++    memcpy(dest->isMatch[i], p->isMatch[i], sizeof(p->isMatch[i]));
++    memcpy(dest->isRep0Long[i], p->isRep0Long[i], sizeof(p->isRep0Long[i]));
++  }
++  for (i = 0; i < kNumLenToPosStates; i++)
++    memcpy(dest->posSlotEncoder[i], p->posSlotEncoder[i], sizeof(p->posSlotEncoder[i]));
++  memcpy(dest->isRep, p->isRep, sizeof(p->isRep));
++  memcpy(dest->isRepG0, p->isRepG0, sizeof(p->isRepG0));
++  memcpy(dest->isRepG1, p->isRepG1, sizeof(p->isRepG1));
++  memcpy(dest->isRepG2, p->isRepG2, sizeof(p->isRepG2));
++  memcpy(dest->posEncoders, p->posEncoders, sizeof(p->posEncoders));
++  memcpy(dest->posAlignEncoder, p->posAlignEncoder, sizeof(p->posAlignEncoder));
++  memcpy(dest->reps, p->reps, sizeof(p->reps));
++  memcpy(dest->litProbs, p->litProbs, (0x300 << dest->lclp) * sizeof(CLzmaProb));
++}*/
++
++SRes LzmaEnc_SetProps(CLzmaEncHandle pp, const CLzmaEncProps *props2)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  CLzmaEncProps props = *props2;
++  LzmaEncProps_Normalize(&props);
++
++  if (props.lc > LZMA_LC_MAX || props.lp > LZMA_LP_MAX || props.pb > LZMA_PB_MAX ||
++      props.dictSize > (1 << kDicLogSizeMaxCompress) || props.dictSize > (1 << 30))
++    return SZ_ERROR_PARAM;
++  p->dictSize = props.dictSize;
++  p->matchFinderCycles = props.mc;
++  {
++    unsigned fb = props.fb;
++    if (fb < 5)
++      fb = 5;
++    if (fb > LZMA_MATCH_LEN_MAX)
++      fb = LZMA_MATCH_LEN_MAX;
++    p->numFastBytes = fb;
++  }
++  p->lc = props.lc;
++  p->lp = props.lp;
++  p->pb = props.pb;
++  p->fastMode = (props.algo == 0);
++  p->matchFinderBase.btMode = props.btMode;
++  {
++    UInt32 numHashBytes = 4;
++    if (props.btMode)
++    {
++      if (props.numHashBytes < 2)
++        numHashBytes = 2;
++      else if (props.numHashBytes < 4)
++        numHashBytes = props.numHashBytes;
++    }
++    p->matchFinderBase.numHashBytes = numHashBytes;
++  }
++
++  p->matchFinderBase.cutValue = props.mc;
++
++  p->writeEndMark = props.writeEndMark;
++
++  #ifndef _7ZIP_ST
++  /*
++  if (newMultiThread != _multiThread)
++  {
++    ReleaseMatchFinder();
++    _multiThread = newMultiThread;
++  }
++  */
++  p->multiThread = (props.numThreads > 1);
++  #endif
++
++  return SZ_OK;
++}
++
++static const int kLiteralNextStates[kNumStates] = {0, 0, 0, 0, 1, 2, 3, 4,  5,  6,   4, 5};
++static const int kMatchNextStates[kNumStates]   = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10};
++static const int kRepNextStates[kNumStates]     = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11};
++static const int kShortRepNextStates[kNumStates]= {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11};
++
++#define IsCharState(s) ((s) < 7)
++
++#define GetLenToPosState(len) (((len) < kNumLenToPosStates + 1) ? (len) - 2 : kNumLenToPosStates - 1)
++
++#define kInfinityPrice (1 << 30)
++
++static void RangeEnc_Construct(CRangeEnc *p)
++{
++  p->outStream = 0;
++  p->bufBase = 0;
++}
++
++#define RangeEnc_GetProcessed(p) ((p)->processed + ((p)->buf - (p)->bufBase) + (p)->cacheSize)
++
++#define RC_BUF_SIZE (1 << 16)
++static int RangeEnc_Alloc(CRangeEnc *p, ISzAlloc *alloc)
++{
++  if (p->bufBase == 0)
++  {
++    p->bufBase = (Byte *)alloc->Alloc(alloc, RC_BUF_SIZE);
++    if (p->bufBase == 0)
++      return 0;
++    p->bufLim = p->bufBase + RC_BUF_SIZE;
++  }
++  return 1;
++}
++
++static void RangeEnc_Free(CRangeEnc *p, ISzAlloc *alloc)
++{
++  alloc->Free(alloc, p->bufBase);
++  p->bufBase = 0;
++}
++
++static void RangeEnc_Init(CRangeEnc *p)
++{
++  /* Stream.Init(); */
++  p->low = 0;
++  p->range = 0xFFFFFFFF;
++  p->cacheSize = 1;
++  p->cache = 0;
++
++  p->buf = p->bufBase;
++
++  p->processed = 0;
++  p->res = SZ_OK;
++}
++
++static void RangeEnc_FlushStream(CRangeEnc *p)
++{
++  size_t num;
++  if (p->res != SZ_OK)
++    return;
++  num = p->buf - p->bufBase;
++  if (num != p->outStream->Write(p->outStream, p->bufBase, num))
++    p->res = SZ_ERROR_WRITE;
++  p->processed += num;
++  p->buf = p->bufBase;
++}
++
++static void MY_FAST_CALL RangeEnc_ShiftLow(CRangeEnc *p)
++{
++  if ((UInt32)p->low < (UInt32)0xFF000000 || (int)(p->low >> 32) != 0)
++  {
++    Byte temp = p->cache;
++    do
++    {
++      Byte *buf = p->buf;
++      *buf++ = (Byte)(temp + (Byte)(p->low >> 32));
++      p->buf = buf;
++      if (buf == p->bufLim)
++        RangeEnc_FlushStream(p);
++      temp = 0xFF;
++    }
++    while (--p->cacheSize != 0);
++    p->cache = (Byte)((UInt32)p->low >> 24);
++  }
++  p->cacheSize++;
++  p->low = (UInt32)p->low << 8;
++}
++
++static void RangeEnc_FlushData(CRangeEnc *p)
++{
++  int i;
++  for (i = 0; i < 5; i++)
++    RangeEnc_ShiftLow(p);
++}
++
++static void RangeEnc_EncodeDirectBits(CRangeEnc *p, UInt32 value, int numBits)
++{
++  do
++  {
++    p->range >>= 1;
++    p->low += p->range & (0 - ((value >> --numBits) & 1));
++    if (p->range < kTopValue)
++    {
++      p->range <<= 8;
++      RangeEnc_ShiftLow(p);
++    }
++  }
++  while (numBits != 0);
++}
++
++static void RangeEnc_EncodeBit(CRangeEnc *p, CLzmaProb *prob, UInt32 symbol)
++{
++  UInt32 ttt = *prob;
++  UInt32 newBound = (p->range >> kNumBitModelTotalBits) * ttt;
++  if (symbol == 0)
++  {
++    p->range = newBound;
++    ttt += (kBitModelTotal - ttt) >> kNumMoveBits;
++  }
++  else
++  {
++    p->low += newBound;
++    p->range -= newBound;
++    ttt -= ttt >> kNumMoveBits;
++  }
++  *prob = (CLzmaProb)ttt;
++  if (p->range < kTopValue)
++  {
++    p->range <<= 8;
++    RangeEnc_ShiftLow(p);
++  }
++}
++
++static void LitEnc_Encode(CRangeEnc *p, CLzmaProb *probs, UInt32 symbol)
++{
++  symbol |= 0x100;
++  do
++  {
++    RangeEnc_EncodeBit(p, probs + (symbol >> 8), (symbol >> 7) & 1);
++    symbol <<= 1;
++  }
++  while (symbol < 0x10000);
++}
++
++static void LitEnc_EncodeMatched(CRangeEnc *p, CLzmaProb *probs, UInt32 symbol, UInt32 matchByte)
++{
++  UInt32 offs = 0x100;
++  symbol |= 0x100;
++  do
++  {
++    matchByte <<= 1;
++    RangeEnc_EncodeBit(p, probs + (offs + (matchByte & offs) + (symbol >> 8)), (symbol >> 7) & 1);
++    symbol <<= 1;
++    offs &= ~(matchByte ^ symbol);
++  }
++  while (symbol < 0x10000);
++}
++
++static void LzmaEnc_InitPriceTables(UInt32 *ProbPrices)
++{
++  UInt32 i;
++  for (i = (1 << kNumMoveReducingBits) / 2; i < kBitModelTotal; i += (1 << kNumMoveReducingBits))
++  {
++    const int kCyclesBits = kNumBitPriceShiftBits;
++    UInt32 w = i;
++    UInt32 bitCount = 0;
++    int j;
++    for (j = 0; j < kCyclesBits; j++)
++    {
++      w = w * w;
++      bitCount <<= 1;
++      while (w >= ((UInt32)1 << 16))
++      {
++        w >>= 1;
++        bitCount++;
++      }
++    }
++    ProbPrices[i >> kNumMoveReducingBits] = ((kNumBitModelTotalBits << kCyclesBits) - 15 - bitCount);
++  }
++}
++
++
++#define GET_PRICE(prob, symbol) \
++  p->ProbPrices[((prob) ^ (((-(int)(symbol))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits];
++
++#define GET_PRICEa(prob, symbol) \
++  ProbPrices[((prob) ^ ((-((int)(symbol))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits];
++
++#define GET_PRICE_0(prob) p->ProbPrices[(prob) >> kNumMoveReducingBits]
++#define GET_PRICE_1(prob) p->ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits]
++
++#define GET_PRICE_0a(prob) ProbPrices[(prob) >> kNumMoveReducingBits]
++#define GET_PRICE_1a(prob) ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits]
++
++static UInt32 LitEnc_GetPrice(const CLzmaProb *probs, UInt32 symbol, UInt32 *ProbPrices)
++{
++  UInt32 price = 0;
++  symbol |= 0x100;
++  do
++  {
++    price += GET_PRICEa(probs[symbol >> 8], (symbol >> 7) & 1);
++    symbol <<= 1;
++  }
++  while (symbol < 0x10000);
++  return price;
++}
++
++static UInt32 LitEnc_GetPriceMatched(const CLzmaProb *probs, UInt32 symbol, UInt32 matchByte, UInt32 *ProbPrices)
++{
++  UInt32 price = 0;
++  UInt32 offs = 0x100;
++  symbol |= 0x100;
++  do
++  {
++    matchByte <<= 1;
++    price += GET_PRICEa(probs[offs + (matchByte & offs) + (symbol >> 8)], (symbol >> 7) & 1);
++    symbol <<= 1;
++    offs &= ~(matchByte ^ symbol);
++  }
++  while (symbol < 0x10000);
++  return price;
++}
++
++
++static void RcTree_Encode(CRangeEnc *rc, CLzmaProb *probs, int numBitLevels, UInt32 symbol)
++{
++  UInt32 m = 1;
++  int i;
++  for (i = numBitLevels; i != 0;)
++  {
++    UInt32 bit;
++    i--;
++    bit = (symbol >> i) & 1;
++    RangeEnc_EncodeBit(rc, probs + m, bit);
++    m = (m << 1) | bit;
++  }
++}
++
++static void RcTree_ReverseEncode(CRangeEnc *rc, CLzmaProb *probs, int numBitLevels, UInt32 symbol)
++{
++  UInt32 m = 1;
++  int i;
++  for (i = 0; i < numBitLevels; i++)
++  {
++    UInt32 bit = symbol & 1;
++    RangeEnc_EncodeBit(rc, probs + m, bit);
++    m = (m << 1) | bit;
++    symbol >>= 1;
++  }
++}
++
++static UInt32 RcTree_GetPrice(const CLzmaProb *probs, int numBitLevels, UInt32 symbol, UInt32 *ProbPrices)
++{
++  UInt32 price = 0;
++  symbol |= (1 << numBitLevels);
++  while (symbol != 1)
++  {
++    price += GET_PRICEa(probs[symbol >> 1], symbol & 1);
++    symbol >>= 1;
++  }
++  return price;
++}
++
++static UInt32 RcTree_ReverseGetPrice(const CLzmaProb *probs, int numBitLevels, UInt32 symbol, UInt32 *ProbPrices)
++{
++  UInt32 price = 0;
++  UInt32 m = 1;
++  int i;
++  for (i = numBitLevels; i != 0; i--)
++  {
++    UInt32 bit = symbol & 1;
++    symbol >>= 1;
++    price += GET_PRICEa(probs[m], bit);
++    m = (m << 1) | bit;
++  }
++  return price;
++}
++
++
++static void LenEnc_Init(CLenEnc *p)
++{
++  unsigned i;
++  p->choice = p->choice2 = kProbInitValue;
++  for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << kLenNumLowBits); i++)
++    p->low[i] = kProbInitValue;
++  for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << kLenNumMidBits); i++)
++    p->mid[i] = kProbInitValue;
++  for (i = 0; i < kLenNumHighSymbols; i++)
++    p->high[i] = kProbInitValue;
++}
++
++static void LenEnc_Encode(CLenEnc *p, CRangeEnc *rc, UInt32 symbol, UInt32 posState)
++{
++  if (symbol < kLenNumLowSymbols)
++  {
++    RangeEnc_EncodeBit(rc, &p->choice, 0);
++    RcTree_Encode(rc, p->low + (posState << kLenNumLowBits), kLenNumLowBits, symbol);
++  }
++  else
++  {
++    RangeEnc_EncodeBit(rc, &p->choice, 1);
++    if (symbol < kLenNumLowSymbols + kLenNumMidSymbols)
++    {
++      RangeEnc_EncodeBit(rc, &p->choice2, 0);
++      RcTree_Encode(rc, p->mid + (posState << kLenNumMidBits), kLenNumMidBits, symbol - kLenNumLowSymbols);
++    }
++    else
++    {
++      RangeEnc_EncodeBit(rc, &p->choice2, 1);
++      RcTree_Encode(rc, p->high, kLenNumHighBits, symbol - kLenNumLowSymbols - kLenNumMidSymbols);
++    }
++  }
++}
++
++static void LenEnc_SetPrices(CLenEnc *p, UInt32 posState, UInt32 numSymbols, UInt32 *prices, UInt32 *ProbPrices)
++{
++  UInt32 a0 = GET_PRICE_0a(p->choice);
++  UInt32 a1 = GET_PRICE_1a(p->choice);
++  UInt32 b0 = a1 + GET_PRICE_0a(p->choice2);
++  UInt32 b1 = a1 + GET_PRICE_1a(p->choice2);
++  UInt32 i = 0;
++  for (i = 0; i < kLenNumLowSymbols; i++)
++  {
++    if (i >= numSymbols)
++      return;
++    prices[i] = a0 + RcTree_GetPrice(p->low + (posState << kLenNumLowBits), kLenNumLowBits, i, ProbPrices);
++  }
++  for (; i < kLenNumLowSymbols + kLenNumMidSymbols; i++)
++  {
++    if (i >= numSymbols)
++      return;
++    prices[i] = b0 + RcTree_GetPrice(p->mid + (posState << kLenNumMidBits), kLenNumMidBits, i - kLenNumLowSymbols, ProbPrices);
++  }
++  for (; i < numSymbols; i++)
++    prices[i] = b1 + RcTree_GetPrice(p->high, kLenNumHighBits, i - kLenNumLowSymbols - kLenNumMidSymbols, ProbPrices);
++}
++
++static void MY_FAST_CALL LenPriceEnc_UpdateTable(CLenPriceEnc *p, UInt32 posState, UInt32 *ProbPrices)
++{
++  LenEnc_SetPrices(&p->p, posState, p->tableSize, p->prices[posState], ProbPrices);
++  p->counters[posState] = p->tableSize;
++}
++
++static void LenPriceEnc_UpdateTables(CLenPriceEnc *p, UInt32 numPosStates, UInt32 *ProbPrices)
++{
++  UInt32 posState;
++  for (posState = 0; posState < numPosStates; posState++)
++    LenPriceEnc_UpdateTable(p, posState, ProbPrices);
++}
++
++static void LenEnc_Encode2(CLenPriceEnc *p, CRangeEnc *rc, UInt32 symbol, UInt32 posState, Bool updatePrice, UInt32 *ProbPrices)
++{
++  LenEnc_Encode(&p->p, rc, symbol, posState);
++  if (updatePrice)
++    if (--p->counters[posState] == 0)
++      LenPriceEnc_UpdateTable(p, posState, ProbPrices);
++}
++
++
++
++
++static void MovePos(CLzmaEnc *p, UInt32 num)
++{
++  #ifdef SHOW_STAT
++  ttt += num;
++  printf("\n MovePos %d", num);
++  #endif
++  if (num != 0)
++  {
++    p->additionalOffset += num;
++    p->matchFinder.Skip(p->matchFinderObj, num);
++  }
++}
++
++static UInt32 ReadMatchDistances(CLzmaEnc *p, UInt32 *numDistancePairsRes)
++{
++  UInt32 lenRes = 0, numPairs;
++  p->numAvail = p->matchFinder.GetNumAvailableBytes(p->matchFinderObj);
++  numPairs = p->matchFinder.GetMatches(p->matchFinderObj, p->matches);
++  #ifdef SHOW_STAT
++  printf("\n i = %d numPairs = %d    ", ttt, numPairs / 2);
++  ttt++;
++  {
++    UInt32 i;
++    for (i = 0; i < numPairs; i += 2)
++      printf("%2d %6d   | ", p->matches[i], p->matches[i + 1]);
++  }
++  #endif
++  if (numPairs > 0)
++  {
++    lenRes = p->matches[numPairs - 2];
++    if (lenRes == p->numFastBytes)
++    {
++      const Byte *pby = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
++      UInt32 distance = p->matches[numPairs - 1] + 1;
++      UInt32 numAvail = p->numAvail;
++      if (numAvail > LZMA_MATCH_LEN_MAX)
++        numAvail = LZMA_MATCH_LEN_MAX;
++      {
++        const Byte *pby2 = pby - distance;
++        for (; lenRes < numAvail && pby[lenRes] == pby2[lenRes]; lenRes++);
++      }
++    }
++  }
++  p->additionalOffset++;
++  *numDistancePairsRes = numPairs;
++  return lenRes;
++}
++
++
++#define MakeAsChar(p) (p)->backPrev = (UInt32)(-1); (p)->prev1IsChar = False;
++#define MakeAsShortRep(p) (p)->backPrev = 0; (p)->prev1IsChar = False;
++#define IsShortRep(p) ((p)->backPrev == 0)
++
++static UInt32 GetRepLen1Price(CLzmaEnc *p, UInt32 state, UInt32 posState)
++{
++  return
++    GET_PRICE_0(p->isRepG0[state]) +
++    GET_PRICE_0(p->isRep0Long[state][posState]);
++}
++
++static UInt32 GetPureRepPrice(CLzmaEnc *p, UInt32 repIndex, UInt32 state, UInt32 posState)
++{
++  UInt32 price;
++  if (repIndex == 0)
++  {
++    price = GET_PRICE_0(p->isRepG0[state]);
++    price += GET_PRICE_1(p->isRep0Long[state][posState]);
++  }
++  else
++  {
++    price = GET_PRICE_1(p->isRepG0[state]);
++    if (repIndex == 1)
++      price += GET_PRICE_0(p->isRepG1[state]);
++    else
++    {
++      price += GET_PRICE_1(p->isRepG1[state]);
++      price += GET_PRICE(p->isRepG2[state], repIndex - 2);
++    }
++  }
++  return price;
++}
++
++static UInt32 GetRepPrice(CLzmaEnc *p, UInt32 repIndex, UInt32 len, UInt32 state, UInt32 posState)
++{
++  return p->repLenEnc.prices[posState][len - LZMA_MATCH_LEN_MIN] +
++    GetPureRepPrice(p, repIndex, state, posState);
++}
++
++static UInt32 Backward(CLzmaEnc *p, UInt32 *backRes, UInt32 cur)
++{
++  UInt32 posMem = p->opt[cur].posPrev;
++  UInt32 backMem = p->opt[cur].backPrev;
++  p->optimumEndIndex = cur;
++  do
++  {
++    if (p->opt[cur].prev1IsChar)
++    {
++      MakeAsChar(&p->opt[posMem])
++      p->opt[posMem].posPrev = posMem - 1;
++      if (p->opt[cur].prev2)
++      {
++        p->opt[posMem - 1].prev1IsChar = False;
++        p->opt[posMem - 1].posPrev = p->opt[cur].posPrev2;
++        p->opt[posMem - 1].backPrev = p->opt[cur].backPrev2;
++      }
++    }
++    {
++      UInt32 posPrev = posMem;
++      UInt32 backCur = backMem;
++
++      backMem = p->opt[posPrev].backPrev;
++      posMem = p->opt[posPrev].posPrev;
++
++      p->opt[posPrev].backPrev = backCur;
++      p->opt[posPrev].posPrev = cur;
++      cur = posPrev;
++    }
++  }
++  while (cur != 0);
++  *backRes = p->opt[0].backPrev;
++  p->optimumCurrentIndex  = p->opt[0].posPrev;
++  return p->optimumCurrentIndex;
++}
++
++#define LIT_PROBS(pos, prevByte) (p->litProbs + ((((pos) & p->lpMask) << p->lc) + ((prevByte) >> (8 - p->lc))) * 0x300)
++
++static UInt32 GetOptimum(CLzmaEnc *p, UInt32 position, UInt32 *backRes)
++{
++  UInt32 numAvail, mainLen, numPairs, repMaxIndex, i, posState, lenEnd, len, cur;
++  UInt32 matchPrice, repMatchPrice, normalMatchPrice;
++  UInt32 reps[LZMA_NUM_REPS], repLens[LZMA_NUM_REPS];
++  UInt32 *matches;
++  const Byte *data;
++  Byte curByte, matchByte;
++  if (p->optimumEndIndex != p->optimumCurrentIndex)
++  {
++    const COptimal *opt = &p->opt[p->optimumCurrentIndex];
++    UInt32 lenRes = opt->posPrev - p->optimumCurrentIndex;
++    *backRes = opt->backPrev;
++    p->optimumCurrentIndex = opt->posPrev;
++    return lenRes;
++  }
++  p->optimumCurrentIndex = p->optimumEndIndex = 0;
++
++  if (p->additionalOffset == 0)
++    mainLen = ReadMatchDistances(p, &numPairs);
++  else
++  {
++    mainLen = p->longestMatchLength;
++    numPairs = p->numPairs;
++  }
++
++  numAvail = p->numAvail;
++  if (numAvail < 2)
++  {
++    *backRes = (UInt32)(-1);
++    return 1;
++  }
++  if (numAvail > LZMA_MATCH_LEN_MAX)
++    numAvail = LZMA_MATCH_LEN_MAX;
++
++  data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
++  repMaxIndex = 0;
++  for (i = 0; i < LZMA_NUM_REPS; i++)
++  {
++    UInt32 lenTest;
++    const Byte *data2;
++    reps[i] = p->reps[i];
++    data2 = data - (reps[i] + 1);
++    if (data[0] != data2[0] || data[1] != data2[1])
++    {
++      repLens[i] = 0;
++      continue;
++    }
++    for (lenTest = 2; lenTest < numAvail && data[lenTest] == data2[lenTest]; lenTest++);
++    repLens[i] = lenTest;
++    if (lenTest > repLens[repMaxIndex])
++      repMaxIndex = i;
++  }
++  if (repLens[repMaxIndex] >= p->numFastBytes)
++  {
++    UInt32 lenRes;
++    *backRes = repMaxIndex;
++    lenRes = repLens[repMaxIndex];
++    MovePos(p, lenRes - 1);
++    return lenRes;
++  }
++
++  matches = p->matches;
++  if (mainLen >= p->numFastBytes)
++  {
++    *backRes = matches[numPairs - 1] + LZMA_NUM_REPS;
++    MovePos(p, mainLen - 1);
++    return mainLen;
++  }
++  curByte = *data;
++  matchByte = *(data - (reps[0] + 1));
++
++  if (mainLen < 2 && curByte != matchByte && repLens[repMaxIndex] < 2)
++  {
++    *backRes = (UInt32)-1;
++    return 1;
++  }
++
++  p->opt[0].state = (CState)p->state;
++
++  posState = (position & p->pbMask);
++
++  {
++    const CLzmaProb *probs = LIT_PROBS(position, *(data - 1));
++    p->opt[1].price = GET_PRICE_0(p->isMatch[p->state][posState]) +
++        (!IsCharState(p->state) ?
++          LitEnc_GetPriceMatched(probs, curByte, matchByte, p->ProbPrices) :
++          LitEnc_GetPrice(probs, curByte, p->ProbPrices));
++  }
++
++  MakeAsChar(&p->opt[1]);
++
++  matchPrice = GET_PRICE_1(p->isMatch[p->state][posState]);
++  repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[p->state]);
++
++  if (matchByte == curByte)
++  {
++    UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(p, p->state, posState);
++    if (shortRepPrice < p->opt[1].price)
++    {
++      p->opt[1].price = shortRepPrice;
++      MakeAsShortRep(&p->opt[1]);
++    }
++  }
++  lenEnd = ((mainLen >= repLens[repMaxIndex]) ? mainLen : repLens[repMaxIndex]);
++
++  if (lenEnd < 2)
++  {
++    *backRes = p->opt[1].backPrev;
++    return 1;
++  }
++
++  p->opt[1].posPrev = 0;
++  for (i = 0; i < LZMA_NUM_REPS; i++)
++    p->opt[0].backs[i] = reps[i];
++
++  len = lenEnd;
++  do
++    p->opt[len--].price = kInfinityPrice;
++  while (len >= 2);
++
++  for (i = 0; i < LZMA_NUM_REPS; i++)
++  {
++    UInt32 repLen = repLens[i];
++    UInt32 price;
++    if (repLen < 2)
++      continue;
++    price = repMatchPrice + GetPureRepPrice(p, i, p->state, posState);
++    do
++    {
++      UInt32 curAndLenPrice = price + p->repLenEnc.prices[posState][repLen - 2];
++      COptimal *opt = &p->opt[repLen];
++      if (curAndLenPrice < opt->price)
++      {
++        opt->price = curAndLenPrice;
++        opt->posPrev = 0;
++        opt->backPrev = i;
++        opt->prev1IsChar = False;
++      }
++    }
++    while (--repLen >= 2);
++  }
++
++  normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[p->state]);
++
++  len = ((repLens[0] >= 2) ? repLens[0] + 1 : 2);
++  if (len <= mainLen)
++  {
++    UInt32 offs = 0;
++    while (len > matches[offs])
++      offs += 2;
++    for (; ; len++)
++    {
++      COptimal *opt;
++      UInt32 distance = matches[offs + 1];
++
++      UInt32 curAndLenPrice = normalMatchPrice + p->lenEnc.prices[posState][len - LZMA_MATCH_LEN_MIN];
++      UInt32 lenToPosState = GetLenToPosState(len);
++      if (distance < kNumFullDistances)
++        curAndLenPrice += p->distancesPrices[lenToPosState][distance];
++      else
++      {
++        UInt32 slot;
++        GetPosSlot2(distance, slot);
++        curAndLenPrice += p->alignPrices[distance & kAlignMask] + p->posSlotPrices[lenToPosState][slot];
++      }
++      opt = &p->opt[len];
++      if (curAndLenPrice < opt->price)
++      {
++        opt->price = curAndLenPrice;
++        opt->posPrev = 0;
++        opt->backPrev = distance + LZMA_NUM_REPS;
++        opt->prev1IsChar = False;
++      }
++      if (len == matches[offs])
++      {
++        offs += 2;
++        if (offs == numPairs)
++          break;
++      }
++    }
++  }
++
++  cur = 0;
++
++    #ifdef SHOW_STAT2
++    if (position >= 0)
++    {
++      unsigned i;
++      printf("\n pos = %4X", position);
++      for (i = cur; i <= lenEnd; i++)
++      printf("\nprice[%4X] = %d", position - cur + i, p->opt[i].price);
++    }
++    #endif
++
++  for (;;)
++  {
++    UInt32 numAvailFull, newLen, numPairs, posPrev, state, posState, startLen;
++    UInt32 curPrice, curAnd1Price, matchPrice, repMatchPrice;
++    Bool nextIsChar;
++    Byte curByte, matchByte;
++    const Byte *data;
++    COptimal *curOpt;
++    COptimal *nextOpt;
++
++    cur++;
++    if (cur == lenEnd)
++      return Backward(p, backRes, cur);
++
++    newLen = ReadMatchDistances(p, &numPairs);
++    if (newLen >= p->numFastBytes)
++    {
++      p->numPairs = numPairs;
++      p->longestMatchLength = newLen;
++      return Backward(p, backRes, cur);
++    }
++    position++;
++    curOpt = &p->opt[cur];
++    posPrev = curOpt->posPrev;
++    if (curOpt->prev1IsChar)
++    {
++      posPrev--;
++      if (curOpt->prev2)
++      {
++        state = p->opt[curOpt->posPrev2].state;
++        if (curOpt->backPrev2 < LZMA_NUM_REPS)
++          state = kRepNextStates[state];
++        else
++          state = kMatchNextStates[state];
++      }
++      else
++        state = p->opt[posPrev].state;
++      state = kLiteralNextStates[state];
++    }
++    else
++      state = p->opt[posPrev].state;
++    if (posPrev == cur - 1)
++    {
++      if (IsShortRep(curOpt))
++        state = kShortRepNextStates[state];
++      else
++        state = kLiteralNextStates[state];
++    }
++    else
++    {
++      UInt32 pos;
++      const COptimal *prevOpt;
++      if (curOpt->prev1IsChar && curOpt->prev2)
++      {
++        posPrev = curOpt->posPrev2;
++        pos = curOpt->backPrev2;
++        state = kRepNextStates[state];
++      }
++      else
++      {
++        pos = curOpt->backPrev;
++        if (pos < LZMA_NUM_REPS)
++          state = kRepNextStates[state];
++        else
++          state = kMatchNextStates[state];
++      }
++      prevOpt = &p->opt[posPrev];
++      if (pos < LZMA_NUM_REPS)
++      {
++        UInt32 i;
++        reps[0] = prevOpt->backs[pos];
++        for (i = 1; i <= pos; i++)
++          reps[i] = prevOpt->backs[i - 1];
++        for (; i < LZMA_NUM_REPS; i++)
++          reps[i] = prevOpt->backs[i];
++      }
++      else
++      {
++        UInt32 i;
++        reps[0] = (pos - LZMA_NUM_REPS);
++        for (i = 1; i < LZMA_NUM_REPS; i++)
++          reps[i] = prevOpt->backs[i - 1];
++      }
++    }
++    curOpt->state = (CState)state;
++
++    curOpt->backs[0] = reps[0];
++    curOpt->backs[1] = reps[1];
++    curOpt->backs[2] = reps[2];
++    curOpt->backs[3] = reps[3];
++
++    curPrice = curOpt->price;
++    nextIsChar = False;
++    data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
++    curByte = *data;
++    matchByte = *(data - (reps[0] + 1));
++
++    posState = (position & p->pbMask);
++
++    curAnd1Price = curPrice + GET_PRICE_0(p->isMatch[state][posState]);
++    {
++      const CLzmaProb *probs = LIT_PROBS(position, *(data - 1));
++      curAnd1Price +=
++        (!IsCharState(state) ?
++          LitEnc_GetPriceMatched(probs, curByte, matchByte, p->ProbPrices) :
++          LitEnc_GetPrice(probs, curByte, p->ProbPrices));
++    }
++
++    nextOpt = &p->opt[cur + 1];
++
++    if (curAnd1Price < nextOpt->price)
++    {
++      nextOpt->price = curAnd1Price;
++      nextOpt->posPrev = cur;
++      MakeAsChar(nextOpt);
++      nextIsChar = True;
++    }
++
++    matchPrice = curPrice + GET_PRICE_1(p->isMatch[state][posState]);
++    repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[state]);
++
++    if (matchByte == curByte && !(nextOpt->posPrev < cur && nextOpt->backPrev == 0))
++    {
++      UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(p, state, posState);
++      if (shortRepPrice <= nextOpt->price)
++      {
++        nextOpt->price = shortRepPrice;
++        nextOpt->posPrev = cur;
++        MakeAsShortRep(nextOpt);
++        nextIsChar = True;
++      }
++    }
++    numAvailFull = p->numAvail;
++    {
++      UInt32 temp = kNumOpts - 1 - cur;
++      if (temp < numAvailFull)
++        numAvailFull = temp;
++    }
++
++    if (numAvailFull < 2)
++      continue;
++    numAvail = (numAvailFull <= p->numFastBytes ? numAvailFull : p->numFastBytes);
++
++    if (!nextIsChar && matchByte != curByte) /* speed optimization */
++    {
++      /* try Literal + rep0 */
++      UInt32 temp;
++      UInt32 lenTest2;
++      const Byte *data2 = data - (reps[0] + 1);
++      UInt32 limit = p->numFastBytes + 1;
++      if (limit > numAvailFull)
++        limit = numAvailFull;
++
++      for (temp = 1; temp < limit && data[temp] == data2[temp]; temp++);
++      lenTest2 = temp - 1;
++      if (lenTest2 >= 2)
++      {
++        UInt32 state2 = kLiteralNextStates[state];
++        UInt32 posStateNext = (position + 1) & p->pbMask;
++        UInt32 nextRepMatchPrice = curAnd1Price +
++            GET_PRICE_1(p->isMatch[state2][posStateNext]) +
++            GET_PRICE_1(p->isRep[state2]);
++        /* for (; lenTest2 >= 2; lenTest2--) */
++        {
++          UInt32 curAndLenPrice;
++          COptimal *opt;
++          UInt32 offset = cur + 1 + lenTest2;
++          while (lenEnd < offset)
++            p->opt[++lenEnd].price = kInfinityPrice;
++          curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext);
++          opt = &p->opt[offset];
++          if (curAndLenPrice < opt->price)
++          {
++            opt->price = curAndLenPrice;
++            opt->posPrev = cur + 1;
++            opt->backPrev = 0;
++            opt->prev1IsChar = True;
++            opt->prev2 = False;
++          }
++        }
++      }
++    }
++
++    startLen = 2; /* speed optimization */
++    {
++    UInt32 repIndex;
++    for (repIndex = 0; repIndex < LZMA_NUM_REPS; repIndex++)
++    {
++      UInt32 lenTest;
++      UInt32 lenTestTemp;
++      UInt32 price;
++      const Byte *data2 = data - (reps[repIndex] + 1);
++      if (data[0] != data2[0] || data[1] != data2[1])
++        continue;
++      for (lenTest = 2; lenTest < numAvail && data[lenTest] == data2[lenTest]; lenTest++);
++      while (lenEnd < cur + lenTest)
++        p->opt[++lenEnd].price = kInfinityPrice;
++      lenTestTemp = lenTest;
++      price = repMatchPrice + GetPureRepPrice(p, repIndex, state, posState);
++      do
++      {
++        UInt32 curAndLenPrice = price + p->repLenEnc.prices[posState][lenTest - 2];
++        COptimal *opt = &p->opt[cur + lenTest];
++        if (curAndLenPrice < opt->price)
++        {
++          opt->price = curAndLenPrice;
++          opt->posPrev = cur;
++          opt->backPrev = repIndex;
++          opt->prev1IsChar = False;
++        }
++      }
++      while (--lenTest >= 2);
++      lenTest = lenTestTemp;
++
++      if (repIndex == 0)
++        startLen = lenTest + 1;
++
++      /* if (_maxMode) */
++        {
++          UInt32 lenTest2 = lenTest + 1;
++          UInt32 limit = lenTest2 + p->numFastBytes;
++          UInt32 nextRepMatchPrice;
++          if (limit > numAvailFull)
++            limit = numAvailFull;
++          for (; lenTest2 < limit && data[lenTest2] == data2[lenTest2]; lenTest2++);
++          lenTest2 -= lenTest + 1;
++          if (lenTest2 >= 2)
++          {
++            UInt32 state2 = kRepNextStates[state];
++            UInt32 posStateNext = (position + lenTest) & p->pbMask;
++            UInt32 curAndLenCharPrice =
++                price + p->repLenEnc.prices[posState][lenTest - 2] +
++                GET_PRICE_0(p->isMatch[state2][posStateNext]) +
++                LitEnc_GetPriceMatched(LIT_PROBS(position + lenTest, data[lenTest - 1]),
++                    data[lenTest], data2[lenTest], p->ProbPrices);
++            state2 = kLiteralNextStates[state2];
++            posStateNext = (position + lenTest + 1) & p->pbMask;
++            nextRepMatchPrice = curAndLenCharPrice +
++                GET_PRICE_1(p->isMatch[state2][posStateNext]) +
++                GET_PRICE_1(p->isRep[state2]);
++
++            /* for (; lenTest2 >= 2; lenTest2--) */
++            {
++              UInt32 curAndLenPrice;
++              COptimal *opt;
++              UInt32 offset = cur + lenTest + 1 + lenTest2;
++              while (lenEnd < offset)
++                p->opt[++lenEnd].price = kInfinityPrice;
++              curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext);
++              opt = &p->opt[offset];
++              if (curAndLenPrice < opt->price)
++              {
++                opt->price = curAndLenPrice;
++                opt->posPrev = cur + lenTest + 1;
++                opt->backPrev = 0;
++                opt->prev1IsChar = True;
++                opt->prev2 = True;
++                opt->posPrev2 = cur;
++                opt->backPrev2 = repIndex;
++              }
++            }
++          }
++        }
++    }
++    }
++    /* for (UInt32 lenTest = 2; lenTest <= newLen; lenTest++) */
++    if (newLen > numAvail)
++    {
++      newLen = numAvail;
++      for (numPairs = 0; newLen > matches[numPairs]; numPairs += 2);
++      matches[numPairs] = newLen;
++      numPairs += 2;
++    }
++    if (newLen >= startLen)
++    {
++      UInt32 normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[state]);
++      UInt32 offs, curBack, posSlot;
++      UInt32 lenTest;
++      while (lenEnd < cur + newLen)
++        p->opt[++lenEnd].price = kInfinityPrice;
++
++      offs = 0;
++      while (startLen > matches[offs])
++        offs += 2;
++      curBack = matches[offs + 1];
++      GetPosSlot2(curBack, posSlot);
++      for (lenTest = /*2*/ startLen; ; lenTest++)
++      {
++        UInt32 curAndLenPrice = normalMatchPrice + p->lenEnc.prices[posState][lenTest - LZMA_MATCH_LEN_MIN];
++        UInt32 lenToPosState = GetLenToPosState(lenTest);
++        COptimal *opt;
++        if (curBack < kNumFullDistances)
++          curAndLenPrice += p->distancesPrices[lenToPosState][curBack];
++        else
++          curAndLenPrice += p->posSlotPrices[lenToPosState][posSlot] + p->alignPrices[curBack & kAlignMask];
++
++        opt = &p->opt[cur + lenTest];
++        if (curAndLenPrice < opt->price)
++        {
++          opt->price = curAndLenPrice;
++          opt->posPrev = cur;
++          opt->backPrev = curBack + LZMA_NUM_REPS;
++          opt->prev1IsChar = False;
++        }
++
++        if (/*_maxMode && */lenTest == matches[offs])
++        {
++          /* Try Match + Literal + Rep0 */
++          const Byte *data2 = data - (curBack + 1);
++          UInt32 lenTest2 = lenTest + 1;
++          UInt32 limit = lenTest2 + p->numFastBytes;
++          UInt32 nextRepMatchPrice;
++          if (limit > numAvailFull)
++            limit = numAvailFull;
++          for (; lenTest2 < limit && data[lenTest2] == data2[lenTest2]; lenTest2++);
++          lenTest2 -= lenTest + 1;
++          if (lenTest2 >= 2)
++          {
++            UInt32 state2 = kMatchNextStates[state];
++            UInt32 posStateNext = (position + lenTest) & p->pbMask;
++            UInt32 curAndLenCharPrice = curAndLenPrice +
++                GET_PRICE_0(p->isMatch[state2][posStateNext]) +
++                LitEnc_GetPriceMatched(LIT_PROBS(position + lenTest, data[lenTest - 1]),
++                    data[lenTest], data2[lenTest], p->ProbPrices);
++            state2 = kLiteralNextStates[state2];
++            posStateNext = (posStateNext + 1) & p->pbMask;
++            nextRepMatchPrice = curAndLenCharPrice +
++                GET_PRICE_1(p->isMatch[state2][posStateNext]) +
++                GET_PRICE_1(p->isRep[state2]);
++
++            /* for (; lenTest2 >= 2; lenTest2--) */
++            {
++              UInt32 offset = cur + lenTest + 1 + lenTest2;
++              UInt32 curAndLenPrice;
++              COptimal *opt;
++              while (lenEnd < offset)
++                p->opt[++lenEnd].price = kInfinityPrice;
++              curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext);
++              opt = &p->opt[offset];
++              if (curAndLenPrice < opt->price)
++              {
++                opt->price = curAndLenPrice;
++                opt->posPrev = cur + lenTest + 1;
++                opt->backPrev = 0;
++                opt->prev1IsChar = True;
++                opt->prev2 = True;
++                opt->posPrev2 = cur;
++                opt->backPrev2 = curBack + LZMA_NUM_REPS;
++              }
++            }
++          }
++          offs += 2;
++          if (offs == numPairs)
++            break;
++          curBack = matches[offs + 1];
++          if (curBack >= kNumFullDistances)
++            GetPosSlot2(curBack, posSlot);
++        }
++      }
++    }
++  }
++}
++
++#define ChangePair(smallDist, bigDist) (((bigDist) >> 7) > (smallDist))
++
++static UInt32 GetOptimumFast(CLzmaEnc *p, UInt32 *backRes)
++{
++  UInt32 numAvail, mainLen, mainDist, numPairs, repIndex, repLen, i;
++  const Byte *data;
++  const UInt32 *matches;
++
++  if (p->additionalOffset == 0)
++    mainLen = ReadMatchDistances(p, &numPairs);
++  else
++  {
++    mainLen = p->longestMatchLength;
++    numPairs = p->numPairs;
++  }
++
++  numAvail = p->numAvail;
++  *backRes = (UInt32)-1;
++  if (numAvail < 2)
++    return 1;
++  if (numAvail > LZMA_MATCH_LEN_MAX)
++    numAvail = LZMA_MATCH_LEN_MAX;
++  data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
++
++  repLen = repIndex = 0;
++  for (i = 0; i < LZMA_NUM_REPS; i++)
++  {
++    UInt32 len;
++    const Byte *data2 = data - (p->reps[i] + 1);
++    if (data[0] != data2[0] || data[1] != data2[1])
++      continue;
++    for (len = 2; len < numAvail && data[len] == data2[len]; len++);
++    if (len >= p->numFastBytes)
++    {
++      *backRes = i;
++      MovePos(p, len - 1);
++      return len;
++    }
++    if (len > repLen)
++    {
++      repIndex = i;
++      repLen = len;
++    }
++  }
++
++  matches = p->matches;
++  if (mainLen >= p->numFastBytes)
++  {
++    *backRes = matches[numPairs - 1] + LZMA_NUM_REPS;
++    MovePos(p, mainLen - 1);
++    return mainLen;
++  }
++
++  mainDist = 0; /* for GCC */
++  if (mainLen >= 2)
++  {
++    mainDist = matches[numPairs - 1];
++    while (numPairs > 2 && mainLen == matches[numPairs - 4] + 1)
++    {
++      if (!ChangePair(matches[numPairs - 3], mainDist))
++        break;
++      numPairs -= 2;
++      mainLen = matches[numPairs - 2];
++      mainDist = matches[numPairs - 1];
++    }
++    if (mainLen == 2 && mainDist >= 0x80)
++      mainLen = 1;
++  }
++
++  if (repLen >= 2 && (
++        (repLen + 1 >= mainLen) ||
++        (repLen + 2 >= mainLen && mainDist >= (1 << 9)) ||
++        (repLen + 3 >= mainLen && mainDist >= (1 << 15))))
++  {
++    *backRes = repIndex;
++    MovePos(p, repLen - 1);
++    return repLen;
++  }
++
++  if (mainLen < 2 || numAvail <= 2)
++    return 1;
++
++  p->longestMatchLength = ReadMatchDistances(p, &p->numPairs);
++  if (p->longestMatchLength >= 2)
++  {
++    UInt32 newDistance = matches[p->numPairs - 1];
++    if ((p->longestMatchLength >= mainLen && newDistance < mainDist) ||
++        (p->longestMatchLength == mainLen + 1 && !ChangePair(mainDist, newDistance)) ||
++        (p->longestMatchLength > mainLen + 1) ||
++        (p->longestMatchLength + 1 >= mainLen && mainLen >= 3 && ChangePair(newDistance, mainDist)))
++      return 1;
++  }
++
++  data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
++  for (i = 0; i < LZMA_NUM_REPS; i++)
++  {
++    UInt32 len, limit;
++    const Byte *data2 = data - (p->reps[i] + 1);
++    if (data[0] != data2[0] || data[1] != data2[1])
++      continue;
++    limit = mainLen - 1;
++    for (len = 2; len < limit && data[len] == data2[len]; len++);
++    if (len >= limit)
++      return 1;
++  }
++  *backRes = mainDist + LZMA_NUM_REPS;
++  MovePos(p, mainLen - 2);
++  return mainLen;
++}
++
++static void WriteEndMarker(CLzmaEnc *p, UInt32 posState)
++{
++  UInt32 len;
++  RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 1);
++  RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 0);
++  p->state = kMatchNextStates[p->state];
++  len = LZMA_MATCH_LEN_MIN;
++  LenEnc_Encode2(&p->lenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices);
++  RcTree_Encode(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], kNumPosSlotBits, (1 << kNumPosSlotBits) - 1);
++  RangeEnc_EncodeDirectBits(&p->rc, (((UInt32)1 << 30) - 1) >> kNumAlignBits, 30 - kNumAlignBits);
++  RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, kAlignMask);
++}
++
++static SRes CheckErrors(CLzmaEnc *p)
++{
++  if (p->result != SZ_OK)
++    return p->result;
++  if (p->rc.res != SZ_OK)
++    p->result = SZ_ERROR_WRITE;
++  if (p->matchFinderBase.result != SZ_OK)
++    p->result = SZ_ERROR_READ;
++  if (p->result != SZ_OK)
++    p->finished = True;
++  return p->result;
++}
++
++static SRes Flush(CLzmaEnc *p, UInt32 nowPos)
++{
++  /* ReleaseMFStream(); */
++  p->finished = True;
++  if (p->writeEndMark)
++    WriteEndMarker(p, nowPos & p->pbMask);
++  RangeEnc_FlushData(&p->rc);
++  RangeEnc_FlushStream(&p->rc);
++  return CheckErrors(p);
++}
++
++static void FillAlignPrices(CLzmaEnc *p)
++{
++  UInt32 i;
++  for (i = 0; i < kAlignTableSize; i++)
++    p->alignPrices[i] = RcTree_ReverseGetPrice(p->posAlignEncoder, kNumAlignBits, i, p->ProbPrices);
++  p->alignPriceCount = 0;
++}
++
++static void FillDistancesPrices(CLzmaEnc *p)
++{
++  UInt32 tempPrices[kNumFullDistances];
++  UInt32 i, lenToPosState;
++  for (i = kStartPosModelIndex; i < kNumFullDistances; i++)
++  {
++    UInt32 posSlot = GetPosSlot1(i);
++    UInt32 footerBits = ((posSlot >> 1) - 1);
++    UInt32 base = ((2 | (posSlot & 1)) << footerBits);
++    tempPrices[i] = RcTree_ReverseGetPrice(p->posEncoders + base - posSlot - 1, footerBits, i - base, p->ProbPrices);
++  }
++
++  for (lenToPosState = 0; lenToPosState < kNumLenToPosStates; lenToPosState++)
++  {
++    UInt32 posSlot;
++    const CLzmaProb *encoder = p->posSlotEncoder[lenToPosState];
++    UInt32 *posSlotPrices = p->posSlotPrices[lenToPosState];
++    for (posSlot = 0; posSlot < p->distTableSize; posSlot++)
++      posSlotPrices[posSlot] = RcTree_GetPrice(encoder, kNumPosSlotBits, posSlot, p->ProbPrices);
++    for (posSlot = kEndPosModelIndex; posSlot < p->distTableSize; posSlot++)
++      posSlotPrices[posSlot] += ((((posSlot >> 1) - 1) - kNumAlignBits) << kNumBitPriceShiftBits);
++
++    {
++      UInt32 *distancesPrices = p->distancesPrices[lenToPosState];
++      UInt32 i;
++      for (i = 0; i < kStartPosModelIndex; i++)
++        distancesPrices[i] = posSlotPrices[i];
++      for (; i < kNumFullDistances; i++)
++        distancesPrices[i] = posSlotPrices[GetPosSlot1(i)] + tempPrices[i];
++    }
++  }
++  p->matchPriceCount = 0;
++}
++
++static void LzmaEnc_Construct(CLzmaEnc *p)
++{
++  RangeEnc_Construct(&p->rc);
++  MatchFinder_Construct(&p->matchFinderBase);
++  #ifndef _7ZIP_ST
++  MatchFinderMt_Construct(&p->matchFinderMt);
++  p->matchFinderMt.MatchFinder = &p->matchFinderBase;
++  #endif
++
++  {
++    CLzmaEncProps props;
++    LzmaEncProps_Init(&props);
++    LzmaEnc_SetProps(p, &props);
++  }
++
++  #ifndef LZMA_LOG_BSR
++  LzmaEnc_FastPosInit(p->g_FastPos);
++  #endif
++
++  LzmaEnc_InitPriceTables(p->ProbPrices);
++  p->litProbs = 0;
++  p->saveState.litProbs = 0;
++}
++
++CLzmaEncHandle LzmaEnc_Create(ISzAlloc *alloc)
++{
++  void *p;
++  p = alloc->Alloc(alloc, sizeof(CLzmaEnc));
++  if (p != 0)
++    LzmaEnc_Construct((CLzmaEnc *)p);
++  return p;
++}
++
++static void LzmaEnc_FreeLits(CLzmaEnc *p, ISzAlloc *alloc)
++{
++  alloc->Free(alloc, p->litProbs);
++  alloc->Free(alloc, p->saveState.litProbs);
++  p->litProbs = 0;
++  p->saveState.litProbs = 0;
++}
++
++static void LzmaEnc_Destruct(CLzmaEnc *p, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  #ifndef _7ZIP_ST
++  MatchFinderMt_Destruct(&p->matchFinderMt, allocBig);
++  #endif
++  MatchFinder_Free(&p->matchFinderBase, allocBig);
++  LzmaEnc_FreeLits(p, alloc);
++  RangeEnc_Free(&p->rc, alloc);
++}
++
++void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  LzmaEnc_Destruct((CLzmaEnc *)p, alloc, allocBig);
++  alloc->Free(alloc, p);
++}
++
++static SRes LzmaEnc_CodeOneBlock(CLzmaEnc *p, Bool useLimits, UInt32 maxPackSize, UInt32 maxUnpackSize)
++{
++  UInt32 nowPos32, startPos32;
++  if (p->needInit)
++  {
++    p->matchFinder.Init(p->matchFinderObj);
++    p->needInit = 0;
++  }
++
++  if (p->finished)
++    return p->result;
++  RINOK(CheckErrors(p));
++
++  nowPos32 = (UInt32)p->nowPos64;
++  startPos32 = nowPos32;
++
++  if (p->nowPos64 == 0)
++  {
++    UInt32 numPairs;
++    Byte curByte;
++    if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0)
++      return Flush(p, nowPos32);
++    ReadMatchDistances(p, &numPairs);
++    RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][0], 0);
++    p->state = kLiteralNextStates[p->state];
++    curByte = p->matchFinder.GetIndexByte(p->matchFinderObj, 0 - p->additionalOffset);
++    LitEnc_Encode(&p->rc, p->litProbs, curByte);
++    p->additionalOffset--;
++    nowPos32++;
++  }
++
++  if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) != 0)
++  for (;;)
++  {
++    UInt32 pos, len, posState;
++
++    if (p->fastMode)
++      len = GetOptimumFast(p, &pos);
++    else
++      len = GetOptimum(p, nowPos32, &pos);
++
++    #ifdef SHOW_STAT2
++    printf("\n pos = %4X,   len = %d   pos = %d", nowPos32, len, pos);
++    #endif
++
++    posState = nowPos32 & p->pbMask;
++    if (len == 1 && pos == (UInt32)-1)
++    {
++      Byte curByte;
++      CLzmaProb *probs;
++      const Byte *data;
++
++      RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 0);
++      data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset;
++      curByte = *data;
++      probs = LIT_PROBS(nowPos32, *(data - 1));
++      if (IsCharState(p->state))
++        LitEnc_Encode(&p->rc, probs, curByte);
++      else
++        LitEnc_EncodeMatched(&p->rc, probs, curByte, *(data - p->reps[0] - 1));
++      p->state = kLiteralNextStates[p->state];
++    }
++    else
++    {
++      RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 1);
++      if (pos < LZMA_NUM_REPS)
++      {
++        RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 1);
++        if (pos == 0)
++        {
++          RangeEnc_EncodeBit(&p->rc, &p->isRepG0[p->state], 0);
++          RangeEnc_EncodeBit(&p->rc, &p->isRep0Long[p->state][posState], ((len == 1) ? 0 : 1));
++        }
++        else
++        {
++          UInt32 distance = p->reps[pos];
++          RangeEnc_EncodeBit(&p->rc, &p->isRepG0[p->state], 1);
++          if (pos == 1)
++            RangeEnc_EncodeBit(&p->rc, &p->isRepG1[p->state], 0);
++          else
++          {
++            RangeEnc_EncodeBit(&p->rc, &p->isRepG1[p->state], 1);
++            RangeEnc_EncodeBit(&p->rc, &p->isRepG2[p->state], pos - 2);
++            if (pos == 3)
++              p->reps[3] = p->reps[2];
++            p->reps[2] = p->reps[1];
++          }
++          p->reps[1] = p->reps[0];
++          p->reps[0] = distance;
++        }
++        if (len == 1)
++          p->state = kShortRepNextStates[p->state];
++        else
++        {
++          LenEnc_Encode2(&p->repLenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices);
++          p->state = kRepNextStates[p->state];
++        }
++      }
++      else
++      {
++        UInt32 posSlot;
++        RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 0);
++        p->state = kMatchNextStates[p->state];
++        LenEnc_Encode2(&p->lenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices);
++        pos -= LZMA_NUM_REPS;
++        GetPosSlot(pos, posSlot);
++        RcTree_Encode(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], kNumPosSlotBits, posSlot);
++
++        if (posSlot >= kStartPosModelIndex)
++        {
++          UInt32 footerBits = ((posSlot >> 1) - 1);
++          UInt32 base = ((2 | (posSlot & 1)) << footerBits);
++          UInt32 posReduced = pos - base;
++
++          if (posSlot < kEndPosModelIndex)
++            RcTree_ReverseEncode(&p->rc, p->posEncoders + base - posSlot - 1, footerBits, posReduced);
++          else
++          {
++            RangeEnc_EncodeDirectBits(&p->rc, posReduced >> kNumAlignBits, footerBits - kNumAlignBits);
++            RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, posReduced & kAlignMask);
++            p->alignPriceCount++;
++          }
++        }
++        p->reps[3] = p->reps[2];
++        p->reps[2] = p->reps[1];
++        p->reps[1] = p->reps[0];
++        p->reps[0] = pos;
++        p->matchPriceCount++;
++      }
++    }
++    p->additionalOffset -= len;
++    nowPos32 += len;
++    if (p->additionalOffset == 0)
++    {
++      UInt32 processed;
++      if (!p->fastMode)
++      {
++        if (p->matchPriceCount >= (1 << 7))
++          FillDistancesPrices(p);
++        if (p->alignPriceCount >= kAlignTableSize)
++          FillAlignPrices(p);
++      }
++      if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0)
++        break;
++      processed = nowPos32 - startPos32;
++      if (useLimits)
++      {
++        if (processed + kNumOpts + 300 >= maxUnpackSize ||
++            RangeEnc_GetProcessed(&p->rc) + kNumOpts * 2 >= maxPackSize)
++          break;
++      }
++      else if (processed >= (1 << 15))
++      {
++        p->nowPos64 += nowPos32 - startPos32;
++        return CheckErrors(p);
++      }
++    }
++  }
++  p->nowPos64 += nowPos32 - startPos32;
++  return Flush(p, nowPos32);
++}
++
++#define kBigHashDicLimit ((UInt32)1 << 24)
++
++static SRes LzmaEnc_Alloc(CLzmaEnc *p, UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  UInt32 beforeSize = kNumOpts;
++  Bool btMode;
++  if (!RangeEnc_Alloc(&p->rc, alloc))
++    return SZ_ERROR_MEM;
++  btMode = (p->matchFinderBase.btMode != 0);
++  #ifndef _7ZIP_ST
++  p->mtMode = (p->multiThread && !p->fastMode && btMode);
++  #endif
++
++  {
++    unsigned lclp = p->lc + p->lp;
++    if (p->litProbs == 0 || p->saveState.litProbs == 0 || p->lclp != lclp)
++    {
++      LzmaEnc_FreeLits(p, alloc);
++      p->litProbs = (CLzmaProb *)alloc->Alloc(alloc, (0x300 << lclp) * sizeof(CLzmaProb));
++      p->saveState.litProbs = (CLzmaProb *)alloc->Alloc(alloc, (0x300 << lclp) * sizeof(CLzmaProb));
++      if (p->litProbs == 0 || p->saveState.litProbs == 0)
++      {
++        LzmaEnc_FreeLits(p, alloc);
++        return SZ_ERROR_MEM;
++      }
++      p->lclp = lclp;
++    }
++  }
++
++  p->matchFinderBase.bigHash = (p->dictSize > kBigHashDicLimit);
++
++  if (beforeSize + p->dictSize < keepWindowSize)
++    beforeSize = keepWindowSize - p->dictSize;
++
++  #ifndef _7ZIP_ST
++  if (p->mtMode)
++  {
++    RINOK(MatchFinderMt_Create(&p->matchFinderMt, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig));
++    p->matchFinderObj = &p->matchFinderMt;
++    MatchFinderMt_CreateVTable(&p->matchFinderMt, &p->matchFinder);
++  }
++  else
++  #endif
++  {
++    if (!MatchFinder_Create(&p->matchFinderBase, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig))
++      return SZ_ERROR_MEM;
++    p->matchFinderObj = &p->matchFinderBase;
++    MatchFinder_CreateVTable(&p->matchFinderBase, &p->matchFinder);
++  }
++  return SZ_OK;
++}
++
++static void LzmaEnc_Init(CLzmaEnc *p)
++{
++  UInt32 i;
++  p->state = 0;
++  for (i = 0 ; i < LZMA_NUM_REPS; i++)
++    p->reps[i] = 0;
++
++  RangeEnc_Init(&p->rc);
++
++
++  for (i = 0; i < kNumStates; i++)
++  {
++    UInt32 j;
++    for (j = 0; j < LZMA_NUM_PB_STATES_MAX; j++)
++    {
++      p->isMatch[i][j] = kProbInitValue;
++      p->isRep0Long[i][j] = kProbInitValue;
++    }
++    p->isRep[i] = kProbInitValue;
++    p->isRepG0[i] = kProbInitValue;
++    p->isRepG1[i] = kProbInitValue;
++    p->isRepG2[i] = kProbInitValue;
++  }
++
++  {
++    UInt32 num = 0x300 << (p->lp + p->lc);
++    for (i = 0; i < num; i++)
++      p->litProbs[i] = kProbInitValue;
++  }
++
++  {
++    for (i = 0; i < kNumLenToPosStates; i++)
++    {
++      CLzmaProb *probs = p->posSlotEncoder[i];
++      UInt32 j;
++      for (j = 0; j < (1 << kNumPosSlotBits); j++)
++        probs[j] = kProbInitValue;
++    }
++  }
++  {
++    for (i = 0; i < kNumFullDistances - kEndPosModelIndex; i++)
++      p->posEncoders[i] = kProbInitValue;
++  }
++
++  LenEnc_Init(&p->lenEnc.p);
++  LenEnc_Init(&p->repLenEnc.p);
++
++  for (i = 0; i < (1 << kNumAlignBits); i++)
++    p->posAlignEncoder[i] = kProbInitValue;
++
++  p->optimumEndIndex = 0;
++  p->optimumCurrentIndex = 0;
++  p->additionalOffset = 0;
++
++  p->pbMask = (1 << p->pb) - 1;
++  p->lpMask = (1 << p->lp) - 1;
++}
++
++static void LzmaEnc_InitPrices(CLzmaEnc *p)
++{
++  if (!p->fastMode)
++  {
++    FillDistancesPrices(p);
++    FillAlignPrices(p);
++  }
++
++  p->lenEnc.tableSize =
++  p->repLenEnc.tableSize =
++      p->numFastBytes + 1 - LZMA_MATCH_LEN_MIN;
++  LenPriceEnc_UpdateTables(&p->lenEnc, 1 << p->pb, p->ProbPrices);
++  LenPriceEnc_UpdateTables(&p->repLenEnc, 1 << p->pb, p->ProbPrices);
++}
++
++static SRes LzmaEnc_AllocAndInit(CLzmaEnc *p, UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  UInt32 i;
++  for (i = 0; i < (UInt32)kDicLogSizeMaxCompress; i++)
++    if (p->dictSize <= ((UInt32)1 << i))
++      break;
++  p->distTableSize = i * 2;
++
++  p->finished = False;
++  p->result = SZ_OK;
++  RINOK(LzmaEnc_Alloc(p, keepWindowSize, alloc, allocBig));
++  LzmaEnc_Init(p);
++  LzmaEnc_InitPrices(p);
++  p->nowPos64 = 0;
++  return SZ_OK;
++}
++
++static SRes LzmaEnc_Prepare(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream,
++    ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  p->matchFinderBase.stream = inStream;
++  p->needInit = 1;
++  p->rc.outStream = outStream;
++  return LzmaEnc_AllocAndInit(p, 0, alloc, allocBig);
++}
++
++/*SRes LzmaEnc_PrepareForLzma2(CLzmaEncHandle pp,
++    ISeqInStream *inStream, UInt32 keepWindowSize,
++    ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  p->matchFinderBase.stream = inStream;
++  p->needInit = 1;
++  return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig);
++}*/
++
++static void LzmaEnc_SetInputBuf(CLzmaEnc *p, const Byte *src, SizeT srcLen)
++{
++  p->matchFinderBase.directInput = 1;
++  p->matchFinderBase.bufferBase = (Byte *)src;
++  p->matchFinderBase.directInputRem = srcLen;
++}
++
++static SRes LzmaEnc_MemPrepare(CLzmaEncHandle pp, const Byte *src, SizeT srcLen,
++    UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  LzmaEnc_SetInputBuf(p, src, srcLen);
++  p->needInit = 1;
++
++  return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig);
++}
++
++static void LzmaEnc_Finish(CLzmaEncHandle pp)
++{
++  #ifndef _7ZIP_ST
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  if (p->mtMode)
++    MatchFinderMt_ReleaseStream(&p->matchFinderMt);
++  #else
++  pp = pp;
++  #endif
++}
++
++typedef struct
++{
++  ISeqOutStream funcTable;
++  Byte *data;
++  SizeT rem;
++  Bool overflow;
++} CSeqOutStreamBuf;
++
++static size_t MyWrite(void *pp, const void *data, size_t size)
++{
++  CSeqOutStreamBuf *p = (CSeqOutStreamBuf *)pp;
++  if (p->rem < size)
++  {
++    size = p->rem;
++    p->overflow = True;
++  }
++  memcpy(p->data, data, size);
++  p->rem -= size;
++  p->data += size;
++  return size;
++}
++
++
++/*UInt32 LzmaEnc_GetNumAvailableBytes(CLzmaEncHandle pp)
++{
++  const CLzmaEnc *p = (CLzmaEnc *)pp;
++  return p->matchFinder.GetNumAvailableBytes(p->matchFinderObj);
++}*/
++
++/*const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle pp)
++{
++  const CLzmaEnc *p = (CLzmaEnc *)pp;
++  return p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset;
++}*/
++
++/* SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, Bool reInit,
++    Byte *dest, size_t *destLen, UInt32 desiredPackSize, UInt32 *unpackSize)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  UInt64 nowPos64;
++  SRes res;
++  CSeqOutStreamBuf outStream;
++
++  outStream.funcTable.Write = MyWrite;
++  outStream.data = dest;
++  outStream.rem = *destLen;
++  outStream.overflow = False;
++
++  p->writeEndMark = False;
++  p->finished = False;
++  p->result = SZ_OK;
++
++  if (reInit)
++    LzmaEnc_Init(p);
++  LzmaEnc_InitPrices(p);
++  nowPos64 = p->nowPos64;
++  RangeEnc_Init(&p->rc);
++  p->rc.outStream = &outStream.funcTable;
++
++  res = LzmaEnc_CodeOneBlock(p, True, desiredPackSize, *unpackSize);
++
++  *unpackSize = (UInt32)(p->nowPos64 - nowPos64);
++  *destLen -= outStream.rem;
++  if (outStream.overflow)
++    return SZ_ERROR_OUTPUT_EOF;
++
++  return res;
++}*/
++
++static SRes LzmaEnc_Encode2(CLzmaEnc *p, ICompressProgress *progress)
++{
++  SRes res = SZ_OK;
++
++  #ifndef _7ZIP_ST
++  Byte allocaDummy[0x300];
++  int i = 0;
++  for (i = 0; i < 16; i++)
++    allocaDummy[i] = (Byte)i;
++  #endif
++
++  for (;;)
++  {
++    res = LzmaEnc_CodeOneBlock(p, False, 0, 0);
++    if (res != SZ_OK || p->finished != 0)
++      break;
++    if (progress != 0)
++    {
++      res = progress->Progress(progress, p->nowPos64, RangeEnc_GetProcessed(&p->rc));
++      if (res != SZ_OK)
++      {
++        res = SZ_ERROR_PROGRESS;
++        break;
++      }
++    }
++  }
++  LzmaEnc_Finish(p);
++  return res;
++}
++
++SRes LzmaEnc_Encode(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream, ICompressProgress *progress,
++    ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  RINOK(LzmaEnc_Prepare(pp, outStream, inStream, alloc, allocBig));
++  return LzmaEnc_Encode2((CLzmaEnc *)pp, progress);
++}
++
++SRes LzmaEnc_WriteProperties(CLzmaEncHandle pp, Byte *props, SizeT *size)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  int i;
++  UInt32 dictSize = p->dictSize;
++  if (*size < LZMA_PROPS_SIZE)
++    return SZ_ERROR_PARAM;
++  *size = LZMA_PROPS_SIZE;
++  props[0] = (Byte)((p->pb * 5 + p->lp) * 9 + p->lc);
++
++  for (i = 11; i <= 30; i++)
++  {
++    if (dictSize <= ((UInt32)2 << i))
++    {
++      dictSize = (2 << i);
++      break;
++    }
++    if (dictSize <= ((UInt32)3 << i))
++    {
++      dictSize = (3 << i);
++      break;
++    }
++  }
++
++  for (i = 0; i < 4; i++)
++    props[1 + i] = (Byte)(dictSize >> (8 * i));
++  return SZ_OK;
++}
++
++SRes LzmaEnc_MemEncode(CLzmaEncHandle pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen,
++    int writeEndMark, ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  SRes res;
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++
++  CSeqOutStreamBuf outStream;
++
++  LzmaEnc_SetInputBuf(p, src, srcLen);
++
++  outStream.funcTable.Write = MyWrite;
++  outStream.data = dest;
++  outStream.rem = *destLen;
++  outStream.overflow = False;
++
++  p->writeEndMark = writeEndMark;
++
++  p->rc.outStream = &outStream.funcTable;
++  res = LzmaEnc_MemPrepare(pp, src, srcLen, 0, alloc, allocBig);
++  if (res == SZ_OK)
++    res = LzmaEnc_Encode2(p, progress);
++
++  *destLen -= outStream.rem;
++  if (outStream.overflow)
++    return SZ_ERROR_OUTPUT_EOF;
++  return res;
++}
++
++SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen,
++    const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark,
++    ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  CLzmaEnc *p = (CLzmaEnc *)LzmaEnc_Create(alloc);
++  SRes res;
++  if (p == 0)
++    return SZ_ERROR_MEM;
++
++  res = LzmaEnc_SetProps(p, props);
++  if (res == SZ_OK)
++  {
++    res = LzmaEnc_WriteProperties(p, propsEncoded, propsSize);
++    if (res == SZ_OK)
++      res = LzmaEnc_MemEncode(p, dest, destLen, src, srcLen,
++          writeEndMark, progress, alloc, allocBig);
++  }
++
++  LzmaEnc_Destroy(p, alloc, allocBig);
++  return res;
++}
+--- /dev/null
++++ b/lib/lzma/Makefile
+@@ -0,0 +1,7 @@
++lzma_compress-objs := LzFind.o LzmaEnc.o
++lzma_decompress-objs := LzmaDec.o
++
++obj-$(CONFIG_LZMA_COMPRESS) += lzma_compress.o
++obj-$(CONFIG_LZMA_DECOMPRESS) += lzma_decompress.o
++
++EXTRA_CFLAGS += -Iinclude/linux -Iinclude/linux/lzma -include types.h
diff --git a/target/linux/generic/pending-6.12/532-jffs2_eofdetect.patch b/target/linux/generic/pending-6.12/532-jffs2_eofdetect.patch
new file mode 100644 (file)
index 0000000..88bb14a
--- /dev/null
@@ -0,0 +1,65 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: fs: jffs2: EOF marker
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ fs/jffs2/build.c | 10 ++++++++++
+ fs/jffs2/scan.c  | 21 +++++++++++++++++++--
+ 2 files changed, 29 insertions(+), 2 deletions(-)
+
+--- a/fs/jffs2/build.c
++++ b/fs/jffs2/build.c
+@@ -117,6 +117,16 @@ static int jffs2_build_filesystem(struct
+       dbg_fsbuild("scanned flash completely\n");
+       jffs2_dbg_dump_block_lists_nolock(c);
++      if (c->flags & (1 << 7)) {
++              printk("%s(): unlocking the mtd device... ", __func__);
++              mtd_unlock(c->mtd, 0, c->mtd->size);
++              printk("done.\n");
++
++              printk("%s(): erasing all blocks after the end marker... ", __func__);
++              jffs2_erase_pending_blocks(c, -1);
++              printk("done.\n");
++      }
++
+       dbg_fsbuild("pass 1 starting\n");
+       c->flags |= JFFS2_SB_FLAG_BUILDING;
+       /* Now scan the directory tree, increasing nlink according to every dirent found. */
+--- a/fs/jffs2/scan.c
++++ b/fs/jffs2/scan.c
+@@ -148,8 +148,14 @@ int jffs2_scan_medium(struct jffs2_sb_in
+               /* reset summary info for next eraseblock scan */
+               jffs2_sum_reset_collected(s);
+-              ret = jffs2_scan_eraseblock(c, jeb, buf_size?flashbuf:(flashbuf+jeb->offset),
+-                                              buf_size, s);
++              if (c->flags & (1 << 7)) {
++                      if (mtd_block_isbad(c->mtd, jeb->offset))
++                              ret = BLK_STATE_BADBLOCK;
++                      else
++                              ret = BLK_STATE_ALLFF;
++              } else
++                      ret = jffs2_scan_eraseblock(c, jeb, buf_size?flashbuf:(flashbuf+jeb->offset),
++                                                      buf_size, s);
+               if (ret < 0)
+                       goto out;
+@@ -569,6 +575,17 @@ full_scan:
+                       return err;
+       }
++      if ((buf[0] == 0xde) &&
++              (buf[1] == 0xad) &&
++              (buf[2] == 0xc0) &&
++              (buf[3] == 0xde)) {
++              /* end of filesystem. erase everything after this point */
++              printk("%s(): End of filesystem marker found at 0x%x\n", __func__, jeb->offset);
++              c->flags |= (1 << 7);
++
++              return BLK_STATE_ALLFF;
++      }
++
+       /* We temporarily use 'ofs' as a pointer into the buffer/jeb */
+       ofs = 0;
+       max_ofs = EMPTY_SCAN_SIZE(c->sector_size);
diff --git a/target/linux/generic/pending-6.12/600-netfilter_conntrack_flush.patch b/target/linux/generic/pending-6.12/600-netfilter_conntrack_flush.patch
new file mode 100644 (file)
index 0000000..52e97e4
--- /dev/null
@@ -0,0 +1,90 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: netfilter: add support for flushing conntrack via /proc
+
+lede-commit 8193bbe59a74d34d6a26d4a8cb857b1952905314
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ net/netfilter/nf_conntrack_standalone.c | 59 ++++++++++++++++++++++++++++++++-
+ 1 file changed, 58 insertions(+), 1 deletion(-)
+
+--- a/net/netfilter/nf_conntrack_standalone.c
++++ b/net/netfilter/nf_conntrack_standalone.c
+@@ -9,6 +9,7 @@
+ #include <linux/percpu.h>
+ #include <linux/netdevice.h>
+ #include <linux/security.h>
++#include <linux/inet.h>
+ #include <net/net_namespace.h>
+ #ifdef CONFIG_SYSCTL
+ #include <linux/sysctl.h>
+@@ -458,6 +459,58 @@ static int ct_cpu_seq_show(struct seq_fi
+       return 0;
+ }
++struct kill_request {
++      u16 family;
++      union nf_inet_addr addr;
++};
++
++static int kill_matching(struct nf_conn *i, void *data)
++{
++      struct kill_request *kr = data;
++      struct nf_conntrack_tuple *t1 = &i->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
++      struct nf_conntrack_tuple *t2 = &i->tuplehash[IP_CT_DIR_REPLY].tuple;
++
++      if (!kr->family)
++              return 1;
++
++      if (t1->src.l3num != kr->family)
++              return 0;
++
++      return (nf_inet_addr_cmp(&kr->addr, &t1->src.u3) ||
++              nf_inet_addr_cmp(&kr->addr, &t1->dst.u3) ||
++              nf_inet_addr_cmp(&kr->addr, &t2->src.u3) ||
++              nf_inet_addr_cmp(&kr->addr, &t2->dst.u3));
++}
++
++static int ct_file_write(struct file *file, char *buf, size_t count)
++{
++      struct seq_file *seq = file->private_data;
++      struct nf_ct_iter_data iter_data;
++      struct kill_request kr = { };
++
++      if (count == 0)
++              return 0;
++
++      if (count >= INET6_ADDRSTRLEN)
++              count = INET6_ADDRSTRLEN - 1;
++
++      if (strnchr(buf, count, ':')) {
++              kr.family = AF_INET6;
++              if (!in6_pton(buf, count, (void *)&kr.addr, '\n', NULL))
++                      return -EINVAL;
++      } else if (strnchr(buf, count, '.')) {
++              kr.family = AF_INET;
++              if (!in4_pton(buf, count, (void *)&kr.addr, '\n', NULL))
++                      return -EINVAL;
++      }
++
++      iter_data.net = seq_file_net(seq);
++      iter_data.data = &kr;
++      nf_ct_iterate_cleanup_net(kill_matching, &iter_data);
++
++      return 0;
++}
++
+ static const struct seq_operations ct_cpu_seq_ops = {
+       .start  = ct_cpu_seq_start,
+       .next   = ct_cpu_seq_next,
+@@ -471,8 +524,9 @@ static int nf_conntrack_standalone_init_
+       kuid_t root_uid;
+       kgid_t root_gid;
+-      pde = proc_create_net("nf_conntrack", 0440, net->proc_net, &ct_seq_ops,
+-                      sizeof(struct ct_iter_state));
++      pde = proc_create_net_data_write("nf_conntrack", 0440, net->proc_net,
++                                       &ct_seq_ops, &ct_file_write,
++                                       sizeof(struct ct_iter_state), NULL);
+       if (!pde)
+               goto out_nf_conntrack;
diff --git a/target/linux/generic/pending-6.12/610-netfilter_match_bypass_default_checks.patch b/target/linux/generic/pending-6.12/610-netfilter_match_bypass_default_checks.patch
new file mode 100644 (file)
index 0000000..fd22200
--- /dev/null
@@ -0,0 +1,110 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: kernel: add a new version of my netfilter speedup patches for linux 2.6.39 and 3.0
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ include/uapi/linux/netfilter_ipv4/ip_tables.h |  1 +
+ net/ipv4/netfilter/ip_tables.c                | 37 +++++++++++++++++++++++++++
+ 2 files changed, 38 insertions(+)
+
+--- a/include/uapi/linux/netfilter_ipv4/ip_tables.h
++++ b/include/uapi/linux/netfilter_ipv4/ip_tables.h
+@@ -89,6 +89,7 @@ struct ipt_ip {
+ #define IPT_F_FRAG            0x01    /* Set if rule is a fragment rule */
+ #define IPT_F_GOTO            0x02    /* Set if jump is a goto */
+ #define IPT_F_MASK            0x03    /* All possible flag bits mask. */
++#define IPT_F_NO_DEF_MATCH    0x80    /* Internal: no default match rules present */
+ /* Values for "inv" field in struct ipt_ip. */
+ #define IPT_INV_VIA_IN                0x01    /* Invert the sense of IN IFACE. */
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -48,6 +48,9 @@ ip_packet_match(const struct iphdr *ip,
+ {
+       unsigned long ret;
++      if (ipinfo->flags & IPT_F_NO_DEF_MATCH)
++              return true;
++
+       if (NF_INVF(ipinfo, IPT_INV_SRCIP,
+                   (ip->saddr & ipinfo->smsk.s_addr) != ipinfo->src.s_addr) ||
+           NF_INVF(ipinfo, IPT_INV_DSTIP,
+@@ -78,6 +81,29 @@ ip_packet_match(const struct iphdr *ip,
+       return true;
+ }
++static void
++ip_checkdefault(struct ipt_ip *ip)
++{
++      static const char iface_mask[IFNAMSIZ] = {};
++
++      if (ip->invflags || ip->flags & IPT_F_FRAG)
++              return;
++
++      if (memcmp(ip->iniface_mask, iface_mask, IFNAMSIZ) != 0)
++              return;
++
++      if (memcmp(ip->outiface_mask, iface_mask, IFNAMSIZ) != 0)
++              return;
++
++      if (ip->smsk.s_addr || ip->dmsk.s_addr)
++              return;
++
++      if (ip->proto)
++              return;
++
++      ip->flags |= IPT_F_NO_DEF_MATCH;
++}
++
+ static bool
+ ip_checkentry(const struct ipt_ip *ip)
+ {
+@@ -523,6 +549,8 @@ find_check_entry(struct ipt_entry *e, st
+       struct xt_mtchk_param mtpar;
+       struct xt_entry_match *ematch;
++      ip_checkdefault(&e->ip);
++
+       if (!xt_percpu_counter_alloc(alloc_state, &e->counters))
+               return -ENOMEM;
+@@ -817,6 +845,7 @@ copy_entries_to_user(unsigned int total_
+       const struct xt_table_info *private = table->private;
+       int ret = 0;
+       const void *loc_cpu_entry;
++      u8 flags;
+       counters = alloc_counters(table);
+       if (IS_ERR(counters))
+@@ -844,6 +873,14 @@ copy_entries_to_user(unsigned int total_
+                       goto free_counters;
+               }
++              flags = e->ip.flags & IPT_F_MASK;
++              if (copy_to_user(userptr + off
++                               + offsetof(struct ipt_entry, ip.flags),
++                               &flags, sizeof(flags)) != 0) {
++                      ret = -EFAULT;
++                      goto free_counters;
++              }
++
+               for (i = sizeof(struct ipt_entry);
+                    i < e->target_offset;
+                    i += m->u.match_size) {
+@@ -1225,12 +1262,15 @@ compat_copy_entry_to_user(struct ipt_ent
+       compat_uint_t origsize;
+       const struct xt_entry_match *ematch;
+       int ret = 0;
++      u8 flags = e->ip.flags & IPT_F_MASK;
+       origsize = *size;
+       ce = *dstptr;
+       if (copy_to_user(ce, e, sizeof(struct ipt_entry)) != 0 ||
+           copy_to_user(&ce->counters, &counters[i],
+-          sizeof(counters[i])) != 0)
++          sizeof(counters[i])) != 0 ||
++          copy_to_user(&ce->ip.flags, &flags,
++          sizeof(flags)) != 0)
+               return -EFAULT;
+       *dstptr += sizeof(struct compat_ipt_entry);
diff --git a/target/linux/generic/pending-6.12/611-netfilter_match_bypass_default_table.patch b/target/linux/generic/pending-6.12/611-netfilter_match_bypass_default_table.patch
new file mode 100644 (file)
index 0000000..9f0efe4
--- /dev/null
@@ -0,0 +1,106 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: netfilter: match bypass default table
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ net/ipv4/netfilter/ip_tables.c | 79 +++++++++++++++++++++++++++++++-----------
+ 1 file changed, 58 insertions(+), 21 deletions(-)
+
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -244,6 +244,33 @@ struct ipt_entry *ipt_next_entry(const s
+       return (void *)entry + entry->next_offset;
+ }
++static bool
++ipt_handle_default_rule(struct ipt_entry *e, unsigned int *verdict)
++{
++      struct xt_entry_target *t;
++      struct xt_standard_target *st;
++
++      if (e->target_offset != sizeof(struct ipt_entry))
++              return false;
++
++      if (!(e->ip.flags & IPT_F_NO_DEF_MATCH))
++              return false;
++
++      t = ipt_get_target(e);
++      if (t->u.kernel.target->target)
++              return false;
++
++      st = (struct xt_standard_target *) t;
++      if (st->verdict == XT_RETURN)
++              return false;
++
++      if (st->verdict >= 0)
++              return false;
++
++      *verdict = (unsigned)(-st->verdict) - 1;
++      return true;
++}
++
+ /* Returns one of the generic firewall policies, like NF_ACCEPT. */
+ unsigned int
+ ipt_do_table(void *priv,
+@@ -265,27 +292,28 @@ ipt_do_table(void *priv,
+       unsigned int addend;
+       /* Initialization */
++      WARN_ON(!(table->valid_hooks & (1 << hook)));
++      local_bh_disable();
++      private = READ_ONCE(table->private); /* Address dependency. */
++      cpu        = smp_processor_id();
++      table_base = private->entries;
++
++      e = get_entry(table_base, private->hook_entry[hook]);
++      if (ipt_handle_default_rule(e, &verdict)) {
++              struct xt_counters *counter;
++
++              counter = xt_get_this_cpu_counter(&e->counters);
++              ADD_COUNTER(*counter, skb->len, 1);
++              local_bh_enable();
++              return verdict;
++      }
++
+       stackidx = 0;
+       ip = ip_hdr(skb);
+       indev = state->in ? state->in->name : nulldevname;
+       outdev = state->out ? state->out->name : nulldevname;
+-      /* We handle fragments by dealing with the first fragment as
+-       * if it was a normal packet.  All other fragments are treated
+-       * normally, except that they will NEVER match rules that ask
+-       * things we don't know, ie. tcp syn flag or ports).  If the
+-       * rule is also a fragment-specific rule, non-fragments won't
+-       * match it. */
+-      acpar.fragoff = ntohs(ip->frag_off) & IP_OFFSET;
+-      acpar.thoff   = ip_hdrlen(skb);
+-      acpar.hotdrop = false;
+-      acpar.state   = state;
+-      WARN_ON(!(table->valid_hooks & (1 << hook)));
+-      local_bh_disable();
+       addend = xt_write_recseq_begin();
+-      private = READ_ONCE(table->private); /* Address dependency. */
+-      cpu        = smp_processor_id();
+-      table_base = private->entries;
+       jumpstack  = (struct ipt_entry **)private->jumpstack[cpu];
+       /* Switch to alternate jumpstack if we're being invoked via TEE.
+@@ -298,7 +326,16 @@ ipt_do_table(void *priv,
+       if (static_key_false(&xt_tee_enabled))
+               jumpstack += private->stacksize * __this_cpu_read(nf_skb_duplicated);
+-      e = get_entry(table_base, private->hook_entry[hook]);
++      /* We handle fragments by dealing with the first fragment as
++       * if it was a normal packet.  All other fragments are treated
++       * normally, except that they will NEVER match rules that ask
++       * things we don't know, ie. tcp syn flag or ports).  If the
++       * rule is also a fragment-specific rule, non-fragments won't
++       * match it. */
++      acpar.fragoff = ntohs(ip->frag_off) & IP_OFFSET;
++      acpar.thoff   = ip_hdrlen(skb);
++      acpar.hotdrop = false;
++      acpar.state   = state;
+       do {
+               const struct xt_entry_target *t;
diff --git a/target/linux/generic/pending-6.12/612-netfilter_match_reduce_memory_access.patch b/target/linux/generic/pending-6.12/612-netfilter_match_reduce_memory_access.patch
new file mode 100644 (file)
index 0000000..7f291fc
--- /dev/null
@@ -0,0 +1,22 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: netfilter: reduce match memory access
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ net/ipv4/netfilter/ip_tables.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -51,9 +51,9 @@ ip_packet_match(const struct iphdr *ip,
+       if (ipinfo->flags & IPT_F_NO_DEF_MATCH)
+               return true;
+-      if (NF_INVF(ipinfo, IPT_INV_SRCIP,
++      if (NF_INVF(ipinfo, IPT_INV_SRCIP, ipinfo->smsk.s_addr &&
+                   (ip->saddr & ipinfo->smsk.s_addr) != ipinfo->src.s_addr) ||
+-          NF_INVF(ipinfo, IPT_INV_DSTIP,
++          NF_INVF(ipinfo, IPT_INV_DSTIP, ipinfo->dmsk.s_addr &&
+                   (ip->daddr & ipinfo->dmsk.s_addr) != ipinfo->dst.s_addr))
+               return false;
diff --git a/target/linux/generic/pending-6.12/620-net-sfp-improve-Huawei-MA5671a-fixup.patch b/target/linux/generic/pending-6.12/620-net-sfp-improve-Huawei-MA5671a-fixup.patch
new file mode 100644 (file)
index 0000000..dd0a4a5
--- /dev/null
@@ -0,0 +1,149 @@
+From patchwork Fri Mar  6 12:29:55 2026
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 8bit
+X-Patchwork-Submitter: =?utf-8?q?=C3=81lvaro_Fern=C3=A1ndez_Rojas?=
+ <noltari@gmail.com>
+X-Patchwork-Id: 14457090
+X-Patchwork-Delegate: kuba@kernel.org
+Received: from mail-wr1-f48.google.com (mail-wr1-f48.google.com
+ [209.85.221.48])
+       (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits))
+       (No client certificate requested)
+       by smtp.subspace.kernel.org (Postfix) with ESMTPS id AF3F4386459
+       for <netdev@vger.kernel.org>; Fri,  6 Mar 2026 12:52:18 +0000 (UTC)
+Authentication-Results: smtp.subspace.kernel.org;
+ arc=none smtp.client-ip=209.85.221.48
+ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;
+       t=1772801540; cv=none;
+ b=LVeywxv8ajenPZ8Kr1arieKosbrf60O9l+ouIPKPFNt5btxWDZ59pIU9BfZjv5n9ifEOyUA/UD0phxnG77+oB/k6UCd7DdGQQASZB3NHq5cvmErbgXm0XG3C8BBxVXU5pF7atPS23kBqM9ptxsv3IaeH/fDFcj6k6SH61rGEpuQ=
+ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org;
+       s=arc-20240116; t=1772801540; c=relaxed/simple;
+       bh=HAy43ssDo0xlUcBDIU7vQZtNnpxG03JPCL6Ldi51ASI=;
+       h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type;
+ b=OBk8kI0I91psFRaIxb6nCnAzQlsc7jrXkOPW8lL7cYCosY08yfQDwAlWBFfdFs/VDuVJjD5VEdeQeMt2K4kWGgjLNXhTrRqgs6JNe7PxALDJKvt+kcJ833TRz3hKl2eb2Ft6WnKPf/6hp5Q3qm8+/Q703ixD4sF/0aDNw1BrDY4=
+ARC-Authentication-Results: i=1; smtp.subspace.kernel.org;
+ dmarc=pass (p=none dis=none) header.from=gmail.com;
+ spf=pass smtp.mailfrom=gmail.com;
+ dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com
+ header.b=RCEse1HL; arc=none smtp.client-ip=209.85.221.48
+Authentication-Results: smtp.subspace.kernel.org;
+ dmarc=pass (p=none dis=none) header.from=gmail.com
+Authentication-Results: smtp.subspace.kernel.org;
+ spf=pass smtp.mailfrom=gmail.com
+Authentication-Results: smtp.subspace.kernel.org;
+       dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com
+ header.b="RCEse1HL"
+Received: by mail-wr1-f48.google.com with SMTP id
+ ffacd0b85a97d-439b7c2788dso4008389f8f.1
+        for <netdev@vger.kernel.org>; Fri, 06 Mar 2026 04:52:18 -0800 (PST)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=20230601; t=1772801537; x=1773406337;
+ darn=vger.kernel.org;
+        h=content-transfer-encoding:mime-version:message-id:date:subject:cc
+         :to:from:from:to:cc:subject:date:message-id:reply-to;
+        bh=y8B8kg8ACcCsMXy3SgsyRYngVEpIsqkcoCsLOS/nNqQ=;
+        b=RCEse1HLoUtQApOdbPXFvYItGrEKWhMZ5FH1L4npAxteGeWOhAEAekijg3Ur83ovNu
+         D7j0Aio5nwazNQz3y4rO88a+svlEbLx5fyxypjkMFUV4PDnOpv7HYjT9Aw1NVdIwO6l+
+         sTgZ1jssfWdVnLQwQe6naotyBRoBV2AugdTmASE0Okxrsi3juIOafyTCxnp4K0weRpaH
+         XodiSWNrkHzZSWM6/wl3D42yExGGPiuDybF+9otR/5TaBWNzrcLkSb73hvP6va35kQWK
+         mnp6OV+L7iHTbxYpTfTm4axD+IZ/Q/dtFxxA6XolA28oMQbRPK0SIHepheSZx4bgl64w
+         FM4w==
+X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=1e100.net; s=20230601; t=1772801537; x=1773406337;
+        h=content-transfer-encoding:mime-version:message-id:date:subject:cc
+         :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date
+         :message-id:reply-to;
+        bh=y8B8kg8ACcCsMXy3SgsyRYngVEpIsqkcoCsLOS/nNqQ=;
+        b=KomubXrbvHQI4WbFxBztyfrvNNRRWm7V46yQSwx0bP8PXKIJP38kAYzK+ZKWhmcd7e
+         LpS7422VcYyLywLRxlevD2YaXsF0CK6e00YpTtixakHxYs/4KxGaU21vfwYV8mRhfu7g
+         HVmxKvNQ6DTdC7wAIGT6TrcZCK4VCvgCx3z9yC62hQc8C6w+9mDnnGPvXNR74ofvvXdC
+         eVZjm56layRoEr4PTpR2F33OVSt8+HRikH7eBzIKtQ5n/lEKtmJKDHRaodAaCyFGWMWa
+         qDVoOR8VI4NIJABfsOT6OqisXLPLf+jkKpGkCY2ioRPRKK9GzW4PgIuNcKvPQilQQkgD
+         Xlnw==
+X-Forwarded-Encrypted: i=1;
+ AJvYcCVcziiSg1n0cDakmiQXH3869FECP24dcIqrZzs8zKakP+vHT958hnq9Bp0alDnLeVtXgo0B8T4=@vger.kernel.org
+X-Gm-Message-State: AOJu0Yx2OF1e3PiuR4Zqpe9qXA6kz6T2CCtro6kv8eL2j4Zh2HCjWywo
+       /rZTavazOZRoq7zTvc4fGZ/yupjkTT9xRPZCKRkM9pc0UuK/KDSP4pan
+X-Gm-Gg: ATEYQzx75s3OlYg8XKMgu042++2+ZPa/CZDw09DYtnwEHHBsuylQF0+eXzcFM166JtP
+       EMuM6Nq/sGQx2WNTPNEyu1BRGci/SV005CzkExhd1KK52D/nC1c76MBxvAtioaI/+5tgNoyCg8v
+       ZFRyiqDReKfJ6JHa3YRI213dTzMluN1sZTYNSqlWI1MwW66gaDCf0myU81ehAfiAff34wmxnm8C
+       PUF0YrLYtgZl1I/ZcYM1npoL3PBOnrhaulSqhbn7S5NaZMkHLrNQm6ns1lof+7Ciju05dQpEcBe
+       pumVg15Dy+PcSXQSSQt4CULH7bbuJvZ0PHJ7dS+74i/OqFSgxD4E7LCqM5ufHYdbESx0/ERaR/z
+       CAyT3oTz6S1oMQCUTPevHjHjTbDOWhu74SqyTZETzwGnjZnfrPMa56ebQVRfYgOYW0bbx6j3O2M
+       v3CSEBiXpdFTdaLuRcqIb56JeDryaHx87SOThqnYP6gMiu7EljKYIhr572Rpgz+UIRkrYyNjL9c
+       BLmrcmhsXX4hU4X5KocoApkO04w
+X-Received: by 2002:a05:600c:8b8b:b0:483:103c:b1ee with SMTP id
+ 5b1f17b1804b1-48526922599mr34173745e9.8.1772801536778;
+        Fri, 06 Mar 2026 04:52:16 -0800 (PST)
+Received: from skynet.lan
+ (2a02-9142-4581-3c00-0000-0000-0000-0008.red-2a02-914.customerbaf.ipv6.rima-tde.net.
+ [2a02:9142:4581:3c00::8])
+        by smtp.gmail.com with ESMTPSA id
+ 5b1f17b1804b1-48527681a3esm76085715e9.4.2026.03.06.04.52.14
+        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
+        Fri, 06 Mar 2026 04:52:15 -0800 (PST)
+From: =?utf-8?q?=C3=81lvaro_Fern=C3=A1ndez_Rojas?= <noltari@gmail.com>
+To: linux@armlinux.org.uk,
+       andrew@lunn.ch,
+       hkallweit1@gmail.com,
+       davem@davemloft.net,
+       edumazet@google.com,
+       kuba@kernel.org,
+       pabeni@redhat.com,
+       mnhagan88@gmail.com,
+       netdev@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+Cc: =?utf-8?q?=C3=81lvaro_Fern=C3=A1ndez_Rojas?= <noltari@gmail.com>
+Subject: [PATCH net v3] net: sfp: improve Huawei MA5671a fixup
+Date: Fri,  6 Mar 2026 13:29:55 +0100
+Message-ID: <20260306125139.213637-1-noltari@gmail.com>
+X-Mailer: git-send-email 2.47.3
+Precedence: bulk
+X-Mailing-List: netdev@vger.kernel.org
+List-Id: <netdev.vger.kernel.org>
+List-Subscribe: <mailto:netdev+subscribe@vger.kernel.org>
+List-Unsubscribe: <mailto:netdev+unsubscribe@vger.kernel.org>
+MIME-Version: 1.0
+X-Patchwork-Delegate: kuba@kernel.org
+
+With the current sfp_fixup_ignore_tx_fault() fixup we ignore the TX_FAULT
+signal, but we also need to apply sfp_fixup_ignore_los() in order to be
+able to communicate with the module even if the fiber isn't connected for
+configuration purposes.
+This is needed for all the MA5671a firmwares, excluding the FS modded
+firmware.
+
+Fixes: 2069624dac19 ("net: sfp: Add tx-fault workaround for Huawei MA5671A SFP ONT")
+Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+---
+ v3: avoid using a vendor name in the function
+ v2: rebase on top of net/main instead of linux/master
+
+ drivers/net/phy/sfp.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -360,6 +360,12 @@ static void sfp_fixup_ignore_tx_fault(st
+       sfp->state_ignore_mask |= SFP_F_TX_FAULT;
+ }
++static void sfp_fixup_ignore_tx_fault_and_los(struct sfp *sfp)
++{
++      sfp_fixup_ignore_tx_fault(sfp);
++      sfp_fixup_ignore_los(sfp);
++}
++
+ static void sfp_fixup_ignore_hw(struct sfp *sfp, unsigned int mask)
+ {
+       sfp->state_hw_mask &= ~mask;
+@@ -523,7 +529,7 @@ static const struct sfp_quirk sfp_quirks
+       // Huawei MA5671A can operate at 2500base-X, but report 1.2GBd NRZ in
+       // their EEPROM
+       SFP_QUIRK("HUAWEI", "MA5671A", sfp_quirk_2500basex,
+-                sfp_fixup_ignore_tx_fault),
++                sfp_fixup_ignore_tx_fault_and_los),
+       // Lantech 8330-262D-E and 8330-265D can operate at 2500base-X, but
+       // incorrectly report 2500MBd NRZ in their EEPROM.
diff --git a/target/linux/generic/pending-6.12/630-packet_socket_type.patch b/target/linux/generic/pending-6.12/630-packet_socket_type.patch
new file mode 100644 (file)
index 0000000..769696e
--- /dev/null
@@ -0,0 +1,138 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: net: add an optimization for dealing with raw sockets
+
+lede-commit: 4898039703d7315f0f3431c860123338ec3be0f6
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ include/uapi/linux/if_packet.h |  3 +++
+ net/packet/af_packet.c         | 34 +++++++++++++++++++++++++++-------
+ net/packet/internal.h          |  1 +
+ 3 files changed, 31 insertions(+), 7 deletions(-)
+
+--- a/include/uapi/linux/if_packet.h
++++ b/include/uapi/linux/if_packet.h
+@@ -33,6 +33,8 @@ struct sockaddr_ll {
+ #define PACKET_KERNEL         7               /* To kernel space      */
+ /* Unused, PACKET_FASTROUTE and PACKET_LOOPBACK are invisible to user space */
+ #define PACKET_FASTROUTE      6               /* Fastrouted frame     */
++#define PACKET_MASK_ANY               0xffffffff      /* mask for packet type bits */
++
+ /* Packet socket options */
+@@ -60,6 +62,7 @@ struct sockaddr_ll {
+ #define PACKET_FANOUT_DATA            22
+ #define PACKET_IGNORE_OUTGOING                23
+ #define PACKET_VNET_HDR_SZ            24
++#define PACKET_RECV_TYPE              25
+ #define PACKET_FANOUT_HASH            0
+ #define PACKET_FANOUT_LB              1
+--- a/net/packet/af_packet.c
++++ b/net/packet/af_packet.c
+@@ -1911,6 +1911,7 @@ static int packet_rcv_spkt(struct sk_buf
+ {
+       struct sock *sk;
+       struct sockaddr_pkt *spkt;
++      struct packet_sock *po;
+       /*
+        *      When we registered the protocol we saved the socket in the data
+@@ -1918,6 +1919,7 @@ static int packet_rcv_spkt(struct sk_buf
+        */
+       sk = pt->af_packet_priv;
++      po = pkt_sk(sk);
+       /*
+        *      Yank back the headers [hope the device set this
+@@ -1930,7 +1932,7 @@ static int packet_rcv_spkt(struct sk_buf
+        *      so that this procedure is noop.
+        */
+-      if (skb->pkt_type == PACKET_LOOPBACK)
++      if (!(po->pkt_type & (1 << skb->pkt_type)))
+               goto out;
+       if (!net_eq(dev_net(dev), sock_net(sk)))
+@@ -2175,12 +2177,12 @@ static int packet_rcv(struct sk_buff *sk
+       int skb_len = skb->len;
+       unsigned int snaplen, res;
+-      if (skb->pkt_type == PACKET_LOOPBACK)
+-              goto drop;
+-
+       sk = pt->af_packet_priv;
+       po = pkt_sk(sk);
++      if (!(po->pkt_type & (1 << skb->pkt_type)))
++              goto drop;
++
+       if (!net_eq(dev_net(dev), sock_net(sk)))
+               goto drop;
+@@ -2304,12 +2306,12 @@ static int tpacket_rcv(struct sk_buff *s
+       BUILD_BUG_ON(TPACKET_ALIGN(sizeof(*h.h2)) != 32);
+       BUILD_BUG_ON(TPACKET_ALIGN(sizeof(*h.h3)) != 48);
+-      if (skb->pkt_type == PACKET_LOOPBACK)
+-              goto drop;
+-
+       sk = pt->af_packet_priv;
+       po = pkt_sk(sk);
++      if (!(po->pkt_type & (1 << skb->pkt_type)))
++              goto drop;
++
+       if (!net_eq(dev_net(dev), sock_net(sk)))
+               goto drop;
+@@ -3429,6 +3431,7 @@ static int packet_create(struct net *net
+       mutex_init(&po->pg_vec_lock);
+       po->rollover = NULL;
+       po->prot_hook.func = packet_rcv;
++      po->pkt_type = PACKET_MASK_ANY & ~(1 << PACKET_LOOPBACK);
+       if (sock->type == SOCK_PACKET)
+               po->prot_hook.func = packet_rcv_spkt;
+@@ -4096,6 +4099,16 @@ packet_setsockopt(struct socket *sock, i
+               packet_sock_flag_set(po, PACKET_SOCK_QDISC_BYPASS, val);
+               return 0;
+       }
++      case PACKET_RECV_TYPE:
++      {
++              unsigned int val;
++              if (optlen != sizeof(val))
++                      return -EINVAL;
++              if (copy_from_sockptr(&val, optval, sizeof(val)))
++                      return -EFAULT;
++              po->pkt_type = val & ~BIT(PACKET_LOOPBACK);
++              return 0;
++      }
+       default:
+               return -ENOPROTOOPT;
+       }
+@@ -4158,6 +4171,13 @@ static int packet_getsockopt(struct sock
+       case PACKET_COPY_THRESH:
+               val = READ_ONCE(pkt_sk(sk)->copy_thresh);
+               break;
++      case PACKET_RECV_TYPE:
++              if (len > sizeof(unsigned int))
++                      len = sizeof(unsigned int);
++              val = po->pkt_type;
++
++              data = &val;
++              break;
+       case PACKET_VERSION:
+               val = po->tp_version;
+               break;
+--- a/net/packet/internal.h
++++ b/net/packet/internal.h
+@@ -131,6 +131,7 @@ struct packet_sock {
+       struct net_device __rcu *cached_dev;
+       struct packet_type      prot_hook ____cacheline_aligned_in_smp;
+       atomic_t                tp_drops ____cacheline_aligned_in_smp;
++      unsigned int            pkt_type;
+ };
+ #define pkt_sk(ptr) container_of_const(ptr, struct packet_sock, sk)
diff --git a/target/linux/generic/pending-6.12/640-net-bridge-fix-switchdev-host-mdb-entry-updates.patch b/target/linux/generic/pending-6.12/640-net-bridge-fix-switchdev-host-mdb-entry-updates.patch
new file mode 100644 (file)
index 0000000..8271e06
--- /dev/null
@@ -0,0 +1,42 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 22 Aug 2024 18:02:17 +0200
+Subject: [PATCH] net: bridge: fix switchdev host mdb entry updates
+
+When a mdb entry is removed, the bridge switchdev code can issue a
+switchdev_port_obj_del call for a port that was not offloaded.
+
+This leads to an imbalance in switchdev_port_obj_add/del calls, since
+br_switchdev_mdb_replay has not been called for the port before.
+
+This can lead to potential multicast forwarding issues and messages such as:
+mt7915e 0000:01:00.0 wl1-ap0: Failed to del Host Multicast Database entry
+       (object id=3) with error: -ENOENT (-2).
+
+Fix this issue by checking the port offload status when iterating over
+lower devs.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/bridge/br_switchdev.c
++++ b/net/bridge/br_switchdev.c
+@@ -571,10 +571,18 @@ static void br_switchdev_host_mdb(struct
+                                 struct net_bridge_mdb_entry *mp, int type)
+ {
+       struct net_device *lower_dev;
++      struct net_bridge_port *port;
+       struct list_head *iter;
+-      netdev_for_each_lower_dev(dev, lower_dev, iter)
++      rcu_read_lock();
++      netdev_for_each_lower_dev(dev, lower_dev, iter) {
++              port = br_port_get_rcu(lower_dev);
++              if (!port || !port->offload_count)
++                      continue;
++
+               br_switchdev_host_mdb_one(dev, lower_dev, mp, type);
++      }
++      rcu_read_unlock();
+ }
+ static int
diff --git a/target/linux/generic/pending-6.12/641-net-bridge-switchdev-Don-t-drop-packets-between-port.patch b/target/linux/generic/pending-6.12/641-net-bridge-switchdev-Don-t-drop-packets-between-port.patch
new file mode 100644 (file)
index 0000000..531e1c9
--- /dev/null
@@ -0,0 +1,38 @@
+From: "Leon M. Busch-George" <leon@georgemail.eu>
+Date: Sun, 20 Oct 2024 18:20:14 +0200
+Subject: [PATCH] net: bridge: switchdev: Don't drop packets between ports with
+ no hwdom
+
+nbp_switchdev_allowed_egress uses hwdom to determine whether or not a
+packet has already been forwarded to a hardware domain. For
+net_bridge_ports that aren't set up to use forward offloading, hwdom is
+set to 0. When both ingress and egress port have no hwdom,
+'cb->src_hwdom != p->hwdom' indicates that the packet is already known in
+the target domain - which it isn't - and the packet is wrongly dropped.
+
+The error was found on a bridge containing a wifi device and a VLAN
+tagging device (e.g. eth0.12). With VLAN filtering, this shouldn't happen.
+
+This patch adds a check for p->hwdom != 0 before comparing hardware
+domains to restore forwarding between ports with hwdom = 0.
+
+fwd_hwdoms are only set for ports with offloading enabled, which also
+implies a valid hwdom, so the check '!test_bit(p->hwdom, &cb->fwd_hwdoms)'
+doesn't fail in this way (yet - fingers crossed..) and it is left in place.
+
+Co-developed-by: Felix Fietkau <nbd@nbd.name>
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+Signed-off-by: Leon M. Busch-George <leon@georgemail.eu>
+---
+
+--- a/net/bridge/br_switchdev.c
++++ b/net/bridge/br_switchdev.c
+@@ -70,7 +70,7 @@ bool nbp_switchdev_allowed_egress(const
+       struct br_input_skb_cb *cb = BR_INPUT_SKB_CB(skb);
+       return !test_bit(p->hwdom, &cb->fwd_hwdoms) &&
+-              (!skb->offload_fwd_mark || cb->src_hwdom != p->hwdom);
++              (!skb->offload_fwd_mark || !p->hwdom || cb->src_hwdom != p->hwdom);
+ }
+ /* Flags that can be offloaded to hardware */
diff --git a/target/linux/generic/pending-6.12/650-net-pppoe-implement-GRO-support.patch b/target/linux/generic/pending-6.12/650-net-pppoe-implement-GRO-support.patch
new file mode 100644 (file)
index 0000000..2aa55f8
--- /dev/null
@@ -0,0 +1,248 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 15 Jul 2025 12:37:45 +0200
+Subject: [PATCH] net: pppoe: implement GRO support
+
+Only handles packets where the pppoe header length field matches the exact
+packet length. Significantly improves rx throughput.
+
+When running NAT traffic through a MediaTek MT7621 devices from a host
+behind PPPoE to a host directly connected via ethernet, the TCP throughput
+that the device is able to handle improves from ~130 Mbit/s to ~630 Mbit/s,
+using fraglist GRO.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/drivers/net/ppp/pppoe.c
++++ b/drivers/net/ppp/pppoe.c
+@@ -77,6 +77,7 @@
+ #include <net/net_namespace.h>
+ #include <net/netns/generic.h>
+ #include <net/sock.h>
++#include <net/gro.h>
+ #include <linux/uaccess.h>
+@@ -434,7 +435,7 @@ static int pppoe_rcv(struct sk_buff *skb
+       if (skb->len < len)
+               goto drop;
+-      if (pskb_trim_rcsum(skb, len))
++      if (!skb_is_gso(skb) && pskb_trim_rcsum(skb, len))
+               goto drop;
+       ph = pppoe_hdr(skb);
+@@ -1176,6 +1177,161 @@ static struct pernet_operations pppoe_ne
+       .size = sizeof(struct pppoe_net),
+ };
++static u16
++compare_pppoe_header(struct pppoe_hdr *phdr, struct pppoe_hdr *phdr2)
++{
++      return (__force __u16)((phdr->sid ^ phdr2->sid) |
++                             (phdr->tag[0].tag_type ^ phdr2->tag[0].tag_type));
++}
++
++static __be16 pppoe_hdr_proto(struct pppoe_hdr *phdr)
++{
++      switch (phdr->tag[0].tag_type) {
++      case cpu_to_be16(PPP_IP):
++              return cpu_to_be16(ETH_P_IP);
++      case cpu_to_be16(PPP_IPV6):
++              return cpu_to_be16(ETH_P_IPV6);
++      default:
++              return 0;
++      }
++
++}
++
++static struct sk_buff *pppoe_gro_receive(struct list_head *head,
++                                       struct sk_buff *skb)
++{
++      const struct packet_offload *ptype;
++      unsigned int hlen, off_pppoe;
++      struct sk_buff *pp = NULL;
++      struct pppoe_hdr *phdr;
++      struct sk_buff *p;
++      int flush = 1;
++      __be16 type;
++
++      off_pppoe = skb_gro_offset(skb);
++      hlen = off_pppoe + sizeof(*phdr);
++      phdr = skb_gro_header(skb, hlen + 2, off_pppoe);
++      if (unlikely(!phdr))
++              goto out;
++
++      /* ignore packets with padding or invalid length */
++      if (skb_gro_len(skb) != be16_to_cpu(phdr->length) + hlen)
++              goto out;
++
++      type = pppoe_hdr_proto(phdr);
++      if (!type)
++              goto out;
++
++      ptype = gro_find_receive_by_type(type);
++      if (!ptype)
++              goto out;
++
++      flush = 0;
++
++      list_for_each_entry(p, head, list) {
++              struct pppoe_hdr *phdr2;
++
++              if (!NAPI_GRO_CB(p)->same_flow)
++                      continue;
++
++              phdr2 = (struct pppoe_hdr *)(p->data + off_pppoe);
++              if (compare_pppoe_header(phdr, phdr2))
++                      NAPI_GRO_CB(p)->same_flow = 0;
++      }
++
++      skb_gro_pull(skb, sizeof(*phdr) + 2);
++      skb_gro_postpull_rcsum(skb, phdr, sizeof(*phdr) + 2);
++
++      pp = indirect_call_gro_receive_inet(ptype->callbacks.gro_receive,
++                                          ipv6_gro_receive, inet_gro_receive,
++                                          head, skb);
++
++out:
++      skb_gro_flush_final(skb, pp, flush);
++
++      return pp;
++}
++
++static int pppoe_gro_complete(struct sk_buff *skb, int nhoff)
++{
++      struct pppoe_hdr *phdr = (struct pppoe_hdr *)(skb->data + nhoff);
++      __be16 type = pppoe_hdr_proto(phdr);
++      struct packet_offload *ptype;
++      int len, err;
++
++      ptype = gro_find_complete_by_type(type);
++      if (!ptype)
++              return -ENOENT;
++
++      err = INDIRECT_CALL_INET(ptype->callbacks.gro_complete,
++                               ipv6_gro_complete, inet_gro_complete,
++                               skb, nhoff + sizeof(*phdr) + 2);
++      if (err)
++              return err;
++
++      len = skb->len - (nhoff + sizeof(*phdr));
++      phdr->length = cpu_to_be16(len);
++
++      return 0;
++}
++
++static struct sk_buff *pppoe_gso_segment(struct sk_buff *skb,
++                                       netdev_features_t features)
++{
++      unsigned int pppoe_hlen = sizeof(struct pppoe_hdr) + 2;
++      struct sk_buff *segs = ERR_PTR(-EINVAL);
++      u16 mac_offset = skb->mac_header;
++      struct packet_offload *ptype;
++      u16 mac_len = skb->mac_len;
++      struct pppoe_hdr *phdr;
++      __be16 orig_type, type;
++      int len, nhoff;
++
++      skb_reset_network_header(skb);
++      nhoff = skb_network_header(skb) - skb_mac_header(skb);
++
++      if (unlikely(!pskb_may_pull(skb, pppoe_hlen)))
++              goto out;
++
++      phdr = (struct pppoe_hdr *)skb_network_header(skb);
++      type = pppoe_hdr_proto(phdr);
++      ptype = gro_find_complete_by_type(type);
++      if (!ptype)
++              goto out;
++
++      orig_type = skb->protocol;
++      __skb_pull(skb, pppoe_hlen);
++      segs = ptype->callbacks.gso_segment(skb, features);
++      if (IS_ERR_OR_NULL(segs)) {
++              skb_gso_error_unwind(skb, orig_type, pppoe_hlen, mac_offset,
++                                   mac_len);
++              goto out;
++      }
++
++      skb = segs;
++      do {
++              phdr = (struct pppoe_hdr *)(skb_mac_header(skb) + nhoff);
++              len = skb->len - (nhoff + sizeof(*phdr));
++              phdr->length = cpu_to_be16(len);
++              skb->network_header = (u8 *)phdr - skb->head;
++              skb->protocol = orig_type;
++              skb_reset_mac_len(skb);
++      } while ((skb = skb->next));
++
++out:
++      return segs;
++}
++
++static struct packet_offload pppoe_packet_offload __read_mostly = {
++      .type = cpu_to_be16(ETH_P_PPP_SES),
++      .priority = 20,
++      .callbacks = {
++              .gro_receive = pppoe_gro_receive,
++              .gro_complete = pppoe_gro_complete,
++              .gso_segment = pppoe_gso_segment,
++      },
++};
++
+ static int __init pppoe_init(void)
+ {
+       int err;
+@@ -1192,6 +1348,7 @@ static int __init pppoe_init(void)
+       if (err)
+               goto out_unregister_pppoe_proto;
++      dev_add_offload(&pppoe_packet_offload);
+       dev_add_pack(&pppoes_ptype);
+       dev_add_pack(&pppoed_ptype);
+       register_netdevice_notifier(&pppoe_notifier);
+@@ -1211,6 +1368,7 @@ static void __exit pppoe_exit(void)
+       unregister_netdevice_notifier(&pppoe_notifier);
+       dev_remove_pack(&pppoed_ptype);
+       dev_remove_pack(&pppoes_ptype);
++      dev_remove_offload(&pppoe_packet_offload);
+       unregister_pppox_proto(PX_PROTO_OE);
+       proto_unregister(&pppoe_sk_proto);
+       unregister_pernet_device(&pppoe_net_ops);
+--- a/net/ipv4/af_inet.c
++++ b/net/ipv4/af_inet.c
+@@ -1546,6 +1546,7 @@ out:
+       return pp;
+ }
++EXPORT_INDIRECT_CALLABLE(inet_gro_receive);
+ static struct sk_buff *ipip_gro_receive(struct list_head *head,
+                                       struct sk_buff *skb)
+@@ -1631,6 +1632,7 @@ int inet_gro_complete(struct sk_buff *sk
+ out:
+       return err;
+ }
++EXPORT_INDIRECT_CALLABLE(inet_gro_complete);
+ static int ipip_gro_complete(struct sk_buff *skb, int nhoff)
+ {
+--- a/net/ipv6/ip6_offload.c
++++ b/net/ipv6/ip6_offload.c
+@@ -306,6 +306,7 @@ out:
+       return pp;
+ }
++EXPORT_INDIRECT_CALLABLE(ipv6_gro_receive);
+ static struct sk_buff *sit_ip6ip6_gro_receive(struct list_head *head,
+                                             struct sk_buff *skb)
+@@ -388,6 +389,7 @@ INDIRECT_CALLABLE_SCOPE int ipv6_gro_com
+ out:
+       return err;
+ }
++EXPORT_INDIRECT_CALLABLE(ipv6_gro_complete);
+ static int sit_gro_complete(struct sk_buff *skb, int nhoff)
+ {
diff --git a/target/linux/generic/pending-6.12/655-increase_skb_pad.patch b/target/linux/generic/pending-6.12/655-increase_skb_pad.patch
new file mode 100644 (file)
index 0000000..7e2d3dc
--- /dev/null
@@ -0,0 +1,20 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: kernel: add a few patches for avoiding unnecessary skb reallocations - significantly improves ethernet<->wireless performance
+
+lede-commit: 6f89cffc9add6939d44a6b54cf9a5e77849aa7fd
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ include/linux/skbuff.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/include/linux/skbuff.h
++++ b/include/linux/skbuff.h
+@@ -3241,7 +3241,7 @@ static inline int pskb_network_may_pull(
+  * NET_IP_ALIGN(2) + ethernet_header(14) + IP_header(20/40) + ports(8)
+  */
+ #ifndef NET_SKB_PAD
+-#define NET_SKB_PAD   max(32, L1_CACHE_BYTES)
++#define NET_SKB_PAD   max(64, L1_CACHE_BYTES)
+ #endif
+ int ___pskb_trim(struct sk_buff *skb, unsigned int len);
diff --git a/target/linux/generic/pending-6.12/666-Add-support-for-MAP-E-FMRs-mesh-mode.patch b/target/linux/generic/pending-6.12/666-Add-support-for-MAP-E-FMRs-mesh-mode.patch
new file mode 100644 (file)
index 0000000..951e7f7
--- /dev/null
@@ -0,0 +1,511 @@
+From: Steven Barth <steven@midlink.org>
+Subject: Add support for MAP-E FMRs (mesh mode)
+
+MAP-E FMRs (draft-ietf-softwire-map-10) are rules for IPv4-communication
+between MAP CEs (mesh mode) without the need to forward such data to a
+border relay. This is similar to how 6rd works but for IPv4 over IPv6.
+
+Signed-off-by: Steven Barth <cyrus@openwrt.org>
+---
+ include/net/ip6_tunnel.h       |  13 ++
+ include/uapi/linux/if_tunnel.h |  13 ++
+ net/ipv6/ip6_tunnel.c          | 276 +++++++++++++++++++++++++++++++++++++++--
+ 3 files changed, 291 insertions(+), 11 deletions(-)
+
+--- a/include/net/ip6_tunnel.h
++++ b/include/net/ip6_tunnel.h
+@@ -18,6 +18,18 @@
+ /* determine capability on a per-packet basis */
+ #define IP6_TNL_F_CAP_PER_PACKET 0x40000
++/* IPv6 tunnel FMR */
++struct __ip6_tnl_fmr {
++      struct __ip6_tnl_fmr *next; /* next fmr in list */
++      struct in6_addr ip6_prefix;
++      struct in_addr ip4_prefix;
++
++      __u8 ip6_prefix_len;
++      __u8 ip4_prefix_len;
++      __u8 ea_len;
++      __u8 offset;
++};
++
+ struct __ip6_tnl_parm {
+       char name[IFNAMSIZ];    /* name of tunnel device */
+       int link;               /* ifindex of underlying L2 interface */
+@@ -29,6 +41,7 @@ struct __ip6_tnl_parm {
+       __u32 flags;            /* tunnel flags */
+       struct in6_addr laddr;  /* local tunnel end-point address */
+       struct in6_addr raddr;  /* remote tunnel end-point address */
++      struct __ip6_tnl_fmr *fmrs;     /* FMRs */
+       IP_TUNNEL_DECLARE_FLAGS(i_flags);
+       IP_TUNNEL_DECLARE_FLAGS(o_flags);
+--- a/include/uapi/linux/if_tunnel.h
++++ b/include/uapi/linux/if_tunnel.h
+@@ -77,10 +77,23 @@ enum {
+       IFLA_IPTUN_ENCAP_DPORT,
+       IFLA_IPTUN_COLLECT_METADATA,
+       IFLA_IPTUN_FWMARK,
++      IFLA_IPTUN_FMRS,
+       __IFLA_IPTUN_MAX,
+ };
+ #define IFLA_IPTUN_MAX        (__IFLA_IPTUN_MAX - 1)
++enum {
++      IFLA_IPTUN_FMR_UNSPEC,
++      IFLA_IPTUN_FMR_IP6_PREFIX,
++      IFLA_IPTUN_FMR_IP4_PREFIX,
++      IFLA_IPTUN_FMR_IP6_PREFIX_LEN,
++      IFLA_IPTUN_FMR_IP4_PREFIX_LEN,
++      IFLA_IPTUN_FMR_EA_LEN,
++      IFLA_IPTUN_FMR_OFFSET,
++      __IFLA_IPTUN_FMR_MAX,
++};
++#define IFLA_IPTUN_FMR_MAX (__IFLA_IPTUN_FMR_MAX - 1)
++
+ enum tunnel_encap_types {
+       TUNNEL_ENCAP_NONE,
+       TUNNEL_ENCAP_FOU,
+--- a/net/ipv6/ip6_tunnel.c
++++ b/net/ipv6/ip6_tunnel.c
+@@ -11,6 +11,9 @@
+  *      linux/net/ipv6/sit.c and linux/net/ipv4/ipip.c
+  *
+  *      RFC 2473
++ *
++ *      Changes:
++ *      Steven Barth <cyrus@openwrt.org>:           MAP-E FMR support
+  */
+ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+@@ -68,9 +71,9 @@ static bool log_ecn_error = true;
+ module_param(log_ecn_error, bool, 0644);
+ MODULE_PARM_DESC(log_ecn_error, "Log packets received with corrupted ECN");
+-static u32 HASH(const struct in6_addr *addr1, const struct in6_addr *addr2)
++static u32 HASH(const struct in6_addr *addr)
+ {
+-      u32 hash = ipv6_addr_hash(addr1) ^ ipv6_addr_hash(addr2);
++      u32 hash = ipv6_addr_hash(addr);
+       return hash_32(hash, IP6_TUNNEL_HASH_SIZE_SHIFT);
+ }
+@@ -115,17 +118,33 @@ static struct ip6_tnl *
+ ip6_tnl_lookup(struct net *net, int link,
+              const struct in6_addr *remote, const struct in6_addr *local)
+ {
+-      unsigned int hash = HASH(remote, local);
++      unsigned int hash = HASH(local);
+       struct ip6_tnl *t, *cand = NULL;
+       struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id);
+       struct in6_addr any;
+       for_each_ip6_tunnel_rcu(ip6n->tnls_r_l[hash]) {
+               if (!ipv6_addr_equal(local, &t->parms.laddr) ||
+-                  !ipv6_addr_equal(remote, &t->parms.raddr) ||
+                   !(t->dev->flags & IFF_UP))
+                       continue;
++              if (!ipv6_addr_equal(remote, &t->parms.raddr)) {
++                      struct __ip6_tnl_fmr *fmr;
++                      bool found = false;
++
++                      for (fmr = t->parms.fmrs; fmr; fmr = fmr->next) {
++                              if (!ipv6_prefix_equal(remote, &fmr->ip6_prefix,
++                                                     fmr->ip6_prefix_len))
++                                      continue;
++
++                              found = true;
++                              break;
++                      }
++
++                      if (!found)
++                              continue;
++              }
++
+               if (link == t->parms.link)
+                       return t;
+               else
+@@ -133,7 +152,7 @@ ip6_tnl_lookup(struct net *net, int link
+       }
+       memset(&any, 0, sizeof(any));
+-      hash = HASH(&any, local);
++      hash = HASH(local);
+       for_each_ip6_tunnel_rcu(ip6n->tnls_r_l[hash]) {
+               if (!ipv6_addr_equal(local, &t->parms.laddr) ||
+                   !ipv6_addr_any(&t->parms.raddr) ||
+@@ -146,7 +165,7 @@ ip6_tnl_lookup(struct net *net, int link
+                       cand = t;
+       }
+-      hash = HASH(remote, &any);
++      hash = HASH(&any);
+       for_each_ip6_tunnel_rcu(ip6n->tnls_r_l[hash]) {
+               if (!ipv6_addr_equal(remote, &t->parms.raddr) ||
+                   !ipv6_addr_any(&t->parms.laddr) ||
+@@ -195,7 +214,7 @@ ip6_tnl_bucket(struct ip6_tnl_net *ip6n,
+       if (!ipv6_addr_any(remote) || !ipv6_addr_any(local)) {
+               prio = 1;
+-              h = HASH(remote, local);
++              h = HASH(local);
+       }
+       return &ip6n->tnls[prio][h];
+ }
+@@ -376,6 +395,12 @@ ip6_tnl_dev_uninit(struct net_device *de
+       struct net *net = t->net;
+       struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id);
++      while (t->parms.fmrs) {
++              struct __ip6_tnl_fmr *next = t->parms.fmrs->next;
++              kfree(t->parms.fmrs);
++              t->parms.fmrs = next;
++      }
++
+       if (dev == ip6n->fb_tnl_dev)
+               RCU_INIT_POINTER(ip6n->tnls_wc[0], NULL);
+       else
+@@ -790,6 +815,107 @@ int ip6_tnl_rcv_ctl(struct ip6_tnl *t,
+ }
+ EXPORT_SYMBOL_GPL(ip6_tnl_rcv_ctl);
++/**
++ * ip4ip6_fmr_calc - calculate target / source IPv6-address based on FMR
++ *   @dest: destination IPv6 address buffer
++ *   @skb: received socket buffer
++ *   @fmr: MAP FMR
++ *   @xmit: Calculate for xmit or rcv
++ **/
++static void ip4ip6_fmr_calc(struct in6_addr *dest,
++              const struct iphdr *iph, const uint8_t *end,
++              const struct __ip6_tnl_fmr *fmr, bool xmit)
++{
++      int psidlen = fmr->ea_len - (32 - fmr->ip4_prefix_len);
++      u8 *portp = NULL;
++      bool use_dest_addr;
++      const struct iphdr *dsth = iph;
++
++      if ((u8*)dsth >= end)
++              return;
++
++      /* find significant IP header */
++      if (iph->protocol == IPPROTO_ICMP) {
++              struct icmphdr *ih = (struct icmphdr*)(((u8*)dsth) + dsth->ihl * 4);
++              if (ih && ((u8*)&ih[1]) <= end && (
++                      ih->type == ICMP_DEST_UNREACH ||
++                      ih->type == ICMP_SOURCE_QUENCH ||
++                      ih->type == ICMP_TIME_EXCEEDED ||
++                      ih->type == ICMP_PARAMETERPROB ||
++                      ih->type == ICMP_REDIRECT))
++                              dsth = (const struct iphdr*)&ih[1];
++      }
++
++      /* in xmit-path use dest port by default and source port only if
++              this is an ICMP reply to something else; vice versa in rcv-path */
++      use_dest_addr = (xmit && dsth == iph) || (!xmit && dsth != iph);
++
++      /* get dst port */
++      if (((u8*)&dsth[1]) <= end && (
++              dsth->protocol == IPPROTO_UDP ||
++              dsth->protocol == IPPROTO_TCP ||
++              dsth->protocol == IPPROTO_SCTP ||
++              dsth->protocol == IPPROTO_DCCP)) {
++                      /* for UDP, TCP, SCTP and DCCP source and dest port
++                      follow IPv4 header directly */
++                      portp = ((u8*)dsth) + dsth->ihl * 4;
++
++                      if (use_dest_addr)
++                              portp += sizeof(u16);
++      } else if (iph->protocol == IPPROTO_ICMP) {
++              struct icmphdr *ih = (struct icmphdr*)(((u8*)dsth) + dsth->ihl * 4);
++
++              /* use icmp identifier as port */
++              if (((u8*)&ih) <= end && (
++                  (use_dest_addr && (
++                  ih->type == ICMP_ECHOREPLY ||
++                      ih->type == ICMP_TIMESTAMPREPLY ||
++                      ih->type == ICMP_INFO_REPLY ||
++                      ih->type == ICMP_ADDRESSREPLY)) ||
++                      (!use_dest_addr && (
++                      ih->type == ICMP_ECHO ||
++                      ih->type == ICMP_TIMESTAMP ||
++                      ih->type == ICMP_INFO_REQUEST ||
++                      ih->type == ICMP_ADDRESS)
++                      )))
++                              portp = (u8*)&ih->un.echo.id;
++      }
++
++      if ((portp && &portp[2] <= end) || psidlen == 0) {
++              int frombyte = fmr->ip6_prefix_len / 8;
++              int fromrem = fmr->ip6_prefix_len % 8;
++              int bytes = sizeof(struct in6_addr) - frombyte;
++              const u32 *addr = (use_dest_addr) ? &iph->daddr : &iph->saddr;
++              u64 eabits = ((u64)ntohl(*addr)) << (32 + fmr->ip4_prefix_len);
++              u64 t = 0;
++
++              /* extract PSID from port and add it to eabits */
++              u16 psidbits = 0;
++              if (psidlen > 0) {
++                      psidbits = ((u16)portp[0]) << 8 | ((u16)portp[1]);
++                      psidbits >>= 16 - psidlen - fmr->offset;
++                      psidbits = (u16)(psidbits << (16 - psidlen));
++                      eabits |= ((u64)psidbits) << (48 - (fmr->ea_len - psidlen));
++              }
++
++              /* rewrite destination address */
++              *dest = fmr->ip6_prefix;
++              memcpy(&dest->s6_addr[10], addr, sizeof(*addr));
++              dest->s6_addr16[7] = htons(psidbits >> (16 - psidlen));
++
++              if (bytes > sizeof(u64))
++                      bytes = sizeof(u64);
++
++              /* insert eabits */
++              memcpy(&t, &dest->s6_addr[frombyte], bytes);
++              t = be64_to_cpu(t) & ~(((((u64)1) << fmr->ea_len) - 1)
++                      << (64 - fmr->ea_len - fromrem));
++              t = cpu_to_be64(t | (eabits >> fromrem));
++              memcpy(&dest->s6_addr[frombyte], &t, bytes);
++      }
++}
++
++
+ static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,
+                        const struct tnl_ptk_info *tpi,
+                        struct metadata_dst *tun_dst,
+@@ -855,6 +981,27 @@ static int __ip6_tnl_rcv(struct ip6_tnl
+       memset(skb->cb, 0, sizeof(struct inet6_skb_parm));
++      if (tpi->proto == htons(ETH_P_IP) && tunnel->parms.fmrs &&
++              !ipv6_addr_equal(&ipv6h->saddr, &tunnel->parms.raddr)) {
++                      /* Packet didn't come from BR, so lookup FMR */
++                      struct __ip6_tnl_fmr *fmr;
++                      struct in6_addr expected = tunnel->parms.raddr;
++                      for (fmr = tunnel->parms.fmrs; fmr; fmr = fmr->next)
++                              if (ipv6_prefix_equal(&ipv6h->saddr,
++                                      &fmr->ip6_prefix, fmr->ip6_prefix_len))
++                                              break;
++
++                      /* Check that IPv6 matches IPv4 source to prevent spoofing */
++                      if (fmr)
++                              ip4ip6_fmr_calc(&expected, ip_hdr(skb),
++                                              skb_tail_pointer(skb), fmr, false);
++
++                      if (!ipv6_addr_equal(&ipv6h->saddr, &expected)) {
++                              rcu_read_unlock();
++                              goto drop;
++                      }
++      }
++
+       __skb_tunnel_rx(skb, tunnel->dev, tunnel->net);
+       err = dscp_ecn_decapsulate(tunnel, ipv6h, skb);
+@@ -1004,6 +1151,7 @@ static void init_tel_txopt(struct ipv6_t
+       opt->ops.opt_nflen = 8;
+ }
++
+ /**
+  * ip6_tnl_addr_conflict - compare packet addresses to tunnel's own
+  *   @t: the outgoing tunnel device
+@@ -1293,6 +1441,7 @@ ipxip6_tnl_xmit(struct sk_buff *skb, str
+               u8 protocol)
+ {
+       struct ip6_tnl *t = netdev_priv(dev);
++      struct __ip6_tnl_fmr *fmr;
+       struct ipv6hdr *ipv6h;
+       const struct iphdr  *iph;
+       int encap_limit = -1;
+@@ -1392,6 +1541,18 @@ ipxip6_tnl_xmit(struct sk_buff *skb, str
+       fl6.flowi6_uid = sock_net_uid(dev_net(dev), NULL);
+       dsfield = INET_ECN_encapsulate(dsfield, orig_dsfield);
++      /* try to find matching FMR */
++      for (fmr = t->parms.fmrs; fmr; fmr = fmr->next) {
++              unsigned mshift = 32 - fmr->ip4_prefix_len;
++              if (ntohl(fmr->ip4_prefix.s_addr) >> mshift ==
++                              ntohl(ip_hdr(skb)->daddr) >> mshift)
++                      break;
++      }
++
++      /* change dstaddr according to FMR */
++      if (fmr)
++              ip4ip6_fmr_calc(&fl6.daddr, ip_hdr(skb), skb_tail_pointer(skb), fmr, true);
++
+       if (iptunnel_handle_offloads(skb, SKB_GSO_IPXIP6))
+               return -1;
+@@ -1545,6 +1706,14 @@ ip6_tnl_change(struct ip6_tnl *t, const
+       t->parms.link = p->link;
+       t->parms.proto = p->proto;
+       t->parms.fwmark = p->fwmark;
++
++      while (t->parms.fmrs) {
++              struct __ip6_tnl_fmr *next = t->parms.fmrs->next;
++              kfree(t->parms.fmrs);
++              t->parms.fmrs = next;
++      }
++      t->parms.fmrs = p->fmrs;
++
+       dst_cache_reset(&t->dst_cache);
+       ip6_tnl_link_config(t);
+ }
+@@ -1579,6 +1748,7 @@ ip6_tnl_parm_from_user(struct __ip6_tnl_
+       p->flowinfo = u->flowinfo;
+       p->link = u->link;
+       p->proto = u->proto;
++      p->fmrs = NULL;
+       memcpy(p->name, u->name, sizeof(u->name));
+ }
+@@ -1962,6 +2132,15 @@ static int ip6_tnl_validate(struct nlatt
+       return 0;
+ }
++static const struct nla_policy ip6_tnl_fmr_policy[IFLA_IPTUN_FMR_MAX + 1] = {
++      [IFLA_IPTUN_FMR_IP6_PREFIX] = { .len = sizeof(struct in6_addr) },
++      [IFLA_IPTUN_FMR_IP4_PREFIX] = { .len = sizeof(struct in_addr) },
++      [IFLA_IPTUN_FMR_IP6_PREFIX_LEN] = { .type = NLA_U8 },
++      [IFLA_IPTUN_FMR_IP4_PREFIX_LEN] = { .type = NLA_U8 },
++      [IFLA_IPTUN_FMR_EA_LEN] = { .type = NLA_U8 },
++      [IFLA_IPTUN_FMR_OFFSET] = { .type = NLA_U8 }
++};
++
+ static void ip6_tnl_netlink_parms(struct nlattr *data[],
+                                 struct __ip6_tnl_parm *parms)
+ {
+@@ -1999,6 +2178,46 @@ static void ip6_tnl_netlink_parms(struct
+       if (data[IFLA_IPTUN_FWMARK])
+               parms->fwmark = nla_get_u32(data[IFLA_IPTUN_FWMARK]);
++
++      if (data[IFLA_IPTUN_FMRS]) {
++              unsigned rem;
++              struct nlattr *fmr;
++              nla_for_each_nested(fmr, data[IFLA_IPTUN_FMRS], rem) {
++                      struct nlattr *fmrd[IFLA_IPTUN_FMR_MAX + 1], *c;
++                      struct __ip6_tnl_fmr *nfmr;
++
++                      nla_parse_nested(fmrd, IFLA_IPTUN_FMR_MAX,
++                              fmr, ip6_tnl_fmr_policy, NULL);
++
++                      if (!(nfmr = kzalloc(sizeof(*nfmr), GFP_KERNEL)))
++                              continue;
++
++                      nfmr->offset = 6;
++
++                      if ((c = fmrd[IFLA_IPTUN_FMR_IP6_PREFIX]))
++                              nla_memcpy(&nfmr->ip6_prefix, fmrd[IFLA_IPTUN_FMR_IP6_PREFIX],
++                                      sizeof(nfmr->ip6_prefix));
++
++                      if ((c = fmrd[IFLA_IPTUN_FMR_IP4_PREFIX]))
++                              nla_memcpy(&nfmr->ip4_prefix, fmrd[IFLA_IPTUN_FMR_IP4_PREFIX],
++                                      sizeof(nfmr->ip4_prefix));
++
++                      if ((c = fmrd[IFLA_IPTUN_FMR_IP6_PREFIX_LEN]))
++                              nfmr->ip6_prefix_len = nla_get_u8(c);
++
++                      if ((c = fmrd[IFLA_IPTUN_FMR_IP4_PREFIX_LEN]))
++                              nfmr->ip4_prefix_len = nla_get_u8(c);
++
++                      if ((c = fmrd[IFLA_IPTUN_FMR_EA_LEN]))
++                              nfmr->ea_len = nla_get_u8(c);
++
++                      if ((c = fmrd[IFLA_IPTUN_FMR_OFFSET]))
++                              nfmr->offset = nla_get_u8(c);
++
++                      nfmr->next = parms->fmrs;
++                      parms->fmrs = nfmr;
++              }
++      }
+ }
+ static int ip6_tnl_newlink(struct net *src_net, struct net_device *dev,
+@@ -2083,6 +2302,12 @@ static void ip6_tnl_dellink(struct net_d
+ static size_t ip6_tnl_get_size(const struct net_device *dev)
+ {
++      const struct ip6_tnl *t = netdev_priv(dev);
++      struct __ip6_tnl_fmr *c;
++      int fmrs = 0;
++      for (c = t->parms.fmrs; c; c = c->next)
++              ++fmrs;
++
+       return
+               /* IFLA_IPTUN_LINK */
+               nla_total_size(4) +
+@@ -2112,6 +2337,24 @@ static size_t ip6_tnl_get_size(const str
+               nla_total_size(0) +
+               /* IFLA_IPTUN_FWMARK */
+               nla_total_size(4) +
++              /* IFLA_IPTUN_FMRS */
++              nla_total_size(0) +
++              (
++                      /* nest */
++                      nla_total_size(0) +
++                      /* IFLA_IPTUN_FMR_IP6_PREFIX */
++                      nla_total_size(sizeof(struct in6_addr)) +
++                      /* IFLA_IPTUN_FMR_IP4_PREFIX */
++                      nla_total_size(sizeof(struct in_addr)) +
++                      /* IFLA_IPTUN_FMR_EA_LEN */
++                      nla_total_size(1) +
++                      /* IFLA_IPTUN_FMR_IP6_PREFIX_LEN */
++                      nla_total_size(1) +
++                      /* IFLA_IPTUN_FMR_IP4_PREFIX_LEN */
++                      nla_total_size(1) +
++                      /* IFLA_IPTUN_FMR_OFFSET */
++                      nla_total_size(1)
++              ) * fmrs +
+               0;
+ }
+@@ -2119,6 +2362,9 @@ static int ip6_tnl_fill_info(struct sk_b
+ {
+       struct ip6_tnl *tunnel = netdev_priv(dev);
+       struct __ip6_tnl_parm *parm = &tunnel->parms;
++      struct __ip6_tnl_fmr *c;
++      int fmrcnt = 0;
++      struct nlattr *fmrs;
+       if (nla_put_u32(skb, IFLA_IPTUN_LINK, parm->link) ||
+           nla_put_in6_addr(skb, IFLA_IPTUN_LOCAL, &parm->laddr) ||
+@@ -2128,9 +2374,27 @@ static int ip6_tnl_fill_info(struct sk_b
+           nla_put_be32(skb, IFLA_IPTUN_FLOWINFO, parm->flowinfo) ||
+           nla_put_u32(skb, IFLA_IPTUN_FLAGS, parm->flags) ||
+           nla_put_u8(skb, IFLA_IPTUN_PROTO, parm->proto) ||
+-          nla_put_u32(skb, IFLA_IPTUN_FWMARK, parm->fwmark))
++          nla_put_u32(skb, IFLA_IPTUN_FWMARK, parm->fwmark) ||
++          !(fmrs = nla_nest_start(skb, IFLA_IPTUN_FMRS)))
+               goto nla_put_failure;
++      for (c = parm->fmrs; c; c = c->next) {
++              struct nlattr *fmr = nla_nest_start(skb, ++fmrcnt);
++              if (!fmr ||
++                      nla_put(skb, IFLA_IPTUN_FMR_IP6_PREFIX,
++                              sizeof(c->ip6_prefix), &c->ip6_prefix) ||
++                      nla_put(skb, IFLA_IPTUN_FMR_IP4_PREFIX,
++                              sizeof(c->ip4_prefix), &c->ip4_prefix) ||
++                      nla_put_u8(skb, IFLA_IPTUN_FMR_IP6_PREFIX_LEN, c->ip6_prefix_len) ||
++                      nla_put_u8(skb, IFLA_IPTUN_FMR_IP4_PREFIX_LEN, c->ip4_prefix_len) ||
++                      nla_put_u8(skb, IFLA_IPTUN_FMR_EA_LEN, c->ea_len) ||
++                      nla_put_u8(skb, IFLA_IPTUN_FMR_OFFSET, c->offset))
++                              goto nla_put_failure;
++
++              nla_nest_end(skb, fmr);
++      }
++      nla_nest_end(skb, fmrs);
++
+       if (nla_put_u16(skb, IFLA_IPTUN_ENCAP_TYPE, tunnel->encap.type) ||
+           nla_put_be16(skb, IFLA_IPTUN_ENCAP_SPORT, tunnel->encap.sport) ||
+           nla_put_be16(skb, IFLA_IPTUN_ENCAP_DPORT, tunnel->encap.dport) ||
+@@ -2170,6 +2434,7 @@ static const struct nla_policy ip6_tnl_p
+       [IFLA_IPTUN_ENCAP_DPORT]        = { .type = NLA_U16 },
+       [IFLA_IPTUN_COLLECT_METADATA]   = { .type = NLA_FLAG },
+       [IFLA_IPTUN_FWMARK]             = { .type = NLA_U32 },
++      [IFLA_IPTUN_FMRS]               = { .type = NLA_NESTED },
+ };
+ static struct rtnl_link_ops ip6_link_ops __read_mostly = {
diff --git a/target/linux/generic/pending-6.12/670-ipv6-allow-rejecting-with-source-address-failed-policy.patch b/target/linux/generic/pending-6.12/670-ipv6-allow-rejecting-with-source-address-failed-policy.patch
new file mode 100644 (file)
index 0000000..7e99fae
--- /dev/null
@@ -0,0 +1,263 @@
+From: Jonas Gorski <jogo@openwrt.org>
+Subject: ipv6: allow rejecting with "source address failed policy"
+
+RFC6204 L-14 requires rejecting traffic from invalid addresses with
+ICMPv6 Destination Unreachable, Code 5 (Source address failed ingress/
+egress policy) on the LAN side, so add an appropriate rule for that.
+
+Signed-off-by: Jonas Gorski <jogo@openwrt.org>
+---
+ include/net/netns/ipv6.h       |  1 +
+ include/uapi/linux/fib_rules.h |  4 +++
+ include/uapi/linux/rtnetlink.h |  1 +
+ net/ipv4/fib_semantics.c       |  4 +++
+ net/ipv4/fib_trie.c            |  1 +
+ net/ipv4/ipmr.c                |  1 +
+ net/ipv6/fib6_rules.c          |  4 +++
+ net/ipv6/ip6mr.c               |  2 ++
+ net/ipv6/route.c               | 58 +++++++++++++++++++++++++++++++++++++++++-
+ 9 files changed, 75 insertions(+), 1 deletion(-)
+
+--- a/include/net/netns/ipv6.h
++++ b/include/net/netns/ipv6.h
+@@ -86,6 +86,7 @@ struct netns_ipv6 {
+       unsigned int            fib6_routes_require_src;
+ #endif
+       struct rt6_info         *ip6_prohibit_entry;
++      struct rt6_info         *ip6_policy_failed_entry;
+       struct rt6_info         *ip6_blk_hole_entry;
+       struct fib6_table       *fib6_local_tbl;
+       struct fib_rules_ops    *fib6_rules_ops;
+--- a/include/uapi/linux/fib_rules.h
++++ b/include/uapi/linux/fib_rules.h
+@@ -83,6 +83,10 @@ enum {
+       FR_ACT_BLACKHOLE,       /* Drop without notification */
+       FR_ACT_UNREACHABLE,     /* Drop with ENETUNREACH */
+       FR_ACT_PROHIBIT,        /* Drop with EACCES */
++      FR_ACT_RES9,
++      FR_ACT_RES10,
++      FR_ACT_RES11,
++      FR_ACT_POLICY_FAILED,   /* Drop with EACCES */
+       __FR_ACT_MAX,
+ };
+--- a/include/uapi/linux/rtnetlink.h
++++ b/include/uapi/linux/rtnetlink.h
+@@ -265,6 +265,7 @@ enum {
+       RTN_THROW,              /* Not in this table            */
+       RTN_NAT,                /* Translate this address       */
+       RTN_XRESOLVE,           /* Use external resolver        */
++      RTN_POLICY_FAILED,      /* Failed ingress/egress policy */
+       __RTN_MAX
+ };
+--- a/net/ipv4/fib_semantics.c
++++ b/net/ipv4/fib_semantics.c
+@@ -145,6 +145,10 @@ const struct fib_prop fib_props[RTN_MAX
+               .error  = -EINVAL,
+               .scope  = RT_SCOPE_NOWHERE,
+       },
++      [RTN_POLICY_FAILED] = {
++              .error  = -EACCES,
++              .scope  = RT_SCOPE_UNIVERSE,
++      },
+ };
+ static void rt_fibinfo_free(struct rtable __rcu **rtp)
+--- a/net/ipv4/fib_trie.c
++++ b/net/ipv4/fib_trie.c
+@@ -2763,6 +2763,7 @@ static const char *const rtn_type_names[
+       [RTN_THROW] = "THROW",
+       [RTN_NAT] = "NAT",
+       [RTN_XRESOLVE] = "XRESOLVE",
++      [RTN_POLICY_FAILED] = "POLICY_FAILED",
+ };
+ static inline const char *rtn_type(char *buf, size_t len, unsigned int t)
+--- a/net/ipv4/ipmr.c
++++ b/net/ipv4/ipmr.c
+@@ -191,6 +191,7 @@ static int ipmr_rule_action(struct fib_r
+       case FR_ACT_UNREACHABLE:
+               return -ENETUNREACH;
+       case FR_ACT_PROHIBIT:
++      case FR_ACT_POLICY_FAILED:
+               return -EACCES;
+       case FR_ACT_BLACKHOLE:
+       default:
+--- a/net/ipv6/fib6_rules.c
++++ b/net/ipv6/fib6_rules.c
+@@ -222,6 +222,10 @@ static int __fib6_rule_action(struct fib
+               err = -EACCES;
+               rt = net->ipv6.ip6_prohibit_entry;
+               goto discard_pkt;
++      case FR_ACT_POLICY_FAILED:
++              err = -EACCES;
++              rt = net->ipv6.ip6_policy_failed_entry;
++              goto discard_pkt;
+       }
+       tb_id = fib_rule_get_table(rule, arg);
+--- a/net/ipv6/ip6mr.c
++++ b/net/ipv6/ip6mr.c
+@@ -180,6 +180,8 @@ static int ip6mr_rule_action(struct fib_
+               return -ENETUNREACH;
+       case FR_ACT_PROHIBIT:
+               return -EACCES;
++      case FR_ACT_POLICY_FAILED:
++              return -EACCES;
+       case FR_ACT_BLACKHOLE:
+       default:
+               return -EINVAL;
+--- a/net/ipv6/route.c
++++ b/net/ipv6/route.c
+@@ -98,6 +98,8 @@ static int           ip6_pkt_discard(struct sk_bu
+ static int            ip6_pkt_discard_out(struct net *net, struct sock *sk, struct sk_buff *skb);
+ static int            ip6_pkt_prohibit(struct sk_buff *skb);
+ static int            ip6_pkt_prohibit_out(struct net *net, struct sock *sk, struct sk_buff *skb);
++static int            ip6_pkt_policy_failed(struct sk_buff *skb);
++static int            ip6_pkt_policy_failed_out(struct net *net, struct sock *sk, struct sk_buff *skb);
+ static void           ip6_link_failure(struct sk_buff *skb);
+ static void           ip6_rt_update_pmtu(struct dst_entry *dst, struct sock *sk,
+                                          struct sk_buff *skb, u32 mtu,
+@@ -316,6 +318,18 @@ static const struct rt6_info ip6_prohibi
+       .rt6i_flags     = (RTF_REJECT | RTF_NONEXTHOP),
+ };
++static const struct rt6_info ip6_policy_failed_entry_template = {
++      .dst = {
++              .__rcuref       = RCUREF_INIT(1),
++              .__use          = 1,
++              .obsolete       = DST_OBSOLETE_FORCE_CHK,
++              .error          = -EACCES,
++              .input          = ip6_pkt_policy_failed,
++              .output         = ip6_pkt_policy_failed_out,
++      },
++      .rt6i_flags     = (RTF_REJECT | RTF_NONEXTHOP),
++};
++
+ static const struct rt6_info ip6_blk_hole_entry_template = {
+       .dst = {
+               .__rcuref       = RCUREF_INIT(1),
+@@ -1086,6 +1100,7 @@ static const int fib6_prop[RTN_MAX + 1]
+       [RTN_BLACKHOLE] = -EINVAL,
+       [RTN_UNREACHABLE] = -EHOSTUNREACH,
+       [RTN_PROHIBIT]  = -EACCES,
++      [RTN_POLICY_FAILED] = -EACCES,
+       [RTN_THROW]     = -EAGAIN,
+       [RTN_NAT]       = -EINVAL,
+       [RTN_XRESOLVE]  = -EINVAL,
+@@ -1121,6 +1136,10 @@ static void ip6_rt_init_dst_reject(struc
+               rt->dst.output = ip6_pkt_prohibit_out;
+               rt->dst.input = ip6_pkt_prohibit;
+               break;
++      case RTN_POLICY_FAILED:
++              rt->dst.output = ip6_pkt_policy_failed_out;
++              rt->dst.input = ip6_pkt_policy_failed;
++              break;
+       case RTN_THROW:
+       case RTN_UNREACHABLE:
+       default:
+@@ -4609,6 +4628,17 @@ static int ip6_pkt_prohibit_out(struct n
+       return ip6_pkt_drop(skb, ICMPV6_ADM_PROHIBITED, IPSTATS_MIB_OUTNOROUTES);
+ }
++static int ip6_pkt_policy_failed(struct sk_buff *skb)
++{
++      return ip6_pkt_drop(skb, ICMPV6_POLICY_FAIL, IPSTATS_MIB_INNOROUTES);
++}
++
++static int ip6_pkt_policy_failed_out(struct net *net, struct sock *sk, struct sk_buff *skb)
++{
++      skb->dev = skb_dst(skb)->dev;
++      return ip6_pkt_drop(skb, ICMPV6_POLICY_FAIL, IPSTATS_MIB_OUTNOROUTES);
++}
++
+ /*
+  *    Allocate a dst for local (unicast / anycast) address.
+  */
+@@ -5100,7 +5130,8 @@ static int rtm_to_fib6_config(struct sk_
+       if (rtm->rtm_type == RTN_UNREACHABLE ||
+           rtm->rtm_type == RTN_BLACKHOLE ||
+           rtm->rtm_type == RTN_PROHIBIT ||
+-          rtm->rtm_type == RTN_THROW)
++          rtm->rtm_type == RTN_THROW ||
++          rtm->rtm_type == RTN_POLICY_FAILED)
+               cfg->fc_flags |= RTF_REJECT;
+       if (rtm->rtm_type == RTN_LOCAL)
+@@ -6371,6 +6402,8 @@ static int ip6_route_dev_notify(struct n
+ #ifdef CONFIG_IPV6_MULTIPLE_TABLES
+               net->ipv6.ip6_prohibit_entry->dst.dev = dev;
+               net->ipv6.ip6_prohibit_entry->rt6i_idev = in6_dev_get(dev);
++              net->ipv6.ip6_policy_failed_entry->dst.dev = dev;
++              net->ipv6.ip6_policy_failed_entry->rt6i_idev = in6_dev_get(dev);
+               net->ipv6.ip6_blk_hole_entry->dst.dev = dev;
+               net->ipv6.ip6_blk_hole_entry->rt6i_idev = in6_dev_get(dev);
+ #endif
+@@ -6382,6 +6415,7 @@ static int ip6_route_dev_notify(struct n
+               in6_dev_put_clear(&net->ipv6.ip6_null_entry->rt6i_idev);
+ #ifdef CONFIG_IPV6_MULTIPLE_TABLES
+               in6_dev_put_clear(&net->ipv6.ip6_prohibit_entry->rt6i_idev);
++              in6_dev_put_clear(&net->ipv6.ip6_policy_failed_entry->rt6i_idev);
+               in6_dev_put_clear(&net->ipv6.ip6_blk_hole_entry->rt6i_idev);
+ #endif
+       }
+@@ -6577,6 +6611,8 @@ static int __net_init ip6_route_net_init
+ #ifdef CONFIG_IPV6_MULTIPLE_TABLES
+       net->ipv6.fib6_has_custom_rules = false;
++
++
+       net->ipv6.ip6_prohibit_entry = kmemdup(&ip6_prohibit_entry_template,
+                                              sizeof(*net->ipv6.ip6_prohibit_entry),
+                                              GFP_KERNEL);
+@@ -6587,11 +6623,21 @@ static int __net_init ip6_route_net_init
+                        ip6_template_metrics, true);
+       INIT_LIST_HEAD(&net->ipv6.ip6_prohibit_entry->dst.rt_uncached);
++      net->ipv6.ip6_policy_failed_entry =
++                              kmemdup(&ip6_policy_failed_entry_template,
++                              sizeof(*net->ipv6.ip6_policy_failed_entry), GFP_KERNEL);
++      if (!net->ipv6.ip6_policy_failed_entry)
++              goto out_ip6_prohibit_entry;
++      net->ipv6.ip6_policy_failed_entry->dst.ops = &net->ipv6.ip6_dst_ops;
++      dst_init_metrics(&net->ipv6.ip6_policy_failed_entry->dst,
++                       ip6_template_metrics, true);
++      INIT_LIST_HEAD(&net->ipv6.ip6_policy_failed_entry->dst.rt_uncached);
++
+       net->ipv6.ip6_blk_hole_entry = kmemdup(&ip6_blk_hole_entry_template,
+                                              sizeof(*net->ipv6.ip6_blk_hole_entry),
+                                              GFP_KERNEL);
+       if (!net->ipv6.ip6_blk_hole_entry)
+-              goto out_ip6_prohibit_entry;
++              goto out_ip6_policy_failed_entry;
+       net->ipv6.ip6_blk_hole_entry->dst.ops = &net->ipv6.ip6_dst_ops;
+       dst_init_metrics(&net->ipv6.ip6_blk_hole_entry->dst,
+                        ip6_template_metrics, true);
+@@ -6618,6 +6664,8 @@ out:
+       return ret;
+ #ifdef CONFIG_IPV6_MULTIPLE_TABLES
++out_ip6_policy_failed_entry:
++      kfree(net->ipv6.ip6_policy_failed_entry);
+ out_ip6_prohibit_entry:
+       kfree(net->ipv6.ip6_prohibit_entry);
+ out_ip6_null_entry:
+@@ -6637,6 +6685,7 @@ static void __net_exit ip6_route_net_exi
+       kfree(net->ipv6.ip6_null_entry);
+ #ifdef CONFIG_IPV6_MULTIPLE_TABLES
+       kfree(net->ipv6.ip6_prohibit_entry);
++      kfree(net->ipv6.ip6_policy_failed_entry);
+       kfree(net->ipv6.ip6_blk_hole_entry);
+ #endif
+       dst_entries_destroy(&net->ipv6.ip6_dst_ops);
+@@ -6720,6 +6769,9 @@ void __init ip6_route_init_special_entri
+       init_net.ipv6.ip6_prohibit_entry->rt6i_idev = in6_dev_get(init_net.loopback_dev);
+       init_net.ipv6.ip6_blk_hole_entry->dst.dev = init_net.loopback_dev;
+       init_net.ipv6.ip6_blk_hole_entry->rt6i_idev = in6_dev_get(init_net.loopback_dev);
++      init_net.ipv6.ip6_policy_failed_entry->dst.dev = init_net.loopback_dev;
++      init_net.ipv6.ip6_policy_failed_entry->rt6i_idev =
++              in6_dev_get(init_net.loopback_dev);
+   #endif
+ }
diff --git a/target/linux/generic/pending-6.12/671-net-provide-defines-for-_POLICY_FAILED-until-all-cod.patch b/target/linux/generic/pending-6.12/671-net-provide-defines-for-_POLICY_FAILED-until-all-cod.patch
new file mode 100644 (file)
index 0000000..7057d36
--- /dev/null
@@ -0,0 +1,50 @@
+From: Jonas Gorski <jogo@openwrt.org>
+Subject: net: provide defines for _POLICY_FAILED until all code is updated
+
+Upstream introduced ICMPV6_POLICY_FAIL for code 5 of destination
+unreachable, conflicting with our name.
+
+Add appropriate defines to allow our code to build with the new
+name until we have updated our local patches for older kernels
+and userspace packages.
+
+Signed-off-by: Jonas Gorski <jogo@openwrt.org>
+---
+ include/uapi/linux/fib_rules.h | 2 ++
+ include/uapi/linux/icmpv6.h    | 2 ++
+ include/uapi/linux/rtnetlink.h | 2 ++
+ 3 files changed, 6 insertions(+)
+
+--- a/include/uapi/linux/fib_rules.h
++++ b/include/uapi/linux/fib_rules.h
+@@ -90,6 +90,8 @@ enum {
+       __FR_ACT_MAX,
+ };
++#define FR_ACT_FAILED_POLICY FR_ACT_POLICY_FAILED
++
+ #define FR_ACT_MAX (__FR_ACT_MAX - 1)
+ #endif
+--- a/include/uapi/linux/icmpv6.h
++++ b/include/uapi/linux/icmpv6.h
+@@ -127,6 +127,8 @@ struct icmp6hdr {
+ #define ICMPV6_POLICY_FAIL            5
+ #define ICMPV6_REJECT_ROUTE           6
++#define ICMPV6_FAILED_POLICY          ICMPV6_POLICY_FAIL
++
+ /*
+  *    Codes for Time Exceeded
+  */
+--- a/include/uapi/linux/rtnetlink.h
++++ b/include/uapi/linux/rtnetlink.h
+@@ -269,6 +269,8 @@ enum {
+       __RTN_MAX
+ };
++#define RTN_FAILED_POLICY RTN_POLICY_FAILED
++
+ #define RTN_MAX (__RTN_MAX - 1)
diff --git a/target/linux/generic/pending-6.12/681-net-remove-NETIF_F_GSO_FRAGLIST-from-NETIF_F_GSO_SOF.patch b/target/linux/generic/pending-6.12/681-net-remove-NETIF_F_GSO_FRAGLIST-from-NETIF_F_GSO_SOF.patch
new file mode 100644 (file)
index 0000000..fdfacd8
--- /dev/null
@@ -0,0 +1,129 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 15 Aug 2024 21:15:13 +0200
+Subject: [PATCH] net: remove NETIF_F_GSO_FRAGLIST from NETIF_F_GSO_SOFTWARE
+
+Several drivers set NETIF_F_GSO_SOFTWARE, but mangle fraglist GRO packets
+in a way that they can't be properly segmented anymore.
+In order to properly deal with this, remove fraglist GSO from
+NETIF_F_GSO_SOFTWARE and switch to NETIF_F_GSO_SOFTWARE_ALL (which includes
+fraglist GSO) in places where it's safe to add.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/drivers/net/dummy.c
++++ b/drivers/net/dummy.c
+@@ -111,7 +111,7 @@ static void dummy_setup(struct net_devic
+       dev->priv_flags |= IFF_LIVE_ADDR_CHANGE | IFF_NO_QUEUE;
+       dev->lltx = true;
+       dev->features   |= NETIF_F_SG | NETIF_F_FRAGLIST;
+-      dev->features   |= NETIF_F_GSO_SOFTWARE;
++      dev->features   |= NETIF_F_GSO_SOFTWARE_ALL;
+       dev->features   |= NETIF_F_HW_CSUM | NETIF_F_HIGHDMA;
+       dev->features   |= NETIF_F_GSO_ENCAP_ALL;
+       dev->hw_features |= dev->features;
+--- a/drivers/net/loopback.c
++++ b/drivers/net/loopback.c
+@@ -174,7 +174,7 @@ static void gen_lo_setup(struct net_devi
+       dev->lltx               = true;
+       dev->netns_local        = true;
+       netif_keep_dst(dev);
+-      dev->hw_features        = NETIF_F_GSO_SOFTWARE;
++      dev->hw_features        = NETIF_F_GSO_SOFTWARE_ALL;
+       dev->features           = NETIF_F_SG | NETIF_F_FRAGLIST
+               | NETIF_F_GSO_SOFTWARE
+               | NETIF_F_HW_CSUM
+--- a/drivers/net/macvlan.c
++++ b/drivers/net/macvlan.c
+@@ -903,7 +903,7 @@ static int macvlan_hwtstamp_set(struct n
+ static struct lock_class_key macvlan_netdev_addr_lock_key;
+ #define ALWAYS_ON_OFFLOADS \
+-      (NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_GSO_SOFTWARE | \
++      (NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_GSO_SOFTWARE_ALL | \
+        NETIF_F_GSO_ROBUST | NETIF_F_GSO_ENCAP_ALL)
+ #define ALWAYS_ON_FEATURES ALWAYS_ON_OFFLOADS
+--- a/include/linux/netdev_features.h
++++ b/include/linux/netdev_features.h
+@@ -211,13 +211,14 @@ static inline int find_next_netdev_featu
+ /* List of features with software fallbacks. */
+ #define NETIF_F_GSO_SOFTWARE  (NETIF_F_ALL_TSO | NETIF_F_GSO_SCTP |        \
+-                               NETIF_F_GSO_UDP_L4 | NETIF_F_GSO_FRAGLIST)
++                               NETIF_F_GSO_UDP_L4)
++#define NETIF_F_GSO_SOFTWARE_ALL (NETIF_F_GSO_SOFTWARE | NETIF_F_GSO_FRAGLIST)
+ /*
+  * If one device supports one of these features, then enable them
+  * for all in netdev_increment_features.
+  */
+-#define NETIF_F_ONE_FOR_ALL   (NETIF_F_GSO_SOFTWARE | NETIF_F_GSO_ROBUST | \
++#define NETIF_F_ONE_FOR_ALL   (NETIF_F_GSO_SOFTWARE_ALL | NETIF_F_GSO_ROBUST | \
+                                NETIF_F_SG | NETIF_F_HIGHDMA |         \
+                                NETIF_F_FRAGLIST | NETIF_F_VLAN_CHALLENGED)
+--- a/net/8021q/vlan.h
++++ b/net/8021q/vlan.h
+@@ -109,7 +109,7 @@ static inline netdev_features_t vlan_tnl
+       netdev_features_t ret;
+       ret = real_dev->hw_enc_features &
+-            (NETIF_F_CSUM_MASK | NETIF_F_GSO_SOFTWARE |
++            (NETIF_F_CSUM_MASK | NETIF_F_GSO_SOFTWARE_ALL |
+              NETIF_F_GSO_ENCAP_ALL);
+       if ((ret & NETIF_F_GSO_ENCAP_ALL) && (ret & NETIF_F_CSUM_MASK))
+--- a/net/8021q/vlan_dev.c
++++ b/net/8021q/vlan_dev.c
+@@ -538,7 +538,7 @@ static int vlan_dev_init(struct net_devi
+               dev->state |= (1 << __LINK_STATE_NOCARRIER);
+       dev->hw_features = NETIF_F_HW_CSUM | NETIF_F_SG |
+-                         NETIF_F_FRAGLIST | NETIF_F_GSO_SOFTWARE |
++                         NETIF_F_FRAGLIST | NETIF_F_GSO_SOFTWARE_ALL |
+                          NETIF_F_GSO_ENCAP_ALL |
+                          NETIF_F_HIGHDMA | NETIF_F_SCTP_CRC |
+                          NETIF_F_FCOE_CRC | NETIF_F_FSO;
+@@ -634,7 +634,7 @@ static netdev_features_t vlan_dev_fix_fe
+       if (lower_features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))
+               lower_features |= NETIF_F_HW_CSUM;
+       features = netdev_intersect_features(features, lower_features);
+-      features |= old_features & (NETIF_F_SOFT_FEATURES | NETIF_F_GSO_SOFTWARE);
++      features |= old_features & (NETIF_F_SOFT_FEATURES | NETIF_F_GSO_SOFTWARE_ALL);
+       return features;
+ }
+--- a/net/core/sock.c
++++ b/net/core/sock.c
+@@ -2557,7 +2557,7 @@ void sk_setup_caps(struct sock *sk, stru
+               icsk->icsk_ack.dst_quick_ack = dst_metric(dst, RTAX_QUICKACK);
+       }
+       if (sk->sk_route_caps & NETIF_F_GSO)
+-              sk->sk_route_caps |= NETIF_F_GSO_SOFTWARE;
++              sk->sk_route_caps |= NETIF_F_GSO_SOFTWARE_ALL;
+       if (unlikely(sk->sk_gso_disabled))
+               sk->sk_route_caps &= ~NETIF_F_GSO_MASK;
+       if (sk_can_gso(sk)) {
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -2023,7 +2023,7 @@ void ieee80211_color_collision_detection
+ /* interface handling */
+ #define MAC80211_SUPPORTED_FEATURES_TX        (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | \
+                                        NETIF_F_HW_CSUM | NETIF_F_SG | \
+-                                       NETIF_F_HIGHDMA | NETIF_F_GSO_SOFTWARE | \
++                                       NETIF_F_HIGHDMA | NETIF_F_GSO_SOFTWARE_ALL | \
+                                        NETIF_F_HW_TC)
+ #define MAC80211_SUPPORTED_FEATURES_RX        (NETIF_F_RXCSUM)
+ #define MAC80211_SUPPORTED_FEATURES   (MAC80211_SUPPORTED_FEATURES_TX | \
+--- a/net/openvswitch/vport-internal_dev.c
++++ b/net/openvswitch/vport-internal_dev.c
+@@ -109,7 +109,7 @@ static void do_setup(struct net_device *
+       netdev->rtnl_link_ops = &internal_dev_link_ops;
+       netdev->features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA |
+-                         NETIF_F_HW_CSUM | NETIF_F_GSO_SOFTWARE |
++                         NETIF_F_HW_CSUM | NETIF_F_GSO_SOFTWARE_ALL |
+                          NETIF_F_GSO_ENCAP_ALL;
+       netdev->vlan_features = netdev->features;
diff --git a/target/linux/generic/pending-6.12/683-of_net-add-mac-address-to-of-tree.patch b/target/linux/generic/pending-6.12/683-of_net-add-mac-address-to-of-tree.patch
new file mode 100644 (file)
index 0000000..0fb02db
--- /dev/null
@@ -0,0 +1,75 @@
+From 8585756342caa6d27008d1ad0c18023e4211a40a Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 12:22:48 +0200
+Subject: [PATCH] of/of_net: write back netdev MAC-address to device-tree
+
+The label-mac logic relies on the mac-address property of a netdev
+devices of-node. However, the mac address can also be stored as a
+different property or read from e.g. an mtd device.
+
+Create this node when reading a mac-address from OF if it does not
+already exist and copy the mac-address used for the device to this
+property. This way, the MAC address can be accessed using procfs.
+
+---
+ net/core/of_net.c | 22 ++++++++++++++++++++++
+ 1 file changed, 22 insertions(+)
+
+--- a/net/core/of_net.c
++++ b/net/core/of_net.c
+@@ -97,6 +97,27 @@ int of_get_mac_address_nvmem(struct devi
+ }
+ EXPORT_SYMBOL(of_get_mac_address_nvmem);
++static int of_add_mac_address(struct device_node *np, u8* addr)
++{
++      struct property *prop;
++
++      prop = kzalloc(sizeof(*prop), GFP_KERNEL);
++      if (!prop)
++              return -ENOMEM;
++
++      prop->name = "mac-address";
++      prop->length = ETH_ALEN;
++      prop->value = kmemdup(addr, ETH_ALEN, GFP_KERNEL);
++      if (!prop->value || of_update_property(np, prop))
++              goto free;
++
++      return 0;
++free:
++      kfree(prop->value);
++      kfree(prop);
++      return -ENOMEM;
++}
++
+ /**
+  * of_get_mac_address()
+  * @np:               Caller's Device Node
+@@ -132,17 +153,23 @@ int of_get_mac_address(struct device_nod
+       ret = of_get_mac_addr(np, "mac-address", addr);
+       if (!ret)
+-              return 0;
++              goto found;
+       ret = of_get_mac_addr(np, "local-mac-address", addr);
+       if (!ret)
+-              return 0;
++              goto found;
+       ret = of_get_mac_addr(np, "address", addr);
+       if (!ret)
+-              return 0;
++              goto found;
+-      return of_get_mac_address_nvmem(np, addr);
++      ret = of_get_mac_address_nvmem(np, addr);
++      if (ret)
++              return ret;
++
++found:
++      ret = of_add_mac_address(np, addr);
++      return ret;
+ }
+ EXPORT_SYMBOL(of_get_mac_address);
diff --git a/target/linux/generic/pending-6.12/690-net-add-missing-check-for-TCP-fraglist-GRO.patch b/target/linux/generic/pending-6.12/690-net-add-missing-check-for-TCP-fraglist-GRO.patch
new file mode 100644 (file)
index 0000000..4684e04
--- /dev/null
@@ -0,0 +1,26 @@
+From 4498f0aa561092bc656bfabe7c4bdae41bc4a5b4 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 7 May 2024 11:24:50 +0200
+Subject: [PATCH] net: add missing check for TCP fraglist GRO
+
+It turns out that the existing checks do not guarantee that the skb can be
+pulled up to the GRO offset. When using the usb r8152 network driver with
+GRO fraglist, the BUG() in __skb_pull is often triggered.
+Fix the crash by adding the missing check.
+
+Fixes: 8d95dc474f85 ("net: add code for TCP fraglist GRO")
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ net/ipv4/tcp_offload.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/net/ipv4/tcp_offload.c
++++ b/net/ipv4/tcp_offload.c
+@@ -355,6 +355,7 @@ struct sk_buff *tcp_gro_receive(struct l
+               flush |= (__force int)(flags ^ tcp_flag_word(th2));
+               flush |= skb->ip_summed != p->ip_summed;
+               flush |= skb->csum_level != p->csum_level;
++              flush |= !pskb_may_pull(skb, skb_gro_offset(skb));
+               flush |= NAPI_GRO_CB(p)->count >= 64;
+               skb_set_network_header(skb, skb_gro_receive_network_offset(skb));
diff --git a/target/linux/generic/pending-6.12/700-netfilter-nft_flow_offload-handle-netdevice-events-f.patch b/target/linux/generic/pending-6.12/700-netfilter-nft_flow_offload-handle-netdevice-events-f.patch
new file mode 100644 (file)
index 0000000..59868b2
--- /dev/null
@@ -0,0 +1,106 @@
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+Date: Thu, 25 Jan 2018 12:58:55 +0100
+Subject: [PATCH] netfilter: nft_flow_offload: handle netdevice events from
+ nf_flow_table
+
+Move the code that deals with device events to the core.
+
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+---
+
+--- a/net/netfilter/nf_flow_table_core.c
++++ b/net/netfilter/nf_flow_table_core.c
+@@ -658,6 +658,23 @@ static struct pernet_operations nf_flow_
+       .exit_batch = nf_flow_table_pernet_exit,
+ };
++static int nf_flow_table_netdev_event(struct notifier_block *this,
++                                    unsigned long event, void *ptr)
++{
++      struct net_device *dev = netdev_notifier_info_to_dev(ptr);
++
++      if (event != NETDEV_DOWN)
++              return NOTIFY_DONE;
++
++      nf_flow_table_cleanup(dev);
++
++      return NOTIFY_DONE;
++}
++
++static struct notifier_block flow_offload_netdev_notifier = {
++      .notifier_call  = nf_flow_table_netdev_event,
++};
++
+ static int __init nf_flow_table_module_init(void)
+ {
+       int ret;
+@@ -674,6 +691,10 @@ static int __init nf_flow_table_module_i
+       if (ret)
+               goto out_bpf;
++      ret = register_netdevice_notifier(&flow_offload_netdev_notifier);
++      if (ret)
++              goto out_bpf;
++
+       return 0;
+ out_bpf:
+@@ -685,6 +706,7 @@ out_offload:
+ static void __exit nf_flow_table_module_exit(void)
+ {
++      unregister_netdevice_notifier(&flow_offload_netdev_notifier);
+       nf_flow_table_offload_exit();
+       unregister_pernet_subsys(&nf_flow_table_net_ops);
+ }
+--- a/net/netfilter/nft_flow_offload.c
++++ b/net/netfilter/nft_flow_offload.c
+@@ -494,47 +494,14 @@ static struct nft_expr_type nft_flow_off
+       .owner          = THIS_MODULE,
+ };
+-static int flow_offload_netdev_event(struct notifier_block *this,
+-                                   unsigned long event, void *ptr)
+-{
+-      struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+-
+-      if (event != NETDEV_DOWN)
+-              return NOTIFY_DONE;
+-
+-      nf_flow_table_cleanup(dev);
+-
+-      return NOTIFY_DONE;
+-}
+-
+-static struct notifier_block flow_offload_netdev_notifier = {
+-      .notifier_call  = flow_offload_netdev_event,
+-};
+-
+ static int __init nft_flow_offload_module_init(void)
+ {
+-      int err;
+-
+-      err = register_netdevice_notifier(&flow_offload_netdev_notifier);
+-      if (err)
+-              goto err;
+-
+-      err = nft_register_expr(&nft_flow_offload_type);
+-      if (err < 0)
+-              goto register_expr;
+-
+-      return 0;
+-
+-register_expr:
+-      unregister_netdevice_notifier(&flow_offload_netdev_notifier);
+-err:
+-      return err;
++      return nft_register_expr(&nft_flow_offload_type);
+ }
+ static void __exit nft_flow_offload_module_exit(void)
+ {
+       nft_unregister_expr(&nft_flow_offload_type);
+-      unregister_netdevice_notifier(&flow_offload_netdev_notifier);
+ }
+ module_init(nft_flow_offload_module_init);
diff --git a/target/linux/generic/pending-6.12/701-netfilter-nf_tables-ignore-EOPNOTSUPP-on-flowtable-d.patch b/target/linux/generic/pending-6.12/701-netfilter-nf_tables-ignore-EOPNOTSUPP-on-flowtable-d.patch
new file mode 100644 (file)
index 0000000..d07b282
--- /dev/null
@@ -0,0 +1,29 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 31 Aug 2023 21:48:38 +0200
+Subject: [PATCH] netfilter: nf_tables: ignore -EOPNOTSUPP on flowtable device
+ offload setup
+
+On many embedded devices, it is common to configure flowtable offloading for
+a mix of different devices, some of which have hardware offload support and
+some of which don't.
+The current code limits the ability of user space to properly set up such a
+configuration by only allowing adding devices with hardware offload support to
+a offload-enabled flowtable.
+Given that offload-enabled flowtables also imply fallback to pure software
+offloading, this limitation makes little sense.
+Fix it by not bailing out when the offload setup returns -EOPNOTSUPP
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/netfilter/nf_tables_api.c
++++ b/net/netfilter/nf_tables_api.c
+@@ -8724,7 +8724,7 @@ static int nft_register_flowtable_net_ho
+               err = flowtable->data.type->setup(&flowtable->data,
+                                                 hook->ops.dev,
+                                                 FLOW_BLOCK_BIND);
+-              if (err < 0)
++              if (err < 0 && err != -EOPNOTSUPP)
+                       goto err_unregister_net_hooks;
+               err = nf_register_net_hook(net, &hook->ops);
diff --git a/target/linux/generic/pending-6.12/702-net-ethernet-mtk_eth_soc-enable-threaded-NAPI.patch b/target/linux/generic/pending-6.12/702-net-ethernet-mtk_eth_soc-enable-threaded-NAPI.patch
new file mode 100644 (file)
index 0000000..2fdb59d
--- /dev/null
@@ -0,0 +1,21 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Mon, 21 Mar 2022 20:39:59 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: enable threaded NAPI
+
+This can improve performance under load by ensuring that NAPI processing is
+not pinned on CPU 0.
+
+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
+@@ -5157,6 +5157,8 @@ static int mtk_probe(struct platform_dev
+               dev_err(eth->dev, "failed to allocated dummy device\n");
+               goto err_unreg_netdev;
+       }
++      eth->dummy_dev->threaded = 1;
++      strcpy(eth->dummy_dev->name, "mtk_eth");
+       netif_napi_add(eth->dummy_dev, &eth->tx_napi, mtk_napi_tx);
+       netif_napi_add(eth->dummy_dev, &eth->rx_napi, mtk_napi_rx);
diff --git a/target/linux/generic/pending-6.12/703-phy-add-detach-callback-to-struct-phy_driver.patch b/target/linux/generic/pending-6.12/703-phy-add-detach-callback-to-struct-phy_driver.patch
new file mode 100644 (file)
index 0000000..4e00118
--- /dev/null
@@ -0,0 +1,38 @@
+From: Gabor Juhos <juhosg@openwrt.org>
+Subject: generic: add detach callback to struct phy_driver
+
+lede-commit: fe61fc2d7d0b3fb348b502f68f98243b3ddf5867
+
+Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
+---
+ drivers/net/phy/phy_device.c | 3 +++
+ include/linux/phy.h          | 6 ++++++
+ 2 files changed, 9 insertions(+)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -2037,6 +2037,9 @@ void phy_detach(struct phy_device *phyde
+               phydev->devlink = NULL;
+       }
++      if (phydev->drv && phydev->drv->detach)
++              phydev->drv->detach(phydev);
++
+       if (phydev->sysfs_links) {
+               if (dev)
+                       sysfs_remove_link(&dev->dev.kobj, "phydev");
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1037,6 +1037,12 @@ struct phy_driver {
+       /** @handle_interrupt: Override default interrupt handling */
+       irqreturn_t (*handle_interrupt)(struct phy_device *phydev);
++      /*
++       * Called before an ethernet device is detached
++       * from the PHY.
++       */
++      void (*detach)(struct phy_device *phydev);
++
+       /** @remove: Clears up any memory if needed */
+       void (*remove)(struct phy_device *phydev);
diff --git a/target/linux/generic/pending-6.12/704-net-phy-register-phy-led_triggers-during-probe-to-av.patch b/target/linux/generic/pending-6.12/704-net-phy-register-phy-led_triggers-during-probe-to-av.patch
new file mode 100644 (file)
index 0000000..126275c
--- /dev/null
@@ -0,0 +1,116 @@
+From 5225349f1e750dfd107a4c5dc97d91fa212dc1ed Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Sat, 21 Feb 2026 14:51:54 -0600
+Subject: [PATCH] net: phy: register phy led_triggers during probe to avoid
+ AB-BA deadlock
+
+There is an AB-BA deadlock when both LEDS_TRIGGER_NETDEV and
+LED_TRIGGER_PHY are enabled:
+
+[ 1362.049207] [<8054e4b8>] led_trigger_register+0x5c/0x1fc             <-- Trying to get lock "triggers_list_lock" via down_write(&triggers_list_lock);
+[ 1362.054536] [<80662830>] phy_led_triggers_register+0xd0/0x234
+[ 1362.060329] [<8065e200>] phy_attach_direct+0x33c/0x40c
+[ 1362.065489] [<80651fc4>] phylink_fwnode_phy_connect+0x15c/0x23c
+[ 1362.071480] [<8066ee18>] mtk_open+0x7c/0xba0
+[ 1362.075849] [<806d714c>] __dev_open+0x280/0x2b0
+[ 1362.080384] [<806d7668>] __dev_change_flags+0x244/0x24c
+[ 1362.085598] [<806d7698>] dev_change_flags+0x28/0x78
+[ 1362.090528] [<807150e4>] dev_ioctl+0x4c0/0x654                       <-- Hold lock "rtnl_mutex" by calling rtnl_lock();
+[ 1362.094985] [<80694360>] sock_ioctl+0x2f4/0x4e0
+[ 1362.099567] [<802e9c4c>] sys_ioctl+0x32c/0xd8c
+[ 1362.104022] [<80014504>] syscall_common+0x34/0x58
+
+Here LED_TRIGGER_PHY is registering LED triggers during phy_attach
+while holding RTNL and then taking triggers_list_lock.
+
+[ 1362.191101] [<806c2640>] register_netdevice_notifier+0x60/0x168      <-- Trying to get lock "rtnl_mutex" via rtnl_lock();
+[ 1362.197073] [<805504ac>] netdev_trig_activate+0x194/0x1e4
+[ 1362.202490] [<8054e28c>] led_trigger_set+0x1d4/0x360                 <-- Hold lock "triggers_list_lock" by down_read(&triggers_list_lock);
+[ 1362.207511] [<8054eb38>] led_trigger_write+0xd8/0x14c
+[ 1362.212566] [<80381d98>] sysfs_kf_bin_write+0x80/0xbc
+[ 1362.217688] [<8037fcd8>] kernfs_fop_write_iter+0x17c/0x28c
+[ 1362.223174] [<802cbd70>] vfs_write+0x21c/0x3c4
+[ 1362.227712] [<802cc0c4>] ksys_write+0x78/0x12c
+[ 1362.232164] [<80014504>] syscall_common+0x34/0x58
+
+Here LEDS_TRIGGER_NETDEV is being enabled on an LED. It first takes
+triggers_list_lock and then RTNL. A classical AB-BA deadlock.
+
+phy_led_triggers_registers() does not require the RTNL, it does not
+make any calls into the network stack which require protection. There
+is also no requirement the PHY has been attached to a MAC, the
+triggers only make use of phydev state. This allows the call to
+phy_led_triggers_registers() to be placed elsewhere. PHY probe() and
+release() don't hold RTNL, so solving the AB-BA deadlock.
+
+Reported-by: Shiji Yang <yangshiji66@outlook.com>
+Closes: https://lore.kernel.org/all/OS7PR01MB13602B128BA1AD3FA38B6D1FFBC69A@OS7PR01MB13602.jpnprd01.prod.outlook.com/
+Fixes: 06f502f57d0d ("leds: trigger: Introduce a NETDEV trigger")
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ drivers/net/phy/phy_device.c | 25 +++++++++++++++++--------
+ 1 file changed, 17 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -1684,8 +1684,6 @@ int phy_attach_direct(struct net_device
+               goto error;
+       phy_resume(phydev);
+-      if (!phydev->is_on_sfp_module)
+-              phy_led_triggers_register(phydev);
+       /**
+        * If the external phy used by current mac interface is managed by
+@@ -2058,9 +2056,6 @@ void phy_detach(struct phy_device *phyde
+       }
+       phydev->phylink = NULL;
+-      if (!phydev->is_on_sfp_module)
+-              phy_led_triggers_unregister(phydev);
+-
+       if (phydev->mdio.dev.driver)
+               module_put(phydev->mdio.dev.driver->owner);
+@@ -3691,17 +3686,28 @@ static int phy_probe(struct device *dev)
+       /* Set the state to READY by default */
+       phydev->state = PHY_READY;
++      /* Register the PHY LED triggers */
++      if (!phydev->is_on_sfp_module)
++              phy_led_triggers_register(phydev);
++
+       /* Get the LEDs from the device tree, and instantiate standard
+        * LEDs for them.
+        */
+       if (IS_ENABLED(CONFIG_PHYLIB_LEDS) && !phy_driver_is_genphy(phydev) &&
+-          !phy_driver_is_genphy_10g(phydev))
++          !phy_driver_is_genphy_10g(phydev)) {
+               err = of_phy_leds(phydev);
++              if (err)
++                      goto out;
++      }
++
++      return 0;
+ out:
++      if (!phydev->is_on_sfp_module)
++              phy_led_triggers_unregister(phydev);
++
+       /* Re-assert the reset signal on error */
+-      if (err)
+-              phy_device_reset(phydev, 1);
++      phy_device_reset(phydev, 1);
+       return err;
+ }
+@@ -3716,6 +3722,9 @@ static int phy_remove(struct device *dev
+           !phy_driver_is_genphy_10g(phydev))
+               phy_leds_unregister(phydev);
++      if (!phydev->is_on_sfp_module)
++              phy_led_triggers_unregister(phydev);
++
+       phydev->state = PHY_DOWN;
+       sfp_bus_del_upstream(phydev->sfp_bus);
diff --git a/target/linux/generic/pending-6.12/705-net-dsa-tag_mtk-add-padding-for-tx-packets.patch b/target/linux/generic/pending-6.12/705-net-dsa-tag_mtk-add-padding-for-tx-packets.patch
new file mode 100644 (file)
index 0000000..f7e4e77
--- /dev/null
@@ -0,0 +1,28 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 6 May 2022 21:38:42 +0200
+Subject: [PATCH] net: dsa: tag_mtk: add padding for tx packets
+
+Padding for transmitted packets needs to account for the special tag.
+With not enough padding, garbage bytes are inserted by the switch at the
+end of small packets.
+
+Fixes: 5cd8985a1909 ("net-next: dsa: add Mediatek tag RX/TX handler")
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/dsa/tag_mtk.c
++++ b/net/dsa/tag_mtk.c
+@@ -29,6 +29,13 @@ static struct sk_buff *mtk_tag_xmit(stru
+       skb_set_queue_mapping(skb, dp->index);
++      /* The Ethernet switch we are interfaced with needs packets to be at
++       * least 64 bytes (including FCS) otherwise their padding might be
++       * corrupted. With tags enabled, we need to make sure that packets are
++       * at least 68 bytes (including FCS and tag).
++       */
++      eth_skb_pad(skb);
++
+       /* 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/pending-6.12/706-net-phy-populate-host_interfaces-when-attaching-PHY.patch b/target/linux/generic/pending-6.12/706-net-phy-populate-host_interfaces-when-attaching-PHY.patch
new file mode 100644 (file)
index 0000000..dea84c7
--- /dev/null
@@ -0,0 +1,57 @@
+From 4e432e530db0056450fbc4a3cee793f16adc39a7 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 8 Oct 2024 23:58:41 +0100
+Subject: [PATCH] net: phy: populate host_interfaces when attaching PHY
+
+Use bitmask of interfaces supported by the MAC for the PHY to choose
+from if the declared interface mode is among those using a single pair
+of SerDes lanes.
+This will allow 2500Base-T PHYs to switch to SGMII on most hosts, which
+results in half-duplex being supported in case the MAC supports them.
+Without this change, 2500Base-T PHYs will always operate in 2500Base-X
+mode with rate-matching, which is not only wasteful in terms of energy
+consumption, but also limits the supported interface modes to
+full-duplex only.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/phy/phylink.c | 21 ++++++++++++++++++++-
+ 1 file changed, 20 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -2273,7 +2273,7 @@ int phylink_fwnode_phy_connect(struct ph
+ {
+       struct fwnode_handle *phy_fwnode;
+       struct phy_device *phy_dev;
+-      int ret;
++      int i, ret;
+       /* Fixed links and 802.3z are handled without needing a PHY */
+       if (pl->cfg_link_an_mode == MLO_AN_FIXED ||
+@@ -2303,6 +2303,25 @@ int phylink_fwnode_phy_connect(struct ph
+       if (pl->config->mac_requires_rxc)
+               flags |= PHY_F_RXC_ALWAYS_ON;
++      /* Assume single-lane SerDes interface modes share the same
++       * lanes and allow the PHY to switch to slower also supported modes
++       */
++      for (i = ARRAY_SIZE(phylink_sfp_interface_preference) - 1; i >= 0; i--) {
++              /* skip unsupported modes */
++              if (!test_bit(phylink_sfp_interface_preference[i], pl->config->supported_interfaces))
++                      continue;
++
++              __set_bit(phylink_sfp_interface_preference[i], phy_dev->host_interfaces);
++
++              /* skip all faster modes */
++              if (phylink_sfp_interface_preference[i] == pl->link_interface)
++                      break;
++      }
++
++      if (test_bit(pl->link_interface, phylink_sfp_interfaces))
++              phy_interface_and(phy_dev->host_interfaces, phylink_sfp_interfaces,
++                                pl->config->supported_interfaces);
++
+       ret = phy_attach_direct(pl->netdev, phy_dev, flags,
+                               pl->link_interface);
+       phy_device_free(phy_dev);
diff --git a/target/linux/generic/pending-6.12/710-bridge-add-knob-for-filtering-rx-tx-BPDU-pack.patch b/target/linux/generic/pending-6.12/710-bridge-add-knob-for-filtering-rx-tx-BPDU-pack.patch
new file mode 100644 (file)
index 0000000..2f90df6
--- /dev/null
@@ -0,0 +1,174 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 27 Aug 2021 12:22:32 +0200
+Subject: [PATCH] bridge: add knob for filtering rx/tx BPDU packets on a port
+
+Some devices (e.g. wireless APs) can't have devices behind them be part of
+a bridge topology with redundant links, due to address limitations.
+Additionally, broadcast traffic on these devices is somewhat expensive, due to
+the low data rate and wakeups of clients in powersave mode.
+This knob can be used to ensure that BPDU packets are never sent or forwarded
+to/from these devices
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/include/linux/if_bridge.h
++++ b/include/linux/if_bridge.h
+@@ -61,6 +61,7 @@ struct br_ip_list {
+ #define BR_PORT_LOCKED                BIT(21)
+ #define BR_PORT_MAB           BIT(22)
+ #define BR_NEIGH_VLAN_SUPPRESS        BIT(23)
++#define BR_BPDU_FILTER                BIT(24)
+ #define BR_DEFAULT_AGEING_TIME        (300 * HZ)
+--- a/net/bridge/br_forward.c
++++ b/net/bridge/br_forward.c
+@@ -202,6 +202,7 @@ void br_flood(struct net_bridge *br, str
+             enum br_pkt_type pkt_type, bool local_rcv, bool local_orig,
+             u16 vid)
+ {
++      const unsigned char *dest = eth_hdr(skb)->h_dest;
+       struct net_bridge_port *prev = NULL;
+       struct net_bridge_port *p;
+@@ -219,6 +220,10 @@ void br_flood(struct net_bridge *br, str
+               case BR_PKT_MULTICAST:
+                       if (!(p->flags & BR_MCAST_FLOOD) && skb->dev != br->dev)
+                               continue;
++                      if ((p->flags & BR_BPDU_FILTER) &&
++                          unlikely(is_link_local_ether_addr(dest) &&
++                                   dest[5] == 0))
++                              continue;
+                       break;
+               case BR_PKT_BROADCAST:
+                       if (!(p->flags & BR_BCAST_FLOOD) && skb->dev != br->dev)
+--- a/net/bridge/br_input.c
++++ b/net/bridge/br_input.c
+@@ -368,6 +368,8 @@ static rx_handler_result_t br_handle_fra
+               fwd_mask |= p->group_fwd_mask;
+               switch (dest[5]) {
+               case 0x00:      /* Bridge Group Address */
++                      if (p->flags & BR_BPDU_FILTER)
++                              goto drop;
+                       /* If STP is turned off,
+                          then must forward to keep loop detection */
+                       if (p->br->stp_enabled == BR_NO_STP ||
+--- a/net/bridge/br_sysfs_if.c
++++ b/net/bridge/br_sysfs_if.c
+@@ -240,6 +240,7 @@ BRPORT_ATTR_FLAG(multicast_flood, BR_MCA
+ BRPORT_ATTR_FLAG(broadcast_flood, BR_BCAST_FLOOD);
+ BRPORT_ATTR_FLAG(neigh_suppress, BR_NEIGH_SUPPRESS);
+ BRPORT_ATTR_FLAG(isolated, BR_ISOLATED);
++BRPORT_ATTR_FLAG(bpdu_filter, BR_BPDU_FILTER);
+ #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
+ static ssize_t show_multicast_router(struct net_bridge_port *p, char *buf)
+@@ -292,6 +293,7 @@ static const struct brport_attribute *br
+       &brport_attr_group_fwd_mask,
+       &brport_attr_neigh_suppress,
+       &brport_attr_isolated,
++      &brport_attr_bpdu_filter,
+       &brport_attr_backup_port,
+       NULL
+ };
+--- a/net/bridge/br_stp_bpdu.c
++++ b/net/bridge/br_stp_bpdu.c
+@@ -80,7 +80,8 @@ void br_send_config_bpdu(struct net_brid
+ {
+       unsigned char buf[35];
+-      if (p->br->stp_enabled != BR_KERNEL_STP)
++      if (p->br->stp_enabled != BR_KERNEL_STP ||
++          (p->flags & BR_BPDU_FILTER))
+               return;
+       buf[0] = 0;
+@@ -127,7 +128,8 @@ void br_send_tcn_bpdu(struct net_bridge_
+ {
+       unsigned char buf[4];
+-      if (p->br->stp_enabled != BR_KERNEL_STP)
++      if (p->br->stp_enabled != BR_KERNEL_STP ||
++          (p->flags & BR_BPDU_FILTER))
+               return;
+       buf[0] = 0;
+@@ -172,6 +174,9 @@ void br_stp_rcv(const struct stp_proto *
+       if (!(br->dev->flags & IFF_UP))
+               goto out;
++      if (p->flags & BR_BPDU_FILTER)
++              goto out;
++
+       if (p->state == BR_STATE_DISABLED)
+               goto out;
+--- a/include/uapi/linux/if_link.h
++++ b/include/uapi/linux/if_link.h
+@@ -1094,6 +1094,7 @@ enum {
+       IFLA_BRPORT_MCAST_MAX_GROUPS,
+       IFLA_BRPORT_NEIGH_VLAN_SUPPRESS,
+       IFLA_BRPORT_BACKUP_NHID,
++      IFLA_BRPORT_BPDU_FILTER,
+       __IFLA_BRPORT_MAX
+ };
+ #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1)
+--- a/net/bridge/br_netlink.c
++++ b/net/bridge/br_netlink.c
+@@ -190,6 +190,7 @@ static inline size_t br_port_info_size(v
+               + nla_total_size(1)     /* IFLA_BRPORT_LOCKED */
+               + nla_total_size(1)     /* IFLA_BRPORT_MAB */
+               + nla_total_size(1)     /* IFLA_BRPORT_NEIGH_VLAN_SUPPRESS */
++              + nla_total_size(1)     /* IFLA_BRPORT_BPDU_FILTER */
+               + nla_total_size(sizeof(struct ifla_bridge_id)) /* IFLA_BRPORT_ROOT_ID */
+               + nla_total_size(sizeof(struct ifla_bridge_id)) /* IFLA_BRPORT_BRIDGE_ID */
+               + nla_total_size(sizeof(u16))   /* IFLA_BRPORT_DESIGNATED_PORT */
+@@ -282,7 +283,8 @@ static int br_port_fill_attrs(struct sk_
+           nla_put_u8(skb, IFLA_BRPORT_LOCKED, !!(p->flags & BR_PORT_LOCKED)) ||
+           nla_put_u8(skb, IFLA_BRPORT_MAB, !!(p->flags & BR_PORT_MAB)) ||
+           nla_put_u8(skb, IFLA_BRPORT_NEIGH_VLAN_SUPPRESS,
+-                     !!(p->flags & BR_NEIGH_VLAN_SUPPRESS)))
++                     !!(p->flags & BR_NEIGH_VLAN_SUPPRESS)) ||
++          nla_put_u8(skb, IFLA_BRPORT_BPDU_FILTER, !!(p->flags & BR_BPDU_FILTER)))
+               return -EMSGSIZE;
+       timerval = br_timer_value(&p->message_age_timer);
+@@ -902,6 +904,7 @@ static const struct nla_policy br_port_p
+       [IFLA_BRPORT_MCAST_MAX_GROUPS] = { .type = NLA_U32 },
+       [IFLA_BRPORT_NEIGH_VLAN_SUPPRESS] = NLA_POLICY_MAX(NLA_U8, 1),
+       [IFLA_BRPORT_BACKUP_NHID] = { .type = NLA_U32 },
++      [IFLA_BRPORT_BPDU_FILTER] = { .type = NLA_U8 },
+ };
+ /* Change the state of the port and notify spanning tree */
+@@ -970,6 +973,7 @@ static int br_setport(struct net_bridge_
+       br_set_port_flag(p, tb, IFLA_BRPORT_MAB, BR_PORT_MAB);
+       br_set_port_flag(p, tb, IFLA_BRPORT_NEIGH_VLAN_SUPPRESS,
+                        BR_NEIGH_VLAN_SUPPRESS);
++      br_set_port_flag(p, tb, IFLA_BRPORT_BPDU_FILTER, BR_BPDU_FILTER);
+       if ((p->flags & BR_PORT_MAB) &&
+           (!(p->flags & BR_PORT_LOCKED) || !(p->flags & BR_LEARNING))) {
+--- a/net/core/rtnetlink.c
++++ b/net/core/rtnetlink.c
+@@ -62,7 +62,7 @@
+ #include "dev.h"
+ #define RTNL_MAX_TYPE         50
+-#define RTNL_SLAVE_MAX_TYPE   44
++#define RTNL_SLAVE_MAX_TYPE   45
+ struct rtnl_link {
+       rtnl_doit_func          doit;
+@@ -5009,7 +5009,9 @@ int ndo_dflt_bridge_getlink(struct sk_bu
+           brport_nla_put_flag(skb, flags, mask,
+                               IFLA_BRPORT_MCAST_FLOOD, BR_MCAST_FLOOD) ||
+           brport_nla_put_flag(skb, flags, mask,
+-                              IFLA_BRPORT_BCAST_FLOOD, BR_BCAST_FLOOD)) {
++                              IFLA_BRPORT_BCAST_FLOOD, BR_BCAST_FLOOD) ||
++          brport_nla_put_flag(skb, flags, mask,
++                              IFLA_BRPORT_BPDU_FILTER, BR_BPDU_FILTER)) {
+               nla_nest_cancel(skb, protinfo);
+               goto nla_put_failure;
+       }
diff --git a/target/linux/generic/pending-6.12/711-01-net-dsa-qca8k-implement-lag_fdb_add-del-ops.patch b/target/linux/generic/pending-6.12/711-01-net-dsa-qca8k-implement-lag_fdb_add-del-ops.patch
new file mode 100644 (file)
index 0000000..0e89159
--- /dev/null
@@ -0,0 +1,86 @@
+From 3b4329230db8750bea7a56ef07f07cbbf5fc6c5a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 4 Jul 2023 22:50:12 +0200
+Subject: [PATCH 19/20] net: dsa: qca8k: implement lag_fdb_add/del ops
+
+Implement lag_fdb_add/del ops to correctly support using LAG interface.
+Qca8k switch supports declaring fdb entry for link aggregation by simply
+setting the DES_PORT bits to all the LAG member.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c   |  2 ++
+ drivers/net/dsa/qca/qca8k-common.c | 48 ++++++++++++++++++++++++++++++
+ drivers/net/dsa/qca/qca8k.h        |  6 ++++
+ 3 files changed, 56 insertions(+)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -2031,6 +2031,8 @@ static const struct dsa_switch_ops qca8k
+       .port_fdb_add           = qca8k_port_fdb_add,
+       .port_fdb_del           = qca8k_port_fdb_del,
+       .port_fdb_dump          = qca8k_port_fdb_dump,
++      .lag_fdb_add            = qca8k_lag_fdb_add,
++      .lag_fdb_del            = qca8k_lag_fdb_del,
+       .port_mdb_add           = qca8k_port_mdb_add,
+       .port_mdb_del           = qca8k_port_mdb_del,
+       .port_mirror_add        = qca8k_port_mirror_add,
+--- a/drivers/net/dsa/qca/qca8k-common.c
++++ b/drivers/net/dsa/qca/qca8k-common.c
+@@ -1234,6 +1234,42 @@ int qca8k_port_lag_leave(struct dsa_swit
+       return qca8k_lag_refresh_portmap(ds, port, lag, true);
+ }
++int qca8k_lag_fdb_add(struct dsa_switch *ds, struct dsa_lag lag,
++                    const unsigned char *addr, u16 vid,
++                    struct dsa_db db)
++{
++      struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++      struct dsa_port *dp;
++      u16 port_mask = 0;
++
++      /* Set the vid to the port vlan id if no vid is set */
++      if (!vid)
++              vid = QCA8K_PORT_VID_DEF;
++
++      dsa_lag_foreach_port(dp, ds->dst, &lag)
++              port_mask |= BIT(dp->index);
++
++      return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
++}
++
++int qca8k_lag_fdb_del(struct dsa_switch *ds, struct dsa_lag lag,
++                    const unsigned char *addr, u16 vid,
++                    struct dsa_db db)
++{
++      struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
++      struct dsa_port *dp;
++      u16 port_mask = 0;
++
++      /* Set the vid to the port vlan id if no vid is set */
++      if (!vid)
++              vid = QCA8K_PORT_VID_DEF;
++
++      dsa_lag_foreach_port(dp, ds->dst, &lag)
++      port_mask |= BIT(dp->index);
++
++      return qca8k_fdb_del(priv, addr, port_mask, vid);
++}
++
+ int qca8k_read_switch_id(struct qca8k_priv *priv)
+ {
+       u32 val;
+--- a/drivers/net/dsa/qca/qca8k.h
++++ b/drivers/net/dsa/qca/qca8k.h
+@@ -592,5 +592,11 @@ int qca8k_port_lag_join(struct dsa_switc
+                       struct netlink_ext_ack *extack);
+ int qca8k_port_lag_leave(struct dsa_switch *ds, int port,
+                        struct dsa_lag lag);
++int qca8k_lag_fdb_add(struct dsa_switch *ds, struct dsa_lag lag,
++                    const unsigned char *addr, u16 vid,
++                    struct dsa_db db);
++int qca8k_lag_fdb_del(struct dsa_switch *ds, struct dsa_lag lag,
++                    const unsigned char *addr, u16 vid,
++                    struct dsa_db db);
+ #endif /* __QCA8K_H */
diff --git a/target/linux/generic/pending-6.12/711-02-net-dsa-qca8k-enable-flooding-to-both-CPU-port.patch b/target/linux/generic/pending-6.12/711-02-net-dsa-qca8k-enable-flooding-to-both-CPU-port.patch
new file mode 100644 (file)
index 0000000..d10bbcf
--- /dev/null
@@ -0,0 +1,37 @@
+From b954d61d9ecfa64450fc178586719dc2a95b92a7 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 20 Jun 2023 21:48:24 +0200
+Subject: [PATCH 3/4] net: dsa: qca8k: enable flooding to both CPU port
+
+To permit a multi-CPU setup, flood all unknown frames to all CPU ports.
+Each CPU port should have correct LOOKUP MEMBER configuration to
+prevent receiving duplicate packets from user ports.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 13 +++++--------
+ 1 file changed, 5 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -1913,15 +1913,12 @@ qca8k_setup(struct dsa_switch *ds)
+               }
+       }
+-      /* Forward all unknown frames to CPU port for Linux processing
+-       * Notice that in multi-cpu config only one port should be set
+-       * for igmp, unknown, multicast and broadcast packet
+-       */
++      /* Forward all unknown frames to CPU port for Linux processing */
+       ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
+-                        FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK, BIT(cpu_port)) |
+-                        FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK, BIT(cpu_port)) |
+-                        FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK, BIT(cpu_port)) |
+-                        FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK, BIT(cpu_port)));
++                        FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK, dsa_cpu_ports(ds)) |
++                        FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK, dsa_cpu_ports(ds)) |
++                        FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK, dsa_cpu_ports(ds)) |
++                        FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK, dsa_cpu_ports(ds)));
+       if (ret)
+               return ret;
diff --git a/target/linux/generic/pending-6.12/711-03-net-dsa-qca8k-add-support-for-port_change_master.patch b/target/linux/generic/pending-6.12/711-03-net-dsa-qca8k-add-support-for-port_change_master.patch
new file mode 100644 (file)
index 0000000..25c6ae3
--- /dev/null
@@ -0,0 +1,158 @@
+From b2d6ebf2f92f8695c83fa6979f4ab579c588df76 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 20 Jun 2023 07:57:38 +0200
+Subject: [PATCH 4/4] net: dsa: qca8k: add support for port_change_master
+
+Add support for port_change_master to permit assigning an alternative
+CPU port if the switch have both CPU port connected or create a LAG on
+both CPU port and assign the LAG as DSA master.
+
+On port change master request, we check if the master is a LAG.
+With LAG we compose the cpu_port_mask with the CPU port in the LAG, if
+master is a simple dsa_port, we derive the index.
+
+Finally we apply the new cpu_port_mask to the LOOKUP MEMBER to permit
+the port to receive packet by the new CPU port setup for the port and we
+refresh the CPU ports LOOKUP MEMBER configuration to reflect the new
+user port state.
+
+port_lag_join/leave is updated to refresh the user ports if we detect
+that the LAG is a DSA master and we have user port using it as a master.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 116 ++++++++++++++++++++++++++++++-
+ 1 file changed, 114 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -1750,6 +1750,117 @@ qca8k_get_tag_protocol(struct dsa_switch
+       return DSA_TAG_PROTO_QCA;
+ }
++static int qca8k_port_change_master(struct dsa_switch *ds, int port,
++                                  struct net_device *master,
++                                  struct netlink_ext_ack *extack)
++{
++      struct dsa_switch_tree *dst = ds->dst;
++      struct qca8k_priv *priv = ds->priv;
++      u8 cpu_port_mask = 0;
++      struct dsa_port *dp;
++      u32 val;
++      int ret;
++
++      /* With LAG of CPU port, compose the mask for port LOOKUP MEMBER */
++      if (netif_is_lag_master(master)) {
++              struct dsa_lag *lag;
++              int id;
++
++              id = dsa_lag_id(dst, master);
++              lag = dsa_lag_by_id(dst, id);
++
++              dsa_lag_foreach_port(dp, dst, lag)
++                      if (dsa_port_is_cpu(dp))
++                              cpu_port_mask |= BIT(dp->index);
++      } else {
++              dp = master->dsa_ptr;
++              cpu_port_mask |= BIT(dp->index);
++      }
++
++      /* Connect port to new cpu port */
++      ret = regmap_read(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(port), &val);
++      if (ret)
++              return ret;
++
++      /* Reset connected CPU port in port LOOKUP MEMBER */
++      val &=  ~dsa_cpu_ports(ds);
++      /* Assign the new CPU port in port LOOKUP MEMBER */
++      val |= cpu_port_mask;
++
++      ret = regmap_update_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(port),
++                               QCA8K_PORT_LOOKUP_MEMBER,
++                               val);
++      if (ret)
++              return ret;
++
++      /* Refresh CPU port LOOKUP MEMBER with new port */
++      dsa_tree_for_each_cpu_port(dp, ds->dst) {
++              u32 reg = QCA8K_PORT_LOOKUP_CTRL(dp->index);
++
++              /* If CPU port in mask assign port, else remove port */
++              if (BIT(dp->index) & cpu_port_mask)
++                      ret = regmap_set_bits(priv->regmap, reg, BIT(port));
++              else
++                      ret = regmap_clear_bits(priv->regmap, reg, BIT(port));
++
++              if (ret)
++                      return ret;
++      }
++
++      return 0;
++}
++
++static int qca8k_port_lag_refresh_user_ports(struct dsa_switch *ds,
++                                           struct dsa_lag lag)
++{
++      struct net_device *lag_dev = lag.dev;
++      struct dsa_port *dp;
++      int ret;
++
++      /* Ignore if LAG is not a DSA master */
++      if (!netif_is_lag_master(lag_dev))
++              return 0;
++
++      dsa_switch_for_each_user_port(dp, ds) {
++              /* Skip if assigned master is not the LAG */
++              if (dsa_port_to_conduit(dp) != lag_dev)
++                      continue;
++
++              ret = qca8k_port_change_master(ds, dp->index,
++                                             lag_dev, NULL);
++              if (ret)
++                      return ret;
++      }
++
++      return 0;
++}
++
++static int qca8xxx_port_lag_join(struct dsa_switch *ds, int port,
++                               struct dsa_lag lag,
++                               struct netdev_lag_upper_info *info,
++                               struct netlink_ext_ack *extack)
++{
++      int ret;
++
++      ret = qca8k_port_lag_join(ds, port, lag, info, extack);
++      if (ret)
++              return ret;
++
++      return qca8k_port_lag_refresh_user_ports(ds, lag);
++}
++
++static int qca8xxx_port_lag_leave(struct dsa_switch *ds, int port,
++                                struct dsa_lag lag)
++{
++      int ret;
++
++      ret = qca8k_port_lag_leave(ds, port, lag);
++      if (ret)
++              return ret;
++
++      return qca8k_port_lag_refresh_user_ports(ds, lag);
++}
++
+ static void
+ qca8k_conduit_change(struct dsa_switch *ds, const struct net_device *conduit,
+                    bool operational)
+@@ -2039,8 +2150,9 @@ static const struct dsa_switch_ops qca8k
+       .port_vlan_del          = qca8k_port_vlan_del,
+       .phylink_get_caps       = qca8k_phylink_get_caps,
+       .get_phy_flags          = qca8k_get_phy_flags,
+-      .port_lag_join          = qca8k_port_lag_join,
+-      .port_lag_leave         = qca8k_port_lag_leave,
++      .port_lag_join          = qca8xxx_port_lag_join,
++      .port_lag_leave         = qca8xxx_port_lag_leave,
++      .port_change_conduit    = qca8k_port_change_master,
+       .conduit_state_change   = qca8k_conduit_change,
+       .connect_tag_protocol   = qca8k_connect_tag_protocol,
+ };
diff --git a/target/linux/generic/pending-6.12/712-net-dsa-qca8k-enable-assisted-learning-on-CPU-port.patch b/target/linux/generic/pending-6.12/712-net-dsa-qca8k-enable-assisted-learning-on-CPU-port.patch
new file mode 100644 (file)
index 0000000..03cf2db
--- /dev/null
@@ -0,0 +1,57 @@
+From 0f6599167c126ce32c85d4f8a1f3d1775a268572 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Fri, 6 Oct 2023 12:44:00 +0200
+Subject: [PATCH] net: dsa: qca8k: enable assisted learning on CPU port
+
+Enable assisted learning on CPU port.
+
+It has been verified that there is a problem in packet roaming
+from one BSS to another in the same security settings from one
+physical R7800 to another physical R7800 where they are in the
+same L2 broadcast domain backhauled/linked together via one
+of the ethernet ports.
+DHCP will fail to complete and traffic cannot flow for around 300
+seconds.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 14 +++++++++-----
+ 1 file changed, 9 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -2022,6 +2022,12 @@ qca8k_setup(struct dsa_switch *ds)
+                       dev_err(priv->dev, "failed enabling QCA header mode on port %d", dp->index);
+                       return ret;
+               }
++
++              /* Disable learning by default on all ports */
++              ret = regmap_clear_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(dp->index),
++                                                              QCA8K_PORT_LOOKUP_LEARN);
++              if (ret)
++                      return ret;
+       }
+       /* Forward all unknown frames to CPU port for Linux processing */
+@@ -2051,11 +2057,6 @@ qca8k_setup(struct dsa_switch *ds)
+               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
+                */
+@@ -2107,6 +2108,9 @@ qca8k_setup(struct dsa_switch *ds)
+       /* Set max number of LAGs supported */
+       ds->num_lag_ids = QCA8K_NUM_LAGS;
++      /* HW learn on CPU port is limited and require manual setting */
++      ds->assisted_learning_on_cpu_port = true;
++
+       return 0;
+ }
diff --git a/target/linux/generic/pending-6.12/713-net-phy-c45-check-validity-of-10GbE-link-partner.patch b/target/linux/generic/pending-6.12/713-net-phy-c45-check-validity-of-10GbE-link-partner.patch
new file mode 100644 (file)
index 0000000..5870dfb
--- /dev/null
@@ -0,0 +1,29 @@
+From 7d719484930bc10c8472c0d43b94b68087541b24 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sat, 22 Apr 2023 01:25:39 +0100
+Subject: [PATCH] net: phy: c45: check validity of 10GbE link-partner
+ advertisement
+
+Only use link-partner advertisement bits for 10GbE modes if they are
+actually valid. Check LOCALOK and REMOTEOK bits and clear 10GbE modes
+unless both of them are set.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+--- a/include/linux/mdio.h
++++ b/include/linux/mdio.h
+@@ -323,8 +323,14 @@ static inline u32 linkmode_adv_to_mii_10
+  * to linkmode advertisement settings. Other bits in advertising aren't changed.
+  */
+ static inline void mii_10gbt_stat_mod_linkmode_lpa_t(unsigned long *advertising,
+-                                                   u32 lpa)
++                                                   u32 lpa_in)
+ {
++      u32 lpa = lpa_in;
++
++      if (!(lpa & MDIO_AN_10GBT_STAT_REMOK) ||
++          !(lpa & MDIO_AN_10GBT_STAT_LOCOK))
++              lpa = 0;
++
+       linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+                        advertising, lpa & MDIO_AN_10GBT_STAT_LP2_5G);
+       linkmode_mod_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
diff --git a/target/linux/generic/pending-6.12/720-01-net-phy-realtek-use-genphy_soft_reset-for-2.5G-PHYs.patch b/target/linux/generic/pending-6.12/720-01-net-phy-realtek-use-genphy_soft_reset-for-2.5G-PHYs.patch
new file mode 100644 (file)
index 0000000..2a0a0e9
--- /dev/null
@@ -0,0 +1,57 @@
+From 85cd45580f5e3b26068cccb7d6173f200e754dc0 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 2 Apr 2023 23:56:16 +0100
+Subject: [PATCH 1/2] net: phy: realtek: use genphy_soft_reset for 2.5G PHYs
+
+Some vendor bootloaders do weird things with those PHYs which result in
+link modes being reported wrongly. Start from a clean sheet by resetting
+the PHY.
+
+Reported-by: Yevhen Kolomeiko <jarvis2709@gmail.com>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -2255,6 +2255,7 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               .name           = "RTL8226 2.5Gbps PHY",
+               .match_phy_device = rtl8226_match_phy_device,
++              .soft_reset     = genphy_soft_reset,
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
+               .read_status    = rtl822x_read_status,
+@@ -2267,6 +2268,7 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               .match_phy_device = rtl8221b_match_phy_device,
+               .name           = "RTL8226B_RTL8221B 2.5Gbps PHY",
++              .soft_reset     = genphy_soft_reset,
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
+               .config_init    = rtl822xb_config_init,
+@@ -2297,6 +2299,7 @@ static struct phy_driver realtek_drvs[]
+       }, {
+               PHY_ID_MATCH_EXACT(0x001cc848),
+               .name           = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY",
++              .soft_reset     = genphy_soft_reset,
+               .get_features   = rtl822x_get_features,
+               .config_aneg    = rtl822x_config_aneg,
+               .config_init    = rtl822xb_config_init,
+@@ -2315,6 +2318,7 @@ static struct phy_driver realtek_drvs[]
+               .name           = "RTL8221B-VB-CG 2.5Gbps PHY",
+               .config_intr    = rtl8221b_config_intr,
+               .handle_interrupt = rtl8221b_handle_interrupt,
++              .soft_reset     = rtl822x_c45_soft_reset,
+               .probe          = rtl822x_probe,
+               .config_init    = rtl822xb_config_init,
+               .inband_caps    = rtl822x_inband_caps,
+@@ -2334,6 +2338,7 @@ static struct phy_driver realtek_drvs[]
+               .name           = "RTL8221B-VM-CG 2.5Gbps PHY",
+               .config_intr    = rtl8221b_config_intr,
+               .handle_interrupt = rtl8221b_handle_interrupt,
++              .soft_reset     = rtl822x_c45_soft_reset,
+               .probe          = rtl822x_probe,
+               .config_init    = rtl822xb_config_init,
+               .inband_caps    = rtl822x_inband_caps,
diff --git a/target/linux/generic/pending-6.12/720-03-net-phy-realtek-make-sure-paged-read-is-protected-by.patch b/target/linux/generic/pending-6.12/720-03-net-phy-realtek-make-sure-paged-read-is-protected-by.patch
new file mode 100644 (file)
index 0000000..8a0db2c
--- /dev/null
@@ -0,0 +1,35 @@
+From 4dd2cc9b91ecb25f278a2c55e07e6455e9000e6b Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sat, 22 Apr 2023 01:21:14 +0100
+Subject: [PATCH] net: phy: realtek: make sure paged read is protected by mutex
+
+As we cannot rely on phy_read_paged function before the PHY is
+identified, the paged read in rtlgen_supports_2_5gbps needs to be open
+coded as it is being called by the match_phy_device function, ie. before
+.read_page and .write_page have been populated.
+
+Make sure it is also protected by the MDIO bus mutex and use
+rtl821x_write_page instead of 3 individually locked MDIO bus operations.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -1823,9 +1823,11 @@ static bool rtlgen_supports_2_5gbps(stru
+ {
+       int val;
+-      phy_write(phydev, RTL821x_PAGE_SELECT, 0xa61);
+-      val = phy_read(phydev, 0x13);
+-      phy_write(phydev, RTL821x_PAGE_SELECT, 0);
++      mutex_lock(&phydev->mdio.bus->mdio_lock);
++      rtl821x_write_page(phydev, 0xa61);
++      val = __phy_read(phydev, 0x13);
++      rtl821x_write_page(phydev, 0);
++      mutex_unlock(&phydev->mdio.bus->mdio_lock);
+       return val >= 0 && val & MDIO_PMA_SPEED_2_5G;
+ }
diff --git a/target/linux/generic/pending-6.12/720-04-net-phy-realtek-setup-aldps.patch b/target/linux/generic/pending-6.12/720-04-net-phy-realtek-setup-aldps.patch
new file mode 100644 (file)
index 0000000..1a33ea5
--- /dev/null
@@ -0,0 +1,93 @@
+From 9155098547fb1172d4fa536f3f6bc9d42f59d08c Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sat, 22 Apr 2023 03:26:01 +0100
+Subject: [PATCH] net: phy: realtek: setup ALDPS on RTL822x
+
+Setup Link Down Power Saving Mode according the DTS property
+just like for RTL821x 1GE PHYs.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -170,6 +170,10 @@
+ #define RTL8224_SRAM_RTCT_LEN(pair)           (0x8028 + (pair) * 4)
++#define RTL8221B_PHYCR1                               0xa430
++#define RTL8221B_PHYCR1_ALDPS_EN              BIT(2)
++#define RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN     BIT(12)
++
+ #define RTL8366RB_POWER_SAVE                  0x15
+ #define RTL8366RB_POWER_SAVE_ON                       BIT(12)
+@@ -216,6 +220,10 @@ struct rtl821x_priv {
+       u16 iner;
+ };
++struct rtl822x_priv {
++      bool enable_aldps;
++};
++
+ static int rtl821x_read_page(struct phy_device *phydev)
+ {
+       return __phy_read(phydev, RTL821x_PAGE_SELECT);
+@@ -1238,6 +1246,18 @@ static int rtl822x_write_mmd(struct phy_
+ static int rtl822x_probe(struct phy_device *phydev)
+ {
++      struct device *dev = &phydev->mdio.dev;
++      struct rtl822x_priv *priv;
++
++      priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++      if (!priv)
++              return -ENOMEM;
++
++      priv->enable_aldps = of_property_read_bool(dev->of_node,
++                                                 "realtek,aldps-enable");
++
++      phydev->priv = priv;
++
+       if (IS_ENABLED(CONFIG_REALTEK_PHY_HWMON) &&
+           phydev->phy_id != RTL_GENERIC_PHYID)
+               return rtl822x_hwmon_init(phydev);
+@@ -1328,6 +1348,19 @@ static int rtl822xb_write_mmd(struct phy
+       return write_ret;
+ }
++static int rtl822x_init_phycr1(struct phy_device *phydev, bool no_aldps)
++{
++      struct rtl822x_priv *priv = phydev->priv;
++      u16 val = 0;
++
++      if (priv->enable_aldps && !no_aldps)
++              val = RTL8221B_PHYCR1_ALDPS_EN | RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN;
++
++      return phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, RTL8221B_PHYCR1,
++                                    RTL8221B_PHYCR1_ALDPS_EN |
++                                    RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN, val);
++}
++
+ static int rtl822x_set_serdes_option_mode(struct phy_device *phydev, bool gen1)
+ {
+       bool has_2500, has_sgmii;
+@@ -1385,7 +1418,15 @@ static int rtl822x_set_serdes_option_mod
+       if (ret < 0)
+               return ret;
+-      return phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6f11, 0x8020);
++      ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6f11, 0x8020);
++      if (ret < 0)
++              return ret;
++
++      ret = rtl822x_init_phycr1(phydev, false);
++      if (ret < 0)
++              return ret;
++
++      return 0;
+ }
+ static int rtl822x_config_init(struct phy_device *phydev)
diff --git a/target/linux/generic/pending-6.12/720-05-net-phy-realtek-detect-early-version-of-RTL8221B.patch b/target/linux/generic/pending-6.12/720-05-net-phy-realtek-detect-early-version-of-RTL8221B.patch
new file mode 100644 (file)
index 0000000..829f4b9
--- /dev/null
@@ -0,0 +1,52 @@
+From 0de82310d2b32e78ff79d42c08b1122a6ede3778 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 30 Apr 2023 00:15:41 +0100
+Subject: [PATCH] net: phy: realtek: detect early version of RTL8221B
+
+Early versions (?) of the RTL8221B PHY cannot be identified in a regular
+Clause-45 bus scan as the PHY doesn't report the implemented MMDs
+correctly but returns 0 instead.
+Implement custom identify function using the PKGID instead of iterating
+over the implemented MMDs.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+[forward-port by @namiltd]
+Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -1908,10 +1908,32 @@ static int rtl8226_match_phy_device(stru
+ static int rtlgen_is_c45_match(struct phy_device *phydev, unsigned int id,
+                              bool is_c45)
+ {
+-      if (phydev->is_c45)
+-              return is_c45 && (id == phydev->c45_ids.device_ids[1]);
+-      else
++      if (phydev->is_c45) {
++              u32 rid;
++
++              if (!is_c45)
++                      return 0;
++
++              rid = phydev->c45_ids.device_ids[1];
++              if ((rid == 0xffffffff) && phydev->mdio.bus->read_c45) {
++                      int val;
++
++                      val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PKGID1);
++                      if (val < 0)
++                              return 0;
++
++                      rid = val << 16;
++                      val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PKGID2);
++                      if (val < 0)
++                              return 0;
++
++                      rid |= val;
++              }
++
++              return (id == rid);
++      } else {
+               return !is_c45 && (id == phydev->phy_id);
++      }
+ }
+ static int rtl8221b_match_phy_device(struct phy_device *phydev,
diff --git a/target/linux/generic/pending-6.12/720-06-net-phy-realtek-mark-existing-MMDs-as-present.patch b/target/linux/generic/pending-6.12/720-06-net-phy-realtek-mark-existing-MMDs-as-present.patch
new file mode 100644 (file)
index 0000000..922d3f4
--- /dev/null
@@ -0,0 +1,27 @@
+From 1addfb042a9d27788a0fb2c2935045b56fd8560e Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 23 Jan 2025 03:25:29 +0000
+Subject: [PATCH] net: phy: realtek: mark existing MMDs as present
+
+When using Clause-45 mode to access RealTek RTL8221B 2.5G PHYs some
+versions of the PHY fail to report the MMDs present on the PHY.
+Mark MMDs PMAPMD, PCS and AN which are always existing according to
+the datasheet as present to fix that.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/phy/realtek/realtek_main.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -1604,6 +1604,9 @@ static int rtl822x_c45_get_features(stru
+       linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT,
+                        phydev->supported);
++      phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_PCS |
++                                      MDIO_DEVS_AN;
++
+       return genphy_c45_pma_read_abilities(phydev);
+ }
diff --git a/target/linux/generic/pending-6.12/720-07-net-phy-realtek-disable-MDIO-broadcast.patch b/target/linux/generic/pending-6.12/720-07-net-phy-realtek-disable-MDIO-broadcast.patch
new file mode 100644 (file)
index 0000000..e37faac
--- /dev/null
@@ -0,0 +1,33 @@
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 30 Jan 2025 05:38:31 +0000
+Subject: [PATCH] net: phy: realtek: disable MDIO broadcast
+
+RealTek's PHYs by default also listen on MDIO address 0 which is defined
+as broadcast address. This can lead to problems if there is an actual PHY
+(such as MT7981 built-in PHY) present at this address, as accessing that
+PHY may then confuse the RealTek PHY.
+
+Disabled listening on the MDIO broadcast address to avoid such problems.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -173,6 +173,7 @@
+ #define RTL8221B_PHYCR1                               0xa430
+ #define RTL8221B_PHYCR1_ALDPS_EN              BIT(2)
+ #define RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN     BIT(12)
++#define RTL8221B_PHYCR1_PHYAD_0_EN            BIT(13)
+ #define RTL8366RB_POWER_SAVE                  0x15
+ #define RTL8366RB_POWER_SAVE_ON                       BIT(12)
+@@ -1358,7 +1359,8 @@ static int rtl822x_init_phycr1(struct ph
+       return phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, RTL8221B_PHYCR1,
+                                     RTL8221B_PHYCR1_ALDPS_EN |
+-                                    RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN, val);
++                                    RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN |
++                                    RTL8221B_PHYCR1_PHYAD_0_EN, val);
+ }
+ static int rtl822x_set_serdes_option_mode(struct phy_device *phydev, bool gen1)
diff --git a/target/linux/generic/pending-6.12/720-08-net-phy-realtek-rate-adapter-in-C22-mode.patch b/target/linux/generic/pending-6.12/720-08-net-phy-realtek-rate-adapter-in-C22-mode.patch
new file mode 100644 (file)
index 0000000..f8a28d5
--- /dev/null
@@ -0,0 +1,22 @@
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Mon, 5 Jan 2026 17:55:02 +0000
+Subject: [PATCH] net: phy: realtek: rate-adapter in C22 mode
+
+Use rate-adapter mode in case the PHY is connected to the host using
+Clause-22 MDIO.
+
+This is necessary because phylink only handles dynamically switching the
+interface mode if the PHY is connected using Clause-45 MDIO.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+--- a/drivers/net/phy/realtek/realtek_main.c
++++ b/drivers/net/phy/realtek/realtek_main.c
+@@ -1387,7 +1387,7 @@ static int rtl822x_set_serdes_option_mod
+               return 0;
+       /* determine SerDes option mode */
+-      if (has_2500 && !has_sgmii) {
++      if (has_2500 && (!has_sgmii || !phydev->is_c45)) {
+               mode = RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX;
+               phydev->rate_matching = RATE_MATCH_PAUSE;
+       } else {
diff --git a/target/linux/generic/pending-6.12/731-net-permit-ieee80211_ptr-even-with-no-CFG82111-suppo.patch b/target/linux/generic/pending-6.12/731-net-permit-ieee80211_ptr-even-with-no-CFG82111-suppo.patch
new file mode 100644 (file)
index 0000000..50d3403
--- /dev/null
@@ -0,0 +1,59 @@
+From 686c603f67ae87bf21a61b5e4b1564443f41c3ee Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 20 Oct 2022 03:34:43 +0200
+Subject: [PATCH] net: permit ieee80211_ptr even with no CFG82111 support
+
+Introduce a new flag CONFIG_CFG80211_HEADERS to compile in ieee80211_ptr
+even if CFG80211 support is not compiled in. This is needed for the
+backports project and for any downstream wireless driver that loads in
+the kernel dynamically.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ include/linux/netdevice.h       | 2 +-
+ net/batman-adv/hard-interface.c | 2 +-
+ net/wireless/Kconfig            | 4 ++++
+ 3 files changed, 6 insertions(+), 2 deletions(-)
+
+--- a/include/linux/netdevice.h
++++ b/include/linux/netdevice.h
+@@ -2224,7 +2224,7 @@ struct net_device {
+ #if IS_ENABLED(CONFIG_AX25)
+       struct ax25_dev __rcu   *ax25_ptr;
+ #endif
+-#if IS_ENABLED(CONFIG_CFG80211)
++#if IS_ENABLED(CONFIG_CFG80211_HEADERS)
+       struct wireless_dev     *ieee80211_ptr;
+ #endif
+ #if IS_ENABLED(CONFIG_IEEE802154) || IS_ENABLED(CONFIG_6LOWPAN)
+--- a/net/batman-adv/hard-interface.c
++++ b/net/batman-adv/hard-interface.c
+@@ -309,7 +309,7 @@ static bool batadv_is_cfg80211_netdev(st
+       if (!net_device)
+               return false;
+-#if IS_ENABLED(CONFIG_CFG80211)
++#if IS_ENABLED(CONFIG_CFG80211_HEADERS)
+       /* cfg80211 drivers have to set ieee80211_ptr */
+       if (net_device->ieee80211_ptr)
+               return true;
+--- a/net/wireless/Kconfig
++++ b/net/wireless/Kconfig
+@@ -26,6 +26,7 @@ config CFG80211
+       # using a different algorithm, though right now they shouldn't
+       # (this is here rather than below to allow it to be a module)
+       select CRYPTO_SHA256 if CFG80211_USE_KERNEL_REGDB_KEYS
++      select CFG80211_HEADERS
+       help
+         cfg80211 is the Linux wireless LAN (802.11) configuration API.
+         Enable this if you have a wireless device.
+@@ -36,6 +37,9 @@ config CFG80211
+         When built as a module it will be called cfg80211.
++config CFG80211_HEADERS
++      bool "cfg80211 - headers support"
++
+ if CFG80211
+ config NL80211_TESTMODE
diff --git a/target/linux/generic/pending-6.12/732-00-net-ethernet-mtk_eth_soc-compile-out-netsys-v2-code-.patch b/target/linux/generic/pending-6.12/732-00-net-ethernet-mtk_eth_soc-compile-out-netsys-v2-code-.patch
new file mode 100644 (file)
index 0000000..ec6ed6e
--- /dev/null
@@ -0,0 +1,44 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 27 Oct 2022 23:39:52 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: compile out netsys v2 code
+ on mt7621
+
+Avoid some branches in the hot path on low-end devices with limited CPU power,
+and reduce code size
+
+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
+@@ -1344,6 +1344,22 @@ 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[];
++#ifdef CONFIG_SOC_MT7621
++static inline bool mtk_is_netsys_v1(struct mtk_eth *eth)
++{
++      return true;
++}
++
++static inline bool mtk_is_netsys_v2_or_greater(struct mtk_eth *eth)
++{
++      return false;
++}
++
++static inline bool mtk_is_netsys_v3_or_greater(struct mtk_eth *eth)
++{
++      return false;
++}
++#else
+ static inline bool mtk_is_netsys_v1(struct mtk_eth *eth)
+ {
+       return eth->soc->version == 1;
+@@ -1358,6 +1374,7 @@ static inline bool mtk_is_netsys_v3_or_g
+ {
+       return eth->soc->version > 2;
+ }
++#endif
+ static inline struct mtk_foe_entry *
+ mtk_foe_get_entry(struct mtk_ppe *ppe, u16 hash)
diff --git a/target/linux/generic/pending-6.12/732-01-net-ethernet-mtk_eth_soc-work-around-issue-with-send.patch b/target/linux/generic/pending-6.12/732-01-net-ethernet-mtk_eth_soc-work-around-issue-with-send.patch
new file mode 100644 (file)
index 0000000..71b20b9
--- /dev/null
@@ -0,0 +1,102 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 3 Nov 2022 12:38:49 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: work around issue with sending
+ small fragments
+
+When lots of frames are sent with a number of very small fragments, an
+internal FIFO can overflow, causing the DMA engine to lock up lock up and
+transmit attempts time out.
+
+Fix this on MT7986 by increasing the reserved FIFO space.
+Fix this on older chips by detecting the presence of small fragments and use
+skb_gso_segment + skb_linearize to deal with them.
+
+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
+@@ -26,6 +26,7 @@
+ #include <linux/bitfield.h>
+ #include <net/dsa.h>
+ #include <net/dst_metadata.h>
++#include <net/gso.h>
+ #include <net/page_pool/helpers.h>
+ #include "mtk_eth_soc.h"
+@@ -1607,12 +1608,28 @@ static void mtk_wake_queue(struct mtk_et
+       }
+ }
++static bool mtk_skb_has_small_frag(struct sk_buff *skb)
++{
++      int min_size = 16;
++      int i;
++
++      if (skb_headlen(skb) < min_size)
++              return true;
++
++      for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
++              if (skb_frag_size(&skb_shinfo(skb)->frags[i]) < min_size)
++                      return true;
++
++      return false;
++}
++
+ static netdev_tx_t mtk_start_xmit(struct sk_buff *skb, struct net_device *dev)
+ {
+       struct mtk_mac *mac = netdev_priv(dev);
+       struct mtk_eth *eth = mac->hw;
+       struct mtk_tx_ring *ring = &eth->tx_ring;
+       struct net_device_stats *stats = &dev->stats;
++      struct sk_buff *segs, *next;
+       bool gso = false;
+       int tx_num;
+@@ -1641,6 +1658,18 @@ static netdev_tx_t mtk_start_xmit(struct
+               return NETDEV_TX_BUSY;
+       }
++      if (mtk_is_netsys_v1(eth) &&
++          skb_is_gso(skb) && mtk_skb_has_small_frag(skb)) {
++              segs = skb_gso_segment(skb, dev->features & ~NETIF_F_ALL_TSO);
++              if (IS_ERR(segs))
++                      goto drop;
++
++              if (segs) {
++                      consume_skb(skb);
++                      skb = segs;
++              }
++      }
++
+       /* TSO: fill MSS info in tcp checksum field */
+       if (skb_is_gso(skb)) {
+               if (skb_cow_head(skb, 0)) {
+@@ -1656,8 +1685,14 @@ static netdev_tx_t mtk_start_xmit(struct
+               }
+       }
+-      if (mtk_tx_map(skb, dev, tx_num, ring, gso) < 0)
+-              goto drop;
++      skb_list_walk_safe(skb, skb, next) {
++              if ((mtk_is_netsys_v1(eth) &&
++                   mtk_skb_has_small_frag(skb) && skb_linearize(skb)) ||
++                  mtk_tx_map(skb, dev, tx_num, ring, gso) < 0) {
++                              stats->tx_dropped++;
++                              dev_kfree_skb_any(skb);
++              }
++      }
+       if (unlikely(atomic_read(&ring->free_count) <= ring->thresh))
+               netif_tx_stop_all_queues(dev);
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -278,7 +278,7 @@
+ #define MTK_CHK_DDONE_EN      BIT(28)
+ #define MTK_DMAD_WR_WDONE     BIT(26)
+ #define MTK_WCOMP_EN          BIT(24)
+-#define MTK_RESV_BUF          (0x40 << 16)
++#define MTK_RESV_BUF          (0x80 << 16)
+ #define MTK_MUTLI_CNT         (0x4 << 12)
+ #define MTK_LEAKY_BUCKET_EN   BIT(11)
diff --git a/target/linux/generic/pending-6.12/732-03-net-ethernet-mtk_eth_soc-optimize-dma-ring-address-i.patch b/target/linux/generic/pending-6.12/732-03-net-ethernet-mtk_eth_soc-optimize-dma-ring-address-i.patch
new file mode 100644 (file)
index 0000000..6a51dbb
--- /dev/null
@@ -0,0 +1,486 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 15 Oct 2024 12:52:56 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: optimize dma ring address/index
+ calculation
+
+Since DMA descriptor sizes are all power of 2, we can avoid costly integer
+division in favor or simple shifts.
+
+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
+@@ -43,6 +43,11 @@ MODULE_PARM_DESC(msg_level, "Message lev
+                                 offsetof(struct mtk_hw_stats, xdp_stats.x) / \
+                                 sizeof(u64) }
++#define RX_DESC_OFS(eth, i) \
++      ((i) << (eth)->soc->rx.desc_shift)
++#define TX_DESC_OFS(eth, i) \
++      ((i) << (eth)->soc->tx.desc_shift)
++
+ static const struct mtk_reg_map mtk_reg_map = {
+       .tx_irq_mask            = 0x1a1c,
+       .tx_irq_status          = 0x1a18,
+@@ -1160,14 +1165,14 @@ static int mtk_init_fq_dma(struct mtk_et
+               eth->scratch_ring = eth->sram_base;
+       else
+               eth->scratch_ring = dma_alloc_coherent(eth->dma_dev,
+-                                                     cnt * soc->tx.desc_size,
++                                                     TX_DESC_OFS(eth, cnt),
+                                                      &eth->phy_scratch_ring,
+                                                      GFP_KERNEL);
+       if (unlikely(!eth->scratch_ring))
+               return -ENOMEM;
+-      phy_ring_tail = eth->phy_scratch_ring + soc->tx.desc_size * (cnt - 1);
++      phy_ring_tail = eth->phy_scratch_ring + TX_DESC_OFS(eth, cnt - 1);
+       for (j = 0; j < DIV_ROUND_UP(soc->tx.fq_dma_size, MTK_FQ_DMA_LENGTH); j++) {
+               len = min_t(int, cnt - j * MTK_FQ_DMA_LENGTH, MTK_FQ_DMA_LENGTH);
+@@ -1186,11 +1191,11 @@ static int mtk_init_fq_dma(struct mtk_et
+               for (i = 0; i < len; i++) {
+                       struct mtk_tx_dma_v2 *txd;
+-                      txd = eth->scratch_ring + (j * MTK_FQ_DMA_LENGTH + i) * soc->tx.desc_size;
++                      txd = eth->scratch_ring + TX_DESC_OFS(eth, j * MTK_FQ_DMA_LENGTH + i);
+                       txd->txd1 = dma_addr + i * MTK_QDMA_PAGE_SIZE;
+                       if (j * MTK_FQ_DMA_LENGTH + i < cnt)
+                               txd->txd2 = eth->phy_scratch_ring +
+-                                          (j * MTK_FQ_DMA_LENGTH + i + 1) * soc->tx.desc_size;
++                                          TX_DESC_OFS(eth, j * MTK_FQ_DMA_LENGTH + i + 1);
+                       txd->txd3 = TX_DMA_PLEN0(MTK_QDMA_PAGE_SIZE);
+                       if (MTK_HAS_CAPS(soc->caps, MTK_36BIT_DMA))
+@@ -1220,9 +1225,9 @@ static void *mtk_qdma_phys_to_virt(struc
+ }
+ static struct mtk_tx_buf *mtk_desc_to_tx_buf(struct mtk_tx_ring *ring,
+-                                           void *txd, u32 txd_size)
++                                           void *txd, u32 txd_shift)
+ {
+-      int idx = (txd - ring->dma) / txd_size;
++      int idx = (txd - ring->dma) >> txd_shift;
+       return &ring->buf[idx];
+ }
+@@ -1233,9 +1238,9 @@ static struct mtk_tx_dma *qdma_to_pdma(s
+       return ring->dma_pdma - (struct mtk_tx_dma *)ring->dma + dma;
+ }
+-static int txd_to_idx(struct mtk_tx_ring *ring, void *dma, u32 txd_size)
++static int txd_to_idx(struct mtk_tx_ring *ring, void *dma, u32 txd_shift)
+ {
+-      return (dma - ring->dma) / txd_size;
++      return (dma - ring->dma) >> txd_shift;
+ }
+ static void mtk_tx_unmap(struct mtk_eth *eth, struct mtk_tx_buf *tx_buf,
+@@ -1443,7 +1448,7 @@ static int mtk_tx_map(struct sk_buff *sk
+       if (itxd == ring->last_free)
+               return -ENOMEM;
+-      itx_buf = mtk_desc_to_tx_buf(ring, itxd, soc->tx.desc_size);
++      itx_buf = mtk_desc_to_tx_buf(ring, itxd, soc->tx.desc_shift);
+       memset(itx_buf, 0, sizeof(*itx_buf));
+       txd_info.addr = dma_map_single(eth->dma_dev, skb->data, txd_info.size,
+@@ -1497,7 +1502,7 @@ static int mtk_tx_map(struct sk_buff *sk
+                       mtk_tx_set_dma_desc(dev, txd, &txd_info);
+                       tx_buf = mtk_desc_to_tx_buf(ring, txd,
+-                                                  soc->tx.desc_size);
++                                                  soc->tx.desc_shift);
+                       if (new_desc)
+                               memset(tx_buf, 0, sizeof(*tx_buf));
+                       tx_buf->data = (void *)MTK_DMA_DUMMY_DESC;
+@@ -1540,7 +1545,7 @@ static int mtk_tx_map(struct sk_buff *sk
+       } else {
+               int next_idx;
+-              next_idx = NEXT_DESP_IDX(txd_to_idx(ring, txd, soc->tx.desc_size),
++              next_idx = NEXT_DESP_IDX(txd_to_idx(ring, txd, soc->tx.desc_shift),
+                                        ring->dma_size);
+               mtk_w32(eth, next_idx, MT7628_TX_CTX_IDX0);
+       }
+@@ -1549,7 +1554,7 @@ static int mtk_tx_map(struct sk_buff *sk
+ err_dma:
+       do {
+-              tx_buf = mtk_desc_to_tx_buf(ring, itxd, soc->tx.desc_size);
++              tx_buf = mtk_desc_to_tx_buf(ring, itxd, soc->tx.desc_shift);
+               /* unmap dma */
+               mtk_tx_unmap(eth, tx_buf, NULL, false);
+@@ -1723,7 +1728,7 @@ static struct mtk_rx_ring *mtk_get_rx_ri
+               ring = &eth->rx_ring[i];
+               idx = NEXT_DESP_IDX(ring->calc_idx, ring->dma_size);
+-              rxd = ring->dma + idx * eth->soc->rx.desc_size;
++              rxd = ring->dma + RX_DESC_OFS(eth, idx);
+               if (rxd->rxd2 & RX_DMA_DONE) {
+                       ring->calc_idx_update = true;
+                       return ring;
+@@ -1891,7 +1896,7 @@ static int mtk_xdp_submit_frame(struct m
+       }
+       htxd = txd;
+-      tx_buf = mtk_desc_to_tx_buf(ring, txd, soc->tx.desc_size);
++      tx_buf = mtk_desc_to_tx_buf(ring, txd, soc->tx.desc_shift);
+       memset(tx_buf, 0, sizeof(*tx_buf));
+       htx_buf = tx_buf;
+@@ -1910,7 +1915,7 @@ static int mtk_xdp_submit_frame(struct m
+                               goto unmap;
+                       tx_buf = mtk_desc_to_tx_buf(ring, txd,
+-                                                  soc->tx.desc_size);
++                                                  soc->tx.desc_shift);
+                       memset(tx_buf, 0, sizeof(*tx_buf));
+                       n_desc++;
+               }
+@@ -1948,7 +1953,7 @@ static int mtk_xdp_submit_frame(struct m
+       } else {
+               int idx;
+-              idx = txd_to_idx(ring, txd, soc->tx.desc_size);
++              idx = txd_to_idx(ring, txd, soc->tx.desc_shift);
+               mtk_w32(eth, NEXT_DESP_IDX(idx, ring->dma_size),
+                       MT7628_TX_CTX_IDX0);
+       }
+@@ -1959,7 +1964,7 @@ static int mtk_xdp_submit_frame(struct m
+ unmap:
+       while (htxd != txd) {
+-              tx_buf = mtk_desc_to_tx_buf(ring, htxd, soc->tx.desc_size);
++              tx_buf = mtk_desc_to_tx_buf(ring, htxd, soc->tx.desc_shift);
+               mtk_tx_unmap(eth, tx_buf, NULL, false);
+               htxd->txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
+@@ -2091,7 +2096,7 @@ static int mtk_poll_rx(struct napi_struc
+                       goto rx_done;
+               idx = NEXT_DESP_IDX(ring->calc_idx, ring->dma_size);
+-              rxd = ring->dma + idx * eth->soc->rx.desc_size;
++              rxd = ring->dma + RX_DESC_OFS(eth, idx);
+               data = ring->data[idx];
+               if (!mtk_rx_get_desc(eth, &trxd, rxd))
+@@ -2355,7 +2360,7 @@ static int mtk_poll_tx_qdma(struct mtk_e
+                       break;
+               tx_buf = mtk_desc_to_tx_buf(ring, desc,
+-                                          eth->soc->tx.desc_size);
++                                          eth->soc->tx.desc_shift);
+               if (!tx_buf->data)
+                       break;
+@@ -2406,7 +2411,7 @@ static int mtk_poll_tx_pdma(struct mtk_e
+               }
+               mtk_tx_unmap(eth, tx_buf, &bq, true);
+-              desc = ring->dma + cpu * eth->soc->tx.desc_size;
++              desc = ring->dma + TX_DESC_OFS(eth, cpu);
+               ring->last_free = desc;
+               atomic_inc(&ring->free_count);
+@@ -2524,7 +2529,7 @@ static int mtk_tx_alloc(struct mtk_eth *
+ {
+       const struct mtk_soc_data *soc = eth->soc;
+       struct mtk_tx_ring *ring = &eth->tx_ring;
+-      int i, sz = soc->tx.desc_size;
++      int i, sz = TX_DESC_OFS(eth, 1);
+       struct mtk_tx_dma_v2 *txd;
+       int ring_size;
+       u32 ofs, val;
+@@ -2571,7 +2576,7 @@ 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, ring_size * sz,
++              ring->dma_pdma = dma_alloc_coherent(eth->dma_dev, TX_DESC_OFS(eth, ring_size),
+                                                   &ring->phys_pdma, GFP_KERNEL);
+               if (!ring->dma_pdma)
+                       goto no_tx_mem;
+@@ -2586,7 +2591,7 @@ static int mtk_tx_alloc(struct mtk_eth *
+       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 + ((ring_size - 1) * sz));
++      ring->last_free_ptr = (u32)(ring->phys + TX_DESC_OFS(eth, ring_size - 1));
+       ring->thresh = MAX_SKB_FRAGS;
+       /* make sure that all changes to the dma ring are flushed before we
+@@ -2598,7 +2603,7 @@ 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 + ((ring_size - 1) * sz),
++                      ring->phys + TX_DESC_OFS(eth, ring_size - 1),
+                       soc->reg_map->qdma.crx_ptr);
+               mtk_w32(eth, ring->last_free_ptr, soc->reg_map->qdma.drx_ptr);
+@@ -2647,14 +2652,14 @@ static void mtk_tx_clean(struct mtk_eth
+       }
+       if (!MTK_HAS_CAPS(soc->caps, MTK_SRAM) && ring->dma) {
+               dma_free_coherent(eth->dma_dev,
+-                                ring->dma_size * soc->tx.desc_size,
++                                TX_DESC_OFS(eth, ring->dma_size),
+                                 ring->dma, ring->phys);
+               ring->dma = NULL;
+       }
+       if (ring->dma_pdma) {
+               dma_free_coherent(eth->dma_dev,
+-                                ring->dma_size * soc->tx.desc_size,
++                                TX_DESC_OFS(eth, ring->dma_size),
+                                 ring->dma_pdma, ring->phys_pdma);
+               ring->dma_pdma = NULL;
+       }
+@@ -2710,15 +2715,13 @@ static int mtk_rx_alloc(struct mtk_eth *
+       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->rx.desc_size,
++                              RX_DESC_OFS(eth, rx_dma_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->tx.desc_size * (ring_no + 1);
+-              ring->phys = tx_ring->phys + tx_ring_size *
+-                           eth->soc->tx.desc_size * (ring_no + 1);
++              ring->dma = tx_ring->dma + TX_DESC_OFS(eth, tx_ring_size * (ring_no + 1));
++              ring->phys = tx_ring->phys + TX_DESC_OFS(eth, tx_ring_size * (ring_no + 1));
+       }
+       if (!ring->dma)
+@@ -2729,7 +2732,7 @@ static int mtk_rx_alloc(struct mtk_eth *
+               dma_addr_t dma_addr;
+               void *data;
+-              rxd = ring->dma + i * eth->soc->rx.desc_size;
++              rxd = ring->dma + RX_DESC_OFS(eth, i);
+               if (ring->page_pool) {
+                       data = mtk_page_pool_get_buff(ring->page_pool,
+                                                     &dma_addr, GFP_KERNEL);
+@@ -2820,7 +2823,7 @@ static void mtk_rx_clean(struct mtk_eth
+                       if (!ring->data[i])
+                               continue;
+-                      rxd = ring->dma + i * eth->soc->rx.desc_size;
++                      rxd = ring->dma + RX_DESC_OFS(eth, i);
+                       if (!rxd->rxd1)
+                               continue;
+@@ -2837,7 +2840,7 @@ static void mtk_rx_clean(struct mtk_eth
+       if (!in_sram && ring->dma) {
+               dma_free_coherent(eth->dma_dev,
+-                                ring->dma_size * eth->soc->rx.desc_size,
++                                RX_DESC_OFS(eth, ring->dma_size),
+                                 ring->dma, ring->phys);
+               ring->dma = NULL;
+       }
+@@ -3208,7 +3211,7 @@ static void mtk_dma_free(struct mtk_eth
+       if (!MTK_HAS_CAPS(soc->caps, MTK_SRAM) && eth->scratch_ring) {
+               dma_free_coherent(eth->dma_dev,
+-                                MTK_QDMA_RING_SIZE * soc->tx.desc_size,
++                                TX_DESC_OFS(eth, MTK_QDMA_RING_SIZE),
+                                 eth->scratch_ring, eth->phy_scratch_ring);
+               eth->scratch_ring = NULL;
+               eth->phy_scratch_ring = 0;
+@@ -5245,6 +5248,9 @@ static void mtk_remove(struct platform_d
+       mtk_mdio_cleanup(eth);
+ }
++#define DESC_SIZE(struct_name)                                \
++      .desc_shift = const_ilog2(sizeof(struct_name))
++
+ static const struct mtk_soc_data mt2701_data = {
+       .reg_map = &mtk_reg_map,
+       .caps = MT7623_CAPS | MTK_HWLRO,
+@@ -5253,14 +5259,14 @@ static const struct mtk_soc_data mt2701_
+       .required_pctl = true,
+       .version = 1,
+       .tx = {
+-              .desc_size = sizeof(struct mtk_tx_dma),
++              DESC_SIZE(struct mtk_tx_dma),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+               .dma_size = MTK_DMA_SIZE(2K),
+               .fq_dma_size = MTK_DMA_SIZE(2K),
+       },
+       .rx = {
+-              .desc_size = sizeof(struct mtk_rx_dma),
++              DESC_SIZE(struct mtk_rx_dma),
+               .irq_done_mask = MTK_RX_DONE_INT,
+               .dma_l4_valid = RX_DMA_L4_VALID,
+               .dma_size = MTK_DMA_SIZE(2K),
+@@ -5281,14 +5287,14 @@ static const struct mtk_soc_data mt7621_
+       .hash_offset = 2,
+       .foe_entry_size = MTK_FOE_ENTRY_V1_SIZE,
+       .tx = {
+-              .desc_size = sizeof(struct mtk_tx_dma),
++              DESC_SIZE(struct mtk_tx_dma),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+               .dma_size = MTK_DMA_SIZE(2K),
+               .fq_dma_size = MTK_DMA_SIZE(2K),
+       },
+       .rx = {
+-              .desc_size = sizeof(struct mtk_rx_dma),
++              DESC_SIZE(struct mtk_rx_dma),
+               .irq_done_mask = MTK_RX_DONE_INT,
+               .dma_l4_valid = RX_DMA_L4_VALID,
+               .dma_size = MTK_DMA_SIZE(2K),
+@@ -5311,14 +5317,14 @@ static const struct mtk_soc_data mt7622_
+       .has_accounting = true,
+       .foe_entry_size = MTK_FOE_ENTRY_V1_SIZE,
+       .tx = {
+-              .desc_size = sizeof(struct mtk_tx_dma),
++              DESC_SIZE(struct mtk_tx_dma),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+               .dma_size = MTK_DMA_SIZE(2K),
+               .fq_dma_size = MTK_DMA_SIZE(2K),
+       },
+       .rx = {
+-              .desc_size = sizeof(struct mtk_rx_dma),
++              DESC_SIZE(struct mtk_rx_dma),
+               .irq_done_mask = MTK_RX_DONE_INT,
+               .dma_l4_valid = RX_DMA_L4_VALID,
+               .dma_size = MTK_DMA_SIZE(2K),
+@@ -5340,14 +5346,14 @@ static const struct mtk_soc_data mt7623_
+       .foe_entry_size = MTK_FOE_ENTRY_V1_SIZE,
+       .disable_pll_modes = true,
+       .tx = {
+-              .desc_size = sizeof(struct mtk_tx_dma),
++              DESC_SIZE(struct mtk_tx_dma),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+               .dma_size = MTK_DMA_SIZE(2K),
+               .fq_dma_size = MTK_DMA_SIZE(2K),
+       },
+       .rx = {
+-              .desc_size = sizeof(struct mtk_rx_dma),
++              DESC_SIZE(struct mtk_rx_dma),
+               .irq_done_mask = MTK_RX_DONE_INT,
+               .dma_l4_valid = RX_DMA_L4_VALID,
+               .dma_size = MTK_DMA_SIZE(2K),
+@@ -5366,14 +5372,14 @@ static const struct mtk_soc_data mt7629_
+       .has_accounting = true,
+       .version = 1,
+       .tx = {
+-              .desc_size = sizeof(struct mtk_tx_dma),
++              DESC_SIZE(struct mtk_tx_dma),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+               .dma_size = MTK_DMA_SIZE(2K),
+               .fq_dma_size = MTK_DMA_SIZE(2K),
+       },
+       .rx = {
+-              .desc_size = sizeof(struct mtk_rx_dma),
++              DESC_SIZE(struct mtk_rx_dma),
+               .irq_done_mask = MTK_RX_DONE_INT,
+               .dma_l4_valid = RX_DMA_L4_VALID,
+               .dma_size = MTK_DMA_SIZE(2K),
+@@ -5396,14 +5402,14 @@ static const struct mtk_soc_data mt7981_
+       .has_accounting = true,
+       .foe_entry_size = MTK_FOE_ENTRY_V2_SIZE,
+       .tx = {
+-              .desc_size = sizeof(struct mtk_tx_dma_v2),
++              DESC_SIZE(struct mtk_tx_dma_v2),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN_V2,
+               .dma_len_offset = 8,
+               .dma_size = MTK_DMA_SIZE(2K),
+               .fq_dma_size = MTK_DMA_SIZE(2K),
+       },
+       .rx = {
+-              .desc_size = sizeof(struct mtk_rx_dma),
++              DESC_SIZE(struct mtk_rx_dma),
+               .irq_done_mask = MTK_RX_DONE_INT,
+               .dma_l4_valid = RX_DMA_L4_VALID_V2,
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+@@ -5426,14 +5432,14 @@ static const struct mtk_soc_data mt7986_
+       .has_accounting = true,
+       .foe_entry_size = MTK_FOE_ENTRY_V2_SIZE,
+       .tx = {
+-              .desc_size = sizeof(struct mtk_tx_dma_v2),
++              DESC_SIZE(struct mtk_tx_dma_v2),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN_V2,
+               .dma_len_offset = 8,
+               .dma_size = MTK_DMA_SIZE(2K),
+               .fq_dma_size = MTK_DMA_SIZE(2K),
+       },
+       .rx = {
+-              .desc_size = sizeof(struct mtk_rx_dma),
++              DESC_SIZE(struct mtk_rx_dma),
+               .irq_done_mask = MTK_RX_DONE_INT,
+               .dma_l4_valid = RX_DMA_L4_VALID_V2,
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+@@ -5456,14 +5462,14 @@ static const struct mtk_soc_data mt7988_
+       .has_accounting = true,
+       .foe_entry_size = MTK_FOE_ENTRY_V3_SIZE,
+       .tx = {
+-              .desc_size = sizeof(struct mtk_tx_dma_v2),
++              DESC_SIZE(struct mtk_tx_dma_v2),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN_V2,
+               .dma_len_offset = 8,
+               .dma_size = MTK_DMA_SIZE(2K),
+               .fq_dma_size = MTK_DMA_SIZE(4K),
+       },
+       .rx = {
+-              .desc_size = sizeof(struct mtk_rx_dma_v2),
++              DESC_SIZE(struct mtk_rx_dma_v2),
+               .irq_done_mask = MTK_RX_DONE_INT_V2,
+               .dma_l4_valid = RX_DMA_L4_VALID_V2,
+               .dma_max_len = MTK_TX_DMA_BUF_LEN_V2,
+@@ -5480,13 +5486,13 @@ static const struct mtk_soc_data rt5350_
+       .required_pctl = false,
+       .version = 1,
+       .tx = {
+-              .desc_size = sizeof(struct mtk_tx_dma),
++              DESC_SIZE(struct mtk_tx_dma),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+               .dma_size = MTK_DMA_SIZE(2K),
+       },
+       .rx = {
+-              .desc_size = sizeof(struct mtk_rx_dma),
++              DESC_SIZE(struct mtk_rx_dma),
+               .irq_done_mask = MTK_RX_DONE_INT,
+               .dma_l4_valid = RX_DMA_L4_VALID_PDMA,
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -1160,7 +1160,7 @@ struct mtk_reg_map {
+  * @foe_entry_size            Foe table entry size.
+  * @has_accounting            Bool indicating support for accounting of
+  *                            offloaded flows.
+- * @desc_size                 Tx/Rx DMA descriptor size.
++ * @desc_shift                        Tx/Rx DMA descriptor size (in power-of-2).
+  * @irq_done_mask             Rx irq done register mask.
+  * @dma_l4_valid              Rx DMA valid register mask.
+  * @dma_max_len                       Max DMA tx/rx buffer length.
+@@ -1181,14 +1181,14 @@ struct mtk_soc_data {
+       bool            has_accounting;
+       bool            disable_pll_modes;
+       struct {
+-              u32     desc_size;
++              u32     desc_shift;
+               u32     dma_max_len;
+               u32     dma_len_offset;
+               u32     dma_size;
+               u32     fq_dma_size;
+       } tx;
+       struct {
+-              u32     desc_size;
++              u32     desc_shift;
+               u32     irq_done_mask;
+               u32     dma_l4_valid;
+               u32     dma_max_len;
diff --git a/target/linux/generic/pending-6.12/732-04-net-ethernet-mtk_eth_soc-shrink-struct-mtk_tx_buf.patch b/target/linux/generic/pending-6.12/732-04-net-ethernet-mtk_eth_soc-shrink-struct-mtk_tx_buf.patch
new file mode 100644 (file)
index 0000000..a2dd04b
--- /dev/null
@@ -0,0 +1,124 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Mon, 14 Jul 2025 10:52:59 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: shrink struct mtk_tx_buf
+
+There is no need to track the difference between dma_map_page
+and dma_map_single, since they're unmapped in exactly the same way.
+Also reorder fields in order to avoid padding.
+
+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
+@@ -1246,32 +1246,19 @@ static int txd_to_idx(struct mtk_tx_ring
+ static void mtk_tx_unmap(struct mtk_eth *eth, struct mtk_tx_buf *tx_buf,
+                        struct xdp_frame_bulk *bq, bool napi)
+ {
+-      if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
+-              if (tx_buf->flags & MTK_TX_FLAGS_SINGLE0) {
+-                      dma_unmap_single(eth->dma_dev,
+-                                       dma_unmap_addr(tx_buf, dma_addr0),
+-                                       dma_unmap_len(tx_buf, dma_len0),
+-                                       DMA_TO_DEVICE);
+-              } else if (tx_buf->flags & MTK_TX_FLAGS_PAGE0) {
+-                      dma_unmap_page(eth->dma_dev,
+-                                     dma_unmap_addr(tx_buf, dma_addr0),
+-                                     dma_unmap_len(tx_buf, dma_len0),
+-                                     DMA_TO_DEVICE);
+-              }
+-      } else {
+-              if (dma_unmap_len(tx_buf, dma_len0)) {
+-                      dma_unmap_page(eth->dma_dev,
+-                                     dma_unmap_addr(tx_buf, dma_addr0),
+-                                     dma_unmap_len(tx_buf, dma_len0),
+-                                     DMA_TO_DEVICE);
+-              }
++      if (dma_unmap_len(tx_buf, dma_len0)) {
++              dma_unmap_page(eth->dma_dev,
++                             dma_unmap_addr(tx_buf, dma_addr0),
++                             dma_unmap_len(tx_buf, dma_len0),
++                             DMA_TO_DEVICE);
++      }
+-              if (dma_unmap_len(tx_buf, dma_len1)) {
+-                      dma_unmap_page(eth->dma_dev,
+-                                     dma_unmap_addr(tx_buf, dma_addr1),
+-                                     dma_unmap_len(tx_buf, dma_len1),
+-                                     DMA_TO_DEVICE);
+-              }
++      if (!MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA) &&
++          dma_unmap_len(tx_buf, dma_len1)) {
++              dma_unmap_page(eth->dma_dev,
++                             dma_unmap_addr(tx_buf, dma_addr1),
++                             dma_unmap_len(tx_buf, dma_len1),
++                             DMA_TO_DEVICE);
+       }
+       if (tx_buf->data && tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
+@@ -1293,7 +1280,6 @@ static void mtk_tx_unmap(struct mtk_eth
+                               xdp_return_frame(xdpf);
+               }
+       }
+-      tx_buf->flags = 0;
+       tx_buf->data = NULL;
+ }
+@@ -1458,7 +1444,6 @@ 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->mac_id = mac->id;
+       setup_tx_buf(eth, itx_buf, itxd_pdma, txd_info.addr, txd_info.size,
+                    k++);
+@@ -1506,7 +1491,6 @@ static int mtk_tx_map(struct sk_buff *sk
+                       if (new_desc)
+                               memset(tx_buf, 0, sizeof(*tx_buf));
+                       tx_buf->data = (void *)MTK_DMA_DUMMY_DESC;
+-                      tx_buf->flags |= MTK_TX_FLAGS_PAGE0;
+                       tx_buf->mac_id = mac->id;
+                       setup_tx_buf(eth, tx_buf, txd_pdma, txd_info.addr,
+@@ -1839,8 +1823,6 @@ static int mtk_xdp_frame_map(struct mtk_
+                                               txd_info->size, DMA_TO_DEVICE);
+               if (unlikely(dma_mapping_error(eth->dma_dev, txd_info->addr)))
+                       return -ENOMEM;
+-
+-              tx_buf->flags |= MTK_TX_FLAGS_SINGLE0;
+       } else {
+               struct page *page = virt_to_head_page(data);
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -701,14 +701,6 @@ struct mtk_hw_stats {
+       struct u64_stats_sync   syncp;
+ };
+-enum mtk_tx_flags {
+-      /* PDMA descriptor can point at 1-2 segments. This enum allows us to
+-       * track how memory was allocated so that it can be freed properly.
+-       */
+-      MTK_TX_FLAGS_SINGLE0    = 0x01,
+-      MTK_TX_FLAGS_PAGE0      = 0x02,
+-};
+-
+ /* This enum allows us to identify how the clock is defined on the array of the
+  * clock in the order
+  */
+@@ -881,13 +873,12 @@ enum mtk_tx_buf_type {
+  */
+ struct mtk_tx_buf {
+       enum mtk_tx_buf_type type;
++      u16 mac_id;
+       void *data;
+-      u16 mac_id;
+-      u16 flags;
+       DEFINE_DMA_UNMAP_ADDR(dma_addr0);
+-      DEFINE_DMA_UNMAP_LEN(dma_len0);
+       DEFINE_DMA_UNMAP_ADDR(dma_addr1);
++      DEFINE_DMA_UNMAP_LEN(dma_len0);
+       DEFINE_DMA_UNMAP_LEN(dma_len1);
+ };
diff --git a/target/linux/generic/pending-6.12/732-05-net-ethernet-mtk_eth_soc-add-support-for-sending-fra.patch b/target/linux/generic/pending-6.12/732-05-net-ethernet-mtk_eth_soc-add-support-for-sending-fra.patch
new file mode 100644 (file)
index 0000000..8dad080
--- /dev/null
@@ -0,0 +1,519 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Mon, 14 Jul 2025 10:41:27 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: add support for sending
+ fraglist GSO packets
+
+When primarily forwarding traffic, TCP fraglist GRO can be noticeably more
+efficient than regular TCP GRO. In order to avoid the overhead of
+unnecessary segmentation on ethernet tx, add support for sending fraglist
+GRO 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
+@@ -18,6 +18,8 @@
+ #include <linux/if_vlan.h>
+ #include <linux/reset.h>
+ #include <linux/tcp.h>
++#include <linux/ip.h>
++#include <linux/ipv6.h>
+ #include <linux/interrupt.h>
+ #include <linux/pinctrl/devinfo.h>
+ #include <linux/phylink.h>
+@@ -27,6 +29,7 @@
+ #include <net/dsa.h>
+ #include <net/dst_metadata.h>
+ #include <net/gso.h>
++#include <net/checksum.h>
+ #include <net/page_pool/helpers.h>
+ #include "mtk_eth_soc.h"
+@@ -1404,119 +1407,251 @@ static void mtk_tx_set_dma_desc(struct n
+               mtk_tx_set_dma_desc_v1(dev, txd, info);
+ }
++struct mtk_tx_map_state {
++    struct mtk_tx_dma *txd, *txd_pdma;
++    struct mtk_tx_buf *tx_buf;
++    int nbuf;
++    int ndesc;
++};
++
++static void
++mtk_tx_map_set_txd(struct mtk_tx_map_state *state, struct mtk_tx_ring *ring,
++                 const struct mtk_soc_data *soc, struct mtk_tx_dma *txd)
++{
++      state->txd = txd;
++      state->txd_pdma = qdma_to_pdma(ring, txd);
++      state->tx_buf = mtk_desc_to_tx_buf(ring, txd, soc->tx.desc_shift);
++      memset(state->tx_buf, 0, sizeof(*state->tx_buf));
++}
++
++static int
++mtk_tx_map_info(struct mtk_eth *eth, struct mtk_tx_ring *ring,
++              struct net_device *dev, struct mtk_tx_map_state *state,
++              struct mtk_tx_dma_desc_info *txd_info)
++{
++      const struct mtk_soc_data *soc = eth->soc;
++      struct mtk_tx_buf *tx_buf = state->tx_buf;
++      struct mtk_tx_dma *txd = state->txd;
++      struct mtk_mac *mac = netdev_priv(dev);
++
++      if (state->nbuf &&
++          (MTK_HAS_CAPS(soc->caps, MTK_QDMA) || (state->nbuf & 1) == 0)) {
++              txd = mtk_qdma_phys_to_virt(ring, txd->txd2);
++              if (txd == ring->last_free)
++                      return -1;
++
++              mtk_tx_map_set_txd(state, ring, soc, txd);
++              state->ndesc++;
++      }
++
++      mtk_tx_set_dma_desc(dev, txd, txd_info);
++      tx_buf = state->tx_buf;
++      tx_buf->data = (void *)MTK_DMA_DUMMY_DESC;
++      tx_buf->mac_id = mac->id;
++
++      setup_tx_buf(eth, tx_buf, state->txd_pdma, txd_info->addr,
++                   txd_info->size, state->nbuf++);
++      return 0;
++}
++
++static void
++mtk_tx_update_ipaddr(struct sk_buff *skb,
++                   struct iphdr *iph, struct tcphdr *th,
++                   __be32 *old_ip, __be32 new_ip)
++{
++      if (*old_ip == new_ip)
++              return;
++
++      inet_proto_csum_replace4(&th->check, skb, *old_ip, new_ip, true);
++      csum_replace4(&iph->check, *old_ip, new_ip);
++      *old_ip = new_ip;
++}
++
++static void
++mtk_tx_update_ip6addr(struct sk_buff *skb, struct ipv6hdr *iph,
++                    struct tcphdr *th, struct in6_addr *old_ip,
++                    const struct in6_addr *new_ip)
++{
++      if (ipv6_addr_equal(old_ip, new_ip))
++              return;
++
++      inet_proto_csum_replace16(&th->check, skb, old_ip->s6_addr32,
++                                new_ip->s6_addr32, true);
++      *old_ip = *new_ip;
++}
++
++static void
++mtk_tx_update_port(struct sk_buff *skb, struct tcphdr *th,
++                 __be16 *old_port, __be16 new_port)
++{
++      if (*old_port == new_port)
++              return;
++
++      inet_proto_csum_replace2(&th->check, skb, *old_port, new_port, false);
++      *old_port = new_port;
++}
++
+ static int mtk_tx_map(struct sk_buff *skb, struct net_device *dev,
+-                    int tx_num, struct mtk_tx_ring *ring, bool gso)
++                    int tx_num, struct mtk_tx_ring *ring, bool gso,
++                    unsigned int header_len)
+ {
+-      struct mtk_tx_dma_desc_info txd_info = {
+-              .size = skb_headlen(skb),
+-              .gso = gso,
+-              .csum = skb->ip_summed == CHECKSUM_PARTIAL,
+-              .vlan = skb_vlan_tag_present(skb),
+-              .qid = skb_get_queue_mapping(skb),
+-              .vlan_tci = skb_vlan_tag_get(skb),
+-              .first = true,
+-              .last = !skb_is_nonlinear(skb),
++      struct mtk_tx_dma_desc_info txd_info;
++      struct mtk_tx_map_state state = {
++              .ndesc = 1,
+       };
+       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;
+-      struct mtk_tx_dma *itxd, *txd;
+-      struct mtk_tx_dma *itxd_pdma, *txd_pdma;
+-      struct mtk_tx_buf *itx_buf, *tx_buf;
+-      int i, n_desc = 1;
++      struct mtk_tx_dma *itxd;
++      struct sk_buff *cur_skb, *next_skb;
+       int queue = skb_get_queue_mapping(skb);
+-      int k = 0;
++      int i, frag_size = skb_headlen(skb);
++      bool gso_v4, gso_fraglist;
++      int offset = 0;
+       txq = netdev_get_tx_queue(dev, queue);
+       itxd = ring->next_free;
+-      itxd_pdma = qdma_to_pdma(ring, itxd);
+       if (itxd == ring->last_free)
+               return -ENOMEM;
+-      itx_buf = mtk_desc_to_tx_buf(ring, itxd, soc->tx.desc_shift);
+-      memset(itx_buf, 0, sizeof(*itx_buf));
++      cur_skb = skb;
++      next_skb = skb_shinfo(skb)->frag_list;
++      mtk_tx_map_set_txd(&state, ring, soc, itxd);
++      gso_v4 = skb_shinfo(skb)->gso_type & SKB_GSO_TCPV4;
++      gso_fraglist = skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST;
+-      txd_info.addr = dma_map_single(eth->dma_dev, skb->data, txd_info.size,
+-                                     DMA_TO_DEVICE);
+-      if (unlikely(dma_mapping_error(eth->dma_dev, txd_info.addr)))
+-              return -ENOMEM;
++next:
++      txd_info = (struct mtk_tx_dma_desc_info){
++              .gso = gso,
++              .qid = queue,
++              .csum = cur_skb->ip_summed == CHECKSUM_PARTIAL || gso,
++              .vlan = skb_vlan_tag_present(skb),
++              .vlan_tci = skb_vlan_tag_get(skb),
++              .first = true,
++      };
+-      mtk_tx_set_dma_desc(dev, itxd, &txd_info);
++      if (cur_skb != skb) {
++              struct tcphdr *th, *th2;
+-      itx_buf->mac_id = mac->id;
+-      setup_tx_buf(eth, itx_buf, itxd_pdma, txd_info.addr, txd_info.size,
+-                   k++);
+-
+-      /* TX SG offload */
+-      txd = itxd;
+-      txd_pdma = qdma_to_pdma(ring, txd);
+-
+-      for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+-              skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+-              unsigned int offset = 0;
+-              int frag_size = skb_frag_size(frag);
++              if (skb_cow_head(cur_skb, header_len))
++                      goto err_dma;
+-              while (frag_size) {
+-                      bool new_desc = true;
++              memcpy(cur_skb->data - header_len, skb->data,
++                     skb_network_offset(skb));
+-                      if (MTK_HAS_CAPS(soc->caps, MTK_QDMA) ||
+-                          (i & 0x1)) {
+-                              txd = mtk_qdma_phys_to_virt(ring, txd->txd2);
+-                              txd_pdma = qdma_to_pdma(ring, txd);
+-                              if (txd == ring->last_free)
+-                                      goto err_dma;
++              th = tcp_hdr(cur_skb);
++              th2 = tcp_hdr(skb);
++              if (gso_v4) {
++                      struct iphdr *iph = ip_hdr(cur_skb);
++                      struct iphdr *iph2 = ip_hdr(skb);
++
++                      mtk_tx_update_ipaddr(skb, iph, th, &iph->saddr,
++                                           iph2->saddr);
++                      mtk_tx_update_ipaddr(skb, iph, th, &iph->daddr,
++                                           iph2->daddr);
++              } else {
++                      struct ipv6hdr *iph = ipv6_hdr(cur_skb);
++                      struct ipv6hdr *iph2 = ipv6_hdr(skb);
+-                              n_desc++;
+-                      } else {
+-                              new_desc = false;
+-                      }
++                      mtk_tx_update_ip6addr(skb, iph, th, &iph->saddr,
++                                            &iph2->saddr);
++                      mtk_tx_update_ip6addr(skb, iph, th, &iph->daddr,
++                                            &iph2->daddr);
++              }
++
++              mtk_tx_update_port(skb, th, &th->source, th2->source);
++              mtk_tx_update_port(skb, th, &th->dest, th2->dest);
++
++              offset = -header_len;
++              frag_size += header_len;
++      } else if (next_skb && gso_fraglist) {
++              unsigned int ip_len = skb_pagelen(skb) - skb_network_offset(skb);
++              if (gso_v4) {
++                      struct iphdr *iph = ip_hdr(cur_skb);
++                      __be16 ip_len_val = cpu_to_be16(ip_len);
+-                      memset(&txd_info, 0, sizeof(struct mtk_tx_dma_desc_info));
++                      csum_replace2(&iph->check, iph->tot_len, ip_len_val);
++                      iph->tot_len = ip_len_val;
++              } else {
++                      struct ipv6hdr *iph = ipv6_hdr(cur_skb);
++                      __be16 ip_len_val = cpu_to_be16(ip_len - sizeof(*iph));
++
++                      iph->payload_len = ip_len_val;
++              }
++      }
++
++next_frag:
++      while (frag_size) {
++              txd_info.size = min_t(unsigned int, frag_size,
++                                    soc->tx.dma_max_len);
++              txd_info.addr = dma_map_single(eth->dma_dev, cur_skb->data + offset,
++                                             txd_info.size, DMA_TO_DEVICE);
++              if (unlikely(dma_mapping_error(eth->dma_dev, txd_info.addr)))
++                      goto err_dma;
++
++              frag_size -= txd_info.size;
++              offset += txd_info.size;
++              txd_info.last = !frag_size && !skb_shinfo(cur_skb)->nr_frags &&
++                              (gso_fraglist || !next_skb);
++              if (mtk_tx_map_info(eth, ring, dev, &state, &txd_info) < 0)
++                      goto err_dma;
++      }
++
++      for (i = 0; i < skb_shinfo(cur_skb)->nr_frags; i++) {
++              skb_frag_t *frag = &skb_shinfo(cur_skb)->frags[i];
++
++              frag_size = skb_frag_size(frag);
++              memset(&txd_info, 0, sizeof(struct mtk_tx_dma_desc_info));
++              txd_info.qid = queue;
++              offset = 0;
++              while (frag_size) {
+                       txd_info.size = min_t(unsigned int, frag_size,
+                                             soc->tx.dma_max_len);
+-                      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,
+-                                                       offset, txd_info.size,
+-                                                       DMA_TO_DEVICE);
++                      txd_info.addr = skb_frag_dma_map(eth->dma_dev, frag, offset,
++                                                       txd_info.size, DMA_TO_DEVICE);
+                       if (unlikely(dma_mapping_error(eth->dma_dev, txd_info.addr)))
+                               goto err_dma;
+-                      mtk_tx_set_dma_desc(dev, txd, &txd_info);
+-
+-                      tx_buf = mtk_desc_to_tx_buf(ring, txd,
+-                                                  soc->tx.desc_shift);
+-                      if (new_desc)
+-                              memset(tx_buf, 0, sizeof(*tx_buf));
+-                      tx_buf->data = (void *)MTK_DMA_DUMMY_DESC;
+-                      tx_buf->mac_id = mac->id;
+-
+-                      setup_tx_buf(eth, tx_buf, txd_pdma, txd_info.addr,
+-                                   txd_info.size, k++);
+-
+                       frag_size -= txd_info.size;
+                       offset += txd_info.size;
++                      txd_info.last = i == skb_shinfo(cur_skb)->nr_frags - 1 &&
++                                      !frag_size && (gso_fraglist || !next_skb);
++                      if (mtk_tx_map_info(eth, ring, dev, &state, &txd_info) < 0)
++                              goto err_dma;
+               }
+       }
+-      /* store skb to cleanup */
+-      itx_buf->type = MTK_TYPE_SKB;
+-      itx_buf->data = skb;
++      if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA) &&
++          (gso_fraglist || !next_skb)) {
++              if (state.nbuf & 0x1) {
++                      state.txd_pdma->txd2 |= TX_DMA_LS0;
++                      state.nbuf++;
++              } else {
++                      state.txd_pdma->txd2 |= TX_DMA_LS1;
++              }
++      }
++
++      if (next_skb) {
++              cur_skb = next_skb;
++              next_skb = cur_skb->next;
++              offset = 0;
++              frag_size = skb_headlen(cur_skb);
++              if (!gso_fraglist)
++                      goto next_frag;
+-      if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
+-              if (k & 0x1)
+-                      txd_pdma->txd2 |= TX_DMA_LS0;
+-              else
+-                      txd_pdma->txd2 |= TX_DMA_LS1;
++              goto next;
+       }
++      /* store skb to cleanup */
++      state.tx_buf->type = MTK_TYPE_SKB;
++      state.tx_buf->data = skb;
++
+       netdev_tx_sent_queue(txq, skb->len);
+       skb_tx_timestamp(skb);
+-      ring->next_free = mtk_qdma_phys_to_virt(ring, txd->txd2);
+-      atomic_sub(n_desc, &ring->free_count);
++      ring->next_free = mtk_qdma_phys_to_virt(ring, state.txd->txd2);
++      atomic_sub(state.ndesc, &ring->free_count);
+       /* make sure that all changes to the dma ring are flushed before we
+        * continue
+@@ -1525,11 +1660,11 @@ static int mtk_tx_map(struct sk_buff *sk
+       if (MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
+               if (netif_xmit_stopped(txq) || !netdev_xmit_more())
+-                      mtk_w32(eth, txd->txd2, soc->reg_map->qdma.ctx_ptr);
++                      mtk_w32(eth, state.txd->txd2, soc->reg_map->qdma.ctx_ptr);
+       } else {
+               int next_idx;
+-              next_idx = NEXT_DESP_IDX(txd_to_idx(ring, txd, soc->tx.desc_shift),
++              next_idx = NEXT_DESP_IDX(txd_to_idx(ring, state.txd, soc->tx.desc_shift),
+                                        ring->dma_size);
+               mtk_w32(eth, next_idx, MT7628_TX_CTX_IDX0);
+       }
+@@ -1538,18 +1673,20 @@ static int mtk_tx_map(struct sk_buff *sk
+ err_dma:
+       do {
+-              tx_buf = mtk_desc_to_tx_buf(ring, itxd, soc->tx.desc_shift);
++              struct mtk_tx_dma *itxd_pdma = qdma_to_pdma(ring, itxd);
++              struct mtk_tx_buf *itx_buf;
++
++              itx_buf = mtk_desc_to_tx_buf(ring, itxd, soc->tx.desc_shift);
+               /* unmap dma */
+-              mtk_tx_unmap(eth, tx_buf, NULL, false);
++              mtk_tx_unmap(eth, itx_buf, NULL, false);
+               itxd->txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
+               if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA))
+                       itxd_pdma->txd2 = TX_DMA_DESP2_DEF;
+               itxd = mtk_qdma_phys_to_virt(ring, itxd->txd2);
+-              itxd_pdma = qdma_to_pdma(ring, itxd);
+-      } while (itxd != txd);
++      } while (itxd != state.txd);
+       return -ENOMEM;
+ }
+@@ -1569,6 +1706,9 @@ static int mtk_cal_txd_req(struct mtk_et
+               nfrags += skb_shinfo(skb)->nr_frags;
+       }
++      for (skb = skb_shinfo(skb)->frag_list; skb; skb = skb->next)
++              nfrags += mtk_cal_txd_req(eth, skb) + 1;
++
+       return nfrags;
+ }
+@@ -1609,9 +1749,29 @@ static bool mtk_skb_has_small_frag(struc
+               if (skb_frag_size(&skb_shinfo(skb)->frags[i]) < min_size)
+                       return true;
++      for (skb = skb_shinfo(skb)->frag_list; skb; skb = skb->next)
++              if (mtk_skb_has_small_frag(skb))
++                      return true;
++
+       return false;
+ }
++static bool mtk_skb_valid_gso(struct mtk_eth *eth, struct sk_buff *skb,
++                            unsigned int header_len)
++{
++      if (mtk_is_netsys_v1(eth) && mtk_skb_has_small_frag(skb))
++              return false;
++
++      if (!(skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST))
++              return true;
++
++      if (skb->encapsulation)
++              return false;
++
++      return skb_pagelen(skb) - header_len == skb_shinfo(skb)->gso_size &&
++             skb_headlen(skb) > header_len;
++}
++
+ static netdev_tx_t mtk_start_xmit(struct sk_buff *skb, struct net_device *dev)
+ {
+       struct mtk_mac *mac = netdev_priv(dev);
+@@ -1619,6 +1779,7 @@ static netdev_tx_t mtk_start_xmit(struct
+       struct mtk_tx_ring *ring = &eth->tx_ring;
+       struct net_device_stats *stats = &dev->stats;
+       struct sk_buff *segs, *next;
++      unsigned int header_len = 0;
+       bool gso = false;
+       int tx_num;
+@@ -1647,37 +1808,42 @@ static netdev_tx_t mtk_start_xmit(struct
+               return NETDEV_TX_BUSY;
+       }
+-      if (mtk_is_netsys_v1(eth) &&
+-          skb_is_gso(skb) && mtk_skb_has_small_frag(skb)) {
+-              segs = skb_gso_segment(skb, dev->features & ~NETIF_F_ALL_TSO);
+-              if (IS_ERR(segs))
+-                      goto drop;
+-
+-              if (segs) {
+-                      consume_skb(skb);
+-                      skb = segs;
+-              }
+-      }
+-
+-      /* TSO: fill MSS info in tcp checksum field */
+       if (skb_is_gso(skb)) {
+-              if (skb_cow_head(skb, 0)) {
+-                      netif_warn(eth, tx_err, dev,
+-                                 "GSO expand head fail.\n");
+-                      goto drop;
++              header_len = skb_tcp_all_headers(skb);
++              if (!mtk_skb_valid_gso(eth, skb, header_len)) {
++                      segs = skb_gso_segment(skb, dev->features & ~NETIF_F_ALL_TSO);
++                      if (IS_ERR(segs))
++                              goto drop;
++
++                      if (segs) {
++                              consume_skb(skb);
++                              skb = segs;
++                      }
++                      goto send;
+               }
++              if ((skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST))
++                      goto send;
++
+               if (skb_shinfo(skb)->gso_type &
+                               (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)) {
++                      /* TSO: fill MSS info in tcp checksum field */
+                       gso = true;
++                      if (skb_cow_head(skb, 0)) {
++                              netif_warn(eth, tx_err, dev,
++                                         "GSO expand head fail.\n");
++                              goto drop;
++                      }
++
+                       tcp_hdr(skb)->check = htons(skb_shinfo(skb)->gso_size);
+               }
+       }
++send:
+       skb_list_walk_safe(skb, skb, next) {
+               if ((mtk_is_netsys_v1(eth) &&
+                    mtk_skb_has_small_frag(skb) && skb_linearize(skb)) ||
+-                  mtk_tx_map(skb, dev, tx_num, ring, gso) < 0) {
++                  mtk_tx_map(skb, dev, tx_num, ring, gso, header_len) < 0) {
+                               stats->tx_dropped++;
+                               dev_kfree_skb_any(skb);
+               }
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -51,6 +51,8 @@
+                                NETIF_F_HW_VLAN_CTAG_TX | \
+                                NETIF_F_SG | NETIF_F_TSO | \
+                                NETIF_F_TSO6 | \
++                               NETIF_F_FRAGLIST | \
++                               NETIF_F_GSO_FRAGLIST | \
+                                NETIF_F_IPV6_CSUM |\
+                                NETIF_F_HW_TC)
+ #define MTK_HW_FEATURES_MT7628        (NETIF_F_SG | NETIF_F_RXCSUM)
diff --git a/target/linux/generic/pending-6.12/733-01-net-ethernet-mtk_eth_soc-use-napi_build_skb.patch b/target/linux/generic/pending-6.12/733-01-net-ethernet-mtk_eth_soc-use-napi_build_skb.patch
new file mode 100644 (file)
index 0000000..f87315a
--- /dev/null
@@ -0,0 +1,30 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Mon, 20 May 2024 14:29:58 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: use napi_build_skb()
+
+The napi_build_skb() can reuse the skb in skb cache per CPU or
+can allocate skbs in bulk, which helps improve the performance.
+
+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
+@@ -2312,7 +2312,7 @@ static int mtk_poll_rx(struct napi_struc
+                       if (ret != XDP_PASS)
+                               goto skip_rx;
+-                      skb = build_skb(data, PAGE_SIZE);
++                      skb = napi_build_skb(data, PAGE_SIZE);
+                       if (unlikely(!skb)) {
+                               page_pool_put_full_page(ring->page_pool,
+                                                       page, true);
+@@ -2350,7 +2350,7 @@ static int mtk_poll_rx(struct napi_struc
+                       dma_unmap_single(eth->dma_dev, ((u64)trxd.rxd1 | addr64),
+                                        ring->buf_size, DMA_FROM_DEVICE);
+-                      skb = build_skb(data, ring->frag_size);
++                      skb = napi_build_skb(data, ring->frag_size);
+                       if (unlikely(!skb)) {
+                               netdev->stats.rx_dropped++;
+                               skb_free_frag(data);
diff --git a/target/linux/generic/pending-6.12/734-net-ethernet-mediatek-enlarge-DMA-reserve-buffer.patch b/target/linux/generic/pending-6.12/734-net-ethernet-mediatek-enlarge-DMA-reserve-buffer.patch
new file mode 100644 (file)
index 0000000..8d0f0b9
--- /dev/null
@@ -0,0 +1,44 @@
+From: Chad Monroe <chad@monroe.io>
+Date: Mon, 16 Sep 2024 19:29:03 -0700
+Subject: [PATCH] net: ethernet: mediatek: increase QDMA RESV_BUF size
+
+Increase QDMA RESV_BUF from 2K to 3K for netsys v2 to match Mediatek SDK[1].
+This helps reduce the possibility of Ethernet transmit timeouts.
+
+[1]: https://git01.mediatek.com/plugins/gitiles/openwrt/feeds/mtk-openwrt-feeds/+/19d8456c3051e5f6dabf42fa770916a2126ea4bf
+
+Signed-off-by: Chad Monroe <chad@monroe.io>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c |    6 ++++--
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h |    1 +
+ 2 files changed, 5 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -282,6 +282,7 @@
+ #define MTK_WCOMP_EN          BIT(24)
+ #define MTK_RESV_BUF          (0x80 << 16)
+ #define MTK_MUTLI_CNT         (0x4 << 12)
++#define MTK_RESV_BUF_MASK     (0xff << 16)
+ #define MTK_LEAKY_BUCKET_EN   BIT(11)
+ /* QDMA Flow Control Register */
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -3483,12 +3483,14 @@ 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_is_netsys_v2_or_greater(eth))
++              if (mtk_is_netsys_v2_or_greater(eth)) {
++                      val &= ~MTK_RESV_BUF_MASK;
+                       val |= MTK_MUTLI_CNT | MTK_RESV_BUF |
+                              MTK_WCOMP_EN | MTK_DMAD_WR_WDONE |
+                              MTK_CHK_DDONE_EN;
+-              else
++              } else {
+                       val |= MTK_RX_BT_32DWORDS;
++              }
+               mtk_w32(eth, val, reg_map->qdma.glo_cfg);
+               mtk_w32(eth,
diff --git a/target/linux/generic/pending-6.12/736-03-net-ethernet-mtk_eth_soc-improve-keeping-track-of-of.patch b/target/linux/generic/pending-6.12/736-03-net-ethernet-mtk_eth_soc-improve-keeping-track-of-of.patch
new file mode 100644 (file)
index 0000000..c503483
--- /dev/null
@@ -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
+@@ -479,42 +479,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;
+@@ -535,7 +536,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);
+ }
+@@ -551,66 +553,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 |= 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
+@@ -653,7 +644,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);
+ }
+@@ -700,8 +692,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;
+@@ -709,30 +701,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)
+@@ -742,9 +734,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);
+@@ -752,20 +746,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;
+               }
+@@ -816,9 +804,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/pending-6.12/736-04-net-ethernet-mediatek-fix-ppe-flow-accounting-for-L2.patch b/target/linux/generic/pending-6.12/736-04-net-ethernet-mediatek-fix-ppe-flow-accounting-for-L2.patch
new file mode 100644 (file)
index 0000000..9141fa6
--- /dev/null
@@ -0,0 +1,343 @@
+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
+@@ -83,9 +83,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");
+@@ -93,17 +93,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);
+@@ -112,19 +126,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 = ((u64)pkt_cnt_high << 16) | pkt_cnt_low;
++              acct->bytes += ((u64)byte_cnt_high << 32) | byte_cnt_low;
++              acct->packets += ((u64)pkt_cnt_high << 16) | pkt_cnt_low;
+       }
+-      return 0;
++      return acct;
+ }
+ static void mtk_ppe_cache_clear(struct mtk_ppe *ppe)
+@@ -522,14 +536,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;
+@@ -554,11 +560,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)
+@@ -569,18 +578,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;
+@@ -589,7 +615,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;
+               }
+@@ -604,10 +632,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;
+@@ -638,6 +685,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);
+ }
+@@ -802,21 +855,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)
+@@ -844,32 +882,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);
+@@ -397,9 +400,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
+@@ -97,7 +97,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
+@@ -522,24 +522,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/pending-6.12/736-05-net-ethernet-mtk_eth_soc-zero-initialize-PPE-flow-ta.patch b/target/linux/generic/pending-6.12/736-05-net-ethernet-mtk_eth_soc-zero-initialize-PPE-flow-ta.patch
new file mode 100644 (file)
index 0000000..2ca5ace
--- /dev/null
@@ -0,0 +1,27 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 12 Sep 2025 14:18:14 +0200
+Subject: [PATCH] net: ethernet: mtk_eth_soc: zero initialize PPE flow table
+
+Avoid picking up flows from last boot or other invalid data
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -914,6 +914,7 @@ struct mtk_ppe *mtk_ppe_init(struct mtk_
+       if (!foe)
+               goto err_free_l2_flows;
++      memset(foe, 0, MTK_PPE_ENTRIES * soc->foe_entry_size);
+       ppe->foe_table = foe;
+       foe_flow_size = (MTK_PPE_ENTRIES / soc->hash_offset) *
+@@ -928,6 +929,7 @@ struct mtk_ppe *mtk_ppe_init(struct mtk_
+               if (!mib)
+                       return NULL;
++              memset(mib, 0, MTK_PPE_ENTRIES * sizeof(*mib));
+               ppe->mib_table = mib;
+               acct = devm_kzalloc(dev, MTK_PPE_ENTRIES * sizeof(*acct),
diff --git a/target/linux/generic/pending-6.12/737-net-ethernet-mtk_eth_soc-add-paths-and-SerDes-modes-.patch b/target/linux/generic/pending-6.12/737-net-ethernet-mtk_eth_soc-add-paths-and-SerDes-modes-.patch
new file mode 100644 (file)
index 0000000..d76797a
--- /dev/null
@@ -0,0 +1,893 @@
+From d5e337e7aecc2e1cc9e96768062610adb95f8f72 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 12 Dec 2023 03:51:14 +0000
+Subject: [PATCH] net: ethernet: mtk_eth_soc: add paths and SerDes modes for
+ MT7988
+
+MT7988 comes with a built-in 2.5G PHY as well as SerDes lanes to
+connect external PHYs or transceivers in USXGMII, 10GBase-R, 5GBase-R,
+2500Base-X, 1000Base-X and Cisco SGMII interface modes.
+
+Implement support for configuring for the new paths to SerDes interfaces
+and the internal 2.5G PHY.
+
+Add USXGMII PCS driver for 10GBase-R, 5GBase-R and USXGMII mode, and
+setup the new PHYA on MT7988 to access the also still existing old
+LynxI PCS for 1000Base-X, 2500Base-X and Cisco SGMII PCS interface
+modes.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_path.c | 122 +++++++-
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c  | 292 +++++++++++++++++--
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h  | 107 ++++++-
+ 3 files changed, 470 insertions(+), 51 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_path.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_path.c
+@@ -31,10 +31,20 @@ static const char *mtk_eth_path_name(u64
+               return "gmac2_rgmii";
+       case MTK_ETH_PATH_GMAC2_SGMII:
+               return "gmac2_sgmii";
++      case MTK_ETH_PATH_GMAC2_2P5GPHY:
++              return "gmac2_2p5gphy";
+       case MTK_ETH_PATH_GMAC2_GEPHY:
+               return "gmac2_gephy";
++      case MTK_ETH_PATH_GMAC3_SGMII:
++              return "gmac3_sgmii";
+       case MTK_ETH_PATH_GDM1_ESW:
+               return "gdm1_esw";
++      case MTK_ETH_PATH_GMAC1_USXGMII:
++              return "gmac1_usxgmii";
++      case MTK_ETH_PATH_GMAC2_USXGMII:
++              return "gmac2_usxgmii";
++      case MTK_ETH_PATH_GMAC3_USXGMII:
++              return "gmac3_usxgmii";
+       default:
+               return "unknown path";
+       }
+@@ -127,6 +137,27 @@ static int set_mux_u3_gmac2_to_qphy(stru
+       return 0;
+ }
++static int set_mux_gmac2_to_2p5gphy(struct mtk_eth *eth, u64 path)
++{
++      int ret;
++
++      if (path == MTK_ETH_PATH_GMAC2_2P5GPHY) {
++              ret = regmap_clear_bits(eth->ethsys, ETHSYS_SYSCFG0, SYSCFG0_SGMII_GMAC2_V2);
++              if (ret)
++                      return ret;
++
++              /* Setup mux to 2p5g PHY */
++              ret = regmap_clear_bits(eth->infra, TOP_MISC_NETSYS_PCS_MUX, MUX_G2_USXGMII_SEL);
++              if (ret)
++                      return ret;
++
++              dev_dbg(eth->dev, "path %s in %s updated\n",
++                      mtk_eth_path_name(path), __func__);
++      }
++
++      return 0;
++}
++
+ static int set_mux_gmac1_gmac2_to_sgmii_rgmii(struct mtk_eth *eth, u64 path)
+ {
+       unsigned int val = 0;
+@@ -165,7 +196,48 @@ static int set_mux_gmac1_gmac2_to_sgmii_
+       return 0;
+ }
+-static int set_mux_gmac12_to_gephy_sgmii(struct mtk_eth *eth, u64 path)
++static int set_mux_gmac123_to_usxgmii(struct mtk_eth *eth, u64 path)
++{
++      unsigned int val = 0;
++      bool updated = true;
++      int mac_id = 0;
++
++      /* Disable SYSCFG1 SGMII */
++      regmap_read(eth->ethsys, ETHSYS_SYSCFG0, &val);
++
++      switch (path) {
++      case MTK_ETH_PATH_GMAC1_USXGMII:
++              val &= ~(u32)SYSCFG0_SGMII_GMAC1_V2;
++              mac_id = MTK_GMAC1_ID;
++              break;
++      case MTK_ETH_PATH_GMAC2_USXGMII:
++              val &= ~(u32)SYSCFG0_SGMII_GMAC2_V2;
++              mac_id = MTK_GMAC2_ID;
++              break;
++      case MTK_ETH_PATH_GMAC3_USXGMII:
++              val &= ~(u32)SYSCFG0_SGMII_GMAC3_V2;
++              mac_id = MTK_GMAC3_ID;
++              break;
++      default:
++              updated = false;
++      };
++
++      if (updated) {
++              regmap_update_bits(eth->ethsys, ETHSYS_SYSCFG0,
++                                 SYSCFG0_SGMII_MASK, val);
++
++              if (mac_id == MTK_GMAC2_ID)
++                      regmap_set_bits(eth->infra, TOP_MISC_NETSYS_PCS_MUX,
++                                      MUX_G2_USXGMII_SEL);
++      }
++
++      dev_dbg(eth->dev, "path %s in %s updated = %d\n",
++              mtk_eth_path_name(path), __func__, updated);
++
++      return 0;
++}
++
++static int set_mux_gmac123_to_gephy_sgmii(struct mtk_eth *eth, u64 path)
+ {
+       unsigned int val = 0;
+       bool updated = true;
+@@ -182,6 +254,9 @@ static int set_mux_gmac12_to_gephy_sgmii
+       case MTK_ETH_PATH_GMAC2_SGMII:
+               val |= SYSCFG0_SGMII_GMAC2_V2;
+               break;
++      case MTK_ETH_PATH_GMAC3_SGMII:
++              val |= SYSCFG0_SGMII_GMAC3_V2;
++              break;
+       default:
+               updated = false;
+       }
+@@ -210,13 +285,25 @@ static const struct mtk_eth_muxc mtk_eth
+               .cap_bit = MTK_ETH_MUX_U3_GMAC2_TO_QPHY,
+               .set_path = set_mux_u3_gmac2_to_qphy,
+       }, {
++              .name = "mux_gmac2_to_2p5gphy",
++              .cap_bit = MTK_ETH_MUX_GMAC2_TO_2P5GPHY,
++              .set_path = set_mux_gmac2_to_2p5gphy,
++      }, {
+               .name = "mux_gmac1_gmac2_to_sgmii_rgmii",
+               .cap_bit = MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII,
+               .set_path = set_mux_gmac1_gmac2_to_sgmii_rgmii,
+       }, {
+               .name = "mux_gmac12_to_gephy_sgmii",
+               .cap_bit = MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII,
+-              .set_path = set_mux_gmac12_to_gephy_sgmii,
++              .set_path = set_mux_gmac123_to_gephy_sgmii,
++      }, {
++              .name = "mux_gmac123_to_gephy_sgmii",
++              .cap_bit = MTK_ETH_MUX_GMAC123_TO_GEPHY_SGMII,
++              .set_path = set_mux_gmac123_to_gephy_sgmii,
++      }, {
++              .name = "mux_gmac123_to_usxgmii",
++              .cap_bit = MTK_ETH_MUX_GMAC123_TO_USXGMII,
++              .set_path = set_mux_gmac123_to_usxgmii,
+       },
+ };
+@@ -249,12 +336,39 @@ out:
+       return err;
+ }
++int mtk_gmac_usxgmii_path_setup(struct mtk_eth *eth, int mac_id)
++{
++      u64 path;
++
++      path = (mac_id == MTK_GMAC1_ID) ?  MTK_ETH_PATH_GMAC1_USXGMII :
++             (mac_id == MTK_GMAC2_ID) ?  MTK_ETH_PATH_GMAC2_USXGMII :
++                                         MTK_ETH_PATH_GMAC3_USXGMII;
++
++      /* Setup proper MUXes along the path */
++      return mtk_eth_mux_setup(eth, path);
++}
++
+ int mtk_gmac_sgmii_path_setup(struct mtk_eth *eth, int mac_id)
+ {
+       u64 path;
+-      path = (mac_id == 0) ?  MTK_ETH_PATH_GMAC1_SGMII :
+-                              MTK_ETH_PATH_GMAC2_SGMII;
++      path = (mac_id == MTK_GMAC1_ID) ? MTK_ETH_PATH_GMAC1_SGMII :
++             (mac_id == MTK_GMAC2_ID) ? MTK_ETH_PATH_GMAC2_SGMII :
++                                        MTK_ETH_PATH_GMAC3_SGMII;
++
++      /* Setup proper MUXes along the path */
++      return mtk_eth_mux_setup(eth, path);
++}
++
++int mtk_gmac_2p5gphy_path_setup(struct mtk_eth *eth, int mac_id)
++{
++      u64 path = 0;
++
++      if (mac_id == MTK_GMAC2_ID)
++              path = MTK_ETH_PATH_GMAC2_2P5GPHY;
++
++      if (!path)
++              return -EINVAL;
+       /* Setup proper MUXes along the path */
+       return mtk_eth_mux_setup(eth, path);
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -24,6 +24,8 @@
+ #include <linux/pinctrl/devinfo.h>
+ #include <linux/phylink.h>
+ #include <linux/pcs/pcs-mtk-lynxi.h>
++#include <linux/pcs/pcs-mtk-usxgmii.h>
++#include <linux/phy/phy.h>
+ #include <linux/jhash.h>
+ #include <linux/bitfield.h>
+ #include <net/dsa.h>
+@@ -522,6 +524,30 @@ static void mtk_setup_bridge_switch(stru
+               MTK_GSW_CFG);
+ }
++static bool mtk_check_gmac23_idle(struct mtk_mac *mac)
++{
++      u32 mac_fsm, gdm_fsm;
++
++      mac_fsm = mtk_r32(mac->hw, MTK_MAC_FSM(mac->id));
++
++      switch (mac->id) {
++      case MTK_GMAC2_ID:
++              gdm_fsm = mtk_r32(mac->hw, MTK_FE_GDM2_FSM);
++              break;
++      case MTK_GMAC3_ID:
++              gdm_fsm = mtk_r32(mac->hw, MTK_FE_GDM3_FSM);
++              break;
++      default:
++              return true;
++      };
++
++      if ((mac_fsm & 0xFFFF0000) == 0x01010000 &&
++          (gdm_fsm & 0xFFFF0000) == 0x00000000)
++              return true;
++
++      return false;
++}
++
+ static struct phylink_pcs *mtk_mac_select_pcs(struct phylink_config *config,
+                                             phy_interface_t interface)
+ {
+@@ -530,6 +556,21 @@ static struct phylink_pcs *mtk_mac_selec
+       struct mtk_eth *eth = mac->hw;
+       unsigned int sid;
++      if (mtk_is_netsys_v3_or_greater(eth)) {
++              switch (interface) {
++              case PHY_INTERFACE_MODE_1000BASEX:
++              case PHY_INTERFACE_MODE_2500BASEX:
++              case PHY_INTERFACE_MODE_SGMII:
++                      return mac->sgmii_pcs;
++              case PHY_INTERFACE_MODE_5GBASER:
++              case PHY_INTERFACE_MODE_10GBASER:
++              case PHY_INTERFACE_MODE_USXGMII:
++                      return mac->usxgmii_pcs;
++              default:
++                      return NULL;
++              }
++      }
++
+       if (interface == PHY_INTERFACE_MODE_SGMII ||
+           phy_interface_mode_is_8023z(interface)) {
+               sid = (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_SGMII)) ?
+@@ -581,7 +622,22 @@ static void mtk_mac_config(struct phylin
+                                       goto init_err;
+                       }
+                       break;
++              case PHY_INTERFACE_MODE_USXGMII:
++              case PHY_INTERFACE_MODE_10GBASER:
++              case PHY_INTERFACE_MODE_5GBASER:
++                      if (MTK_HAS_CAPS(eth->soc->caps, MTK_USXGMII)) {
++                              err = mtk_gmac_usxgmii_path_setup(eth, mac->id);
++                              if (err)
++                                      goto init_err;
++                      }
++                      break;
+               case PHY_INTERFACE_MODE_INTERNAL:
++                      if (mac->id == MTK_GMAC2_ID &&
++                          MTK_HAS_CAPS(eth->soc->caps, MTK_2P5GPHY)) {
++                              err = mtk_gmac_2p5gphy_path_setup(eth, mac->id);
++                              if (err)
++                                      goto init_err;
++                      }
+                       break;
+               default:
+                       goto err_phy;
+@@ -628,8 +684,6 @@ static void mtk_mac_config(struct phylin
+               val &= ~SYSCFG0_GE_MODE(SYSCFG0_GE_MASK, mac->id);
+               val |= SYSCFG0_GE_MODE(ge_mode, mac->id);
+               regmap_write(eth->ethsys, ETHSYS_SYSCFG0, val);
+-
+-              mac->interface = state->interface;
+       }
+       /* SGMII */
+@@ -646,21 +700,40 @@ static void mtk_mac_config(struct phylin
+               /* Save the syscfg0 value for mac_finish */
+               mac->syscfg0 = val;
+-      } else if (phylink_autoneg_inband(mode)) {
++      } else if (state->interface != PHY_INTERFACE_MODE_USXGMII &&
++                 state->interface != PHY_INTERFACE_MODE_10GBASER &&
++                 state->interface != PHY_INTERFACE_MODE_5GBASER &&
++                 phylink_autoneg_inband(mode)) {
+               dev_err(eth->dev,
+-                      "In-band mode not supported in non SGMII mode!\n");
++                      "In-band mode not supported in non-SerDes modes!\n");
+               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));
++      if (mtk_is_netsys_v3_or_greater(eth)) {
++              if (mtk_interface_mode_is_xgmii(state->interface)) {
++                      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));
++
++                      if (mac->id == MTK_GMAC1_ID)
++                              mtk_setup_bridge_switch(eth);
++              } else {
++                      mtk_w32(eth, 0, MTK_GDMA_EG_CTRL(mac->id));
+-              mtk_setup_bridge_switch(eth);
++                      /* FIXME: In current hardware design, we have to reset FE
++                       * when swtiching XGDM to GDM. Therefore, here trigger an SER
++                       * to let GDM go back to the initial state.
++                       */
++                      if ((mtk_interface_mode_is_xgmii(mac->interface) ||
++                           mac->interface == PHY_INTERFACE_MODE_NA) &&
++                          !mtk_check_gmac23_idle(mac) &&
++                          !test_bit(MTK_RESETTING, &eth->state))
++                              schedule_work(&eth->pending_work);
++              }
+       }
++      mac->interface = state->interface;
++
+       return;
+ err_phy:
+@@ -673,6 +746,18 @@ init_err:
+               mac->id, phy_modes(state->interface), err);
+ }
++static int mtk_mac_prepare(struct phylink_config *config, unsigned int mode,
++                         phy_interface_t interface)
++{
++      struct mtk_mac *mac = container_of(config, struct mtk_mac,
++                                         phylink_config);
++
++      if (mac->pextp && mac->interface != interface)
++              phy_reset(mac->pextp);
++
++      return 0;
++}
++
+ static int mtk_mac_finish(struct phylink_config *config, unsigned int mode,
+                         phy_interface_t interface)
+ {
+@@ -681,6 +766,10 @@ static int mtk_mac_finish(struct phylink
+       struct mtk_eth *eth = mac->hw;
+       u32 mcr_cur, mcr_new;
++      /* Setup PMA/PMD */
++      if (mac->pextp)
++              phy_set_mode_ext(mac->pextp, PHY_MODE_ETHERNET, interface);
++
+       /* Enable SGMII */
+       if (interface == PHY_INTERFACE_MODE_SGMII ||
+           phy_interface_mode_is_8023z(interface))
+@@ -705,10 +794,14 @@ static void mtk_mac_link_down(struct phy
+ {
+       struct mtk_mac *mac = container_of(config, struct mtk_mac,
+                                          phylink_config);
+-      u32 mcr = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
+-      mcr &= ~(MAC_MCR_TX_EN | MAC_MCR_RX_EN | MAC_MCR_FORCE_LINK);
+-      mtk_w32(mac->hw, mcr, MTK_MAC_MCR(mac->id));
++      if (!mtk_interface_mode_is_xgmii(interface)) {
++              mtk_m32(mac->hw, MAC_MCR_TX_EN | MAC_MCR_RX_EN | MAC_MCR_FORCE_LINK, 0, MTK_MAC_MCR(mac->id));
++              if (mtk_is_netsys_v3_or_greater(mac->hw))
++                      mtk_m32(mac->hw, MTK_XGMAC_FORCE_LINK(mac->id), 0, MTK_XGMAC_STS(mac->id));
++      } else if (mtk_is_netsys_v3_or_greater(mac->hw) && mac->id != MTK_GMAC1_ID) {
++              mtk_m32(mac->hw, XMAC_MCR_TRX_DISABLE, XMAC_MCR_TRX_DISABLE, MTK_XMAC_MCR(mac->id));
++      }
+ }
+ static void mtk_set_queue_speed(struct mtk_eth *eth, unsigned int idx,
+@@ -780,13 +873,11 @@ static void mtk_set_queue_speed(struct m
+       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,
+-                          int speed, int duplex, bool tx_pause, bool rx_pause)
++static void mtk_gdm_mac_link_up(struct mtk_mac *mac,
++                              struct phy_device *phy,
++                              unsigned int mode, phy_interface_t interface,
++                              int speed, int duplex, bool tx_pause, bool rx_pause)
+ {
+-      struct mtk_mac *mac = container_of(config, struct mtk_mac,
+-                                         phylink_config);
+       u32 mcr;
+       mcr = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
+@@ -830,9 +921,63 @@ static void mtk_mac_link_up(struct phyli
+       mtk_w32(mac->hw, mcr, MTK_MAC_MCR(mac->id));
+ }
++static void mtk_xgdm_mac_link_up(struct mtk_mac *mac,
++                               struct phy_device *phy,
++                               unsigned int mode, phy_interface_t interface,
++                               int speed, int duplex, bool tx_pause, bool rx_pause)
++{
++      u32 mcr, force_link = 0;
++
++      if (mac->id == MTK_GMAC1_ID)
++              return;
++
++      /* Eliminate the interference(before link-up) caused by PHY noise */
++      mtk_m32(mac->hw, XMAC_LOGIC_RST, 0, MTK_XMAC_LOGIC_RST(mac->id));
++      mdelay(20);
++      mtk_m32(mac->hw, XMAC_GLB_CNTCLR, XMAC_GLB_CNTCLR, MTK_XMAC_CNT_CTRL(mac->id));
++
++      if (mac->interface == PHY_INTERFACE_MODE_INTERNAL || mac->id == MTK_GMAC3_ID)
++              force_link = MTK_XGMAC_FORCE_LINK(mac->id);
++
++      mtk_m32(mac->hw, MTK_XGMAC_FORCE_LINK(mac->id), force_link, MTK_XGMAC_STS(mac->id));
++
++      mcr = mtk_r32(mac->hw, MTK_XMAC_MCR(mac->id));
++      mcr &= ~(XMAC_MCR_FORCE_TX_FC | XMAC_MCR_FORCE_RX_FC | XMAC_MCR_TRX_DISABLE);
++      /* Configure pause modes -
++       * phylink will avoid these for half duplex
++       */
++      if (tx_pause)
++              mcr |= XMAC_MCR_FORCE_TX_FC;
++      if (rx_pause)
++              mcr |= XMAC_MCR_FORCE_RX_FC;
++
++      mtk_w32(mac->hw, mcr, MTK_XMAC_MCR(mac->id));
++}
++
++static void mtk_mac_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 mtk_mac *mac = container_of(config, struct mtk_mac,
++                                         phylink_config);
++
++      if (mtk_is_netsys_v3_or_greater(mac->hw) && mtk_interface_mode_is_xgmii(interface))
++              mtk_xgdm_mac_link_up(mac, phy, mode, interface, speed, duplex,
++                                   tx_pause, rx_pause);
++      else
++              mtk_gdm_mac_link_up(mac, phy, mode, interface, speed, duplex,
++                                  tx_pause, rx_pause);
++
++      /* Repeat pextp setup to tune link */
++      if (mac->pextp)
++              phy_set_mode_ext(mac->pextp, PHY_MODE_ETHERNET, interface);
++}
++
+ static const struct phylink_mac_ops mtk_phylink_ops = {
+       .mac_select_pcs = mtk_mac_select_pcs,
+       .mac_config = mtk_mac_config,
++      .mac_prepare = mtk_mac_prepare,
+       .mac_finish = mtk_mac_finish,
+       .mac_link_down = mtk_mac_link_down,
+       .mac_link_up = mtk_mac_link_up,
+@@ -3591,6 +3736,9 @@ static int mtk_open(struct net_device *d
+       ppe_num = eth->soc->ppe_num;
++      if (mac->pextp)
++              phy_power_on(mac->pextp);
++
+       err = phylink_of_phy_connect(mac->phylink, mac->of_node, 0);
+       if (err) {
+               netdev_err(dev, "%s: could not attach PHY: %d\n", __func__,
+@@ -3738,6 +3886,9 @@ static int mtk_stop(struct net_device *d
+       for (i = 0; i < ARRAY_SIZE(eth->ppe); i++)
+               mtk_ppe_stop(eth->ppe[i]);
++      if (mac->pextp)
++              phy_power_off(mac->pextp);
++
+       return 0;
+ }
+@@ -4837,6 +4988,7 @@ static const struct net_device_ops mtk_n
+ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np)
+ {
+       const __be32 *_id = of_get_property(np, "reg", NULL);
++      struct device_node *pcs_np;
+       phy_interface_t phy_mode;
+       struct phylink *phylink;
+       struct mtk_mac *mac;
+@@ -4875,16 +5027,44 @@ static int mtk_add_mac(struct mtk_eth *e
+       mac->id = id;
+       mac->hw = eth;
+       mac->of_node = np;
++      pcs_np = of_parse_phandle(mac->of_node, "pcs-handle", 0);
++      if (pcs_np) {
++              mac->sgmii_pcs = mtk_pcs_lynxi_get(eth->dev, pcs_np);
++              if (IS_ERR(mac->sgmii_pcs)) {
++                      if (PTR_ERR(mac->sgmii_pcs) != -EPROBE_DEFER)
++                              dev_err(eth->dev,
++                                      "cannot select SGMII PCS, error %ld\n",
++                                      PTR_ERR(mac->sgmii_pcs));
++
++                      err = PTR_ERR(mac->sgmii_pcs);
++                      goto free_netdev;
++              }
++      }
+-      err = of_get_ethdev_address(mac->of_node, eth->netdev[id]);
+-      if (err == -EPROBE_DEFER)
+-              return err;
++      pcs_np = of_parse_phandle(mac->of_node, "pcs-handle", 1);
++      if (pcs_np) {
++              mac->usxgmii_pcs = mtk_usxgmii_pcs_get(eth->dev, pcs_np);
++              if (IS_ERR(mac->usxgmii_pcs)) {
++                      if (PTR_ERR(mac->usxgmii_pcs) != -EPROBE_DEFER)
++                              dev_err(eth->dev,
++                                      "cannot select USXGMII PCS, error %ld\n",
++                                      PTR_ERR(mac->usxgmii_pcs));
++
++                      err = PTR_ERR(mac->usxgmii_pcs);
++                      goto free_netdev;
++              }
++      }
+-      if (err) {
+-              /* If the mac address is invalid, use random mac address */
+-              eth_hw_addr_random(eth->netdev[id]);
+-              dev_err(eth->dev, "generated random MAC address %pM\n",
+-                      eth->netdev[id]->dev_addr);
++      if (mtk_is_netsys_v3_or_greater(eth) && (mac->sgmii_pcs || mac->usxgmii_pcs)) {
++              mac->pextp = devm_of_phy_optional_get(eth->dev, mac->of_node, NULL);
++              if (IS_ERR(mac->pextp)) {
++                      if (PTR_ERR(mac->pextp) != -EPROBE_DEFER)
++                              dev_err(eth->dev, "cannot get PHY, error %ld\n",
++                                      PTR_ERR(mac->pextp));
++
++                      err = PTR_ERR(mac->pextp);
++                      goto free_netdev;
++              }
+       }
+       memset(mac->hwlro_ip, 0, sizeof(mac->hwlro_ip));
+@@ -4967,8 +5147,21 @@ static int mtk_add_mac(struct mtk_eth *e
+               phy_interface_zero(mac->phylink_config.supported_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+                         mac->phylink_config.supported_interfaces);
++      } else if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_USXGMII)) {
++              mac->phylink_config.mac_capabilities |= MAC_5000FD | MAC_10000FD;
++              __set_bit(PHY_INTERFACE_MODE_5GBASER,
++                        mac->phylink_config.supported_interfaces);
++              __set_bit(PHY_INTERFACE_MODE_10GBASER,
++                        mac->phylink_config.supported_interfaces);
++              __set_bit(PHY_INTERFACE_MODE_USXGMII,
++                        mac->phylink_config.supported_interfaces);
+       }
++      if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_2P5GPHY) &&
++          id == MTK_GMAC2_ID)
++              __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);
+@@ -5019,6 +5212,26 @@ free_netdev:
+       return err;
+ }
++static int mtk_mac_assign_address(struct mtk_eth *eth, int i, bool test_defer_only)
++{
++      int err = of_get_ethdev_address(eth->mac[i]->of_node, eth->netdev[i]);
++
++      if (err == -EPROBE_DEFER)
++              return err;
++
++      if (test_defer_only)
++              return 0;
++
++      if (err) {
++              /* If the mac address is invalid, use random mac address */
++              eth_hw_addr_random(eth->netdev[i]);
++              dev_err(eth->dev, "generated random MAC address %pM\n",
++                      eth->netdev[i]);
++      }
++
++      return 0;
++}
++
+ void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev)
+ {
+       struct net_device *dev, *tmp;
+@@ -5165,7 +5378,8 @@ static int mtk_probe(struct platform_dev
+                       regmap_write(cci, 0, 3);
+       }
+-      if (MTK_HAS_CAPS(eth->soc->caps, MTK_SGMII)) {
++      if (MTK_HAS_CAPS(eth->soc->caps, MTK_SGMII) &&
++          !mtk_is_netsys_v3_or_greater(eth)) {
+               err = mtk_sgmii_init(eth);
+               if (err)
+@@ -5276,6 +5490,24 @@ static int mtk_probe(struct platform_dev
+               }
+       }
++      for (i = 0; i < MTK_MAX_DEVS; i++) {
++              if (!eth->netdev[i])
++                      continue;
++
++              err = mtk_mac_assign_address(eth, i, true);
++              if (err)
++                      goto err_deinit_hw;
++      }
++
++      for (i = 0; i < MTK_MAX_DEVS; i++) {
++              if (!eth->netdev[i])
++                      continue;
++
++              err = mtk_mac_assign_address(eth, i, false);
++              if (err)
++                      goto err_deinit_hw;
++      }
++
+       if (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_INT)) {
+               err = devm_request_irq(eth->dev, eth->irq[0],
+                                      mtk_handle_irq, 0,
+@@ -5386,6 +5618,11 @@ static void mtk_remove(struct platform_d
+               mtk_stop(eth->netdev[i]);
+               mac = netdev_priv(eth->netdev[i]);
+               phylink_disconnect_phy(mac->phylink);
++              if (mac->sgmii_pcs)
++                      mtk_pcs_lynxi_put(mac->sgmii_pcs);
++
++              if (mac->usxgmii_pcs)
++                      mtk_usxgmii_pcs_put(mac->usxgmii_pcs);
+       }
+       mtk_wed_exit();
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -15,6 +15,7 @@
+ #include <linux/u64_stats_sync.h>
+ #include <linux/refcount.h>
+ #include <linux/phylink.h>
++#include <linux/reset.h>
+ #include <linux/rhashtable.h>
+ #include <linux/dim.h>
+ #include <linux/bitfield.h>
+@@ -527,6 +528,21 @@
+ #define INTF_MODE_RGMII_1000    (TRGMII_MODE | TRGMII_CENTRAL_ALIGNED)
+ #define INTF_MODE_RGMII_10_100  0
++/* XFI Mac control registers */
++#define MTK_XMAC_BASE(x)      (0x12000 + (((x) - 1) * 0x1000))
++#define MTK_XMAC_MCR(x)               (MTK_XMAC_BASE(x))
++#define XMAC_MCR_TRX_DISABLE  0xf
++#define XMAC_MCR_FORCE_TX_FC  BIT(5)
++#define XMAC_MCR_FORCE_RX_FC  BIT(4)
++
++/* XFI Mac logic reset registers */
++#define MTK_XMAC_LOGIC_RST(x) (MTK_XMAC_BASE(x) + 0x10)
++#define XMAC_LOGIC_RST                BIT(0)
++
++/* XFI Mac count global control */
++#define MTK_XMAC_CNT_CTRL(x)  (MTK_XMAC_BASE(x) + 0x100)
++#define XMAC_GLB_CNTCLR               BIT(0)
++
+ /* GPIO port control registers for GMAC 2*/
+ #define GPIO_OD33_CTRL8               0x4c0
+ #define GPIO_BIAS_CTRL                0xed0
+@@ -552,6 +568,7 @@
+ #define SYSCFG0_SGMII_GMAC2    ((3 << 8) & SYSCFG0_SGMII_MASK)
+ #define SYSCFG0_SGMII_GMAC1_V2 BIT(9)
+ #define SYSCFG0_SGMII_GMAC2_V2 BIT(8)
++#define SYSCFG0_SGMII_GMAC3_V2 BIT(7)
+ /* ethernet subsystem clock register */
+@@ -590,6 +607,11 @@
+ #define GEPHY_MAC_SEL          BIT(1)
+ /* Top misc registers */
++#define TOP_MISC_NETSYS_PCS_MUX       0x0
++#define NETSYS_PCS_MUX_MASK   GENMASK(1, 0)
++#define       MUX_G2_USXGMII_SEL      BIT(1)
++#define MUX_HSGMII1_G1_SEL    BIT(0)
++
+ #define USB_PHY_SWITCH_REG    0x218
+ #define QPHY_SEL_MASK         GENMASK(1, 0)
+ #define SGMII_QPHY_SEL                0x2
+@@ -614,6 +636,8 @@
+ #define MT7628_SDM_RBCNT      (MT7628_SDM_OFFSET + 0x10c)
+ #define MT7628_SDM_CS_ERR     (MT7628_SDM_OFFSET + 0x110)
++/* Debug Purpose Register */
++#define MTK_PSE_FQFC_CFG      0x100
+ #define MTK_FE_CDM1_FSM               0x220
+ #define MTK_FE_CDM2_FSM               0x224
+ #define MTK_FE_CDM3_FSM               0x238
+@@ -622,6 +646,11 @@
+ #define MTK_FE_CDM6_FSM               0x328
+ #define MTK_FE_GDM1_FSM               0x228
+ #define MTK_FE_GDM2_FSM               0x22C
++#define MTK_FE_GDM3_FSM               0x23C
++#define MTK_FE_PSE_FREE               0x240
++#define MTK_FE_DROP_FQ                0x244
++#define MTK_FE_DROP_FC                0x248
++#define MTK_FE_DROP_PPE               0x24C
+ #define MTK_MAC_FSM(x)                (0x1010C + ((x) * 0x100))
+@@ -945,6 +974,8 @@ enum mkt_eth_capabilities {
+       MTK_RGMII_BIT = 0,
+       MTK_TRGMII_BIT,
+       MTK_SGMII_BIT,
++      MTK_USXGMII_BIT,
++      MTK_2P5GPHY_BIT,
+       MTK_ESW_BIT,
+       MTK_GEPHY_BIT,
+       MTK_MUX_BIT,
+@@ -965,8 +996,11 @@ enum mkt_eth_capabilities {
+       MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT,
+       MTK_ETH_MUX_GMAC2_GMAC0_TO_GEPHY_BIT,
+       MTK_ETH_MUX_U3_GMAC2_TO_QPHY_BIT,
++      MTK_ETH_MUX_GMAC2_TO_2P5GPHY_BIT,
+       MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII_BIT,
+       MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII_BIT,
++      MTK_ETH_MUX_GMAC123_TO_GEPHY_SGMII_BIT,
++      MTK_ETH_MUX_GMAC123_TO_USXGMII_BIT,
+       /* PATH BITS */
+       MTK_ETH_PATH_GMAC1_RGMII_BIT,
+@@ -974,14 +1008,21 @@ enum mkt_eth_capabilities {
+       MTK_ETH_PATH_GMAC1_SGMII_BIT,
+       MTK_ETH_PATH_GMAC2_RGMII_BIT,
+       MTK_ETH_PATH_GMAC2_SGMII_BIT,
++      MTK_ETH_PATH_GMAC2_2P5GPHY_BIT,
+       MTK_ETH_PATH_GMAC2_GEPHY_BIT,
++      MTK_ETH_PATH_GMAC3_SGMII_BIT,
+       MTK_ETH_PATH_GDM1_ESW_BIT,
++      MTK_ETH_PATH_GMAC1_USXGMII_BIT,
++      MTK_ETH_PATH_GMAC2_USXGMII_BIT,
++      MTK_ETH_PATH_GMAC3_USXGMII_BIT,
+ };
+ /* Supported hardware group on SoCs */
+ #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_USXGMII           BIT_ULL(MTK_USXGMII_BIT)
++#define MTK_2P5GPHY           BIT_ULL(MTK_2P5GPHY_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)
+@@ -1004,10 +1045,16 @@ enum mkt_eth_capabilities {
+       BIT_ULL(MTK_ETH_MUX_GMAC2_GMAC0_TO_GEPHY_BIT)
+ #define MTK_ETH_MUX_U3_GMAC2_TO_QPHY          \
+       BIT_ULL(MTK_ETH_MUX_U3_GMAC2_TO_QPHY_BIT)
++#define MTK_ETH_MUX_GMAC2_TO_2P5GPHY          \
++      BIT_ULL(MTK_ETH_MUX_GMAC2_TO_2P5GPHY_BIT)
+ #define MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII        \
+       BIT_ULL(MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII_BIT)
+ #define MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII     \
+       BIT_ULL(MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII_BIT)
++#define MTK_ETH_MUX_GMAC123_TO_GEPHY_SGMII    \
++      BIT_ULL(MTK_ETH_MUX_GMAC123_TO_GEPHY_SGMII_BIT)
++#define MTK_ETH_MUX_GMAC123_TO_USXGMII        \
++      BIT_ULL(MTK_ETH_MUX_GMAC123_TO_USXGMII_BIT)
+ /* Supported path present on SoCs */
+ #define MTK_ETH_PATH_GMAC1_RGMII      BIT_ULL(MTK_ETH_PATH_GMAC1_RGMII_BIT)
+@@ -1015,8 +1062,13 @@ enum mkt_eth_capabilities {
+ #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_2P5GPHY    BIT_ULL(MTK_ETH_PATH_GMAC2_2P5GPHY_BIT)
+ #define MTK_ETH_PATH_GMAC2_GEPHY      BIT_ULL(MTK_ETH_PATH_GMAC2_GEPHY_BIT)
++#define MTK_ETH_PATH_GMAC3_SGMII      BIT_ULL(MTK_ETH_PATH_GMAC3_SGMII_BIT)
+ #define MTK_ETH_PATH_GDM1_ESW         BIT_ULL(MTK_ETH_PATH_GDM1_ESW_BIT)
++#define MTK_ETH_PATH_GMAC1_USXGMII    BIT_ULL(MTK_ETH_PATH_GMAC1_USXGMII_BIT)
++#define MTK_ETH_PATH_GMAC2_USXGMII    BIT_ULL(MTK_ETH_PATH_GMAC2_USXGMII_BIT)
++#define MTK_ETH_PATH_GMAC3_USXGMII    BIT_ULL(MTK_ETH_PATH_GMAC3_USXGMII_BIT)
+ #define MTK_GMAC1_RGMII               (MTK_ETH_PATH_GMAC1_RGMII | MTK_RGMII)
+ #define MTK_GMAC1_TRGMII      (MTK_ETH_PATH_GMAC1_TRGMII | MTK_TRGMII)
+@@ -1024,7 +1076,12 @@ enum mkt_eth_capabilities {
+ #define MTK_GMAC2_RGMII               (MTK_ETH_PATH_GMAC2_RGMII | MTK_RGMII)
+ #define MTK_GMAC2_SGMII               (MTK_ETH_PATH_GMAC2_SGMII | MTK_SGMII)
+ #define MTK_GMAC2_GEPHY               (MTK_ETH_PATH_GMAC2_GEPHY | MTK_GEPHY)
++#define MTK_GMAC2_2P5GPHY     (MTK_ETH_PATH_GMAC2_2P5GPHY | MTK_2P5GPHY)
++#define MTK_GMAC3_SGMII               (MTK_ETH_PATH_GMAC3_SGMII | MTK_SGMII)
+ #define MTK_GDM1_ESW          (MTK_ETH_PATH_GDM1_ESW | MTK_ESW)
++#define MTK_GMAC1_USXGMII     (MTK_ETH_PATH_GMAC1_USXGMII | MTK_USXGMII)
++#define MTK_GMAC2_USXGMII     (MTK_ETH_PATH_GMAC2_USXGMII | MTK_USXGMII)
++#define MTK_GMAC3_USXGMII     (MTK_ETH_PATH_GMAC3_USXGMII | MTK_USXGMII)
+ /* MUXes present on SoCs */
+ /* 0: GDM1 -> GMAC1, 1: GDM1 -> ESW */
+@@ -1043,10 +1100,20 @@ enum mkt_eth_capabilities {
+       (MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII | MTK_MUX | \
+       MTK_SHARED_SGMII)
++/* 2: GMAC2 -> XGMII */
++#define MTK_MUX_GMAC2_TO_2P5GPHY      \
++      (MTK_ETH_MUX_GMAC2_TO_2P5GPHY | MTK_MUX | MTK_INFRA)
++
+ /* 0: GMACx -> GEPHY, 1: GMACx -> SGMII where x is 1 or 2 */
+ #define MTK_MUX_GMAC12_TO_GEPHY_SGMII   \
+       (MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII | MTK_MUX)
++#define MTK_MUX_GMAC123_TO_GEPHY_SGMII   \
++      (MTK_ETH_MUX_GMAC123_TO_GEPHY_SGMII | MTK_MUX)
++
++#define MTK_MUX_GMAC123_TO_USXGMII   \
++      (MTK_ETH_MUX_GMAC123_TO_USXGMII | MTK_MUX | MTK_INFRA)
++
+ #define MTK_HAS_CAPS(caps, _x)                (((caps) & (_x)) == (_x))
+ #define MT7621_CAPS  (MTK_GMAC1_RGMII | MTK_GMAC1_TRGMII | \
+@@ -1078,8 +1145,12 @@ enum mkt_eth_capabilities {
+                     MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \
+                     MTK_RSTCTRL_PPE1 | MTK_SRAM)
+-#define MT7988_CAPS  (MTK_36BIT_DMA | MTK_GDM1_ESW | MTK_QDMA | \
+-                    MTK_RSTCTRL_PPE1 | MTK_RSTCTRL_PPE2 | MTK_SRAM)
++#define MT7988_CAPS  (MTK_36BIT_DMA | MTK_GDM1_ESW | MTK_GMAC1_SGMII | \
++                    MTK_GMAC2_2P5GPHY | MTK_GMAC2_SGMII | MTK_GMAC2_USXGMII | \
++                    MTK_GMAC3_SGMII | MTK_GMAC3_USXGMII | \
++                    MTK_MUX_GMAC123_TO_GEPHY_SGMII | \
++                    MTK_MUX_GMAC123_TO_USXGMII | MTK_MUX_GMAC2_TO_2P5GPHY | \
++                    MTK_QDMA | MTK_RSTCTRL_PPE1 | MTK_RSTCTRL_PPE2 | MTK_SRAM)
+ struct mtk_tx_dma_desc_info {
+       dma_addr_t      addr;
+@@ -1327,6 +1398,9 @@ struct mtk_mac {
+       struct device_node              *of_node;
+       struct phylink                  *phylink;
+       struct phylink_config           phylink_config;
++      struct phylink_pcs              *sgmii_pcs;
++      struct phylink_pcs              *usxgmii_pcs;
++      struct phy                      *pextp;
+       struct mtk_eth                  *hw;
+       struct mtk_hw_stats             *hw_stats;
+       __be32                          hwlro_ip[MTK_MAX_LRO_IP_CNT];
+@@ -1450,6 +1524,19 @@ static inline u32 mtk_get_ib2_multicast_
+       return MTK_FOE_IB2_MULTICAST;
+ }
++static inline bool mtk_interface_mode_is_xgmii(phy_interface_t interface)
++{
++      switch (interface) {
++      case PHY_INTERFACE_MODE_INTERNAL:
++      case PHY_INTERFACE_MODE_USXGMII:
++      case PHY_INTERFACE_MODE_10GBASER:
++      case PHY_INTERFACE_MODE_5GBASER:
++              return true;
++      default:
++              return false;
++      }
++}
++
+ /* read the hardware status register */
+ void mtk_stats_update_mac(struct mtk_mac *mac);
+@@ -1458,8 +1545,10 @@ u32 mtk_r32(struct mtk_eth *eth, unsigne
+ 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_2p5gphy_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);
++int mtk_gmac_usxgmii_path_setup(struct mtk_eth *eth, int mac_id);
+ int mtk_eth_offload_init(struct mtk_eth *eth, u8 id);
+ int mtk_eth_setup_tc(struct net_device *dev, enum tc_setup_type type,
diff --git a/target/linux/generic/pending-6.12/738-01-net-ethernet-mtk_eth_soc-reduce-rx-ring-size-for-older.patch b/target/linux/generic/pending-6.12/738-01-net-ethernet-mtk_eth_soc-reduce-rx-ring-size-for-older.patch
new file mode 100644 (file)
index 0000000..95abd94
--- /dev/null
@@ -0,0 +1,104 @@
+From: Felix Fietkau <nbd@nbd.name>
+Subject: [PATCH net-next 3/4] net: ethernet: mtk_eth_soc: reduce rx ring size for older chipsets
+Date: Tue, 15 Oct 2024 13:09:37 +0200
+
+Commit c57e55819443 ("net: ethernet: mtk_eth_soc: handle dma buffer
+size soc specific") resolved some tx timeout issues by bumping FQ and
+tx ring sizes from 512 to 2048 entries (the value used in the MediaTek
+SDK), however it also changed the rx ring size for all chipsets in the
+same way.
+
+Based on a few tests, it seems that a symmetric rx/tx ring size of 2048
+really only makes sense on MT7988, which is capable of 10G ethernet links.
+
+Older chipsets are typically deployed in systems that are more memory
+constrained and don't actually need the larger rings to handle received
+packets.
+
+In order to reduce wasted memory set the ring size based on the SoC to
+the following values:
+- 2048 on MT7988
+- 1024 on MT7986
+- 512 (previous value) on everything else, except:
+- 256 on RT5350 (the oldest supported chipset)
+
+Fixes: c57e55819443 ("net: ethernet: mtk_eth_soc: handle dma buffer size soc specific")
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 16 ++++++++--------
+ 1 file changed, 8 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -5656,7 +5656,7 @@ static const struct mtk_soc_data mt2701_
+               DESC_SIZE(struct mtk_rx_dma),
+               .irq_done_mask = MTK_RX_DONE_INT,
+               .dma_l4_valid = RX_DMA_L4_VALID,
+-              .dma_size = MTK_DMA_SIZE(2K),
++              .dma_size = MTK_DMA_SIZE(512),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+       },
+@@ -5684,7 +5684,7 @@ static const struct mtk_soc_data mt7621_
+               DESC_SIZE(struct mtk_rx_dma),
+               .irq_done_mask = MTK_RX_DONE_INT,
+               .dma_l4_valid = RX_DMA_L4_VALID,
+-              .dma_size = MTK_DMA_SIZE(2K),
++              .dma_size = MTK_DMA_SIZE(512),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+       },
+@@ -5714,7 +5714,7 @@ static const struct mtk_soc_data mt7622_
+               DESC_SIZE(struct mtk_rx_dma),
+               .irq_done_mask = MTK_RX_DONE_INT,
+               .dma_l4_valid = RX_DMA_L4_VALID,
+-              .dma_size = MTK_DMA_SIZE(2K),
++              .dma_size = MTK_DMA_SIZE(512),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+       },
+@@ -5743,7 +5743,7 @@ static const struct mtk_soc_data mt7623_
+               DESC_SIZE(struct mtk_rx_dma),
+               .irq_done_mask = MTK_RX_DONE_INT,
+               .dma_l4_valid = RX_DMA_L4_VALID,
+-              .dma_size = MTK_DMA_SIZE(2K),
++              .dma_size = MTK_DMA_SIZE(512),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+       },
+@@ -5769,7 +5769,7 @@ static const struct mtk_soc_data mt7629_
+               DESC_SIZE(struct mtk_rx_dma),
+               .irq_done_mask = MTK_RX_DONE_INT,
+               .dma_l4_valid = RX_DMA_L4_VALID,
+-              .dma_size = MTK_DMA_SIZE(2K),
++              .dma_size = MTK_DMA_SIZE(512),
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+       },
+@@ -5801,7 +5801,7 @@ static const struct mtk_soc_data mt7981_
+               .dma_l4_valid = RX_DMA_L4_VALID_V2,
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+-              .dma_size = MTK_DMA_SIZE(2K),
++              .dma_size = MTK_DMA_SIZE(512),
+       },
+ };
+@@ -5831,7 +5831,7 @@ static const struct mtk_soc_data mt7986_
+               .dma_l4_valid = RX_DMA_L4_VALID_V2,
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+-              .dma_size = MTK_DMA_SIZE(2K),
++              .dma_size = MTK_DMA_SIZE(1K),
+       },
+ };
+@@ -5884,7 +5884,7 @@ static const struct mtk_soc_data rt5350_
+               .dma_l4_valid = RX_DMA_L4_VALID_PDMA,
+               .dma_max_len = MTK_TX_DMA_BUF_LEN,
+               .dma_len_offset = 16,
+-              .dma_size = MTK_DMA_SIZE(2K),
++              .dma_size = MTK_DMA_SIZE(256),
+       },
+ };
diff --git a/target/linux/generic/pending-6.12/738-02-net-ethernet-mtk_eth_soc-do-not-enable-page-pool-sta.patch b/target/linux/generic/pending-6.12/738-02-net-ethernet-mtk_eth_soc-do-not-enable-page-pool-sta.patch
new file mode 100644 (file)
index 0000000..53216ca
--- /dev/null
@@ -0,0 +1,43 @@
+From: Danila Romanov <pervokur@gmail.com>
+Date: Wed, 22 Jan 2025 06:48:45 +0100
+Subject: [PATCH] net: ethernet: mtk_eth_soc: do not enable page pool stats by
+ default
+
+There is no reason for it to be enabled by default.
+Align mtk_eth_soc driver to mt76 driver.
+
+This option incurs additional CPU cost in allocation and recycle paths
+and additional memory cost to store the statistics.
+
+Signed-off-by: Danila Romanov <pervokur@gmail.com>
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/drivers/net/ethernet/mediatek/Kconfig
++++ b/drivers/net/ethernet/mediatek/Kconfig
+@@ -26,7 +26,6 @@ config NET_MEDIATEK_SOC
+       select PHYLINK
+       select DIMLIB
+       select PAGE_POOL
+-      select PAGE_POOL_STATS
+       select PCS_MTK_LYNXI
+       select REGMAP_MMIO
+       help
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -4756,6 +4756,7 @@ static int mtk_get_sset_count(struct net
+ static void mtk_ethtool_pp_stats(struct mtk_eth *eth, u64 *data)
+ {
++#ifdef CONFIG_PAGE_POOL_STATS
+       struct page_pool_stats stats = {};
+       int i;
+@@ -4768,6 +4769,7 @@ static void mtk_ethtool_pp_stats(struct
+               page_pool_get_stats(ring->page_pool, &stats);
+       }
+       page_pool_ethtool_stats_get(data, &stats);
++#endif
+ }
+ static void mtk_get_ethtool_stats(struct net_device *dev,
diff --git a/target/linux/generic/pending-6.12/739-01-dt-bindings-phy-mediatek-xfi-tphy-add-new-bindings.patch b/target/linux/generic/pending-6.12/739-01-dt-bindings-phy-mediatek-xfi-tphy-add-new-bindings.patch
new file mode 100644 (file)
index 0000000..1f1c40b
--- /dev/null
@@ -0,0 +1,136 @@
+From patchwork Thu Feb  1 21:52:20 2024
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+X-Patchwork-Submitter: Daniel Golle <daniel@makrotopia.org>
+X-Patchwork-Id: 13541842
+Date: Thu, 1 Feb 2024 21:52:20 +0000
+From: Daniel Golle <daniel@makrotopia.org>
+To: Bc-bocun Chen <bc-bocun.chen@mediatek.com>,
+       Steven Liu <steven.liu@mediatek.com>,
+       John Crispin <john@phrozen.org>,
+       Chunfeng Yun <chunfeng.yun@mediatek.com>,
+       Vinod Koul <vkoul@kernel.org>,
+       Kishon Vijay Abraham I <kishon@kernel.org>,
+       Rob Herring <robh@kernel.org>,
+       Krzysztof Kozlowski <krzysztof.kozlowski+dt@linaro.org>,
+       Conor Dooley <conor+dt@kernel.org>,
+       Daniel Golle <daniel@makrotopia.org>,
+       Qingfang Deng <dqfext@gmail.com>,
+       SkyLake Huang <SkyLake.Huang@mediatek.com>,
+       Matthias Brugger <matthias.bgg@gmail.com>,
+       AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>,
+       Philipp Zabel <p.zabel@pengutronix.de>,
+       linux-arm-kernel@lists.infradead.org,
+       linux-mediatek@lists.infradead.org, linux-phy@lists.infradead.org,
+       devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
+       netdev@vger.kernel.org
+Subject: [PATCH 1/2] dt-bindings: phy: mediatek,xfi-tphy: add new bindings
+Message-ID: 
+ <702afb0c1246d95c90b22e57105304028bdd3083.1706823233.git.daniel@makrotopia.org>
+MIME-Version: 1.0
+Content-Disposition: inline
+List-Id: Linux Phy Mailing list <linux-phy.lists.infradead.org>
+
+Add bindings for the MediaTek XFI T-PHY Ethernet SerDes PHY found in the
+MediaTek MT7988 SoC which can operate at various interfaces modes:
+
+via USXGMII PCS:
+ * USXGMII
+ * 10GBase-R
+ * 5GBase-R
+
+via LynxI SGMII PCS:
+ * 2500Base-X
+ * 1000Base-X
+ * Cisco SGMII (MAC side)
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ .../bindings/phy/mediatek,xfi-tphy.yaml       | 80 +++++++++++++++++++
+ 1 file changed, 80 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/phy/mediatek,xfi-tphy.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/phy/mediatek,xfi-tphy.yaml
+@@ -0,0 +1,80 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/phy/mediatek,xfi-tphy.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: MediaTek XFI T-PHY
++
++maintainers:
++  - Daniel Golle <daniel@makrotopia.org>
++
++description:
++  The MediaTek XFI SerDes T-PHY provides the physical SerDes lanes
++  used by the (10G/5G) USXGMII PCS and (1G/2.5G) LynxI PCS found in
++  MediaTek's 10G-capabale SoCs.
++
++properties:
++  $nodename:
++    pattern: "^phy@[0-9a-f]+$"
++
++  compatible:
++    const: mediatek,mt7988-xfi-tphy
++
++  reg:
++    maxItems: 1
++
++  clocks:
++    items:
++      - description: XFI PHY clock
++      - description: XFI register clock
++
++  clock-names:
++    items:
++      - const: xfipll
++      - const: topxtal
++
++  resets:
++    items:
++      - description: PEXTP reset
++
++  mediatek,usxgmii-performance-errata:
++    $ref: /schemas/types.yaml#/definitions/flag
++    description:
++      One instance of the T-PHY on MT7988 suffers from a performance
++      problem in 10GBase-R mode which needs a work-around in the driver.
++      The work-around is enabled using this flag.
++
++  "#phy-cells":
++    const: 0
++
++required:
++  - compatible
++  - reg
++  - clocks
++  - clock-names
++  - resets
++  - "#phy-cells"
++
++additionalProperties: false
++
++examples:
++  - |
++    #include <dt-bindings/clock/mediatek,mt7988-clk.h>
++    soc {
++      #address-cells = <2>;
++      #size-cells = <2>;
++
++      phy@11f20000 {
++        compatible = "mediatek,mt7988-xfi-tphy";
++        reg = <0 0x11f20000 0 0x10000>;
++        clocks = <&xfi_pll CLK_XFIPLL_PLL_EN>,
++                 <&topckgen CLK_TOP_XFI_PHY_0_XTAL_SEL>;
++        clock-names = "xfipll", "topxtal";
++        resets = <&watchdog 14>;
++        mediatek,usxgmii-performance-errata;
++        #phy-cells = <0>;
++      };
++    };
++
++...
diff --git a/target/linux/generic/pending-6.12/739-03-net-pcs-pcs-mtk-lynxi-add-platform-driver-for-MT7988.patch b/target/linux/generic/pending-6.12/739-03-net-pcs-pcs-mtk-lynxi-add-platform-driver-for-MT7988.patch
new file mode 100644 (file)
index 0000000..0e4a63e
--- /dev/null
@@ -0,0 +1,369 @@
+From 4b1a2716299c0e96a698044aebf3f80513509ae7 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 12 Dec 2023 03:47:18 +0000
+Subject: [PATCH 3/5] net: pcs: pcs-mtk-lynxi: add platform driver for MT7988
+
+Introduce a proper platform MFD driver for the LynxI (H)SGMII PCS which
+is going to initially be used for the MT7988 SoC.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/pcs/pcs-mtk-lynxi.c   | 227 ++++++++++++++++++++++++++++--
+ include/linux/pcs/pcs-mtk-lynxi.h |  11 ++
+ 2 files changed, 227 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/pcs/pcs-mtk-lynxi.c
++++ b/drivers/net/pcs/pcs-mtk-lynxi.c
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ // Copyright (c) 2018-2019 MediaTek Inc.
+-/* A library for MediaTek SGMII circuit
++/* A library and platform driver for the MediaTek LynxI SGMII circuit
+  *
+  * Author: Sean Wang <sean.wang@mediatek.com>
+  * Author: Alexander Couzens <lynxis@fe80.eu>
+@@ -8,11 +8,17 @@
+  *
+  */
++#include <linux/clk.h>
+ #include <linux/mdio.h>
++#include <linux/mfd/syscon.h>
++#include <linux/mutex.h>
+ #include <linux/of.h>
++#include <linux/of_platform.h>
+ #include <linux/pcs/pcs-mtk-lynxi.h>
+ #include <linux/phylink.h>
++#include <linux/platform_device.h>
+ #include <linux/regmap.h>
++#include <linux/reset.h>
+ /* SGMII subsystem config registers */
+ /* BMCR (low 16) BMSR (high 16) */
+@@ -65,6 +71,8 @@
+ #define SGMII_PN_SWAP_MASK            GENMASK(1, 0)
+ #define SGMII_PN_SWAP_TX_RX           (BIT(0) | BIT(1))
++#define MTK_NETSYS_V3_AMA_RGC3                0x128
++
+ /* struct mtk_pcs_lynxi -  This structure holds each sgmii regmap andassociated
+  *                         data
+  * @regmap:                The register map pointing at the range used to setup
+@@ -74,15 +82,29 @@
+  * @interface:             Currently configured interface mode
+  * @pcs:                   Phylink PCS structure
+  * @flags:                 Flags indicating hardware properties
++ * @rstc:                  Reset controller
++ * @sgmii_sel:             SGMII Register Clock
++ * @sgmii_rx:              SGMII RX Clock
++ * @sgmii_tx:              SGMII TX Clock
++ * @node:                  List node
+  */
+ struct mtk_pcs_lynxi {
+       struct regmap           *regmap;
++      struct device           *dev;
+       u32                     ana_rgc3;
+       phy_interface_t         interface;
+       struct                  phylink_pcs pcs;
+       u32                     flags;
++      struct reset_control    *rstc;
++      struct clk              *sgmii_sel;
++      struct clk              *sgmii_rx;
++      struct clk              *sgmii_tx;
++      struct list_head        node;
+ };
++static LIST_HEAD(mtk_pcs_lynxi_instances);
++static DEFINE_MUTEX(instance_mutex);
++
+ static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs)
+ {
+       return container_of(pcs, struct mtk_pcs_lynxi, pcs);
+@@ -117,6 +139,17 @@ static void mtk_pcs_lynxi_get_state(stru
+                                        FIELD_GET(SGMII_LPA, adv));
+ }
++static void mtk_sgmii_reset(struct mtk_pcs_lynxi *mpcs)
++{
++      if (!mpcs->rstc)
++              return;
++
++      reset_control_assert(mpcs->rstc);
++      udelay(100);
++      reset_control_deassert(mpcs->rstc);
++      mdelay(1);
++}
++
+ static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+                               phy_interface_t interface,
+                               const unsigned long *advertising,
+@@ -162,6 +195,7 @@ static int mtk_pcs_lynxi_config(struct p
+                               SGMII_PHYA_PWD);
+               /* Reset SGMII PCS state */
++              mtk_sgmii_reset(mpcs);
+               regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0,
+                               SGMII_SW_RESET);
+@@ -248,10 +282,29 @@ static void mtk_pcs_lynxi_link_up(struct
+       }
+ }
++static int mtk_pcs_lynxi_enable(struct phylink_pcs *pcs)
++{
++      struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
++
++      if (mpcs->sgmii_tx && mpcs->sgmii_rx) {
++              clk_prepare_enable(mpcs->sgmii_rx);
++              clk_prepare_enable(mpcs->sgmii_tx);
++      }
++
++      return 0;
++}
++
+ static void mtk_pcs_lynxi_disable(struct phylink_pcs *pcs)
+ {
+       struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
++      regmap_set_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, SGMII_PHYA_PWD);
++
++      if (mpcs->sgmii_tx && mpcs->sgmii_rx) {
++              clk_disable_unprepare(mpcs->sgmii_tx);
++              clk_disable_unprepare(mpcs->sgmii_rx);
++      }
++
+       mpcs->interface = PHY_INTERFACE_MODE_NA;
+ }
+@@ -262,11 +315,12 @@ static const struct phylink_pcs_ops mtk_
+       .pcs_an_restart = mtk_pcs_lynxi_restart_an,
+       .pcs_link_up = mtk_pcs_lynxi_link_up,
+       .pcs_disable = mtk_pcs_lynxi_disable,
++      .pcs_enable = mtk_pcs_lynxi_enable,
+ };
+-struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
+-                                       struct regmap *regmap, u32 ana_rgc3,
+-                                       u32 flags)
++static struct phylink_pcs *mtk_pcs_lynxi_init(struct device *dev, struct regmap *regmap,
++                                            u32 ana_rgc3, u32 flags,
++                                            struct mtk_pcs_lynxi *prealloc)
+ {
+       struct mtk_pcs_lynxi *mpcs;
+       u32 id, ver;
+@@ -274,29 +328,33 @@ struct phylink_pcs *mtk_pcs_lynxi_create
+       ret = regmap_read(regmap, SGMSYS_PCS_DEVICE_ID, &id);
+       if (ret < 0)
+-              return NULL;
++              return ERR_PTR(ret);
+       if (id != SGMII_LYNXI_DEV_ID) {
+               dev_err(dev, "unknown PCS device id %08x\n", id);
+-              return NULL;
++              return ERR_PTR(-ENODEV);
+       }
+       ret = regmap_read(regmap, SGMSYS_PCS_SCRATCH, &ver);
+       if (ret < 0)
+-              return NULL;
++              return ERR_PTR(ret);
+       ver = FIELD_GET(SGMII_DEV_VERSION, ver);
+       if (ver != 0x1) {
+               dev_err(dev, "unknown PCS device version %04x\n", ver);
+-              return NULL;
++              return ERR_PTR(-ENODEV);
+       }
+       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;
++      if (prealloc) {
++              mpcs = prealloc;
++      } else {
++              mpcs = kzalloc(sizeof(*mpcs), GFP_KERNEL);
++              if (!mpcs)
++                      return ERR_PTR(-ENOMEM);
++      };
+       mpcs->ana_rgc3 = ana_rgc3;
+       mpcs->regmap = regmap;
+@@ -307,6 +365,13 @@ struct phylink_pcs *mtk_pcs_lynxi_create
+       mpcs->interface = PHY_INTERFACE_MODE_NA;
+       return &mpcs->pcs;
++};
++
++struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
++                                       struct regmap *regmap, u32 ana_rgc3,
++                                       u32 flags)
++{
++      return mtk_pcs_lynxi_init(dev, regmap, ana_rgc3, flags, NULL);
+ }
+ EXPORT_SYMBOL(mtk_pcs_lynxi_create);
+@@ -319,5 +384,142 @@ void mtk_pcs_lynxi_destroy(struct phylin
+ }
+ EXPORT_SYMBOL(mtk_pcs_lynxi_destroy);
++static int mtk_pcs_lynxi_probe(struct platform_device *pdev)
++{
++      struct device *dev = &pdev->dev;
++      struct device_node *np = dev->of_node;
++      struct mtk_pcs_lynxi *mpcs;
++      struct phylink_pcs *pcs;
++      struct regmap *regmap;
++      u32 flags = 0;
++
++      mpcs = devm_kzalloc(dev, sizeof(*mpcs), GFP_KERNEL);
++      if (!mpcs)
++              return -ENOMEM;
++
++      mpcs->dev = dev;
++      regmap = syscon_node_to_regmap(np->parent);
++      if (IS_ERR(regmap))
++              return PTR_ERR(regmap);
++
++      if (of_property_read_bool(np->parent, "mediatek,pnswap"))
++              flags |= MTK_SGMII_FLAG_PN_SWAP;
++
++      mpcs->rstc = of_reset_control_get_shared(np->parent, NULL);
++      if (IS_ERR(mpcs->rstc))
++              return PTR_ERR(mpcs->rstc);
++
++      reset_control_deassert(mpcs->rstc);
++      mpcs->sgmii_sel = devm_clk_get_enabled(dev, "sgmii_sel");
++      if (IS_ERR(mpcs->sgmii_sel))
++              return PTR_ERR(mpcs->sgmii_sel);
++
++      mpcs->sgmii_rx = devm_clk_get(dev, "sgmii_rx");
++      if (IS_ERR(mpcs->sgmii_rx))
++              return PTR_ERR(mpcs->sgmii_rx);
++
++      mpcs->sgmii_tx = devm_clk_get(dev, "sgmii_tx");
++      if (IS_ERR(mpcs->sgmii_tx))
++              return PTR_ERR(mpcs->sgmii_tx);
++
++      pcs = mtk_pcs_lynxi_init(dev, regmap, (uintptr_t)of_device_get_match_data(dev),
++                               flags, mpcs);
++      if (IS_ERR(pcs))
++              return PTR_ERR(pcs);
++
++      regmap_set_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, SGMII_PHYA_PWD);
++
++      platform_set_drvdata(pdev, mpcs);
++
++      mutex_lock(&instance_mutex);
++      list_add_tail(&mpcs->node, &mtk_pcs_lynxi_instances);
++      mutex_unlock(&instance_mutex);
++
++      return 0;
++}
++
++static void mtk_pcs_lynxi_remove(struct platform_device *pdev)
++{
++      struct device *dev = &pdev->dev;
++      struct mtk_pcs_lynxi *cur, *tmp;
++
++      mutex_lock(&instance_mutex);
++      list_for_each_entry_safe(cur, tmp, &mtk_pcs_lynxi_instances, node)
++              if (cur->dev == dev) {
++                      list_del(&cur->node);
++                      kfree(cur);
++                      break;
++              }
++      mutex_unlock(&instance_mutex);
++}
++
++static const struct of_device_id mtk_pcs_lynxi_of_match[] = {
++      { .compatible = "mediatek,mt7988-sgmii", .data = (void *)MTK_NETSYS_V3_AMA_RGC3 },
++      { /* sentinel */ },
++};
++MODULE_DEVICE_TABLE(of, mtk_pcs_lynxi_of_match);
++
++struct phylink_pcs *mtk_pcs_lynxi_get(struct device *dev, struct device_node *np)
++{
++      struct platform_device *pdev;
++      struct mtk_pcs_lynxi *mpcs;
++
++      if (!np)
++              return NULL;
++
++      if (!of_device_is_available(np))
++              return ERR_PTR(-ENODEV);
++
++      if (!of_match_node(mtk_pcs_lynxi_of_match, np))
++              return ERR_PTR(-EINVAL);
++
++      pdev = of_find_device_by_node(np);
++      if (!pdev || !platform_get_drvdata(pdev)) {
++              if (pdev)
++                      put_device(&pdev->dev);
++              return ERR_PTR(-EPROBE_DEFER);
++      }
++
++      mpcs = platform_get_drvdata(pdev);
++      device_link_add(dev, mpcs->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
++
++      return &mpcs->pcs;
++}
++EXPORT_SYMBOL(mtk_pcs_lynxi_get);
++
++void mtk_pcs_lynxi_put(struct phylink_pcs *pcs)
++{
++      struct mtk_pcs_lynxi *cur, *mpcs = NULL;
++
++      if (!pcs)
++              return;
++
++      mutex_lock(&instance_mutex);
++      list_for_each_entry(cur, &mtk_pcs_lynxi_instances, node)
++              if (pcs == &cur->pcs) {
++                      mpcs = cur;
++                      break;
++              }
++      mutex_unlock(&instance_mutex);
++
++      if (WARN_ON(!mpcs))
++              return;
++
++      put_device(mpcs->dev);
++}
++EXPORT_SYMBOL(mtk_pcs_lynxi_put);
++
++static struct platform_driver mtk_pcs_lynxi_driver = {
++      .driver = {
++              .name                   = "mtk-pcs-lynxi",
++              .suppress_bind_attrs    = true,
++              .of_match_table         = mtk_pcs_lynxi_of_match,
++      },
++      .probe = mtk_pcs_lynxi_probe,
++      .remove_new = mtk_pcs_lynxi_remove,
++};
++module_platform_driver(mtk_pcs_lynxi_driver);
++
++MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
+ MODULE_DESCRIPTION("MediaTek SGMII library for LynxI");
+ MODULE_LICENSE("GPL");
+--- a/include/linux/pcs/pcs-mtk-lynxi.h
++++ b/include/linux/pcs/pcs-mtk-lynxi.h
+@@ -10,4 +10,15 @@ struct phylink_pcs *mtk_pcs_lynxi_create
+                                        struct regmap *regmap,
+                                        u32 ana_rgc3, u32 flags);
+ void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs);
++
++#if IS_ENABLED(CONFIG_PCS_MTK_LYNXI)
++struct phylink_pcs *mtk_pcs_lynxi_get(struct device *dev, struct device_node *np);
++void mtk_pcs_lynxi_put(struct phylink_pcs *pcs);
++#else
++static inline struct phylink_pcs *mtk_pcs_lynxi_get(struct device *dev, struct device_node *np)
++{
++      return NULL;
++}
++static inline void mtk_pcs_lynxi_put(struct phylink_pcs *pcs) { }
++#endif /* IS_ENABLED(CONFIG_PCS_MTK_LYNXI) */
+ #endif
diff --git a/target/linux/generic/pending-6.12/739-04-dt-bindings-net-pcs-add-bindings-for-MediaTek-USXGMI.patch b/target/linux/generic/pending-6.12/739-04-dt-bindings-net-pcs-add-bindings-for-MediaTek-USXGMI.patch
new file mode 100644 (file)
index 0000000..215bd2c
--- /dev/null
@@ -0,0 +1,81 @@
+From 7d88d79c0f65b27a92754d7547f7af098b3de67b Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 12 Dec 2023 03:47:31 +0000
+Subject: [PATCH 4/5] dt-bindings: net: pcs: add bindings for MediaTek USXGMII
+ PCS
+
+MediaTek's USXGMII can be found in the MT7988 SoC. We need to access
+it in order to configure and monitor the Ethernet SerDes link in
+USXGMII, 10GBase-R and 5GBase-R mode. By including a wrapped
+legacy 1000Base-X/2500Base-X/Cisco SGMII LynxI PCS as well, those
+interface modes are also available.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ .../bindings/net/pcs/mediatek,usxgmii.yaml    | 60 +++++++++++++++++++
+ 1 file changed, 60 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/net/pcs/mediatek,usxgmii.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/net/pcs/mediatek,usxgmii.yaml
+@@ -0,0 +1,60 @@
++# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/net/pcs/mediatek,usxgmii.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: MediaTek USXGMII PCS
++
++maintainers:
++  - Daniel Golle <daniel@makrotopia.org>
++
++description:
++  The MediaTek USXGMII PCS provides physical link control and status
++  for USXGMII, 10GBase-R and 5GBase-R links on the SerDes interfaces
++  provided by the PEXTP PHY.
++  In order to also support legacy 2500Base-X, 1000Base-X and Cisco
++  SGMII an existing mediatek,*-sgmiisys LynxI PCS is wrapped to
++  provide those interfaces modes on the same SerDes interfaces shared
++  with the USXGMII PCS.
++
++properties:
++  $nodename:
++    pattern: "^pcs@[0-9a-f]+$"
++
++  compatible:
++    const: mediatek,mt7988-usxgmiisys
++
++  reg:
++    maxItems: 1
++
++  clocks:
++    items:
++      - description: USXGMII top-level clock
++
++  resets:
++    items:
++      - description: XFI reset
++
++required:
++  - compatible
++  - reg
++  - clocks
++  - resets
++
++additionalProperties: false
++
++examples:
++  - |
++    #include <dt-bindings/clock/mediatek,mt7988-clk.h>
++    #define MT7988_TOPRGU_XFI0_GRST 12
++    soc {
++      #address-cells = <2>;
++      #size-cells = <2>;
++        usxgmiisys0: pcs@10080000 {
++          compatible = "mediatek,mt7988-usxgmiisys";
++          reg = <0 0x10080000 0 0x1000>;
++          clocks = <&topckgen CLK_TOP_USXGMII_SBUS_0_SEL>;
++          resets = <&watchdog MT7988_TOPRGU_XFI0_GRST>;
++        };
++    };
diff --git a/target/linux/generic/pending-6.12/739-05-net-pcs-add-driver-for-MediaTek-USXGMII-PCS.patch b/target/linux/generic/pending-6.12/739-05-net-pcs-add-driver-for-MediaTek-USXGMII-PCS.patch
new file mode 100644 (file)
index 0000000..04790ce
--- /dev/null
@@ -0,0 +1,545 @@
+From dde0e95fff92e9f5009f3bea75278e0e34a48822 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 12 Dec 2023 03:47:47 +0000
+Subject: [PATCH 5/5] net: pcs: add driver for MediaTek USXGMII PCS
+
+Add driver for USXGMII PCS found in the MediaTek MT7988 SoC and supporting
+USXGMII, 10GBase-R and 5GBase-R interface modes.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ MAINTAINERS                         |   2 +
+ drivers/net/pcs/Kconfig             |  11 +
+ drivers/net/pcs/Makefile            |   1 +
+ drivers/net/pcs/pcs-mtk-usxgmii.c   | 456 ++++++++++++++++++++++++++++
+ include/linux/pcs/pcs-mtk-usxgmii.h |  27 ++
+ 5 files changed, 497 insertions(+)
+ create mode 100644 drivers/net/pcs/pcs-mtk-usxgmii.c
+ create mode 100644 include/linux/pcs/pcs-mtk-usxgmii.h
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -14432,7 +14432,9 @@ M:     Daniel Golle <daniel@makrotopia.org>
+ L:    netdev@vger.kernel.org
+ S:    Maintained
+ F:    drivers/net/pcs/pcs-mtk-lynxi.c
++F:    drivers/net/pcs/pcs-mtk-usxgmii.c
+ F:    include/linux/pcs/pcs-mtk-lynxi.h
++F:    include/linux/pcs/pcs-mtk-usxgmii.h
+ MEDIATEK ETHERNET PHY DRIVERS
+ M:    Daniel Golle <daniel@makrotopia.org>
+--- a/drivers/net/pcs/Kconfig
++++ b/drivers/net/pcs/Kconfig
+@@ -25,6 +25,17 @@ config PCS_MTK_LYNXI
+         This module provides helpers to phylink for managing the LynxI PCS
+         which is part of MediaTek's SoC and Ethernet switch ICs.
++config PCS_MTK_USXGMII
++      tristate "MediaTek USXGMII PCS"
++      select PCS_MTK_LYNXI
++      select PHY_MTK_PEXTP
++      select PHYLINK
++      help
++        This module provides a driver for MediaTek's USXGMII PCS supporting
++        10GBase-R, 5GBase-R and USXGMII interface modes.
++        1000Base-X, 2500Base-X and Cisco SGMII are supported on the same
++        differential pairs via an embedded LynxI PHY.
++
+ config PCS_RZN1_MIIC
+       tristate "Renesas RZ/N1 MII converter"
+       depends on OF && (ARCH_RZN1 || COMPILE_TEST)
+--- a/drivers/net/pcs/Makefile
++++ b/drivers/net/pcs/Makefile
+@@ -8,3 +8,4 @@ obj-$(CONFIG_PCS_XPCS)         += pcs_xpcs.o
+ obj-$(CONFIG_PCS_LYNX)                += pcs-lynx.o
+ obj-$(CONFIG_PCS_MTK_LYNXI)   += pcs-mtk-lynxi.o
+ obj-$(CONFIG_PCS_RZN1_MIIC)   += pcs-rzn1-miic.o
++obj-$(CONFIG_PCS_MTK_USXGMII) += pcs-mtk-usxgmii.o
+--- /dev/null
++++ b/drivers/net/pcs/pcs-mtk-usxgmii.c
+@@ -0,0 +1,454 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (c) 2023 MediaTek Inc.
++ * Author: Henry Yen <henry.yen@mediatek.com>
++ *         Daniel Golle <daniel@makrotopia.org>
++ */
++
++#include <linux/clk.h>
++#include <linux/io.h>
++#include <linux/mfd/syscon.h>
++#include <linux/mdio.h>
++#include <linux/mutex.h>
++#include <linux/of.h>
++#include <linux/of_platform.h>
++#include <linux/reset.h>
++#include <linux/pcs/pcs-mtk-usxgmii.h>
++#include <linux/platform_device.h>
++
++/* USXGMII subsystem config registers */
++/* Register to control speed */
++#define RG_PHY_TOP_SPEED_CTRL1                        0x80c
++#define USXGMII_RATE_UPDATE_MODE              BIT(31)
++#define USXGMII_MAC_CK_GATED                  BIT(29)
++#define USXGMII_IF_FORCE_EN                   BIT(28)
++#define USXGMII_RATE_ADAPT_MODE                       GENMASK(10, 8)
++#define USXGMII_RATE_ADAPT_MODE_X1            0
++#define USXGMII_RATE_ADAPT_MODE_X2            1
++#define USXGMII_RATE_ADAPT_MODE_X4            2
++#define USXGMII_RATE_ADAPT_MODE_X10           3
++#define USXGMII_RATE_ADAPT_MODE_X100          4
++#define USXGMII_RATE_ADAPT_MODE_X5            5
++#define USXGMII_RATE_ADAPT_MODE_X50           6
++#define USXGMII_XFI_RX_MODE                   GENMASK(6, 4)
++#define USXGMII_XFI_TX_MODE                   GENMASK(2, 0)
++#define USXGMII_XFI_MODE_10G                  0
++#define USXGMII_XFI_MODE_5G                   1
++#define USXGMII_XFI_MODE_2P5G                 3
++
++/* Register to control PCS AN */
++#define RG_PCS_AN_CTRL0                               0x810
++#define USXGMII_AN_RESTART                    BIT(31)
++#define USXGMII_AN_SYNC_CNT                   GENMASK(30, 11)
++#define USXGMII_AN_ENABLE                     BIT(0)
++
++#define RG_PCS_AN_CTRL2                               0x818
++#define USXGMII_LINK_TIMER_IDLE_DETECT                GENMASK(29, 20)
++#define USXGMII_LINK_TIMER_COMP_ACK_DETECT    GENMASK(19, 10)
++#define USXGMII_LINK_TIMER_AN_RESTART         GENMASK(9, 0)
++
++/* Register to read PCS AN status */
++#define RG_PCS_AN_STS0                                0x81c
++#define USXGMII_LPA                           GENMASK(15, 0)
++#define USXGMII_LPA_LATCH                     BIT(31)
++
++/* Register to read PCS link status */
++#define RG_PCS_RX_STATUS0                     0x904
++#define RG_PCS_RX_STATUS_UPDATE                       BIT(16)
++#define RG_PCS_RX_LINK_STATUS                 BIT(2)
++
++/* struct mtk_usxgmii_pcs - This structure holds each usxgmii PCS
++ * @pcs:              Phylink PCS structure
++ * @dev:              Pointer to device structure
++ * @base:             IO memory to access PCS hardware
++ * @clk:              Pointer to USXGMII clk
++ * @reset:            Pointer to USXGMII reset control
++ * @interface:                Currently selected interface mode
++ * @neg_mode:         Currently used phylink neg_mode
++ * @node:             List node
++ */
++struct mtk_usxgmii_pcs {
++      struct phylink_pcs              pcs;
++      struct device                   *dev;
++      void __iomem                    *base;
++      struct clk                      *clk;
++      struct reset_control            *reset;
++      phy_interface_t                 interface;
++      unsigned int                    neg_mode;
++      struct list_head                node;
++};
++
++static LIST_HEAD(mtk_usxgmii_pcs_instances);
++static DEFINE_MUTEX(instance_mutex);
++
++static u32 mtk_r32(struct mtk_usxgmii_pcs *mpcs, unsigned int reg)
++{
++      return ioread32(mpcs->base + reg);
++}
++
++static void mtk_m32(struct mtk_usxgmii_pcs *mpcs, unsigned int reg, u32 mask, u32 set)
++{
++      u32 val;
++
++      val = ioread32(mpcs->base + reg);
++      val &= ~mask;
++      val |= set;
++      iowrite32(val, mpcs->base + reg);
++}
++
++static struct mtk_usxgmii_pcs *pcs_to_mtk_usxgmii_pcs(struct phylink_pcs *pcs)
++{
++      return container_of(pcs, struct mtk_usxgmii_pcs, pcs);
++}
++
++static void mtk_usxgmii_reset(struct mtk_usxgmii_pcs *mpcs)
++{
++      reset_control_assert(mpcs->reset);
++      udelay(100);
++      reset_control_deassert(mpcs->reset);
++
++      mdelay(10);
++}
++
++static int mtk_usxgmii_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
++                                phy_interface_t interface,
++                                const unsigned long *advertising,
++                                bool permit_pause_to_mac)
++{
++      struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
++      unsigned int an_ctrl = 0, link_timer = 0, xfi_mode = 0, adapt_mode = 0;
++      bool mode_changed = false;
++
++      if (interface == PHY_INTERFACE_MODE_USXGMII) {
++              an_ctrl = FIELD_PREP(USXGMII_AN_SYNC_CNT, 0x1FF) | USXGMII_AN_ENABLE;
++              link_timer = FIELD_PREP(USXGMII_LINK_TIMER_IDLE_DETECT, 0x7B) |
++                           FIELD_PREP(USXGMII_LINK_TIMER_COMP_ACK_DETECT, 0x7B) |
++                           FIELD_PREP(USXGMII_LINK_TIMER_AN_RESTART, 0x7B);
++              xfi_mode = FIELD_PREP(USXGMII_XFI_RX_MODE, USXGMII_XFI_MODE_10G) |
++                         FIELD_PREP(USXGMII_XFI_TX_MODE, USXGMII_XFI_MODE_10G);
++      } else if (interface == PHY_INTERFACE_MODE_10GBASER) {
++              an_ctrl = FIELD_PREP(USXGMII_AN_SYNC_CNT, 0x1FF);
++              link_timer = FIELD_PREP(USXGMII_LINK_TIMER_IDLE_DETECT, 0x7B) |
++                           FIELD_PREP(USXGMII_LINK_TIMER_COMP_ACK_DETECT, 0x7B) |
++                           FIELD_PREP(USXGMII_LINK_TIMER_AN_RESTART, 0x7B);
++              xfi_mode = FIELD_PREP(USXGMII_XFI_RX_MODE, USXGMII_XFI_MODE_10G) |
++                         FIELD_PREP(USXGMII_XFI_TX_MODE, USXGMII_XFI_MODE_10G);
++              adapt_mode = USXGMII_RATE_UPDATE_MODE;
++      } else if (interface == PHY_INTERFACE_MODE_5GBASER) {
++              an_ctrl = FIELD_PREP(USXGMII_AN_SYNC_CNT, 0xFF);
++              link_timer = FIELD_PREP(USXGMII_LINK_TIMER_IDLE_DETECT, 0x3D) |
++                           FIELD_PREP(USXGMII_LINK_TIMER_COMP_ACK_DETECT, 0x3D) |
++                           FIELD_PREP(USXGMII_LINK_TIMER_AN_RESTART, 0x3D);
++              xfi_mode = FIELD_PREP(USXGMII_XFI_RX_MODE, USXGMII_XFI_MODE_5G) |
++                         FIELD_PREP(USXGMII_XFI_TX_MODE, USXGMII_XFI_MODE_5G);
++              adapt_mode = USXGMII_RATE_UPDATE_MODE;
++      } else {
++              return -EINVAL;
++      }
++
++      adapt_mode |= FIELD_PREP(USXGMII_RATE_ADAPT_MODE, USXGMII_RATE_ADAPT_MODE_X1);
++
++      if (mpcs->interface != interface) {
++              mpcs->interface = interface;
++              mode_changed = true;
++      }
++
++      mtk_usxgmii_reset(mpcs);
++
++      /* Setup USXGMII AN ctrl */
++      mtk_m32(mpcs, RG_PCS_AN_CTRL0,
++              USXGMII_AN_SYNC_CNT | USXGMII_AN_ENABLE,
++              an_ctrl);
++
++      mtk_m32(mpcs, RG_PCS_AN_CTRL2,
++              USXGMII_LINK_TIMER_IDLE_DETECT |
++              USXGMII_LINK_TIMER_COMP_ACK_DETECT |
++              USXGMII_LINK_TIMER_AN_RESTART,
++              link_timer);
++
++      mpcs->neg_mode = neg_mode;
++
++      /* Gated MAC CK */
++      mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1,
++              USXGMII_MAC_CK_GATED, USXGMII_MAC_CK_GATED);
++
++      /* Enable interface force mode */
++      mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1,
++              USXGMII_IF_FORCE_EN, USXGMII_IF_FORCE_EN);
++
++      /* Setup USXGMII adapt mode */
++      mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1,
++              USXGMII_RATE_UPDATE_MODE | USXGMII_RATE_ADAPT_MODE,
++              adapt_mode);
++
++      /* Setup USXGMII speed */
++      mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1,
++              USXGMII_XFI_RX_MODE | USXGMII_XFI_TX_MODE,
++              xfi_mode);
++
++      usleep_range(1, 10);
++
++      /* Un-gated MAC CK */
++      mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1, USXGMII_MAC_CK_GATED, 0);
++
++      usleep_range(1, 10);
++
++      /* Disable interface force mode for the AN mode */
++      if (an_ctrl & USXGMII_AN_ENABLE)
++              mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1, USXGMII_IF_FORCE_EN, 0);
++
++      return mode_changed;
++}
++
++static void mtk_usxgmii_pcs_get_fixed_speed(struct mtk_usxgmii_pcs *mpcs,
++                                          struct phylink_link_state *state)
++{
++      u32 val = mtk_r32(mpcs, RG_PHY_TOP_SPEED_CTRL1);
++      int speed;
++
++      /* Calculate speed from interface speed and rate adapt mode */
++      switch (FIELD_GET(USXGMII_XFI_RX_MODE, val)) {
++      case USXGMII_XFI_MODE_10G:
++              speed = 10000;
++              break;
++      case USXGMII_XFI_MODE_5G:
++              speed = 5000;
++              break;
++      case USXGMII_XFI_MODE_2P5G:
++              speed = 2500;
++              break;
++      default:
++              state->speed = SPEED_UNKNOWN;
++              return;
++      }
++
++      switch (FIELD_GET(USXGMII_RATE_ADAPT_MODE, val)) {
++      case USXGMII_RATE_ADAPT_MODE_X100:
++              speed /= 100;
++              break;
++      case USXGMII_RATE_ADAPT_MODE_X50:
++              speed /= 50;
++              break;
++      case USXGMII_RATE_ADAPT_MODE_X10:
++              speed /= 10;
++              break;
++      case USXGMII_RATE_ADAPT_MODE_X5:
++              speed /= 5;
++              break;
++      case USXGMII_RATE_ADAPT_MODE_X4:
++              speed /= 4;
++              break;
++      case USXGMII_RATE_ADAPT_MODE_X2:
++              speed /= 2;
++              break;
++      case USXGMII_RATE_ADAPT_MODE_X1:
++              break;
++      default:
++              state->speed = SPEED_UNKNOWN;
++              return;
++      }
++
++      state->speed = speed;
++      state->duplex = DUPLEX_FULL;
++}
++
++static void mtk_usxgmii_pcs_get_an_state(struct mtk_usxgmii_pcs *mpcs,
++                                       struct phylink_link_state *state)
++{
++      u16 lpa;
++
++      /* Refresh LPA by toggling LPA_LATCH */
++      mtk_m32(mpcs, RG_PCS_AN_STS0, USXGMII_LPA_LATCH, USXGMII_LPA_LATCH);
++      ndelay(1020);
++      mtk_m32(mpcs, RG_PCS_AN_STS0, USXGMII_LPA_LATCH, 0);
++      ndelay(1020);
++      lpa = FIELD_GET(USXGMII_LPA, mtk_r32(mpcs, RG_PCS_AN_STS0));
++
++      phylink_decode_usxgmii_word(state, lpa);
++}
++
++static void mtk_usxgmii_pcs_get_state(struct phylink_pcs *pcs,
++                                    struct phylink_link_state *state)
++{
++      struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
++
++      /* Refresh USXGMII link status by toggling RG_PCS_AN_STATUS_UPDATE */
++      mtk_m32(mpcs, RG_PCS_RX_STATUS0, RG_PCS_RX_STATUS_UPDATE,
++              RG_PCS_RX_STATUS_UPDATE);
++      ndelay(1020);
++      mtk_m32(mpcs, RG_PCS_RX_STATUS0, RG_PCS_RX_STATUS_UPDATE, 0);
++      ndelay(1020);
++
++      /* Read USXGMII link status */
++      state->link = FIELD_GET(RG_PCS_RX_LINK_STATUS,
++                              mtk_r32(mpcs, RG_PCS_RX_STATUS0));
++
++      /* Continuously repeat re-configuration sequence until link comes up */
++      if (!state->link) {
++              mtk_usxgmii_pcs_config(pcs, mpcs->neg_mode,
++                                     state->interface, NULL, false);
++              return;
++      }
++
++      if (FIELD_GET(USXGMII_AN_ENABLE, mtk_r32(mpcs, RG_PCS_AN_CTRL0)))
++              mtk_usxgmii_pcs_get_an_state(mpcs, state);
++      else
++              mtk_usxgmii_pcs_get_fixed_speed(mpcs, state);
++}
++
++static void mtk_usxgmii_pcs_restart_an(struct phylink_pcs *pcs)
++{
++      struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
++
++      mtk_m32(mpcs, RG_PCS_AN_CTRL0, USXGMII_AN_RESTART, USXGMII_AN_RESTART);
++}
++
++static void mtk_usxgmii_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
++                                  phy_interface_t interface,
++                                  int speed, int duplex)
++{
++      /* Reconfiguring USXGMII to ensure the quality of the RX signal
++       * after the line side link up.
++       */
++      mtk_usxgmii_pcs_config(pcs, neg_mode, interface, NULL, false);
++}
++
++static void mtk_usxgmii_pcs_disable(struct phylink_pcs *pcs)
++{
++      struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
++
++      mpcs->interface = PHY_INTERFACE_MODE_NA;
++      mpcs->neg_mode = -1;
++}
++
++static const struct phylink_pcs_ops mtk_usxgmii_pcs_ops = {
++      .pcs_config = mtk_usxgmii_pcs_config,
++      .pcs_get_state = mtk_usxgmii_pcs_get_state,
++      .pcs_an_restart = mtk_usxgmii_pcs_restart_an,
++      .pcs_link_up = mtk_usxgmii_pcs_link_up,
++      .pcs_disable = mtk_usxgmii_pcs_disable,
++};
++
++static int mtk_usxgmii_probe(struct platform_device *pdev)
++{
++      struct device *dev = &pdev->dev;
++      struct mtk_usxgmii_pcs *mpcs;
++
++      mpcs = devm_kzalloc(dev, sizeof(*mpcs), GFP_KERNEL);
++      if (!mpcs)
++              return -ENOMEM;
++
++      mpcs->base = devm_platform_ioremap_resource(pdev, 0);
++      if (IS_ERR(mpcs->base))
++              return PTR_ERR(mpcs->base);
++
++      mpcs->dev = dev;
++      mpcs->pcs.ops = &mtk_usxgmii_pcs_ops;
++      mpcs->pcs.poll = true;
++      mpcs->pcs.neg_mode = true;
++      mpcs->interface = PHY_INTERFACE_MODE_NA;
++      mpcs->neg_mode = -1;
++
++      mpcs->clk = devm_clk_get_enabled(mpcs->dev, NULL);
++      if (IS_ERR(mpcs->clk))
++              return PTR_ERR(mpcs->clk);
++
++      mpcs->reset = devm_reset_control_get_shared(dev, NULL);
++      if (IS_ERR(mpcs->reset))
++              return PTR_ERR(mpcs->reset);
++
++      reset_control_deassert(mpcs->reset);
++
++      platform_set_drvdata(pdev, mpcs);
++
++      mutex_lock(&instance_mutex);
++      list_add_tail(&mpcs->node, &mtk_usxgmii_pcs_instances);
++      mutex_unlock(&instance_mutex);
++
++      return 0;
++}
++
++static void mtk_usxgmii_remove(struct platform_device *pdev)
++{
++      struct device *dev = &pdev->dev;
++      struct mtk_usxgmii_pcs *cur, *tmp;
++
++      mutex_lock(&instance_mutex);
++      list_for_each_entry_safe(cur, tmp, &mtk_usxgmii_pcs_instances, node)
++              if (cur->dev == dev) {
++                      list_del(&cur->node);
++                      break;
++              }
++      mutex_unlock(&instance_mutex);
++}
++
++static const struct of_device_id mtk_usxgmii_of_mtable[] = {
++      { .compatible = "mediatek,mt7988-usxgmiisys" },
++      { /* sentinel */ },
++};
++MODULE_DEVICE_TABLE(of, mtk_usxgmii_of_mtable);
++
++struct phylink_pcs *mtk_usxgmii_pcs_get(struct device *dev, struct device_node *np)
++{
++      struct platform_device *pdev;
++      struct mtk_usxgmii_pcs *mpcs;
++
++      if (!np)
++              return NULL;
++
++      if (!of_device_is_available(np))
++              return ERR_PTR(-ENODEV);
++
++      if (!of_match_node(mtk_usxgmii_of_mtable, np))
++              return ERR_PTR(-EINVAL);
++
++      pdev = of_find_device_by_node(np);
++      if (!pdev || !platform_get_drvdata(pdev)) {
++              if (pdev)
++                      put_device(&pdev->dev);
++              return ERR_PTR(-EPROBE_DEFER);
++      }
++
++      mpcs = platform_get_drvdata(pdev);
++      device_link_add(dev, mpcs->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
++
++      return &mpcs->pcs;
++}
++EXPORT_SYMBOL(mtk_usxgmii_pcs_get);
++
++void mtk_usxgmii_pcs_put(struct phylink_pcs *pcs)
++{
++      struct mtk_usxgmii_pcs *cur, *mpcs = NULL;
++
++      if (!pcs)
++              return;
++
++      mutex_lock(&instance_mutex);
++      list_for_each_entry(cur, &mtk_usxgmii_pcs_instances, node)
++              if (pcs == &cur->pcs) {
++                      mpcs = cur;
++                      break;
++              }
++      mutex_unlock(&instance_mutex);
++
++      if (WARN_ON(!mpcs))
++              return;
++
++      put_device(mpcs->dev);
++}
++EXPORT_SYMBOL(mtk_usxgmii_pcs_put);
++
++static struct platform_driver mtk_usxgmii_driver = {
++      .driver = {
++              .name                   = "mtk_usxgmii",
++              .suppress_bind_attrs    = true,
++              .of_match_table         = mtk_usxgmii_of_mtable,
++      },
++      .probe = mtk_usxgmii_probe,
++      .remove_new = mtk_usxgmii_remove,
++};
++module_platform_driver(mtk_usxgmii_driver);
++
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("MediaTek USXGMII PCS driver");
++MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
+--- /dev/null
++++ b/include/linux/pcs/pcs-mtk-usxgmii.h
+@@ -0,0 +1,27 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++#ifndef __LINUX_PCS_MTK_USXGMII_H
++#define __LINUX_PCS_MTK_USXGMII_H
++
++#include <linux/phylink.h>
++
++/**
++ * mtk_usxgmii_select_pcs() - Get MediaTek PCS instance
++ * @np:               Pointer to device node indentifying a MediaTek USXGMII PCS
++ * @mode:     Ethernet PHY interface mode
++ *
++ * Return PCS identified by a device node and the PHY interface mode in use
++ *
++ * Return:    Pointer to phylink PCS instance of NULL
++ */
++#if IS_ENABLED(CONFIG_PCS_MTK_USXGMII)
++struct phylink_pcs *mtk_usxgmii_pcs_get(struct device *dev, struct device_node *np);
++void mtk_usxgmii_pcs_put(struct phylink_pcs *pcs);
++#else
++static inline struct phylink_pcs *mtk_usxgmii_pcs_get(struct device *dev, struct device_node *np)
++{
++      return NULL;
++}
++static inline void mtk_usxgmii_pcs_put(struct phylink_pcs *pcs) { }
++#endif /* IS_ENABLED(CONFIG_PCS_MTK_USXGMII) */
++
++#endif /* __LINUX_PCS_MTK_USXGMII_H */
diff --git a/target/linux/generic/pending-6.12/740-net-phy-motorcomm-Add-missing-include.patch b/target/linux/generic/pending-6.12/740-net-phy-motorcomm-Add-missing-include.patch
new file mode 100644 (file)
index 0000000..2a1f908
--- /dev/null
@@ -0,0 +1,22 @@
+From 6f291aa7da199c6486cc229b055dcbcd5cee7a21 Mon Sep 17 00:00:00 2001
+From: Hauke Mehrtens <hauke@hauke-m.de>
+Date: Sun, 21 May 2023 22:24:56 +0200
+Subject: [PATCH] net: phy: motorcomm: Add missing include
+
+Directly include linux/bitfield.h which provides FIELD_PREP.
+
+Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
+---
+ drivers/net/phy/motorcomm.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/phy/motorcomm.c
++++ b/drivers/net/phy/motorcomm.c
+@@ -6,6 +6,7 @@
+  * Author: Frank <Frank.Sae@motor-comm.com>
+  */
++#include <linux/bitfield.h>
+ #include <linux/etherdevice.h>
+ #include <linux/kernel.h>
+ #include <linux/module.h>
diff --git a/target/linux/generic/pending-6.12/741-net-phy-broadcom-update-dependency-condition.patch b/target/linux/generic/pending-6.12/741-net-phy-broadcom-update-dependency-condition.patch
new file mode 100644 (file)
index 0000000..7a52bd5
--- /dev/null
@@ -0,0 +1,35 @@
+From 1be3688b3eaa7ea2d9e19bd29ae6a6a51c121a0b Mon Sep 17 00:00:00 2001
+From: David Bauer <mail@david-bauer.net>
+Date: Sat, 16 Nov 2024 22:36:15 +0100
+Subject: [PATCH] net: phy: broadcom: update dependency condition
+
+The broadcom PHY driver only has to depend upon PTP_1588_CLOCK_OPTIONAL
+if NETWORK_PHY_TIMESTAMPING is enabled. The PTP functionality is stubbed
+in this case.
+
+Reflect this circumstance in the dependence condition. This allows to
+build the driver as a built-in module even if PTP is built as a module.
+
+This is required to include the broadcom PHY module regardless of the
+built-setting of the PTP subsystem. On ath79 (and probably more)
+targets with Broadcom PHY, Gigabit operation is currently broken as the
+PHY driver is only built as a module in case all kernel-packages are
+built. Due to this circumstance, affected devices fall back to using the
+generic PHY driver.
+
+Signed-off-by: David Bauer <mail@david-bauer.net>
+---
+ drivers/net/phy/Kconfig | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -151,7 +151,7 @@ config BROADCOM_PHY
+       tristate "Broadcom 54XX PHYs"
+       select BCM_NET_PHYLIB
+       select BCM_NET_PHYPTP if NETWORK_PHY_TIMESTAMPING
+-      depends on PTP_1588_CLOCK_OPTIONAL
++      depends on NETWORK_PHY_TIMESTAMPING=n || PTP_1588_CLOCK_OPTIONAL
+       help
+         Currently supports the BCM5411, BCM5421, BCM5461, BCM54616S, BCM5464,
+         BCM5481, BCM54810 and BCM5482 PHYs.
diff --git a/target/linux/generic/pending-6.12/750-net-sfp-add-quirk-for-QINIYEK-BJ-SFP-10G-T-copper-SF.patch b/target/linux/generic/pending-6.12/750-net-sfp-add-quirk-for-QINIYEK-BJ-SFP-10G-T-copper-SF.patch
new file mode 100644 (file)
index 0000000..fc2d26c
--- /dev/null
@@ -0,0 +1,26 @@
+From 51a16863b04b1c2b28d45d80934f2267547734b7 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Fri, 9 May 2025 16:33:27 +0100
+Subject: [PATCH] net: sfp: add quirk for QINIYEK BJ-SFP-10G-T copper SFP+
+ module
+
+Add quirk for a copper SFP+ module that identifies itself as "QINIYEK"
+"BJ-SFP-10G-T".
+
+It uses RollBall protocol to talk to the built-in RealTek RTL8261N PHY.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/phy/sfp.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -557,6 +557,7 @@ static const struct sfp_quirk sfp_quirks
+       SFP_QUIRK_S("OEM", "SFP-2.5G-BX10-U", sfp_quirk_2500basex),
+       SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc),
+       SFP_QUIRK_F("OEM", "RTSFP-10G", sfp_fixup_rollball_cc),
++      SFP_QUIRK_F("QINIYEK", "BJ-SFP-10G-T", sfp_fixup_fs_10gt),
+       SFP_QUIRK_F("Turris", "RTSFP-2.5G", sfp_fixup_rollball),
+       SFP_QUIRK_F("Turris", "RTSFP-10", sfp_fixup_rollball),
+       SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball),
diff --git a/target/linux/generic/pending-6.12/760-01-net-dsa-move-dsa_bridge_ports-helper-to-dsa.h.patch b/target/linux/generic/pending-6.12/760-01-net-dsa-move-dsa_bridge_ports-helper-to-dsa.h.patch
new file mode 100644 (file)
index 0000000..c0e77d1
--- /dev/null
@@ -0,0 +1,38 @@
+From de6dd19a3edd1dc6400fecf77610e438441a02ac Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 25 Mar 2026 17:54:11 +0000
+Subject: [PATCH 10/35] net: dsa: move dsa_bridge_ports() helper to dsa.h
+
+The yt921x driver contains a helper to create a bitmap of ports
+which are members of a bridge.
+
+Move the helper as static inline function into dsa.h, so other driver
+can make use of it as well.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ include/net/dsa.h | 13 +++++++++++++
+ 1 file changed, 13 insertions(+)
+
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -832,6 +832,19 @@ dsa_tree_offloads_bridge_dev(struct dsa_
+       return false;
+ }
++static inline u32
++dsa_bridge_ports(struct dsa_switch *ds, const struct net_device *bdev)
++{
++      struct dsa_port *dp;
++      u32 mask = 0;
++
++      dsa_switch_for_each_user_port(dp, ds)
++              if (dsa_port_offloads_bridge_dev(dp, bdev))
++                      mask |= BIT(dp->index);
++
++      return mask;
++}
++
+ static inline bool dsa_port_tree_same(const struct dsa_port *a,
+                                     const struct dsa_port *b)
+ {
diff --git a/target/linux/generic/pending-6.12/760-02-net-dsa-add-bridge-member-iteration-macro.patch b/target/linux/generic/pending-6.12/760-02-net-dsa-add-bridge-member-iteration-macro.patch
new file mode 100644 (file)
index 0000000..37891ff
--- /dev/null
@@ -0,0 +1,45 @@
+From 880cde7abf58cb1316382ae7f59aac93c313e8fe Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 25 Mar 2026 17:54:41 +0000
+Subject: [PATCH 11/35] net: dsa: add bridge member iteration macro
+
+Drivers that offload bridges need to iterate over the ports that are
+members of a given bridge, for example to rebuild per-port forwarding
+bitmaps when membership changes. Currently drivers typically open-code
+this by combining dsa_switch_for_each_user_port() with a
+dsa_port_offloads_bridge_dev() check, or cache bridge membership
+within the driver.
+
+Add dsa_switch_for_each_bridge_member() macro to express this pattern
+directly, and use it for the existing dsa_bridge_ports() inline
+helper.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ include/net/dsa.h | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -832,15 +832,18 @@ dsa_tree_offloads_bridge_dev(struct dsa_
+       return false;
+ }
++#define dsa_switch_for_each_bridge_member(_dp, _ds, _bdev) \
++      dsa_switch_for_each_user_port(_dp, _ds) \
++              if (dsa_port_offloads_bridge_dev(_dp, _bdev))
++
+ static inline u32
+ dsa_bridge_ports(struct dsa_switch *ds, const struct net_device *bdev)
+ {
+       struct dsa_port *dp;
+       u32 mask = 0;
+-      dsa_switch_for_each_user_port(dp, ds)
+-              if (dsa_port_offloads_bridge_dev(dp, bdev))
+-                      mask |= BIT(dp->index);
++      dsa_switch_for_each_bridge_member(dp, ds, bdev)
++              mask |= BIT(dp->index);
+       return mask;
+ }
diff --git a/target/linux/generic/pending-6.12/760-03-dsa-tag_mxl862xx-set-dsa_default_offload_fwd_mark.patch b/target/linux/generic/pending-6.12/760-03-dsa-tag_mxl862xx-set-dsa_default_offload_fwd_mark.patch
new file mode 100644 (file)
index 0000000..7c82499
--- /dev/null
@@ -0,0 +1,29 @@
+From 149bb02d5bf031a1eb85f91377f54913de3a08ff Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 25 Mar 2026 17:54:52 +0000
+Subject: [PATCH 12/35] dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark()
+
+The MxL862xx offloads bridge forwarding in hardware, so set
+dsa_default_offload_fwd_mark() to avoid duplicate forwarding of
+packets of (eg. flooded) frames arriving at the CPU port.
+
+Link-local frames are directly trapped to the CPU port only, so don't
+set dsa_default_offload_fwd_mark() on those.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ net/dsa/tag_mxl862xx.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/net/dsa/tag_mxl862xx.c
++++ b/net/dsa/tag_mxl862xx.c
+@@ -86,6 +86,9 @@ static struct sk_buff *mxl862_tag_rcv(st
+               return NULL;
+       }
++      if (likely(!is_link_local_ether_addr(eth_hdr(skb)->h_dest)))
++              dsa_default_offload_fwd_mark(skb);
++
+       /* remove the MxL862xx special tag between the MAC addresses and the
+        * current ethertype field.
+        */
diff --git a/target/linux/generic/pending-6.12/760-04-net-dsa-mxl862xx-implement-bridge-offloading.patch b/target/linux/generic/pending-6.12/760-04-net-dsa-mxl862xx-implement-bridge-offloading.patch
new file mode 100644 (file)
index 0000000..9d1c1c9
--- /dev/null
@@ -0,0 +1,1382 @@
+From 5acdee6df2fbd4a9b02045694227f25cb1d4e5e0 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 25 Mar 2026 17:55:08 +0000
+Subject: [PATCH 13/35] net: dsa: mxl862xx: implement bridge offloading
+
+Implement joining and leaving bridges as well as add, delete and dump
+operations on isolated FDBs, port MDB membership management, and
+setting a port's STP state.
+
+The switch supports a maximum of 63 bridges, however, up to 12 may
+be used as "single-port bridges" to isolate standalone ports.
+Allowing up to 48 bridges to be offloaded seems more than enough on
+that hardware, hence that is set as max_num_bridges.
+
+A total of 128 bridge ports are supported in the bridge portmap, and
+virtual bridge ports have to be used eg. for link-aggregation, hence
+potentially exceeding the number of hardware ports.
+
+The firmware-assigned bridge identifier (FID) for each offloaded bridge
+is stored in an array used to map DSA bridge num to firmware bridge ID,
+avoiding the need for a driver-private bridge tracking structure.
+Bridge member portmaps are rebuilt on join/leave using
+dsa_switch_for_each_bridge_member().
+
+As there are now more users of the BRIDGEPORT_CONFIG_SET API and the
+state of each port is cached locally, introduce a helper function
+mxl862xx_set_bridge_port(struct dsa_switch *ds, int port) which is
+then used to replace the direct calls to the API in
+mxl862xx_setup_cpu_bridge() and mxl862xx_add_single_port_bridge().
+
+Note that there is no convenient way to control flooding on per-port
+level, so the driver is using a 0-rate QoS meter setup as a stopper in
+lack of any better option. In order to be perfect the firmware-enforced
+minimum bucket size is bypassed by directly writing 0s to the relevant
+registers -- without that at least one 64-byte packet could still
+pass before the meter would change from 'yellow' into 'red' state.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 ++++++-
+ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h |  20 +-
+ drivers/net/dsa/mxl862xx/mxl862xx.c     | 752 ++++++++++++++++++++++--
+ drivers/net/dsa/mxl862xx/mxl862xx.h     | 133 +++++
+ 4 files changed, 1087 insertions(+), 43 deletions(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+@@ -3,6 +3,7 @@
+ #ifndef __MXL862XX_API_H
+ #define __MXL862XX_API_H
++#include <linux/bits.h>
+ #include <linux/if_ether.h>
+ /**
+@@ -35,6 +36,168 @@ struct mxl862xx_register_mod {
+ } __packed;
+ /**
++ * enum mxl862xx_mac_table_filter - Source/Destination MAC address filtering
++ *
++ * @MXL862XX_MAC_FILTER_NONE: no filter
++ * @MXL862XX_MAC_FILTER_SRC: source address filter
++ * @MXL862XX_MAC_FILTER_DEST: destination address filter
++ * @MXL862XX_MAC_FILTER_BOTH: both source and destination filter
++ */
++enum mxl862xx_mac_table_filter {
++      MXL862XX_MAC_FILTER_NONE = 0,
++      MXL862XX_MAC_FILTER_SRC = BIT(0),
++      MXL862XX_MAC_FILTER_DEST = BIT(1),
++      MXL862XX_MAC_FILTER_BOTH = BIT(0) | BIT(1),
++};
++
++#define MXL862XX_TCI_VLAN_ID          GENMASK(11, 0)
++#define MXL862XX_TCI_VLAN_CFI_DEI     BIT(12)
++#define MXL862XX_TCI_VLAN_PRI         GENMASK(15, 13)
++
++/* Set in port_id to use port_map[] as a portmap bitmap instead of a single
++ * port ID. When clear, port_id selects one port; when set, the firmware
++ * ignores the lower bits of port_id and writes port_map[] directly into
++ * the PCE bridge port map.
++ */
++#define MXL862XX_PORTMAP_FLAG         BIT(31)
++
++/**
++ * struct mxl862xx_mac_table_add - MAC Table Entry to be added
++ * @fid: Filtering Identifier (FID) (not supported by all switches)
++ * @port_id: Ethernet Port number
++ * @port_map: Bridge Port Map
++ * @sub_if_id: Sub-Interface Identifier Destination
++ * @age_timer: Aging Time in seconds
++ * @vlan_id: STAG VLAN Id
++ * @static_entry: Static Entry (value will be aged out if not set to static)
++ * @traffic_class: Egress queue traffic class
++ * @mac: MAC Address to add to the table
++ * @filter_flag: See &enum mxl862xx_mac_table_filter
++ * @igmp_controlled: Packet is marked as IGMP controlled if destination MAC
++ *                   address matches MAC in this entry
++ * @associated_mac: Associated Mac address
++ * @tci: TCI for B-Step
++ *    Bit [0:11] - VLAN ID
++ *    Bit [12] - VLAN CFI/DEI
++ *    Bit [13:15] - VLAN PRI
++ */
++struct mxl862xx_mac_table_add {
++      __le16 fid;
++      __le32 port_id;
++      __le16 port_map[8];
++      __le16 sub_if_id;
++      __le32 age_timer;
++      __le16 vlan_id;
++      u8 static_entry;
++      u8 traffic_class;
++      u8 mac[ETH_ALEN];
++      u8 filter_flag;
++      u8 igmp_controlled;
++      u8 associated_mac[ETH_ALEN];
++      __le16 tci;
++} __packed;
++
++/**
++ * struct mxl862xx_mac_table_remove - MAC Table Entry to be removed
++ * @fid: Filtering Identifier (FID)
++ * @mac: MAC Address to be removed from the table.
++ * @filter_flag: See &enum mxl862xx_mac_table_filter
++ * @tci: TCI for B-Step
++ *    Bit [0:11] - VLAN ID
++ *    Bit [12] - VLAN CFI/DEI
++ *    Bit [13:15] - VLAN PRI
++ */
++struct mxl862xx_mac_table_remove {
++      __le16 fid;
++      u8 mac[ETH_ALEN];
++      u8 filter_flag;
++      __le16 tci;
++} __packed;
++
++/**
++ * struct mxl862xx_mac_table_read - MAC Table Entry to be read
++ * @initial: Restart the get operation from the beginning of the table
++ * @last: Indicates that the read operation returned last entry
++ * @fid: Get the MAC table entry belonging to the given Filtering Identifier
++ * @port_id: The Bridge Port ID
++ * @port_map: Bridge Port Map
++ * @age_timer: Aging Time
++ * @vlan_id: STAG VLAN Id
++ * @static_entry: Indicates if this is a Static Entry
++ * @sub_if_id: Sub-Interface Identifier Destination
++ * @mac: MAC Address. Filled out by the switch API implementation.
++ * @filter_flag: See &enum mxl862xx_mac_table_filter
++ * @igmp_controlled: Packet is marked as IGMP controlled if destination MAC
++ *                   address matches the MAC in this entry
++ * @entry_changed: Indicate if the Entry has Changed
++ * @associated_mac: Associated MAC address
++ * @hit_status: MAC Table Hit Status Update
++ * @tci: TCI for B-Step
++ *    Bit [0:11] - VLAN ID
++ *    Bit [12] - VLAN CFI/DEI
++ *    Bit [13:15] - VLAN PRI
++ * @first_bridge_port_id: The port this MAC address has first been learned.
++ *                        This is used for loop detection.
++ */
++struct mxl862xx_mac_table_read {
++      u8 initial;
++      u8 last;
++      __le16 fid;
++      __le32 port_id;
++      __le16 port_map[8];
++      __le32 age_timer;
++      __le16 vlan_id;
++      u8 static_entry;
++      __le16 sub_if_id;
++      u8 mac[ETH_ALEN];
++      u8 filter_flag;
++      u8 igmp_controlled;
++      u8 entry_changed;
++      u8 associated_mac[ETH_ALEN];
++      u8 hit_status;
++      __le16 tci;
++      __le16 first_bridge_port_id;
++} __packed;
++
++/**
++ * struct mxl862xx_mac_table_query - MAC Table Entry key-based lookup
++ * @mac: MAC Address to search for (input)
++ * @fid: Filtering Identifier (input)
++ * @found: Set by firmware: 1 if entry was found, 0 if not
++ * @port_id: Bridge Port ID (output; MSB set if portmap mode)
++ * @port_map: Bridge Port Map (output; valid for static entries)
++ * @sub_if_id: Sub-Interface Identifier Destination
++ * @age_timer: Aging Time
++ * @vlan_id: STAG VLAN Id
++ * @static_entry: Indicates if this is a Static Entry
++ * @filter_flag: See &enum mxl862xx_mac_table_filter (input+output)
++ * @igmp_controlled: IGMP controlled flag
++ * @entry_changed: Entry changed flag
++ * @associated_mac: Associated MAC address
++ * @hit_status: MAC Table Hit Status Update
++ * @tci: TCI (VLAN ID + CFI/DEI + PRI) (input)
++ * @first_bridge_port_id: First learned bridge port
++ */
++struct mxl862xx_mac_table_query {
++      u8 mac[ETH_ALEN];
++      __le16 fid;
++      u8 found;
++      __le32 port_id;
++      __le16 port_map[8];
++      __le16 sub_if_id;
++      __le32 age_timer;
++      __le16 vlan_id;
++      u8 static_entry;
++      u8 filter_flag;
++      u8 igmp_controlled;
++      u8 entry_changed;
++      u8 associated_mac[ETH_ALEN];
++      u8 hit_status;
++      __le16 tci;
++      __le16 first_bridge_port_id;
++} __packed;
++
++/**
+  * enum mxl862xx_mac_clear_type - MAC table clear type
+  * @MXL862XX_MAC_CLEAR_PHY_PORT: clear dynamic entries based on port_id
+  * @MXL862XX_MAC_CLEAR_DYNAMIC: clear all dynamic entries
+@@ -139,6 +302,40 @@ enum mxl862xx_bridge_port_egress_meter {
+ };
+ /**
++ * struct mxl862xx_qos_meter_cfg - Rate meter configuration
++ * @enable: Enable/disable meter
++ * @meter_id: Meter ID (assigned by firmware on alloc)
++ * @meter_name: Meter name string
++ * @meter_type: Meter algorithm type (srTCM = 0, trTCM = 1)
++ * @cbs: Committed Burst Size (in bytes)
++ * @res1: Reserved
++ * @ebs: Excess Burst Size (in bytes)
++ * @res2: Reserved
++ * @rate: Committed Information Rate (in kbit/s)
++ * @pi_rate: Peak Information Rate (in kbit/s)
++ * @colour_blind_mode: Colour-blind mode enable
++ * @pkt_mode: Packet mode enable
++ * @local_overhd: Local overhead accounting enable
++ * @local_overhd_val: Local overhead accounting value
++ */
++struct mxl862xx_qos_meter_cfg {
++      u8 enable;
++      __le16 meter_id;
++      char meter_name[32];
++      __le32 meter_type;
++      __le32 cbs;
++      __le32 res1;
++      __le32 ebs;
++      __le32 res2;
++      __le32 rate;
++      __le32 pi_rate;
++      u8 colour_blind_mode;
++      u8 pkt_mode;
++      u8 local_overhd;
++      __le16 local_overhd_val;
++} __packed;
++
++/**
+  * enum mxl862xx_bridge_forward_mode - Bridge forwarding type of packet
+  * @MXL862XX_BRIDGE_FORWARD_FLOOD: Packet is flooded to port members of
+  *                                 ingress bridge port
+@@ -456,7 +653,7 @@ struct mxl862xx_pmapper {
+  */
+ struct mxl862xx_bridge_port_config {
+       __le16 bridge_port_id;
+-      __le32 mask; /* enum mxl862xx_bridge_port_config_mask  */
++      __le32 mask; /* enum mxl862xx_bridge_port_config_mask */
+       __le16 bridge_id;
+       u8 ingress_extended_vlan_enable;
+       __le16 ingress_extended_vlan_block_id;
+@@ -659,6 +856,32 @@ struct mxl862xx_ctp_port_assignment {
+ } __packed;
+ /**
++ * enum mxl862xx_stp_port_state - Spanning Tree Protocol port states
++ * @MXL862XX_STP_PORT_STATE_FORWARD: Forwarding state
++ * @MXL862XX_STP_PORT_STATE_DISABLE: Disabled/Discarding state
++ * @MXL862XX_STP_PORT_STATE_LEARNING: Learning state
++ * @MXL862XX_STP_PORT_STATE_BLOCKING: Blocking/Listening
++ */
++enum mxl862xx_stp_port_state {
++      MXL862XX_STP_PORT_STATE_FORWARD = 0,
++      MXL862XX_STP_PORT_STATE_DISABLE,
++      MXL862XX_STP_PORT_STATE_LEARNING,
++      MXL862XX_STP_PORT_STATE_BLOCKING,
++};
++
++/**
++ * struct mxl862xx_stp_port_cfg - Configures the Spanning Tree Protocol state
++ * @port_id: Port number
++ * @fid: Filtering Identifier (FID)
++ * @port_state: See &enum mxl862xx_stp_port_state
++ */
++struct mxl862xx_stp_port_cfg {
++      __le16 port_id;
++      __le16 fid;
++      __le32 port_state; /* enum mxl862xx_stp_port_state */
++} __packed;
++
++/**
+  * struct mxl862xx_sys_fw_image_version - Firmware version information
+  * @iv_major: firmware major version
+  * @iv_minor: firmware minor version
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+@@ -15,12 +15,15 @@
+ #define MXL862XX_BRDG_MAGIC           0x300
+ #define MXL862XX_BRDGPORT_MAGIC               0x400
+ #define MXL862XX_CTP_MAGIC            0x500
++#define MXL862XX_QOS_MAGIC            0x600
+ #define MXL862XX_SWMAC_MAGIC          0xa00
++#define MXL862XX_STP_MAGIC            0xf00
+ #define MXL862XX_SS_MAGIC             0x1600
+ #define GPY_GPY2XX_MAGIC              0x1800
+ #define SYS_MISC_MAGIC                        0x1900
+ #define MXL862XX_COMMON_CFGGET                (MXL862XX_COMMON_MAGIC + 0x9)
++#define MXL862XX_COMMON_CFGSET                (MXL862XX_COMMON_MAGIC + 0xa)
+ #define MXL862XX_COMMON_REGISTERMOD   (MXL862XX_COMMON_MAGIC + 0x11)
+ #define MXL862XX_BRIDGE_ALLOC         (MXL862XX_BRDG_MAGIC + 0x1)
+@@ -35,14 +38,23 @@
+ #define MXL862XX_CTP_PORTASSIGNMENTSET        (MXL862XX_CTP_MAGIC + 0x3)
++#define MXL862XX_QOS_METERCFGSET      (MXL862XX_QOS_MAGIC + 0x2)
++#define MXL862XX_QOS_METERALLOC               (MXL862XX_QOS_MAGIC + 0x2a)
++
++#define MXL862XX_MAC_TABLEENTRYADD    (MXL862XX_SWMAC_MAGIC + 0x2)
++#define MXL862XX_MAC_TABLEENTRYREAD   (MXL862XX_SWMAC_MAGIC + 0x3)
++#define MXL862XX_MAC_TABLEENTRYQUERY  (MXL862XX_SWMAC_MAGIC + 0x4)
++#define MXL862XX_MAC_TABLEENTRYREMOVE (MXL862XX_SWMAC_MAGIC + 0x5)
+ #define MXL862XX_MAC_TABLECLEARCOND   (MXL862XX_SWMAC_MAGIC + 0x8)
+-#define MXL862XX_SS_SPTAG_SET         (MXL862XX_SS_MAGIC + 0x02)
++#define MXL862XX_SS_SPTAG_SET         (MXL862XX_SS_MAGIC + 0x2)
++
++#define MXL862XX_STP_PORTCFGSET               (MXL862XX_STP_MAGIC + 0x2)
+-#define INT_GPHY_READ                 (GPY_GPY2XX_MAGIC + 0x01)
+-#define INT_GPHY_WRITE                        (GPY_GPY2XX_MAGIC + 0x02)
++#define INT_GPHY_READ                 (GPY_GPY2XX_MAGIC + 0x1)
++#define INT_GPHY_WRITE                        (GPY_GPY2XX_MAGIC + 0x2)
+-#define SYS_MISC_FW_VERSION           (SYS_MISC_MAGIC + 0x02)
++#define SYS_MISC_FW_VERSION           (SYS_MISC_MAGIC + 0x2)
+ #define MMD_API_MAXIMUM_ID            0x7fff
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -7,8 +7,11 @@
+  * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
+  */
+-#include <linux/module.h>
++#include <linux/bitfield.h>
+ #include <linux/delay.h>
++#include <linux/etherdevice.h>
++#include <linux/if_bridge.h>
++#include <linux/module.h>
+ #include <linux/of_device.h>
+ #include <linux/of_mdio.h>
+ #include <linux/phy.h>
+@@ -36,6 +39,17 @@
+ #define MXL862XX_READY_TIMEOUT_MS     10000
+ #define MXL862XX_READY_POLL_MS                100
++#define MXL862XX_TCM_INST_SEL         0xe00
++#define MXL862XX_TCM_CBS              0xe12
++#define MXL862XX_TCM_EBS              0xe13
++
++static const int mxl862xx_flood_meters[] = {
++      MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC,
++      MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP,
++      MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP,
++      MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST,
++};
++
+ static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds,
+                                                      int port,
+                                                      enum dsa_tag_protocol m)
+@@ -168,6 +182,225 @@ static int mxl862xx_setup_mdio(struct ds
+       return ret;
+ }
++static int mxl862xx_bridge_config_fwd(struct dsa_switch *ds, u16 bridge_id,
++                                    bool ucast_flood, bool mcast_flood,
++                                    bool bcast_flood)
++{
++      struct mxl862xx_bridge_config bridge_config = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      int ret;
++
++      bridge_config.mask = cpu_to_le32(MXL862XX_BRIDGE_CONFIG_MASK_FORWARDING_MODE);
++      bridge_config.bridge_id = cpu_to_le16(bridge_id);
++
++      bridge_config.forward_unknown_unicast = cpu_to_le32(ucast_flood ?
++              MXL862XX_BRIDGE_FORWARD_FLOOD : MXL862XX_BRIDGE_FORWARD_DISCARD);
++
++      bridge_config.forward_unknown_multicast_ip = cpu_to_le32(mcast_flood ?
++              MXL862XX_BRIDGE_FORWARD_FLOOD : MXL862XX_BRIDGE_FORWARD_DISCARD);
++      bridge_config.forward_unknown_multicast_non_ip =
++              bridge_config.forward_unknown_multicast_ip;
++
++      bridge_config.forward_broadcast = cpu_to_le32(bcast_flood ?
++              MXL862XX_BRIDGE_FORWARD_FLOOD : MXL862XX_BRIDGE_FORWARD_DISCARD);
++
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_BRIDGE_CONFIGSET, bridge_config);
++      if (ret)
++              dev_err(ds->dev, "failed to configure bridge %u forwarding: %d\n",
++                      bridge_id, ret);
++
++      return ret;
++}
++
++/* Allocate a single zero-rate meter shared by all ports and flood types.
++ * All flood-blocking egress sub-meters point to this one meter so that any
++ * packet hitting this meter is unconditionally dropped.
++ *
++ * The firmware API requires CBS >= 64 (its bs2ls encoder clamps smaller
++ * values), so the meter is initially configured with CBS=EBS=64.
++ * A zero-rate bucket starts full at CBS bytes, which would let one packet
++ * through before the bucket empties. To eliminate this one-packet leak we
++ * override CBS and EBS to zero via direct register writes after the API call;
++ * the hardware accepts CBS=0 and immediately flags the bucket as exceeded,
++ * so no traffic can ever pass.
++ */
++static int mxl862xx_setup_drop_meter(struct dsa_switch *ds)
++{
++      struct mxl862xx_qos_meter_cfg meter = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_register_mod reg;
++      int ret;
++
++      /* meter_id=0 means auto-alloc */
++      ret = MXL862XX_API_READ(priv, MXL862XX_QOS_METERALLOC, meter);
++      if (ret)
++              return ret;
++
++      meter.enable = true;
++      meter.cbs = cpu_to_le32(64);
++      meter.ebs = cpu_to_le32(64);
++      snprintf(meter.meter_name, sizeof(meter.meter_name), "drop");
++
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_QOS_METERCFGSET, meter);
++      if (ret)
++              return ret;
++
++      priv->drop_meter = le16_to_cpu(meter.meter_id);
++
++      /* Select the meter instance for subsequent TCM register access. */
++      reg.addr = cpu_to_le16(MXL862XX_TCM_INST_SEL);
++      reg.data = cpu_to_le16(priv->drop_meter);
++      reg.mask = cpu_to_le16(0xffff);
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
++      if (ret)
++              return ret;
++
++      /* Zero CBS so the committed bucket starts empty (exceeded). */
++      reg.addr = cpu_to_le16(MXL862XX_TCM_CBS);
++      reg.data = 0;
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
++      if (ret)
++              return ret;
++
++      /* Zero EBS so the excess bucket starts empty (exceeded). */
++      reg.addr = cpu_to_le16(MXL862XX_TCM_EBS);
++      return MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
++}
++
++static int mxl862xx_set_bridge_port(struct dsa_switch *ds, int port)
++{
++      struct mxl862xx_bridge_port_config br_port_cfg = {};
++      struct dsa_port *dp = dsa_to_port(ds, port);
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
++      u16 bridge_id = dp->bridge ?
++              priv->bridges[dp->bridge->num] : p->fid;
++      bool enable;
++      int i, idx;
++
++      br_port_cfg.bridge_port_id = cpu_to_le16(port);
++      br_port_cfg.bridge_id = cpu_to_le16(bridge_id);
++      br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER);
++      br_port_cfg.src_mac_learning_disable = !p->learning;
++
++      mxl862xx_fw_portmap_from_bitmap(br_port_cfg.bridge_port_map, p->portmap);
++
++      for (i = 0; i < ARRAY_SIZE(mxl862xx_flood_meters); i++) {
++              idx = mxl862xx_flood_meters[i];
++              enable = !!(p->flood_block & BIT(idx));
++
++              br_port_cfg.egress_traffic_sub_meter_id[idx] =
++                      enable ? cpu_to_le16(priv->drop_meter) : 0;
++              br_port_cfg.egress_sub_metering_enable[idx] = enable;
++      }
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET,
++                                br_port_cfg);
++}
++
++static int mxl862xx_sync_bridge_members(struct dsa_switch *ds,
++                                      const struct dsa_bridge *bridge)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct dsa_port *dp, *member_dp;
++      int port, err, ret = 0;
++
++      dsa_switch_for_each_bridge_member(dp, ds, bridge->dev) {
++              port = dp->index;
++
++              bitmap_zero(priv->ports[port].portmap,
++                          MXL862XX_MAX_BRIDGE_PORTS);
++
++              dsa_switch_for_each_bridge_member(member_dp, ds, bridge->dev) {
++                      if (member_dp->index != port)
++                              __set_bit(member_dp->index,
++                                        priv->ports[port].portmap);
++              }
++              __set_bit(dp->cpu_dp->index, priv->ports[port].portmap);
++
++              err = mxl862xx_set_bridge_port(ds, port);
++              if (err)
++                      ret = err;
++      }
++
++      return ret;
++}
++
++/**
++ * mxl862xx_allocate_bridge - Allocate a firmware bridge instance
++ * @priv: driver private data
++ * @bridge_id: output -- firmware bridge ID assigned by the firmware
++ *
++ * Newly allocated bridges default to flooding all traffic classes
++ * (unknown unicast, multicast, broadcast).  Callers that need
++ * different forwarding behavior must call mxl862xx_bridge_config_fwd()
++ * after allocation.
++ *
++ * Return: 0 on success, negative errno on failure.
++ */
++static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv, u16 *bridge_id)
++{
++      struct mxl862xx_bridge_alloc br_alloc = {};
++      int ret;
++
++      ret = MXL862XX_API_READ(priv, MXL862XX_BRIDGE_ALLOC, br_alloc);
++      if (ret)
++              return ret;
++
++      *bridge_id = le16_to_cpu(br_alloc.bridge_id);
++      return 0;
++}
++
++static void mxl862xx_free_bridge(struct dsa_switch *ds,
++                               const struct dsa_bridge *bridge)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      u16 fw_id = priv->bridges[bridge->num];
++      struct mxl862xx_bridge_alloc br_alloc = {
++              .bridge_id = cpu_to_le16(fw_id),
++      };
++      int ret;
++
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_BRIDGE_FREE, br_alloc);
++      if (ret) {
++              dev_err(ds->dev, "failed to free fw bridge %u: %pe\n",
++                      fw_id, ERR_PTR(ret));
++              return;
++      }
++
++      priv->bridges[bridge->num] = 0;
++}
++
++static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
++{
++      struct dsa_port *dp = dsa_to_port(ds, port);
++      struct mxl862xx_priv *priv = ds->priv;
++      int ret;
++
++      ret = mxl862xx_allocate_bridge(priv, &priv->ports[port].fid);
++      if (ret) {
++              dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
++              return ret;
++      }
++
++      priv->ports[port].learning = false;
++      bitmap_zero(priv->ports[port].portmap, MXL862XX_MAX_BRIDGE_PORTS);
++      __set_bit(dp->cpu_dp->index, priv->ports[port].portmap);
++
++      ret = mxl862xx_set_bridge_port(ds, port);
++      if (ret)
++              return ret;
++
++      /* Standalone ports should not flood unknown unicast or multicast
++       * towards the CPU by default; only broadcast is needed initially.
++       */
++      return mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
++                                       false, false, true);
++}
++
+ static int mxl862xx_setup(struct dsa_switch *ds)
+ {
+       struct mxl862xx_priv *priv = ds->priv;
+@@ -181,6 +414,10 @@ static int mxl862xx_setup(struct dsa_swi
+       if (ret)
+               return ret;
++      ret = mxl862xx_setup_drop_meter(ds);
++      if (ret)
++              return ret;
++
+       return mxl862xx_setup_mdio(ds);
+ }
+@@ -260,66 +497,87 @@ static int mxl862xx_configure_sp_tag_pro
+ static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port)
+ {
+-      struct mxl862xx_bridge_port_config br_port_cfg = {};
+       struct mxl862xx_priv *priv = ds->priv;
+-      u16 bridge_port_map = 0;
+       struct dsa_port *dp;
+-      /* CPU port bridge setup */
+-      br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
+-                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
+-                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING);
+-
+-      br_port_cfg.bridge_port_id = cpu_to_le16(port);
+-      br_port_cfg.src_mac_learning_disable = false;
+-      br_port_cfg.vlan_src_mac_vid_enable = true;
+-      br_port_cfg.vlan_dst_mac_vid_enable = true;
++      priv->ports[port].fid = MXL862XX_DEFAULT_BRIDGE;
++      priv->ports[port].learning = true;
+       /* include all assigned user ports in the CPU portmap */
++      bitmap_zero(priv->ports[port].portmap, MXL862XX_MAX_BRIDGE_PORTS);
+       dsa_switch_for_each_user_port(dp, ds) {
+               /* it's safe to rely on cpu_dp being valid for user ports */
+               if (dp->cpu_dp->index != port)
+                       continue;
+-              bridge_port_map |= BIT(dp->index);
++              __set_bit(dp->index, priv->ports[port].portmap);
+       }
+-      br_port_cfg.bridge_port_map[0] |= cpu_to_le16(bridge_port_map);
+-      return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
++      return mxl862xx_set_bridge_port(ds, port);
+ }
+-static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
++static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
++                                   const struct dsa_bridge bridge,
++                                   bool *tx_fwd_offload,
++                                   struct netlink_ext_ack *extack)
+ {
+-      struct mxl862xx_bridge_port_config br_port_cfg = {};
+-      struct dsa_port *dp = dsa_to_port(ds, port);
+-      struct mxl862xx_bridge_alloc br_alloc = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      u16 fw_id;
+       int ret;
+-      ret = MXL862XX_API_READ(ds->priv, MXL862XX_BRIDGE_ALLOC, br_alloc);
+-      if (ret) {
+-              dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
+-              return ret;
++      if (!priv->bridges[bridge.num]) {
++              ret = mxl862xx_allocate_bridge(priv, &fw_id);
++              if (ret)
++                      return ret;
++
++              priv->bridges[bridge.num] = fw_id;
++
++              /* Free bridge here on error, DSA rollback won't. */
++              ret = mxl862xx_sync_bridge_members(ds, &bridge);
++              if (ret) {
++                      mxl862xx_free_bridge(ds, &bridge);
++                      return ret;
++              }
++
++              return 0;
+       }
+-      br_port_cfg.bridge_id = br_alloc.bridge_id;
+-      br_port_cfg.bridge_port_id = cpu_to_le16(port);
+-      br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
+-                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
+-                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
+-                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING);
+-      br_port_cfg.src_mac_learning_disable = true;
+-      br_port_cfg.vlan_src_mac_vid_enable = false;
+-      br_port_cfg.vlan_dst_mac_vid_enable = false;
+-      /* As this function is only called for user ports it is safe to rely on
+-       * cpu_dp being valid
++      return mxl862xx_sync_bridge_members(ds, &bridge);
++}
++
++static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
++                                     const struct dsa_bridge bridge)
++{
++      struct dsa_port *dp = dsa_to_port(ds, port);
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
++      int err;
++
++      err = mxl862xx_sync_bridge_members(ds, &bridge);
++      if (err)
++              dev_err(ds->dev,
++                      "failed to sync bridge members after port %d left: %pe\n",
++                      port, ERR_PTR(err));
++
++      /* Revert leaving port, omitted by the sync above, to its
++       * single-port bridge
+        */
+-      br_port_cfg.bridge_port_map[0] = cpu_to_le16(BIT(dp->cpu_dp->index));
++      bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
++      __set_bit(dp->cpu_dp->index, p->portmap);
++      p->flood_block = 0;
++      err = mxl862xx_set_bridge_port(ds, port);
++      if (err)
++              dev_err(ds->dev,
++                      "failed to update bridge port %d state: %pe\n", port,
++                      ERR_PTR(err));
+-      return MXL862XX_API_WRITE(ds->priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
++      if (!dsa_bridge_ports(ds, bridge.dev))
++              mxl862xx_free_bridge(ds, &bridge);
+ }
+ static int mxl862xx_port_setup(struct dsa_switch *ds, int port)
+ {
++      struct mxl862xx_priv *priv = ds->priv;
+       struct dsa_port *dp = dsa_to_port(ds, port);
+       bool is_cpu_port = dsa_port_is_cpu(dp);
+       int ret;
+@@ -352,7 +610,31 @@ static int mxl862xx_port_setup(struct ds
+               return mxl862xx_setup_cpu_bridge(ds, port);
+       /* setup single-port bridge for user ports */
+-      return mxl862xx_add_single_port_bridge(ds, port);
++      ret = mxl862xx_add_single_port_bridge(ds, port);
++      if (ret)
++              return ret;
++
++      priv->ports[port].setup_done = true;
++
++      return 0;
++}
++
++static void mxl862xx_port_teardown(struct dsa_switch *ds, int port)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct dsa_port *dp = dsa_to_port(ds, port);
++
++      if (dsa_port_is_unused(dp) || dsa_port_is_dsa(dp))
++              return;
++
++      /* Prevent deferred host_flood_work from acting on stale state.
++       * The flag is checked under rtnl_lock() by the worker; since
++       * teardown also runs under RTNL, this is race-free.
++       *
++       * HW EVLAN/VF blocks are not freed here -- the firmware receives
++       * a full reset on the next probe, which reclaims all resources.
++       */
++      priv->ports[port].setup_done = false;
+ }
+ static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
+@@ -365,14 +647,385 @@ static void mxl862xx_phylink_get_caps(st
+                 config->supported_interfaces);
+ }
++static int mxl862xx_get_fid(struct dsa_switch *ds, struct dsa_db db)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++
++      switch (db.type) {
++      case DSA_DB_PORT:
++              return priv->ports[db.dp->index].fid;
++
++      case DSA_DB_BRIDGE:
++              if (!priv->bridges[db.bridge.num])
++                      return -ENOENT;
++              return priv->bridges[db.bridge.num];
++
++      default:
++              return -EOPNOTSUPP;
++      }
++}
++
++static int mxl862xx_port_fdb_add(struct dsa_switch *ds, int port,
++                               const unsigned char *addr, u16 vid, struct dsa_db db)
++{
++      struct mxl862xx_mac_table_add param = {};
++      int fid = mxl862xx_get_fid(ds, db), ret;
++      struct mxl862xx_priv *priv = ds->priv;
++
++      if (fid < 0)
++              return fid;
++
++      param.port_id = cpu_to_le32(port);
++      param.static_entry = true;
++      param.fid = cpu_to_le16(fid);
++      param.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid));
++      ether_addr_copy(param.mac, addr);
++
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, param);
++      if (ret)
++              dev_err(ds->dev, "failed to add FDB entry on port %d\n", port);
++
++      return ret;
++}
++
++static int mxl862xx_port_fdb_del(struct dsa_switch *ds, int port,
++                               const unsigned char *addr, u16 vid, const struct dsa_db db)
++{
++      struct mxl862xx_mac_table_remove param = {};
++      int fid = mxl862xx_get_fid(ds, db), ret;
++      struct mxl862xx_priv *priv = ds->priv;
++
++      if (fid < 0)
++              return fid;
++
++      param.fid = cpu_to_le16(fid);
++      param.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid));
++      ether_addr_copy(param.mac, addr);
++
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE, param);
++      if (ret)
++              dev_err(ds->dev, "failed to remove FDB entry on port %d\n", port);
++
++      return ret;
++}
++
++static int mxl862xx_port_fdb_dump(struct dsa_switch *ds, int port,
++                                dsa_fdb_dump_cb_t *cb, void *data)
++{
++      struct mxl862xx_mac_table_read param = { .initial = 1 };
++      struct mxl862xx_priv *priv = ds->priv;
++      u32 entry_port_id;
++      int ret;
++
++      while (true) {
++              ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYREAD, param);
++              if (ret)
++                      return ret;
++
++              if (param.last)
++                      break;
++
++              entry_port_id = le32_to_cpu(param.port_id);
++
++              if (entry_port_id == port) {
++                      ret = cb(param.mac, FIELD_GET(MXL862XX_TCI_VLAN_ID,
++                                                    le16_to_cpu(param.tci)),
++                               param.static_entry, data);
++                      if (ret)
++                              return ret;
++              }
++
++              memset(&param, 0, sizeof(param));
++      }
++
++      return 0;
++}
++
++static int mxl862xx_port_mdb_add(struct dsa_switch *ds, int port,
++                               const struct switchdev_obj_port_mdb *mdb,
++                               const struct dsa_db db)
++{
++      struct mxl862xx_mac_table_query qparam = {};
++      struct mxl862xx_mac_table_add aparam = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      int fid, ret;
++
++      fid = mxl862xx_get_fid(ds, db);
++      if (fid < 0)
++              return fid;
++
++      /* Look up existing entry by {MAC, FID, TCI} */
++      ether_addr_copy(qparam.mac, mdb->addr);
++      qparam.fid = cpu_to_le16(fid);
++      qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
++
++      ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);
++      if (ret)
++              return ret;
++
++      /* Build the ADD command using portmap mode */
++      ether_addr_copy(aparam.mac, mdb->addr);
++      aparam.fid = cpu_to_le16(fid);
++      aparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
++      aparam.static_entry = true;
++      aparam.port_id = cpu_to_le32(MXL862XX_PORTMAP_FLAG);
++
++      /* Merge with existing portmap if entry already exists */
++      if (qparam.found)
++              memcpy(aparam.port_map, qparam.port_map,
++                     sizeof(aparam.port_map));
++
++      mxl862xx_fw_portmap_set_bit(aparam.port_map, port);
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, aparam);
++}
++
++static int mxl862xx_port_mdb_del(struct dsa_switch *ds, int port,
++                               const struct switchdev_obj_port_mdb *mdb,
++                               const struct dsa_db db)
++{
++      struct mxl862xx_mac_table_remove rparam = {};
++      struct mxl862xx_mac_table_query qparam = {};
++      struct mxl862xx_mac_table_add aparam = {};
++      int fid = mxl862xx_get_fid(ds, db), ret;
++      struct mxl862xx_priv *priv = ds->priv;
++
++      if (fid < 0)
++              return fid;
++
++      /* Look up existing entry */
++      qparam.fid = cpu_to_le16(fid);
++      qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
++      ether_addr_copy(qparam.mac, mdb->addr);
++
++      ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);
++      if (ret)
++              return ret;
++
++      if (!qparam.found)
++              return 0;
++
++      mxl862xx_fw_portmap_clear_bit(qparam.port_map, port);
++
++      if (mxl862xx_fw_portmap_is_empty(qparam.port_map)) {
++              /* No ports left — remove the entry entirely */
++              rparam.fid = cpu_to_le16(fid);
++              rparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
++              ether_addr_copy(rparam.mac, mdb->addr);
++              ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE, rparam);
++      } else {
++              /* Write back with reduced portmap */
++              aparam.fid = cpu_to_le16(fid);
++              aparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
++              ether_addr_copy(aparam.mac, mdb->addr);
++              aparam.static_entry = true;
++              aparam.port_id = cpu_to_le32(MXL862XX_PORTMAP_FLAG);
++              memcpy(aparam.port_map, qparam.port_map, sizeof(aparam.port_map));
++              ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, aparam);
++      }
++
++      return ret;
++}
++
++static int mxl862xx_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
++{
++      struct mxl862xx_cfg param = {};
++      int ret;
++
++      ret = MXL862XX_API_READ(ds->priv, MXL862XX_COMMON_CFGGET, param);
++      if (ret) {
++              dev_err(ds->dev, "failed to read switch config\n");
++              return ret;
++      }
++
++      param.mac_table_age_timer = cpu_to_le32(MXL862XX_AGETIMER_CUSTOM);
++      param.age_timer = cpu_to_le32(msecs / 1000);
++      ret = MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_CFGSET, param);
++      if (ret)
++              dev_err(ds->dev, "failed to set ageing\n");
++
++      return ret;
++}
++
++static void mxl862xx_port_stp_state_set(struct dsa_switch *ds, int port,
++                                      u8 state)
++{
++      struct mxl862xx_stp_port_cfg param = {
++              .port_id = cpu_to_le16(port),
++      };
++      struct mxl862xx_priv *priv = ds->priv;
++      int ret;
++
++      switch (state) {
++      case BR_STATE_DISABLED:
++              param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_DISABLE);
++              break;
++      case BR_STATE_BLOCKING:
++      case BR_STATE_LISTENING:
++              param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_BLOCKING);
++              break;
++      case BR_STATE_LEARNING:
++              param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_LEARNING);
++              break;
++      case BR_STATE_FORWARDING:
++              param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_FORWARD);
++              break;
++      default:
++              dev_err(ds->dev, "invalid STP state: %d\n", state);
++              return;
++      }
++
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_STP_PORTCFGSET, param);
++      if (ret) {
++              dev_err(ds->dev, "failed to set STP state on port %d\n", port);
++              return;
++      }
++
++      /* The firmware may re-enable MAC learning as a side-effect of entering
++       * LEARNING or FORWARDING state (per 802.1D defaults).
++       * Re-apply the driver's intended learning and metering config so that
++       * standalone ports keep learning disabled.
++       * This is likely to get fixed in future firmware releases, however,
++       * the additional API call even then doesn't hurt much.
++       */
++      ret = mxl862xx_set_bridge_port(ds, port);
++      if (ret)
++              dev_err(ds->dev, "failed to reapply brport flags on port %d\n",
++                      port);
++
++      mxl862xx_port_fast_age(ds, port);
++}
++
++/* Deferred work handler for host flood configuration.
++ *
++ * port_set_host_flood is called from atomic context (under
++ * netif_addr_lock), so firmware calls must be deferred.  The worker
++ * acquires rtnl_lock() to serialize with DSA callbacks that access the
++ * same driver state.
++ */
++static void mxl862xx_host_flood_work_fn(struct work_struct *work)
++{
++      struct mxl862xx_port *p = container_of(work, struct mxl862xx_port,
++                                             host_flood_work);
++      struct mxl862xx_priv *priv = p->priv;
++      struct dsa_switch *ds = priv->ds;
++      int port = p - priv->ports;
++      bool uc, mc;
++
++      rtnl_lock();
++
++      /* Port may have been torn down between scheduling and now. */
++      if (!p->setup_done) {
++              rtnl_unlock();
++              return;
++      }
++
++      uc = p->host_flood_uc;
++      mc = p->host_flood_mc;
++
++      /* The hardware controls unknown-unicast/multicast forwarding per FID
++       * (bridge), not per source port.  For bridged ports all members share
++       * one FID, so we cannot selectively suppress flooding to the CPU for
++       * one source port while allowing it for another.  Silently ignore the
++       * request -- the excess flooding towards the CPU is harmless.
++       */
++      if (!dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
++              mxl862xx_bridge_config_fwd(ds, p->fid, uc, mc, true);
++
++      rtnl_unlock();
++}
++
++static void mxl862xx_port_set_host_flood(struct dsa_switch *ds, int port,
++                                       bool uc, bool mc)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
++
++      p->host_flood_uc = uc;
++      p->host_flood_mc = mc;
++      schedule_work(&p->host_flood_work);
++}
++
++static int mxl862xx_port_pre_bridge_flags(struct dsa_switch *ds, int port,
++                                        const struct switchdev_brport_flags flags,
++                                        struct netlink_ext_ack *extack)
++{
++      if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD |
++                         BR_LEARNING))
++              return -EINVAL;
++
++      return 0;
++}
++
++static int mxl862xx_port_bridge_flags(struct dsa_switch *ds, int port,
++                                    const struct switchdev_brport_flags flags,
++                                    struct netlink_ext_ack *extack)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      unsigned long old_block = priv->ports[port].flood_block;
++      unsigned long block = old_block;
++      bool need_update = false;
++      int ret;
++
++      if (flags.mask & BR_FLOOD) {
++              if (flags.val & BR_FLOOD)
++                      block &= ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC);
++              else
++                      block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC);
++      }
++
++      if (flags.mask & BR_MCAST_FLOOD) {
++              if (flags.val & BR_MCAST_FLOOD) {
++                      block &= ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP);
++                      block &= ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP);
++              } else {
++                      block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP);
++                      block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP);
++              }
++      }
++
++      if (flags.mask & BR_BCAST_FLOOD) {
++              if (flags.val & BR_BCAST_FLOOD)
++                      block &= ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST);
++              else
++                      block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST);
++      }
++
++      if (flags.mask & BR_LEARNING)
++              priv->ports[port].learning = !!(flags.val & BR_LEARNING);
++
++      need_update = (block != old_block) || (flags.mask & BR_LEARNING);
++      if (need_update) {
++              priv->ports[port].flood_block = block;
++              ret = mxl862xx_set_bridge_port(ds, port);
++              if (ret)
++                      return ret;
++      }
++
++      return 0;
++}
++
+ static const struct dsa_switch_ops mxl862xx_switch_ops = {
+       .get_tag_protocol = mxl862xx_get_tag_protocol,
+       .setup = mxl862xx_setup,
+       .port_setup = mxl862xx_port_setup,
++      .port_teardown = mxl862xx_port_teardown,
+       .phylink_get_caps = mxl862xx_phylink_get_caps,
+       .port_enable = mxl862xx_port_enable,
+       .port_disable = mxl862xx_port_disable,
+       .port_fast_age = mxl862xx_port_fast_age,
++      .set_ageing_time = mxl862xx_set_ageing_time,
++      .port_bridge_join = mxl862xx_port_bridge_join,
++      .port_bridge_leave = mxl862xx_port_bridge_leave,
++      .port_pre_bridge_flags = mxl862xx_port_pre_bridge_flags,
++      .port_bridge_flags = mxl862xx_port_bridge_flags,
++      .port_stp_state_set = mxl862xx_port_stp_state_set,
++      .port_set_host_flood = mxl862xx_port_set_host_flood,
++      .port_fdb_add = mxl862xx_port_fdb_add,
++      .port_fdb_del = mxl862xx_port_fdb_del,
++      .port_fdb_dump = mxl862xx_port_fdb_dump,
++      .port_mdb_add = mxl862xx_port_mdb_add,
++      .port_mdb_del = mxl862xx_port_mdb_del,
+ };
+ static void mxl862xx_phylink_mac_config(struct phylink_config *config,
+@@ -407,6 +1060,7 @@ static int mxl862xx_probe(struct mdio_de
+       struct device *dev = &mdiodev->dev;
+       struct mxl862xx_priv *priv;
+       struct dsa_switch *ds;
++      int i;
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+@@ -424,8 +1078,17 @@ static int mxl862xx_probe(struct mdio_de
+       ds->ops = &mxl862xx_switch_ops;
+       ds->phylink_mac_ops = &mxl862xx_phylink_mac_ops;
+       ds->num_ports = MXL862XX_MAX_PORTS;
++      ds->fdb_isolation = true;
++      ds->max_num_bridges = MXL862XX_MAX_BRIDGES;
++
+       mxl862xx_host_init(priv);
++      for (i = 0; i < MXL862XX_MAX_PORTS; i++) {
++              priv->ports[i].priv = priv;
++              INIT_WORK(&priv->ports[i].host_flood_work,
++                        mxl862xx_host_flood_work_fn);
++      }
++
+       dev_set_drvdata(dev, ds);
+       return dsa_register_switch(ds);
+@@ -435,6 +1098,7 @@ static void mxl862xx_remove(struct mdio_
+ {
+       struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
+       struct mxl862xx_priv *priv;
++      int i;
+       if (!ds)
+               return;
+@@ -444,12 +1108,21 @@ static void mxl862xx_remove(struct mdio_
+       dsa_unregister_switch(ds);
+       mxl862xx_host_shutdown(priv);
++
++      /* Cancel any pending host flood work.  dsa_unregister_switch()
++       * has already called port_teardown (which sets setup_done=false),
++       * but a worker could still be blocked on rtnl_lock().  Since we
++       * are now outside RTNL, cancel_work_sync() will not deadlock.
++       */
++      for (i = 0; i < MXL862XX_MAX_PORTS; i++)
++              cancel_work_sync(&priv->ports[i].host_flood_work);
+ }
+ static void mxl862xx_shutdown(struct mdio_device *mdiodev)
+ {
+       struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
+       struct mxl862xx_priv *priv;
++      int i;
+       if (!ds)
+               return;
+@@ -460,6 +1133,9 @@ static void mxl862xx_shutdown(struct mdi
+       mxl862xx_host_shutdown(priv);
++      for (i = 0; i < MXL862XX_MAX_PORTS; i++)
++              cancel_work_sync(&priv->ports[i].host_flood_work);
++
+       dev_set_drvdata(&mdiodev->dev, NULL);
+ }
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -4,15 +4,148 @@
+ #define __MXL862XX_H
+ #include <linux/mdio.h>
++#include <linux/workqueue.h>
+ #include <net/dsa.h>
++struct mxl862xx_priv;
++
+ #define MXL862XX_MAX_PORTS            17
++#define MXL862XX_DEFAULT_BRIDGE               0
++#define MXL862XX_MAX_BRIDGES          48
++#define MXL862XX_MAX_BRIDGE_PORTS     128
++
++/* Number of __le16 words in a firmware portmap (128-bit bitmap). */
++#define MXL862XX_FW_PORTMAP_WORDS     (MXL862XX_MAX_BRIDGE_PORTS / 16)
++
++/**
++ * mxl862xx_fw_portmap_from_bitmap - convert a kernel bitmap to a firmware
++ *                                   portmap (__le16[8])
++ * @dst: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
++ * @src: kernel bitmap of at least MXL862XX_MAX_BRIDGE_PORTS bits
++ */
++static inline void
++mxl862xx_fw_portmap_from_bitmap(__le16 *dst, const unsigned long *src)
++{
++      int i;
++
++      for (i = 0; i < MXL862XX_FW_PORTMAP_WORDS; i++)
++              dst[i] = cpu_to_le16(bitmap_read(src, i * 16, 16));
++}
++
++/**
++ * mxl862xx_fw_portmap_to_bitmap - convert a firmware portmap (__le16[8]) to
++ *                                 a kernel bitmap
++ * @dst: kernel bitmap of at least MXL862XX_MAX_BRIDGE_PORTS bits
++ * @src: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
++ */
++static inline void
++mxl862xx_fw_portmap_to_bitmap(unsigned long *dst, const __le16 *src)
++{
++      int i;
++
++      bitmap_zero(dst, MXL862XX_MAX_BRIDGE_PORTS);
++      for (i = 0; i < MXL862XX_FW_PORTMAP_WORDS; i++)
++              bitmap_write(dst, le16_to_cpu(src[i]), i * 16, 16);
++}
++
++/**
++ * mxl862xx_fw_portmap_set_bit - set a single port bit in a firmware portmap
++ * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
++ * @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1)
++ */
++static inline void mxl862xx_fw_portmap_set_bit(__le16 *map, int port)
++{
++      map[port / 16] |= cpu_to_le16(BIT(port % 16));
++}
++
++/**
++ * mxl862xx_fw_portmap_clear_bit - clear a single port bit in a firmware portmap
++ * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
++ * @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1)
++ */
++static inline void mxl862xx_fw_portmap_clear_bit(__le16 *map, int port)
++{
++      map[port / 16] &= ~cpu_to_le16(BIT(port % 16));
++}
++
++/**
++ * mxl862xx_fw_portmap_is_empty - check whether a firmware portmap has no
++ *                                bits set
++ * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
++ *
++ * Return: true if every word in @map is zero.
++ */
++static inline bool mxl862xx_fw_portmap_is_empty(const __le16 *map)
++{
++      int i;
++
++      for (i = 0; i < MXL862XX_FW_PORTMAP_WORDS; i++)
++              if (map[i])
++                      return false;
++      return true;
++}
++
++/**
++ * struct mxl862xx_port - per-port state tracked by the driver
++ * @priv:                back-pointer to switch private data; needed by
++ *                       deferred work handlers to access ds and priv
++ * @fid:                 firmware FID for the permanent single-port bridge;
++ *                       kept alive for the lifetime of the port so traffic is
++ *                       never forwarded while the port is unbridged
++ * @portmap:             bitmap of switch port indices that share the current
++ *                       bridge with this port
++ * @flood_block:         bitmask of firmware meter indices that are currently
++ *                       rate-limiting flood traffic on this port (zero-rate
++ *                       meters used to block flooding)
++ * @learning:            true when address learning is enabled on this port
++ * @setup_done:          set at end of port_setup, cleared at start of
++ *                       port_teardown; guards deferred work against
++ *                       acting on torn-down state
++ * @host_flood_uc:       desired host unicast flood state (true = flood);
++ *                       updated atomically by port_set_host_flood, consumed
++ *                       by the deferred host_flood_work
++ * @host_flood_mc:       desired host multicast flood state (true = flood)
++ * @host_flood_work:     deferred work for applying host flood changes;
++ *                       port_set_host_flood runs in atomic context (under
++ *                       netif_addr_lock) so firmware calls must be deferred.
++ *                       The worker acquires rtnl_lock() to serialize with
++ *                       DSA callbacks and checks @setup_done to avoid
++ *                       acting on torn-down ports.
++ */
++struct mxl862xx_port {
++      struct mxl862xx_priv *priv;
++      u16 fid;
++      DECLARE_BITMAP(portmap, MXL862XX_MAX_BRIDGE_PORTS);
++      unsigned long flood_block;
++      bool learning;
++      bool setup_done;
++      bool host_flood_uc;
++      bool host_flood_mc;
++      struct work_struct host_flood_work;
++};
++/**
++ * struct mxl862xx_priv - driver private data for an MxL862xx switch
++ * @ds:            pointer to the DSA switch instance
++ * @mdiodev:       MDIO device used to communicate with the switch firmware
++ * @crc_err_work:  deferred work for shutting down all ports on MDIO CRC errors
++ * @crc_err:       set atomically before CRC-triggered shutdown, cleared after
++ * @drop_meter:    index of the single shared zero-rate firmware meter used
++ *                 to unconditionally drop traffic (used to block flooding)
++ * @ports:         per-port state, indexed by switch port number
++ * @bridges:       maps DSA bridge number to firmware bridge ID;
++ *                 zero means no firmware bridge allocated for that
++ *                 DSA bridge number.  Indexed by dsa_bridge.num
++ *                 (0 .. ds->max_num_bridges).
++ */
+ struct mxl862xx_priv {
+       struct dsa_switch *ds;
+       struct mdio_device *mdiodev;
+       struct work_struct crc_err_work;
+       unsigned long crc_err;
++      u16 drop_meter;
++      struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
++      u16 bridges[MXL862XX_MAX_BRIDGES + 1];
+ };
+ #endif /* __MXL862XX_H */
diff --git a/target/linux/generic/pending-6.12/760-05-net-dsa-mxl862xx-implement-VLAN-functionality.patch b/target/linux/generic/pending-6.12/760-05-net-dsa-mxl862xx-implement-VLAN-functionality.patch
new file mode 100644 (file)
index 0000000..1098087
--- /dev/null
@@ -0,0 +1,1652 @@
+From 7286ac4f850339aac37dd52633f4a70816b621a8 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 10 Mar 2026 02:36:00 +0000
+Subject: [PATCH 14/35] net: dsa: mxl862xx: implement VLAN functionality
+
+Add VLAN support using both the Extended VLAN (EVLAN) engine and the
+VLAN Filter (VF) engine in a hybrid architecture that allows a higher
+number of VIDs than either engine could achieve alone.
+
+The VLAN Filter engine handles per-port VID membership checks with
+discard-unmatched semantics. The Extended VLAN engine handles PVID
+insertion on ingress (via fixed catchall rules) and tag stripping on
+egress (2 rules per untagged VID). Tagged-only VIDs need no EVLAN
+egress rules at all, so they consume only a VF entry.
+
+Both engines draw from shared 1024-entry hardware pools. The VF pool
+is divided equally among user ports for VID membership, while the
+EVLAN pool is partitioned into small fixed-size ingress blocks (7
+entries of catchall rules per port) and variable-size egress blocks
+for tag stripping.
+
+With 5 user ports this yields up to 204 VIDs per port (limited by VF),
+of which up to 98 can be untagged (limited by EVLAN egress budget).
+With 9 user ports the numbers are 113 total and 53 untagged.
+
+Wire up .port_vlan_add, .port_vlan_del, and .port_vlan_filtering.
+Reprogram all EVLAN rules when the PVID or filtering mode changes.
+Detach blocks from the bridge port before freeing them on bridge leave
+to satisfy the firmware's internal refcount.
+
+Future optimizations could increase VID capacity by dynamically sizing
+the egress EVLAN blocks based on actual per-port untagged VID counts
+rather than worst-case pre-allocation, or by sharing EVLAN egress and
+VLAN Filter blocks across ports with identical VID sets.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h | 329 ++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h |  12 +
+ drivers/net/dsa/mxl862xx/mxl862xx.c     | 960 +++++++++++++++++++++++-
+ drivers/net/dsa/mxl862xx/mxl862xx.h     | 110 ++-
+ 4 files changed, 1386 insertions(+), 25 deletions(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+@@ -732,6 +732,335 @@ struct mxl862xx_cfg {
+ } __packed;
+ /**
++ * enum mxl862xx_extended_vlan_filter_type - Extended VLAN filter tag type
++ * @MXL862XX_EXTENDEDVLAN_FILTER_TYPE_NORMAL: Normal tagged
++ * @MXL862XX_EXTENDEDVLAN_FILTER_TYPE_NO_FILTER: No filter (wildcard)
++ * @MXL862XX_EXTENDEDVLAN_FILTER_TYPE_DEFAULT: Default entry
++ * @MXL862XX_EXTENDEDVLAN_FILTER_TYPE_NO_TAG: Untagged
++ */
++enum mxl862xx_extended_vlan_filter_type {
++      MXL862XX_EXTENDEDVLAN_FILTER_TYPE_NORMAL = 0,
++      MXL862XX_EXTENDEDVLAN_FILTER_TYPE_NO_FILTER = 1,
++      MXL862XX_EXTENDEDVLAN_FILTER_TYPE_DEFAULT = 2,
++      MXL862XX_EXTENDEDVLAN_FILTER_TYPE_NO_TAG = 3,
++};
++
++/**
++ * enum mxl862xx_extended_vlan_filter_tpid - Extended VLAN filter TPID
++ * @MXL862XX_EXTENDEDVLAN_FILTER_TPID_NO_FILTER: No TPID filter
++ * @MXL862XX_EXTENDEDVLAN_FILTER_TPID_8021Q: 802.1Q TPID
++ * @MXL862XX_EXTENDEDVLAN_FILTER_TPID_VTETYPE: VLAN type extension
++ */
++enum mxl862xx_extended_vlan_filter_tpid {
++      MXL862XX_EXTENDEDVLAN_FILTER_TPID_NO_FILTER = 0,
++      MXL862XX_EXTENDEDVLAN_FILTER_TPID_8021Q = 1,
++      MXL862XX_EXTENDEDVLAN_FILTER_TPID_VTETYPE = 2,
++};
++
++/**
++ * enum mxl862xx_extended_vlan_filter_dei - Extended VLAN filter DEI
++ * @MXL862XX_EXTENDEDVLAN_FILTER_DEI_NO_FILTER: No DEI filter
++ * @MXL862XX_EXTENDEDVLAN_FILTER_DEI_0: DEI = 0
++ * @MXL862XX_EXTENDEDVLAN_FILTER_DEI_1: DEI = 1
++ */
++enum mxl862xx_extended_vlan_filter_dei {
++      MXL862XX_EXTENDEDVLAN_FILTER_DEI_NO_FILTER = 0,
++      MXL862XX_EXTENDEDVLAN_FILTER_DEI_0 = 1,
++      MXL862XX_EXTENDEDVLAN_FILTER_DEI_1 = 2,
++};
++
++/**
++ * enum mxl862xx_extended_vlan_treatment_remove_tag - Tag removal action
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG: Do not remove tag
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_REMOVE_1_TAG: Remove one tag
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_REMOVE_2_TAG: Remove two tags
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_DISCARD_UPSTREAM: Discard frame
++ */
++enum mxl862xx_extended_vlan_treatment_remove_tag {
++      MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG = 0,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_REMOVE_1_TAG = 1,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_REMOVE_2_TAG = 2,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_DISCARD_UPSTREAM = 3,
++};
++
++/**
++ * enum mxl862xx_extended_vlan_treatment_priority - Treatment priority mode
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_PRIORITY_VAL: Use explicit value
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRORITY: Copy from inner tag
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRORITY: Copy from outer tag
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_DSCP: Derive from DSCP
++ */
++enum mxl862xx_extended_vlan_treatment_priority {
++      MXL862XX_EXTENDEDVLAN_TREATMENT_PRIORITY_VAL = 0,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRORITY = 1,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRORITY = 2,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_DSCP = 3,
++};
++
++/**
++ * enum mxl862xx_extended_vlan_treatment_vid - Treatment VID mode
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_VID_VAL: Use explicit VID value
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_VID: Copy from inner tag
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_VID: Copy from outer tag
++ */
++enum mxl862xx_extended_vlan_treatment_vid {
++      MXL862XX_EXTENDEDVLAN_TREATMENT_VID_VAL = 0,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_VID = 1,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_VID = 2,
++};
++
++/**
++ * enum mxl862xx_extended_vlan_treatment_tpid - Treatment TPID mode
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_TPID: Copy from inner tag
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_TPID: Copy from outer tag
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_VTETYPE: Use VLAN type extension
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_8021Q: Use 802.1Q TPID
++ */
++enum mxl862xx_extended_vlan_treatment_tpid {
++      MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_TPID = 0,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_TPID = 1,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_VTETYPE = 2,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_8021Q = 3,
++};
++
++/**
++ * enum mxl862xx_extended_vlan_treatment_dei - Treatment DEI mode
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_DEI: Copy from inner tag
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_DEI: Copy from outer tag
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_DEI_0: Set DEI to 0
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_DEI_1: Set DEI to 1
++ */
++enum mxl862xx_extended_vlan_treatment_dei {
++      MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_DEI = 0,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_DEI = 1,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_DEI_0 = 2,
++      MXL862XX_EXTENDEDVLAN_TREATMENT_DEI_1 = 3,
++};
++
++/**
++ * enum mxl862xx_extended_vlan_4_tpid_mode - 4-TPID mode selector
++ * @MXL862XX_EXTENDEDVLAN_TPID_VTETYPE_1: VLAN TPID type 1
++ * @MXL862XX_EXTENDEDVLAN_TPID_VTETYPE_2: VLAN TPID type 2
++ * @MXL862XX_EXTENDEDVLAN_TPID_VTETYPE_3: VLAN TPID type 3
++ * @MXL862XX_EXTENDEDVLAN_TPID_VTETYPE_4: VLAN TPID type 4
++ */
++enum mxl862xx_extended_vlan_4_tpid_mode {
++      MXL862XX_EXTENDEDVLAN_TPID_VTETYPE_1 = 0,
++      MXL862XX_EXTENDEDVLAN_TPID_VTETYPE_2 = 1,
++      MXL862XX_EXTENDEDVLAN_TPID_VTETYPE_3 = 2,
++      MXL862XX_EXTENDEDVLAN_TPID_VTETYPE_4 = 3,
++};
++
++/**
++ * enum mxl862xx_extended_vlan_filter_ethertype - Filter EtherType match
++ * @MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_NO_FILTER: No filter
++ * @MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_IPOE: IPoE
++ * @MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_PPPOE: PPPoE
++ * @MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_ARP: ARP
++ * @MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_IPV6IPOE: IPv6 IPoE
++ * @MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_EAPOL: EAPOL
++ * @MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_DHCPV4: DHCPv4
++ * @MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_DHCPV6: DHCPv6
++ */
++enum mxl862xx_extended_vlan_filter_ethertype {
++      MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_NO_FILTER = 0,
++      MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_IPOE = 1,
++      MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_PPPOE = 2,
++      MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_ARP = 3,
++      MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_IPV6IPOE = 4,
++      MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_EAPOL = 5,
++      MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_DHCPV4 = 6,
++      MXL862XX_EXTENDEDVLAN_FILTER_ETHERTYPE_DHCPV6 = 7,
++};
++
++/**
++ * struct mxl862xx_extendedvlan_filter_vlan - Per-tag filter in Extended VLAN
++ * @type: Tag presence/type match (see &enum mxl862xx_extended_vlan_filter_type)
++ * @priority_enable: Enable PCP value matching
++ * @priority_val: PCP value to match
++ * @vid_enable: Enable VID matching
++ * @vid_val: VID value to match
++ * @tpid: TPID match mode (see &enum mxl862xx_extended_vlan_filter_tpid)
++ * @dei: DEI match mode (see &enum mxl862xx_extended_vlan_filter_dei)
++ */
++struct mxl862xx_extendedvlan_filter_vlan {
++      __le32 type;
++      u8 priority_enable;
++      __le32 priority_val;
++      u8 vid_enable;
++      __le32 vid_val;
++      __le32 tpid;
++      __le32 dei;
++} __packed;
++
++/**
++ * struct mxl862xx_extendedvlan_filter - Extended VLAN filter configuration
++ * @original_packet_filter_mode: If true, filter on original (pre-treatment)
++ *                               packet
++ * @filter_4_tpid_mode: 4-TPID mode (see &enum mxl862xx_extended_vlan_4_tpid_mode)
++ * @outer_vlan: Outer VLAN tag filter
++ * @inner_vlan: Inner VLAN tag filter
++ * @ether_type: EtherType filter (see
++ *              &enum mxl862xx_extended_vlan_filter_ethertype)
++ */
++struct mxl862xx_extendedvlan_filter {
++      u8 original_packet_filter_mode;
++      __le32 filter_4_tpid_mode;
++      struct mxl862xx_extendedvlan_filter_vlan outer_vlan;
++      struct mxl862xx_extendedvlan_filter_vlan inner_vlan;
++      __le32 ether_type;
++} __packed;
++
++/**
++ * struct mxl862xx_extendedvlan_treatment_vlan - Per-tag treatment in
++ *                                               Extended VLAN
++ * @priority_mode: Priority assignment mode
++ *                 (see &enum mxl862xx_extended_vlan_treatment_priority)
++ * @priority_val: Priority value (when mode is VAL)
++ * @vid_mode: VID assignment mode
++ *            (see &enum mxl862xx_extended_vlan_treatment_vid)
++ * @vid_val: VID value (when mode is VAL)
++ * @tpid: TPID assignment mode
++ *        (see &enum mxl862xx_extended_vlan_treatment_tpid)
++ * @dei: DEI assignment mode
++ *       (see &enum mxl862xx_extended_vlan_treatment_dei)
++ */
++struct mxl862xx_extendedvlan_treatment_vlan {
++      __le32 priority_mode;
++      __le32 priority_val;
++      __le32 vid_mode;
++      __le32 vid_val;
++      __le32 tpid;
++      __le32 dei;
++} __packed;
++
++/**
++ * struct mxl862xx_extendedvlan_treatment - Extended VLAN treatment
++ * @remove_tag: Tag removal action
++ *              (see &enum mxl862xx_extended_vlan_treatment_remove_tag)
++ * @treatment_4_tpid_mode: 4-TPID treatment mode
++ * @add_outer_vlan: Add outer VLAN tag
++ * @outer_vlan: Outer VLAN tag treatment parameters
++ * @add_inner_vlan: Add inner VLAN tag
++ * @inner_vlan: Inner VLAN tag treatment parameters
++ * @reassign_bridge_port: Reassign to different bridge port
++ * @new_bridge_port_id: New bridge port ID
++ * @new_dscp_enable: Enable new DSCP assignment
++ * @new_dscp: New DSCP value
++ * @new_traffic_class_enable: Enable new traffic class assignment
++ * @new_traffic_class: New traffic class value
++ * @new_meter_enable: Enable new metering
++ * @s_new_traffic_meter_id: New traffic meter ID
++ * @dscp2pcp_map: DSCP to PCP mapping table (64 entries)
++ * @loopback_enable: Enable loopback
++ * @da_sa_swap_enable: Enable DA/SA swap
++ * @mirror_enable: Enable mirroring
++ */
++struct mxl862xx_extendedvlan_treatment {
++      __le32 remove_tag;
++      __le32 treatment_4_tpid_mode;
++      u8 add_outer_vlan;
++      struct mxl862xx_extendedvlan_treatment_vlan outer_vlan;
++      u8 add_inner_vlan;
++      struct mxl862xx_extendedvlan_treatment_vlan inner_vlan;
++      u8 reassign_bridge_port;
++      __le16 new_bridge_port_id;
++      u8 new_dscp_enable;
++      __le16 new_dscp;
++      u8 new_traffic_class_enable;
++      u8 new_traffic_class;
++      u8 new_meter_enable;
++      __le16 s_new_traffic_meter_id;
++      u8 dscp2pcp_map[64];
++      u8 loopback_enable;
++      u8 da_sa_swap_enable;
++      u8 mirror_enable;
++} __packed;
++
++/**
++ * struct mxl862xx_extendedvlan_alloc - Extended VLAN block allocation
++ * @number_of_entries: Number of entries to allocate (input) / allocated
++ *                     (output)
++ * @extended_vlan_block_id: Block ID assigned by firmware (output on alloc,
++ *                          input on free)
++ *
++ * Used with %MXL862XX_EXTENDEDVLAN_ALLOC and %MXL862XX_EXTENDEDVLAN_FREE.
++ */
++struct mxl862xx_extendedvlan_alloc {
++      __le16 number_of_entries;
++      __le16 extended_vlan_block_id;
++} __packed;
++
++/**
++ * struct mxl862xx_extendedvlan_config - Extended VLAN entry configuration
++ * @extended_vlan_block_id: Block ID from allocation
++ * @entry_index: Entry index within the block
++ * @filter: Filter (match) configuration
++ * @treatment: Treatment (action) configuration
++ *
++ * Used with %MXL862XX_EXTENDEDVLAN_SET and %MXL862XX_EXTENDEDVLAN_GET.
++ */
++struct mxl862xx_extendedvlan_config {
++      __le16 extended_vlan_block_id;
++      __le16 entry_index;
++      struct mxl862xx_extendedvlan_filter filter;
++      struct mxl862xx_extendedvlan_treatment treatment;
++} __packed;
++
++/**
++ * enum mxl862xx_vlan_filter_tci_mask - VLAN Filter TCI mask
++ * @MXL862XX_VLAN_FILTER_TCI_MASK_VID: TCI mask for VLAN ID
++ * @MXL862XX_VLAN_FILTER_TCI_MASK_PCP: TCI mask for VLAN PCP
++ * @MXL862XX_VLAN_FILTER_TCI_MASK_TCI: TCI mask for VLAN TCI
++ */
++enum mxl862xx_vlan_filter_tci_mask {
++      MXL862XX_VLAN_FILTER_TCI_MASK_VID = 0,
++      MXL862XX_VLAN_FILTER_TCI_MASK_PCP = 1,
++      MXL862XX_VLAN_FILTER_TCI_MASK_TCI = 2,
++};
++
++/**
++ * struct mxl862xx_vlanfilter_alloc - VLAN Filter block allocation
++ * @number_of_entries: Number of entries to allocate (input) / allocated
++ *                     (output)
++ * @vlan_filter_block_id: Block ID assigned by firmware (output on alloc,
++ *                        input on free)
++ * @discard_untagged: Discard untagged packets
++ * @discard_unmatched_tagged: Discard tagged packets that do not match any
++ *                            entry in the block
++ * @use_default_port_vid: Use default port VLAN ID for filtering
++ *
++ * Used with %MXL862XX_VLANFILTER_ALLOC and %MXL862XX_VLANFILTER_FREE.
++ */
++struct mxl862xx_vlanfilter_alloc {
++      __le16 number_of_entries;
++      __le16 vlan_filter_block_id;
++      u8 discard_untagged;
++      u8 discard_unmatched_tagged;
++      u8 use_default_port_vid;
++} __packed;
++
++/**
++ * struct mxl862xx_vlanfilter_config - VLAN Filter entry configuration
++ * @vlan_filter_block_id: Block ID from allocation
++ * @entry_index: Entry index within the block
++ * @vlan_filter_mask: TCI field(s) to match (see
++ *                    &enum mxl862xx_vlan_filter_tci_mask)
++ * @val: TCI value(s) to match (VID, PCP, or full TCI depending on mask)
++ * @discard_matched: When true, discard frames matching this entry;
++ *                   when false, allow them
++ *
++ * Used with %MXL862XX_VLANFILTER_SET and %MXL862XX_VLANFILTER_GET.
++ */
++struct mxl862xx_vlanfilter_config {
++      __le16 vlan_filter_block_id;
++      __le16 entry_index;
++      __le32 vlan_filter_mask; /* enum mxl862xx_vlan_filter_tci_mask */
++      __le32 val;
++      u8 discard_matched;
++} __packed;
++
++/**
+  * enum mxl862xx_ss_sp_tag_mask - Special tag valid field indicator bits
+  * @MXL862XX_SS_SP_TAG_MASK_RX: valid RX special tag mode
+  * @MXL862XX_SS_SP_TAG_MASK_TX: valid TX special tag mode
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+@@ -17,6 +17,8 @@
+ #define MXL862XX_CTP_MAGIC            0x500
+ #define MXL862XX_QOS_MAGIC            0x600
+ #define MXL862XX_SWMAC_MAGIC          0xa00
++#define MXL862XX_EXTVLAN_MAGIC                0xb00
++#define MXL862XX_VLANFILTER_MAGIC     0xc00
+ #define MXL862XX_STP_MAGIC            0xf00
+ #define MXL862XX_SS_MAGIC             0x1600
+ #define GPY_GPY2XX_MAGIC              0x1800
+@@ -47,6 +49,16 @@
+ #define MXL862XX_MAC_TABLEENTRYREMOVE (MXL862XX_SWMAC_MAGIC + 0x5)
+ #define MXL862XX_MAC_TABLECLEARCOND   (MXL862XX_SWMAC_MAGIC + 0x8)
++#define MXL862XX_EXTENDEDVLAN_ALLOC   (MXL862XX_EXTVLAN_MAGIC + 0x1)
++#define MXL862XX_EXTENDEDVLAN_SET     (MXL862XX_EXTVLAN_MAGIC + 0x2)
++#define MXL862XX_EXTENDEDVLAN_GET     (MXL862XX_EXTVLAN_MAGIC + 0x3)
++#define MXL862XX_EXTENDEDVLAN_FREE    (MXL862XX_EXTVLAN_MAGIC + 0x4)
++
++#define MXL862XX_VLANFILTER_ALLOC     (MXL862XX_VLANFILTER_MAGIC + 0x1)
++#define MXL862XX_VLANFILTER_SET               (MXL862XX_VLANFILTER_MAGIC + 0x2)
++#define MXL862XX_VLANFILTER_GET               (MXL862XX_VLANFILTER_MAGIC + 0x3)
++#define MXL862XX_VLANFILTER_FREE      (MXL862XX_VLANFILTER_MAGIC + 0x4)
++
+ #define MXL862XX_SS_SPTAG_SET         (MXL862XX_SS_MAGIC + 0x2)
+ #define MXL862XX_STP_PORTCFGSET               (MXL862XX_STP_MAGIC + 0x2)
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -50,6 +50,88 @@ static const int mxl862xx_flood_meters[]
+       MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST,
+ };
++enum mxl862xx_evlan_action {
++      EVLAN_ACCEPT,                   /* pass-through, no tag removal */
++      EVLAN_STRIP_IF_UNTAGGED,        /* remove 1 tag if entry's untagged flag set */
++      EVLAN_DISCARD,                  /* discard upstream */
++      EVLAN_PVID_OR_DISCARD,          /* insert PVID tag or discard if no PVID */
++      EVLAN_PVID_OR_PASS,             /* insert PVID tag or pass-through */
++      EVLAN_STRIP1_AND_PVID_OR_DISCARD,/* strip 1 tag + insert PVID, or discard */
++};
++
++struct mxl862xx_evlan_rule_desc {
++      u8 outer_type;          /* enum mxl862xx_extended_vlan_filter_type */
++      u8 inner_type;          /* enum mxl862xx_extended_vlan_filter_type */
++      u8 outer_tpid;          /* enum mxl862xx_extended_vlan_filter_tpid */
++      u8 inner_tpid;          /* enum mxl862xx_extended_vlan_filter_tpid */
++      bool match_vid;         /* true: match on VID from the vid parameter */
++      u8 action;              /* enum mxl862xx_evlan_action */
++};
++
++/* Shorthand constants for readability */
++#define FT_NORMAL     MXL862XX_EXTENDEDVLAN_FILTER_TYPE_NORMAL
++#define FT_NO_FILTER  MXL862XX_EXTENDEDVLAN_FILTER_TYPE_NO_FILTER
++#define FT_DEFAULT    MXL862XX_EXTENDEDVLAN_FILTER_TYPE_DEFAULT
++#define FT_NO_TAG     MXL862XX_EXTENDEDVLAN_FILTER_TYPE_NO_TAG
++#define TP_NONE               MXL862XX_EXTENDEDVLAN_FILTER_TPID_NO_FILTER
++#define TP_8021Q      MXL862XX_EXTENDEDVLAN_FILTER_TPID_8021Q
++
++/*
++ * VLAN-aware ingress: 7 final catchall rules.
++ *
++ * VLAN Filter handles VID membership for tagged frames, so the
++ * Extended VLAN ingress block only needs to handle:
++ * - Priority-tagged (VID=0): strip + insert PVID
++ * - Untagged: insert PVID or discard
++ * - Standard 802.1Q VID>0: pass through (VF handles membership)
++ * - Non-8021Q TPID (0x88A8 etc.): treat as untagged
++ *
++ * Rule ordering is critical: the EVLAN engine scans entries in
++ * ascending index order and stops at the first match.
++ *
++ * The 802.1Q ACCEPT rules (indices 3--4) must appear BEFORE the
++ * NO_FILTER catchalls (indices 5--6). NO_FILTER matches any tag
++ * regardless of TPID, so without the ACCEPT guard, it would also
++ * catch standard 802.1Q VID>0 frames and corrupt them. With the
++ * guard, 802.1Q VID>0 frames match the ACCEPT rules first and
++ * pass through untouched; only non-8021Q TPID frames fall through
++ * to the NO_FILTER catchalls.
++ */
++static const struct mxl862xx_evlan_rule_desc ingress_aware_final[] = {
++      /* 802.1p / priority-tagged (VID 0): strip + PVID */
++      { FT_NORMAL,    FT_NORMAL, TP_8021Q, TP_8021Q, true,  EVLAN_STRIP1_AND_PVID_OR_DISCARD },
++      { FT_NORMAL,    FT_NO_TAG, TP_8021Q, TP_NONE,  true,  EVLAN_STRIP1_AND_PVID_OR_DISCARD },
++      /* Untagged: PVID insertion or discard */
++      { FT_NO_TAG,    FT_NO_TAG, TP_NONE,  TP_NONE,  false, EVLAN_PVID_OR_DISCARD },
++      /* 802.1Q VID>0: accept - VF handles membership.
++       * match_vid=false means any VID; VID=0 is already caught above.
++       */
++      { FT_NORMAL,    FT_NORMAL, TP_8021Q, TP_8021Q, false, EVLAN_ACCEPT },
++      { FT_NORMAL,    FT_NO_TAG, TP_8021Q, TP_NONE,  false, EVLAN_ACCEPT },
++      /* Non-8021Q TPID (0x88A8 etc.): treat as untagged - strip + PVID */
++      { FT_NO_FILTER, FT_NO_FILTER, TP_NONE, TP_NONE, false, EVLAN_STRIP1_AND_PVID_OR_DISCARD },
++      { FT_NO_FILTER, FT_NO_TAG,    TP_NONE, TP_NONE, false, EVLAN_STRIP1_AND_PVID_OR_DISCARD },
++};
++
++/*
++ * VID-specific accept rules (VLAN-aware, standard tag, 2 per VID).
++ * Outer tag carries the VLAN; inner may or may not be present.
++ */
++static const struct mxl862xx_evlan_rule_desc vid_accept_standard[] = {
++      { FT_NORMAL, FT_NORMAL, TP_8021Q, TP_8021Q, true, EVLAN_STRIP_IF_UNTAGGED },
++      { FT_NORMAL, FT_NO_TAG, TP_8021Q, TP_NONE,  true, EVLAN_STRIP_IF_UNTAGGED },
++};
++
++/*
++ * VID-specific accept rules for VLAN-unaware egress.
++ * The HW sees the MxL tag as outer, real VLAN tag as inner.
++ * match on inner VID with outer=NO_FILTER.
++ */
++static const struct mxl862xx_evlan_rule_desc vid_accept_egress_unaware[] = {
++      { FT_NO_FILTER, FT_NORMAL, TP_NONE, TP_8021Q, true, EVLAN_STRIP_IF_UNTAGGED },
++      { FT_NO_FILTER, FT_NO_TAG, TP_NONE, TP_NONE,  true, EVLAN_STRIP_IF_UNTAGGED },
++};
++
+ static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds,
+                                                      int port,
+                                                      enum dsa_tag_protocol m)
+@@ -275,6 +357,7 @@ static int mxl862xx_set_bridge_port(stru
+       struct mxl862xx_port *p = &priv->ports[port];
+       u16 bridge_id = dp->bridge ?
+               priv->bridges[dp->bridge->num] : p->fid;
++      u16 vf_scan;
+       bool enable;
+       int i, idx;
+@@ -283,9 +366,69 @@ static int mxl862xx_set_bridge_port(stru
+       br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
+                                      MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
+                                      MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
+-                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER);
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER |
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN |
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN |
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN_FILTER |
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER1 |
++                                     MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING);
+       br_port_cfg.src_mac_learning_disable = !p->learning;
++      /* Extended VLAN block assignments.
++       * Ingress: block_size is sent as-is (all entries are finals).
++       * Egress: n_active narrows the scan window to only the
++       * entries actually written by evlan_program_egress.
++       */
++      br_port_cfg.ingress_extended_vlan_enable = p->ingress_evlan.in_use;
++      br_port_cfg.ingress_extended_vlan_block_id =
++              cpu_to_le16(p->ingress_evlan.block_id);
++      br_port_cfg.ingress_extended_vlan_block_size =
++              cpu_to_le16(p->ingress_evlan.block_size);
++      br_port_cfg.egress_extended_vlan_enable = p->egress_evlan.in_use;
++      br_port_cfg.egress_extended_vlan_block_id =
++              cpu_to_le16(p->egress_evlan.block_id);
++      br_port_cfg.egress_extended_vlan_block_size =
++              cpu_to_le16(p->egress_evlan.n_active);
++
++      /* VLAN Filter block assignments (per-port).
++       * The block_size sent to the firmware narrows the HW scan
++       * window to [block_id, block_id + active_count), relying on
++       * discard_unmatched_tagged for frames outside that range.
++       * When active_count=0, send 1 to scan only the DISCARD
++       * sentinel at index 0 (block_size=0 would disable narrowing
++       * and scan the entire allocated block).
++       *
++       * The bridge check ensures VF is disabled when the port
++       * leaves the bridge, without needing to prematurely clear
++       * vlan_filtering (which the DSA framework handles later via
++       * port_vlan_filtering).
++       */
++      if (p->vf.allocated && p->vlan_filtering &&
++          dsa_port_bridge_dev_get(dp)) {
++              vf_scan = max_t(u16, p->vf.active_count, 1);
++              br_port_cfg.ingress_vlan_filter_enable = 1;
++              br_port_cfg.ingress_vlan_filter_block_id =
++                      cpu_to_le16(p->vf.block_id);
++              br_port_cfg.ingress_vlan_filter_block_size =
++                      cpu_to_le16(vf_scan);
++
++              br_port_cfg.egress_vlan_filter1enable = 1;
++              br_port_cfg.egress_vlan_filter1block_id =
++                      cpu_to_le16(p->vf.block_id);
++              br_port_cfg.egress_vlan_filter1block_size =
++                      cpu_to_le16(vf_scan);
++      } else {
++              br_port_cfg.ingress_vlan_filter_enable = 0;
++              br_port_cfg.egress_vlan_filter1enable = 0;
++      }
++
++      /* IVL when VLAN-aware: include VID in FDB lookup keys so that
++       * learned entries are per-VID. In VLAN-unaware mode, SVL is
++       * used (VID excluded from key).
++       */
++      br_port_cfg.vlan_src_mac_vid_enable = p->vlan_filtering;
++      br_port_cfg.vlan_dst_mac_vid_enable = p->vlan_filtering;
++
+       mxl862xx_fw_portmap_from_bitmap(br_port_cfg.bridge_port_map, p->portmap);
+       for (i = 0; i < ARRAY_SIZE(mxl862xx_flood_meters); i++) {
+@@ -329,13 +472,131 @@ static int mxl862xx_sync_bridge_members(
+       return ret;
+ }
++static void mxl862xx_evlan_block_init(struct mxl862xx_evlan_block *blk,
++                                    u16 size)
++{
++      blk->allocated = false;
++      blk->in_use = false;
++      blk->block_id = 0;
++      blk->block_size = size;
++      blk->n_active = 0;
++}
++
++static int mxl862xx_evlan_block_alloc(struct mxl862xx_priv *priv,
++                                    struct mxl862xx_evlan_block *blk)
++{
++      struct mxl862xx_extendedvlan_alloc param = {};
++      int ret;
++
++      param.number_of_entries = cpu_to_le16(blk->block_size);
++
++      ret = MXL862XX_API_READ(priv, MXL862XX_EXTENDEDVLAN_ALLOC, param);
++      if (ret)
++              return ret;
++
++      blk->block_id = le16_to_cpu(param.extended_vlan_block_id);
++      blk->allocated = true;
++
++      return 0;
++}
++
++/**
++ * mxl862xx_vf_init - Initialize per-port VF block software state
++ * @vf: VLAN Filter block to initialize
++ * @size: block size (entries per port)
++ */
++static void mxl862xx_vf_init(struct mxl862xx_vf_block *vf, u16 size)
++{
++      vf->allocated = false;
++      vf->block_id = 0;
++      vf->block_size = size;
++      vf->active_count = 0;
++      INIT_LIST_HEAD(&vf->vids);
++}
++
++/**
++ * mxl862xx_vf_block_alloc - Allocate a VLAN Filter block from firmware
++ * @priv: driver private data
++ * @size: number of entries to allocate
++ * @block_id: output -- block ID assigned by firmware
++ *
++ * Allocates a contiguous VLAN Filter block and configures it to discard
++ * unmatched tagged frames (VID membership enforcement).
++ */
++static int mxl862xx_vf_block_alloc(struct mxl862xx_priv *priv,
++                                 u16 size, u16 *block_id)
++{
++      struct mxl862xx_vlanfilter_alloc param = {};
++      int ret;
++
++      param.number_of_entries = cpu_to_le16(size);
++      param.discard_untagged = 0;
++      param.discard_unmatched_tagged = 1;
++
++      ret = MXL862XX_API_READ(priv, MXL862XX_VLANFILTER_ALLOC, param);
++      if (ret)
++              return ret;
++
++      *block_id = le16_to_cpu(param.vlan_filter_block_id);
++      return 0;
++}
++
++/**
++ * mxl862xx_vf_entry_discard - Write a DISCARD entry to plug an unused slot
++ * @priv: driver private data
++ * @block_id: HW VLAN Filter block ID
++ * @index: entry index within the block
++ *
++ * Unwritten VLAN Filter entries default to VID=0 / ALLOW which would
++ * leak VID 0 traffic. This writes a DISCARD entry to plug the slot.
++ */
++static int mxl862xx_vf_entry_discard(struct mxl862xx_priv *priv,
++                                   u16 block_id, u16 index)
++{
++      struct mxl862xx_vlanfilter_config cfg = {};
++
++      cfg.vlan_filter_block_id = cpu_to_le16(block_id);
++      cfg.entry_index = cpu_to_le16(index);
++      cfg.vlan_filter_mask = cpu_to_le32(MXL862XX_VLAN_FILTER_TCI_MASK_VID);
++      cfg.val = cpu_to_le32(0);
++      cfg.discard_matched = 1;
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_VLANFILTER_SET, cfg);
++}
++
++/**
++ * mxl862xx_vf_alloc - Allocate the port's VF HW block
++ * @priv: driver private data
++ * @vf: VLAN Filter block (must have been initialized via mxl862xx_vf_init)
++ *
++ * Allocates the block and writes a DISCARD sentinel at index 0 so that
++ * when active_count is 0, the single-entry scan window blocks VID-0
++ * (which would otherwise match the zeroed-out default and be allowed).
++ * Called once per port from port_setup.
++ */
++static int mxl862xx_vf_alloc(struct mxl862xx_priv *priv,
++                           struct mxl862xx_vf_block *vf)
++{
++      int ret;
++
++      ret = mxl862xx_vf_block_alloc(priv, vf->block_size, &vf->block_id);
++      if (ret)
++              return ret;
++
++      vf->allocated = true;
++      vf->active_count = 0;
++
++      /* Sentinel: block VID-0 when scan window covers only index 0 */
++      return mxl862xx_vf_entry_discard(priv, vf->block_id, 0);
++}
++
+ /**
+  * mxl862xx_allocate_bridge - Allocate a firmware bridge instance
+  * @priv: driver private data
+  * @bridge_id: output -- firmware bridge ID assigned by the firmware
+  *
+  * Newly allocated bridges default to flooding all traffic classes
+- * (unknown unicast, multicast, broadcast).  Callers that need
++ * (unknown unicast, multicast, broadcast). Callers that need
+  * different forwarding behavior must call mxl862xx_bridge_config_fwd()
+  * after allocation.
+  *
+@@ -404,6 +665,9 @@ static int mxl862xx_add_single_port_brid
+ static int mxl862xx_setup(struct dsa_switch *ds)
+ {
+       struct mxl862xx_priv *priv = ds->priv;
++      int n_user_ports = 0, max_vlans;
++      int ingress_finals, vid_rules;
++      struct dsa_port *dp;
+       int ret;
+       ret = mxl862xx_reset(priv);
+@@ -414,6 +678,50 @@ static int mxl862xx_setup(struct dsa_swi
+       if (ret)
+               return ret;
++      /* Calculate Extended VLAN block sizes.
++       * With VLAN Filter handling VID membership checks:
++       *   Ingress: only final catchall rules (PVID insertion, 802.1Q
++       *            accept, non-8021Q TPID handling, discard).
++       *            Block sized to exactly fit the finals -- no per-VID
++       *            ingress EVLAN rules are needed. (7 entries.)
++       *   Egress:  2 rules per VID that needs tag stripping (untagged VIDs).
++       *            No egress final catchalls -- VLAN Filter does the discard.
++       *   CPU:     EVLAN is left disabled on CPU ports -- frames pass
++       *            through without EVLAN processing.
++       *
++       * Total EVLAN budget:
++       *   n_user_ports * (ingress + egress) ≤ 1024.
++       * Ingress blocks are small (7 entries), so almost all capacity
++       * goes to egress VID rules.
++       */
++      dsa_switch_for_each_user_port(dp, ds)
++              n_user_ports++;
++
++      if (n_user_ports) {
++              ingress_finals = ARRAY_SIZE(ingress_aware_final);
++              vid_rules = ARRAY_SIZE(vid_accept_standard);
++
++              /* Ingress block: fixed at finals count (7 entries) */
++              priv->evlan_ingress_size = ingress_finals;
++
++              /* Egress block: remaining budget divided equally among
++               * user ports. Each untagged VID needs vid_rules (2)
++               * EVLAN entries for tag stripping. Tagged-only VIDs
++               * need no EVLAN rules at all.
++               */
++              max_vlans = (MXL862XX_TOTAL_EVLAN_ENTRIES -
++                           n_user_ports * ingress_finals) /
++                          (n_user_ports * vid_rules);
++              priv->evlan_egress_size = vid_rules * max_vlans;
++
++              /* VLAN Filter block: one per user port. The 1024-entry
++               * table is divided equally among user ports. Each port
++               * gets its own VF block for per-port VID membership --
++               * discard_unmatched_tagged handles the rest.
++               */
++              priv->vf_block_size = MXL862XX_TOTAL_VF_ENTRIES / n_user_ports;
++      }
++
+       ret = mxl862xx_setup_drop_meter(ds);
+       if (ret)
+               return ret;
+@@ -495,27 +803,616 @@ static int mxl862xx_configure_sp_tag_pro
+       return MXL862XX_API_WRITE(ds->priv, MXL862XX_SS_SPTAG_SET, tag);
+ }
++/**
++ * mxl862xx_evlan_write_rule - Write a single Extended VLAN rule to hardware
++ * @priv: driver private data
++ * @block_id: HW Extended VLAN block ID
++ * @entry_index: entry index within the block
++ * @desc: rule descriptor (filter type + action)
++ * @vid: VLAN ID for VID-specific rules (ignored when !desc->match_vid)
++ * @untagged: strip tag on egress for EVLAN_STRIP_IF_UNTAGGED action
++ * @pvid: port VLAN ID for PVID insertion rules (0 = no PVID)
++ *
++ * Translates a compact rule descriptor into a full firmware
++ * mxl862xx_extendedvlan_config struct and writes it via the API.
++ */
++static int mxl862xx_evlan_write_rule(struct mxl862xx_priv *priv,
++                                   u16 block_id, u16 entry_index,
++                                   const struct mxl862xx_evlan_rule_desc *desc,
++                                   u16 vid, bool untagged, u16 pvid)
++{
++      struct mxl862xx_extendedvlan_config cfg = {};
++      struct mxl862xx_extendedvlan_filter_vlan *fv;
++
++      cfg.extended_vlan_block_id = cpu_to_le16(block_id);
++      cfg.entry_index = cpu_to_le16(entry_index);
++
++      /* Populate filter */
++      cfg.filter.outer_vlan.type = cpu_to_le32(desc->outer_type);
++      cfg.filter.inner_vlan.type = cpu_to_le32(desc->inner_type);
++      cfg.filter.outer_vlan.tpid = cpu_to_le32(desc->outer_tpid);
++      cfg.filter.inner_vlan.tpid = cpu_to_le32(desc->inner_tpid);
++
++      if (desc->match_vid) {
++              /* For egress unaware: outer=NO_FILTER, match on inner tag */
++              if (desc->outer_type == FT_NO_FILTER)
++                      fv = &cfg.filter.inner_vlan;
++              else
++                      fv = &cfg.filter.outer_vlan;
++
++              fv->vid_enable = 1;
++              fv->vid_val = cpu_to_le32(vid);
++      }
++
++      /* Populate treatment based on action */
++      switch (desc->action) {
++      case EVLAN_ACCEPT:
++              cfg.treatment.remove_tag =
++                      cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
++              break;
++
++      case EVLAN_STRIP_IF_UNTAGGED:
++              cfg.treatment.remove_tag = cpu_to_le32(untagged ?
++                      MXL862XX_EXTENDEDVLAN_TREATMENT_REMOVE_1_TAG :
++                      MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
++              break;
++
++      case EVLAN_DISCARD:
++              cfg.treatment.remove_tag =
++                      cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_DISCARD_UPSTREAM);
++              break;
++
++      case EVLAN_PVID_OR_DISCARD:
++              if (pvid) {
++                      cfg.treatment.remove_tag =
++                              cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
++                      cfg.treatment.add_outer_vlan = 1;
++                      cfg.treatment.outer_vlan.vid_mode =
++                              cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_VID_VAL);
++                      cfg.treatment.outer_vlan.vid_val = cpu_to_le32(pvid);
++                      cfg.treatment.outer_vlan.tpid =
++                              cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_8021Q);
++              } else {
++                      cfg.treatment.remove_tag =
++                              cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_DISCARD_UPSTREAM);
++              }
++              break;
++
++      case EVLAN_PVID_OR_PASS:
++              if (pvid) {
++                      cfg.treatment.remove_tag =
++                              cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
++                      cfg.treatment.add_outer_vlan = 1;
++                      cfg.treatment.outer_vlan.vid_mode =
++                              cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_VID_VAL);
++                      cfg.treatment.outer_vlan.vid_val = cpu_to_le32(pvid);
++                      cfg.treatment.outer_vlan.tpid =
++                              cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_8021Q);
++              } else {
++                      cfg.treatment.remove_tag =
++                              cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
++              }
++              break;
++
++      case EVLAN_STRIP1_AND_PVID_OR_DISCARD:
++              if (pvid) {
++                      cfg.treatment.remove_tag =
++                              cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_REMOVE_1_TAG);
++                      cfg.treatment.add_outer_vlan = 1;
++                      cfg.treatment.outer_vlan.vid_mode =
++                              cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_VID_VAL);
++                      cfg.treatment.outer_vlan.vid_val = cpu_to_le32(pvid);
++                      cfg.treatment.outer_vlan.tpid =
++                              cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_8021Q);
++              } else {
++                      cfg.treatment.remove_tag =
++                              cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_DISCARD_UPSTREAM);
++              }
++              break;
++      }
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_EXTENDEDVLAN_SET, cfg);
++}
++
++/**
++ * mxl862xx_evlan_deactivate_entry - Reset an Extended VLAN entry to no-op
++ * @priv: driver private data
++ * @block_id: HW Extended VLAN block ID
++ * @entry_index: entry index within the block
++ *
++ * Writes a zeroed-out config to the firmware, which deactivates the
++ * rule (making it transparent / no-op).
++ */
++static int mxl862xx_evlan_deactivate_entry(struct mxl862xx_priv *priv,
++                                         u16 block_id, u16 entry_index)
++{
++      struct mxl862xx_extendedvlan_config cfg = {};
++
++      cfg.extended_vlan_block_id = cpu_to_le16(block_id);
++      cfg.entry_index = cpu_to_le16(entry_index);
++
++      /* Use an unreachable filter (DEFAULT+DEFAULT) with DISCARD treatment.
++       * A zeroed entry would have NORMAL+NORMAL filter which matches
++       * real double-tagged traffic and passes it through.
++       */
++      cfg.filter.outer_vlan.type =
++              cpu_to_le32(MXL862XX_EXTENDEDVLAN_FILTER_TYPE_DEFAULT);
++      cfg.filter.inner_vlan.type =
++              cpu_to_le32(MXL862XX_EXTENDEDVLAN_FILTER_TYPE_DEFAULT);
++      cfg.treatment.remove_tag =
++              cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_DISCARD_UPSTREAM);
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_EXTENDEDVLAN_SET, cfg);
++}
++
++/**
++ * mxl862xx_evlan_write_final_rules - Write catchall rules to the ingress block
++ * @priv: driver private data
++ * @blk: Extended VLAN block (already allocated)
++ * @rules: array of rule descriptors for the final rules
++ * @n_rules: number of final rules
++ * @pvid: port VLAN ID (for PVID insertion rules)
++ *
++ * Writes final catchall rules starting at block_size - n_rules. With
++ * VLAN Filter handling VID membership, only the ingress block uses
++ * finals, and the block is sized to exactly fit them (no VID entries),
++ * so the rules fill the entire block.
++ */
++static int mxl862xx_evlan_write_final_rules(struct mxl862xx_priv *priv,
++                                          struct mxl862xx_evlan_block *blk,
++                                          const struct mxl862xx_evlan_rule_desc *rules,
++                                          int n_rules, u16 pvid)
++{
++      u16 start_idx = blk->block_size - n_rules;
++      int i, ret;
++
++      for (i = 0; i < n_rules; i++) {
++              ret = mxl862xx_evlan_write_rule(priv, blk->block_id,
++                                              start_idx + i, &rules[i],
++                                              0, false, pvid);
++              if (ret)
++                      return ret;
++      }
++
++      return 0;
++}
++
++/**
++ * mxl862xx_vf_entry_set - Write a single VLAN Filter entry
++ * @priv: driver private data
++ * @block_id: HW VLAN Filter block ID
++ * @index: entry index within the block
++ * @vid: VLAN ID to allow
++ *
++ * Writes an ALLOW entry (discard_matched=false) for the given VID.
++ */
++static int mxl862xx_vf_entry_set(struct mxl862xx_priv *priv,
++                               u16 block_id, u16 index, u16 vid)
++{
++      struct mxl862xx_vlanfilter_config cfg = {};
++
++      cfg.vlan_filter_block_id = cpu_to_le16(block_id);
++      cfg.entry_index = cpu_to_le16(index);
++      cfg.vlan_filter_mask = cpu_to_le32(MXL862XX_VLAN_FILTER_TCI_MASK_VID);
++      cfg.val = cpu_to_le32(vid);
++      cfg.discard_matched = 0;
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_VLANFILTER_SET, cfg);
++}
++
++/**
++ * mxl862xx_vf_find_vid - Find a VID entry in a VF block
++ * @vf: VLAN Filter block to search
++ * @vid: VLAN ID to find
++ */
++static struct mxl862xx_vf_vid *
++mxl862xx_vf_find_vid(struct mxl862xx_vf_block *vf, u16 vid)
++{
++      struct mxl862xx_vf_vid *ve;
++
++      list_for_each_entry(ve, &vf->vids, list)
++              if (ve->vid == vid)
++                      return ve;
++
++      return NULL;
++}
++
++/**
++ * mxl862xx_vf_add_vid - Add a VID to a port's VLAN Filter block
++ * @priv: driver private data
++ * @vf: VLAN Filter block
++ * @vid: VLAN ID to add
++ * @untagged: whether this VID should strip tags on egress
++ *
++ * Idempotent. Writes an ALLOW entry at active_count and increments
++ * active_count. If the VID already exists, only the untagged flag
++ * is updated. The HW block must be allocated before calling this.
++ */
++static int mxl862xx_vf_add_vid(struct mxl862xx_priv *priv,
++                             struct mxl862xx_vf_block *vf,
++                             u16 vid, bool untagged)
++{
++      struct mxl862xx_vf_vid *ve;
++      int ret;
++
++      ve = mxl862xx_vf_find_vid(vf, vid);
++      if (ve) {
++              ve->untagged = untagged;
++              return 0;
++      }
++
++      if (vf->active_count >= vf->block_size)
++              return -ENOSPC;
++
++      ve = kzalloc(sizeof(*ve), GFP_KERNEL);
++      if (!ve)
++              return -ENOMEM;
++
++      ve->vid = vid;
++      ve->index = vf->active_count;
++      ve->untagged = untagged;
++
++      ret = mxl862xx_vf_entry_set(priv, vf->block_id, ve->index, vid);
++      if (ret) {
++              kfree(ve);
++              return ret;
++      }
++
++      list_add_tail(&ve->list, &vf->vids);
++      vf->active_count++;
++
++      return 0;
++}
++
++/**
++ * mxl862xx_vf_del_vid - Remove a VID from a port's VLAN Filter block
++ * @priv: driver private data
++ * @vf: VLAN Filter block
++ * @vid: VLAN ID to remove
++ *
++ * Swap-compacts: the last active entry is moved into the gap,
++ * active_count is decremented, and the old last slot is plugged
++ * with DISCARD. When active_count drops to 0, a DISCARD sentinel
++ * is restored at index 0.
++ */
++static int mxl862xx_vf_del_vid(struct mxl862xx_priv *priv,
++                             struct mxl862xx_vf_block *vf, u16 vid)
++{
++      struct mxl862xx_vf_vid *ve, *last_ve;
++      u16 gap, last;
++      int ret;
++
++      ve = mxl862xx_vf_find_vid(vf, vid);
++      if (!ve)
++              return 0;
++
++      if (!vf->allocated) {
++              /* Software-only state -- just remove the tracking entry */
++              list_del(&ve->list);
++              kfree(ve);
++              vf->active_count--;
++              return 0;
++      }
++
++      gap = ve->index;
++      last = vf->active_count - 1;
++
++      if (vf->active_count == 1) {
++              /* Last VID -- restore DISCARD sentinel at index 0 */
++              ret = mxl862xx_vf_entry_discard(priv, vf->block_id, 0);
++              if (ret)
++                      return ret;
++      } else if (gap < last) {
++              /* Swap: move the last ALLOW entry into the gap */
++              last_ve = NULL;
++              list_for_each_entry(last_ve, &vf->vids, list)
++                      if (last_ve->index == last)
++                              break;
++
++              if (WARN_ON(!last_ve || last_ve->index != last))
++                      return -EINVAL;
++
++              ret = mxl862xx_vf_entry_set(priv, vf->block_id,
++                                          gap, last_ve->vid);
++              if (ret)
++                      return ret;
++
++              last_ve->index = gap;
++
++              /* Plug the old last slot with DISCARD */
++              ret = mxl862xx_vf_entry_discard(priv, vf->block_id, last);
++              if (ret)
++                      return ret;
++      } else {
++              /* Deleting the last entry -- just plug it */
++              ret = mxl862xx_vf_entry_discard(priv, vf->block_id, last);
++              if (ret)
++                      return ret;
++      }
++
++      list_del(&ve->list);
++      kfree(ve);
++      vf->active_count--;
++
++      return 0;
++}
++
++/**
++ * mxl862xx_evlan_program_ingress - Write the fixed ingress catchall rules
++ * @priv: driver private data
++ * @port: port number
++ *
++ * In VLAN-aware mode the ingress EVLAN block handles PVID insertion for
++ * untagged/priority-tagged frames, passes through standard 802.1Q
++ * tagged frames for VF membership checking, and treats non-8021Q TPID
++ * frames as untagged. The block is sized to exactly fit the 7 catchall
++ * rules and is rewritten whenever PVID changes.
++ *
++ * In VLAN-unaware mode the firmware passes frames through unchanged when
++ * no ingress block is assigned, so nothing is programmed.
++ */
++static int mxl862xx_evlan_program_ingress(struct mxl862xx_priv *priv, int port)
++{
++      struct mxl862xx_port *p = &priv->ports[port];
++      struct mxl862xx_evlan_block *blk = &p->ingress_evlan;
++
++      if (!p->vlan_filtering)
++              return 0;
++
++      blk->in_use = true;
++      blk->n_active = blk->block_size;
++
++      return mxl862xx_evlan_write_final_rules(priv, blk,
++                                              ingress_aware_final,
++                                              ARRAY_SIZE(ingress_aware_final),
++                                              p->pvid);
++}
++
++/**
++ * mxl862xx_evlan_program_egress - Reprogram all egress tag-stripping rules
++ * @priv: driver private data
++ * @port: port number
++ *
++ * Walks the port's VF VID list and writes 2 EVLAN rules per VID that
++ * needs egress tag stripping. In VLAN-aware mode only untagged VIDs
++ * need rules (tagged VIDs pass through EVLAN untouched). In unaware
++ * mode every VID gets rules.
++ *
++ * Entries are packed starting at index 0, and the scan window
++ * (n_active) is narrowed so stale entries beyond it are never matched.
++ */
++static int mxl862xx_evlan_program_egress(struct mxl862xx_priv *priv, int port)
++{
++      struct mxl862xx_port *p = &priv->ports[port];
++      struct mxl862xx_evlan_block *blk = &p->egress_evlan;
++      const struct mxl862xx_evlan_rule_desc *vid_rules;
++      struct mxl862xx_vf_vid *vfv;
++      u16 old_active = blk->n_active;
++      u16 idx = 0, i;
++      int n_vid, ret;
++
++      if (p->vlan_filtering) {
++              vid_rules = vid_accept_standard;
++              n_vid = ARRAY_SIZE(vid_accept_standard);
++      } else {
++              vid_rules = vid_accept_egress_unaware;
++              n_vid = ARRAY_SIZE(vid_accept_egress_unaware);
++      }
++
++      list_for_each_entry(vfv, &p->vf.vids, list) {
++              /* In VLAN-aware mode tagged-only VIDs need no EVLAN
++               * rules -- VLAN Filter handles membership.
++               */
++              if (p->vlan_filtering && !vfv->untagged)
++                      continue;
++
++              if (idx + n_vid > blk->block_size)
++                      return -ENOSPC;
++
++              ret = mxl862xx_evlan_write_rule(priv, blk->block_id,
++                                              idx++, &vid_rules[0],
++                                              vfv->vid, vfv->untagged,
++                                              p->pvid);
++              if (ret)
++                      return ret;
++
++              if (n_vid > 1) {
++                      ret = mxl862xx_evlan_write_rule(priv, blk->block_id,
++                                                      idx++, &vid_rules[1],
++                                                      vfv->vid,
++                                                      vfv->untagged,
++                                                      p->pvid);
++                      if (ret)
++                              return ret;
++              }
++      }
++
++      /* Deactivate stale entries that are no longer needed.
++       * This closes the brief window between writing the new rules
++       * and set_bridge_port narrowing the scan window.
++       */
++      for (i = idx; i < old_active; i++) {
++              ret = mxl862xx_evlan_deactivate_entry(priv,
++                                                    blk->block_id,
++                                                    i);
++              if (ret)
++                      return ret;
++      }
++
++      blk->n_active = idx;
++      blk->in_use = idx > 0;
++
++      return 0;
++}
++
++static int mxl862xx_port_vlan_filtering(struct dsa_switch *ds, int port,
++                                      bool vlan_filtering,
++                                      struct netlink_ext_ack *extack)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
++      bool changed = (p->vlan_filtering != vlan_filtering);
++      int ret;
++
++      p->vlan_filtering = vlan_filtering;
++
++      /* Reprogram Extended VLAN rules if filtering mode changed */
++      if (changed) {
++              /* When leaving VLAN-aware mode, release the ingress HW
++               * block. The firmware passes frames through unchanged
++               * when no ingress EVLAN block is assigned, so the block
++               * is unnecessary in unaware mode.
++               */
++              if (!vlan_filtering)
++                      p->ingress_evlan.in_use = false;
++
++              ret = mxl862xx_evlan_program_ingress(priv, port);
++              if (ret)
++                      return ret;
++
++              ret = mxl862xx_evlan_program_egress(priv, port);
++              if (ret)
++                      return ret;
++      }
++
++      /* Push VLAN-based MAC learning flags and (possibly newly
++       * allocated) ingress block to hardware.
++       */
++      return mxl862xx_set_bridge_port(ds, port);
++}
++
++static int mxl862xx_port_vlan_add(struct dsa_switch *ds, int port,
++                                const struct switchdev_obj_port_vlan *vlan,
++                                struct netlink_ext_ack *extack)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
++      bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
++      u16 vid = vlan->vid;
++      u16 old_pvid = p->pvid;
++      bool pvid_changed = false;
++      int ret;
++
++      /* CPU port is VLAN-transparent: the SP tag handles port
++       * identification and the host-side DSA tagger manages VLAN
++       * delivery. Egress EVLAN catchalls are set up once in
++       * setup_cpu_bridge; no per-VID VF/EVLAN programming needed.
++       */
++      if (dsa_is_cpu_port(ds, port))
++              return 0;
++
++      /* Update PVID tracking */
++      if (vlan->flags & BRIDGE_VLAN_INFO_PVID) {
++              if (p->pvid != vid) {
++                      p->pvid = vid;
++                      pvid_changed = true;
++              }
++      } else if (p->pvid == vid) {
++              p->pvid = 0;
++              pvid_changed = true;
++      }
++
++      /* Add/update VID in this port's VLAN Filter block.
++       * VF must be updated before programming egress EVLAN because
++       * evlan_program_egress walks the VF VID list.
++       */
++      ret = mxl862xx_vf_add_vid(priv, &p->vf, vid, untagged);
++      if (ret)
++              goto err_pvid;
++
++      /* Reprogram ingress finals if PVID changed */
++      if (pvid_changed) {
++              ret = mxl862xx_evlan_program_ingress(priv, port);
++              if (ret)
++                      goto err_pvid;
++      }
++
++      /* Reprogram egress tag-stripping rules (walks VF VID list) */
++      ret = mxl862xx_evlan_program_egress(priv, port);
++      if (ret)
++              goto err_pvid;
++
++      /* Apply VLAN block IDs and MAC learning flags to bridge port */
++      ret = mxl862xx_set_bridge_port(ds, port);
++      if (ret)
++              goto err_pvid;
++
++      return 0;
++
++err_pvid:
++      p->pvid = old_pvid;
++      return ret;
++}
++
++static int mxl862xx_port_vlan_del(struct dsa_switch *ds, int port,
++                                const struct switchdev_obj_port_vlan *vlan)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
++      u16 vid = vlan->vid;
++      bool pvid_changed = false;
++      int ret;
++
++      if (dsa_is_cpu_port(ds, port))
++              return 0;
++
++      /* Clear PVID if we're deleting it */
++      if (p->pvid == vid) {
++              p->pvid = 0;
++              pvid_changed = true;
++      }
++
++      /* Remove VID from this port's VLAN Filter block.
++       * Must happen before egress reprogram so the VID is no
++       * longer in the list that evlan_program_egress walks.
++       */
++      ret = mxl862xx_vf_del_vid(priv, &p->vf, vid);
++      if (ret)
++              return ret;
++
++      /* Reprogram egress tag-stripping rules (VID is now gone) */
++      ret = mxl862xx_evlan_program_egress(priv, port);
++      if (ret)
++              return ret;
++
++      /* If PVID changed, reprogram ingress finals */
++      if (pvid_changed) {
++              ret = mxl862xx_evlan_program_ingress(priv, port);
++              if (ret)
++                      return ret;
++      }
++
++      return mxl862xx_set_bridge_port(ds, port);
++}
++
+ static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port)
+ {
+       struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
+       struct dsa_port *dp;
+-      priv->ports[port].fid = MXL862XX_DEFAULT_BRIDGE;
+-      priv->ports[port].learning = true;
++      p->fid = MXL862XX_DEFAULT_BRIDGE;
++      p->learning = true;
++
++      /* EVLAN is left disabled on CPU ports -- frames pass through
++       * without EVLAN processing. Only the portmap and bridge
++       * assignment need to be configured.
++       */
+       /* include all assigned user ports in the CPU portmap */
+-      bitmap_zero(priv->ports[port].portmap, MXL862XX_MAX_BRIDGE_PORTS);
++      bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
+       dsa_switch_for_each_user_port(dp, ds) {
+               /* it's safe to rely on cpu_dp being valid for user ports */
+               if (dp->cpu_dp->index != port)
+                       continue;
+-              __set_bit(dp->index, priv->ports[port].portmap);
++              __set_bit(dp->index, p->portmap);
+       }
+       return mxl862xx_set_bridge_port(ds, port);
+ }
++
+ static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
+                                    const struct dsa_bridge bridge,
+                                    bool *tx_fwd_offload,
+@@ -565,6 +1462,22 @@ static void mxl862xx_port_bridge_leave(s
+       bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
+       __set_bit(dp->cpu_dp->index, p->portmap);
+       p->flood_block = 0;
++
++      /* Detach EVLAN and VF blocks from the bridge port BEFORE freeing
++       * them. The firmware tracks a usage count per block and rejects
++       * FREE while the count is non-zero.
++       *
++       * For EVLAN: setting in_use=false makes set_bridge_port send
++       * enable=false, which decrements the firmware refcount.
++       *
++       * For VF: set_bridge_port sees dp->bridge == NULL (DSA already
++       * cleared it) and sends vlan_filter_enable=0, which decrements
++       * the firmware VF refcount.
++       */
++      p->pvid = 0;
++      p->ingress_evlan.in_use = false;
++      p->egress_evlan.in_use = false;
++
+       err = mxl862xx_set_bridge_port(ds, port);
+       if (err)
+               dev_err(ds->dev,
+@@ -614,6 +1527,28 @@ static int mxl862xx_port_setup(struct ds
+       if (ret)
+               return ret;
++      /* Initialize and pre-allocate per-port EVLAN and VF blocks for
++       * user ports. CPU ports do not use EVLAN or VF -- frames pass
++       * through without processing. Pre-allocation avoids firmware
++       * EVLAN table fragmentation and simplifies control flow.
++       */
++      mxl862xx_evlan_block_init(&priv->ports[port].ingress_evlan,
++                                priv->evlan_ingress_size);
++      ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].ingress_evlan);
++      if (ret)
++              return ret;
++
++      mxl862xx_evlan_block_init(&priv->ports[port].egress_evlan,
++                                priv->evlan_egress_size);
++      ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].egress_evlan);
++      if (ret)
++              return ret;
++
++      mxl862xx_vf_init(&priv->ports[port].vf, priv->vf_block_size);
++      ret = mxl862xx_vf_alloc(priv, &priv->ports[port].vf);
++      if (ret)
++              return ret;
++
+       priv->ports[port].setup_done = true;
+       return 0;
+@@ -808,7 +1743,7 @@ static int mxl862xx_port_mdb_del(struct
+       mxl862xx_fw_portmap_clear_bit(qparam.port_map, port);
+       if (mxl862xx_fw_portmap_is_empty(qparam.port_map)) {
+-              /* No ports left — remove the entry entirely */
++              /* No ports left -- remove the entry entirely */
+               rparam.fid = cpu_to_le16(fid);
+               rparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+               ether_addr_copy(rparam.mac, mdb->addr);
+@@ -899,7 +1834,7 @@ static void mxl862xx_port_stp_state_set(
+ /* Deferred work handler for host flood configuration.
+  *
+  * port_set_host_flood is called from atomic context (under
+- * netif_addr_lock), so firmware calls must be deferred.  The worker
++ * netif_addr_lock), so firmware calls must be deferred. The worker
+  * acquires rtnl_lock() to serialize with DSA callbacks that access the
+  * same driver state.
+  */
+@@ -924,9 +1859,9 @@ static void mxl862xx_host_flood_work_fn(
+       mc = p->host_flood_mc;
+       /* The hardware controls unknown-unicast/multicast forwarding per FID
+-       * (bridge), not per source port.  For bridged ports all members share
++       * (bridge), not per source port. For bridged ports all members share
+        * one FID, so we cannot selectively suppress flooding to the CPU for
+-       * one source port while allowing it for another.  Silently ignore the
++       * one source port while allowing it for another. Silently ignore the
+        * request -- the excess flooding towards the CPU is harmless.
+        */
+       if (!dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
+@@ -1026,6 +1961,9 @@ static const struct dsa_switch_ops mxl86
+       .port_fdb_dump = mxl862xx_port_fdb_dump,
+       .port_mdb_add = mxl862xx_port_mdb_add,
+       .port_mdb_del = mxl862xx_port_mdb_del,
++      .port_vlan_filtering = mxl862xx_port_vlan_filtering,
++      .port_vlan_add = mxl862xx_port_vlan_add,
++      .port_vlan_del = mxl862xx_port_vlan_del,
+ };
+ static void mxl862xx_phylink_mac_config(struct phylink_config *config,
+@@ -1111,7 +2049,7 @@ static void mxl862xx_remove(struct mdio_
+       /* Cancel any pending host flood work.  dsa_unregister_switch()
+        * has already called port_teardown (which sets setup_done=false),
+-       * but a worker could still be blocked on rtnl_lock().  Since we
++       * but a worker could still be blocked on rtnl_lock(). Since we
+        * are now outside RTNL, cancel_work_sync() will not deadlock.
+        */
+       for (i = 0; i < MXL862XX_MAX_PORTS; i++)
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -13,6 +13,8 @@ struct mxl862xx_priv;
+ #define MXL862XX_DEFAULT_BRIDGE               0
+ #define MXL862XX_MAX_BRIDGES          48
+ #define MXL862XX_MAX_BRIDGE_PORTS     128
++#define MXL862XX_TOTAL_EVLAN_ENTRIES  1024
++#define MXL862XX_TOTAL_VF_ENTRIES     1024
+ /* Number of __le16 words in a firmware portmap (128-bit bitmap). */
+ #define MXL862XX_FW_PORTMAP_WORDS     (MXL862XX_MAX_BRIDGE_PORTS / 16)
+@@ -86,12 +88,72 @@ static inline bool mxl862xx_fw_portmap_i
+ }
+ /**
++ * struct mxl862xx_vf_vid - Per-VID entry within a VLAN Filter block
++ * @list:     Linked into &mxl862xx_vf_block.vids
++ * @vid:      VLAN ID
++ * @index:    Entry index within the VLAN Filter HW block
++ * @untagged: Strip tag on egress for this VID (drives EVLAN tag-stripping)
++ */
++struct mxl862xx_vf_vid {
++      struct list_head list;
++      u16 vid;
++      u16 index;
++      bool untagged;
++};
++
++/**
++ * struct mxl862xx_vf_block - Per-port VLAN Filter block
++ * @allocated:    Whether the HW block has been allocated via VLANFILTER_ALLOC
++ * @block_id:     HW VLAN Filter block ID from VLANFILTER_ALLOC
++ * @block_size:   Total entries allocated in this block
++ * @active_count: Number of ALLOW entries at indices [0, active_count).
++ *                The bridge port config sends max(active_count, 1) as
++ *                block_size to narrow the HW scan window.
++ *                discard_unmatched_tagged handles frames outside this range.
++ * @vids:         List of &mxl862xx_vf_vid entries programmed in this block
++ */
++struct mxl862xx_vf_block {
++      bool allocated;
++      u16 block_id;
++      u16 block_size;
++      u16 active_count;
++      struct list_head vids;
++};
++
++/**
++ * struct mxl862xx_evlan_block - Per-port per-direction extended VLAN block
++ * @allocated:  Whether the HW block has been allocated via EXTENDEDVLAN_ALLOC.
++ *              Guards alloc/free idempotency--the block_id is only valid
++ *              while allocated is true.
++ * @in_use:     Whether the EVLAN engine should be enabled for this block
++ *              on the bridge port (sent as the enable flag in
++ *              set_bridge_port). Can be false while allocated is still
++ *              true -- e.g. when all egress VIDs are removed (idx == 0 in
++ *              evlan_program_egress) the block stays allocated for
++ *              potential reuse, but the engine is disabled so an empty
++ *              rule set does not discard all traffic.
++ * @block_id:   HW block ID from EXTENDEDVLAN_ALLOC
++ * @block_size: Total entries allocated
++ * @n_active:   Number of HW entries currently written. The bridge port
++ *              config sends this as the egress scan window, so entries
++ *              beyond n_active are never scanned. Always equals
++ *              block_size for ingress blocks (fixed catchall rules).
++ */
++struct mxl862xx_evlan_block {
++      bool allocated;
++      bool in_use;
++      u16 block_id;
++      u16 block_size;
++      u16 n_active;
++};
++
++/**
+  * struct mxl862xx_port - per-port state tracked by the driver
+  * @priv:                back-pointer to switch private data; needed by
+  *                       deferred work handlers to access ds and priv
+- * @fid:                 firmware FID for the permanent single-port bridge;
+- *                       kept alive for the lifetime of the port so traffic is
+- *                       never forwarded while the port is unbridged
++ * @fid:                 firmware FID for the permanent single-port bridge; kept
++ *                       alive for the lifetime of the port so traffic is never
++ *                       forwarded while the port is unbridged
+  * @portmap:             bitmap of switch port indices that share the current
+  *                       bridge with this port
+  * @flood_block:         bitmask of firmware meter indices that are currently
+@@ -101,6 +163,11 @@ static inline bool mxl862xx_fw_portmap_i
+  * @setup_done:          set at end of port_setup, cleared at start of
+  *                       port_teardown; guards deferred work against
+  *                       acting on torn-down state
++ * @pvid:                port VLAN ID (native VLAN) assigned to untagged traffic
++ * @vlan_filtering:      true when VLAN filtering is enabled on this port
++ * @vf:                  per-port VLAN Filter block state
++ * @ingress_evlan:       ingress extended VLAN block state
++ * @egress_evlan:        egress extended VLAN block state
+  * @host_flood_uc:       desired host unicast flood state (true = flood);
+  *                       updated atomically by port_set_host_flood, consumed
+  *                       by the deferred host_flood_work
+@@ -119,6 +186,12 @@ struct mxl862xx_port {
+       unsigned long flood_block;
+       bool learning;
+       bool setup_done;
++      /* VLAN state */
++      u16 pvid;
++      bool vlan_filtering;
++      struct mxl862xx_vf_block vf;
++      struct mxl862xx_evlan_block ingress_evlan;
++      struct mxl862xx_evlan_block egress_evlan;
+       bool host_flood_uc;
+       bool host_flood_mc;
+       struct work_struct host_flood_work;
+@@ -126,17 +199,23 @@ struct mxl862xx_port {
+ /**
+  * struct mxl862xx_priv - driver private data for an MxL862xx switch
+- * @ds:            pointer to the DSA switch instance
+- * @mdiodev:       MDIO device used to communicate with the switch firmware
+- * @crc_err_work:  deferred work for shutting down all ports on MDIO CRC errors
+- * @crc_err:       set atomically before CRC-triggered shutdown, cleared after
+- * @drop_meter:    index of the single shared zero-rate firmware meter used
+- *                 to unconditionally drop traffic (used to block flooding)
+- * @ports:         per-port state, indexed by switch port number
+- * @bridges:       maps DSA bridge number to firmware bridge ID;
+- *                 zero means no firmware bridge allocated for that
+- *                 DSA bridge number.  Indexed by dsa_bridge.num
+- *                 (0 .. ds->max_num_bridges).
++ * @ds:                 pointer to the DSA switch instance
++ * @mdiodev:            MDIO device used to communicate with the switch firmware
++ * @crc_err_work:       deferred work for shutting down all ports on MDIO CRC
++ *                      errors
++ * @crc_err:            set atomically before CRC-triggered shutdown, cleared
++ *                      after
++ * @drop_meter:         index of the single shared zero-rate firmware meter
++ *                      used to unconditionally drop traffic (used to block
++ *                      flooding)
++ * @ports:              per-port state, indexed by switch port number
++ * @bridges:            maps DSA bridge number to firmware bridge ID;
++ *                      zero means no firmware bridge allocated for that
++ *                      DSA bridge number. Indexed by dsa_bridge.num
++ *                      (0 .. ds->max_num_bridges).
++ * @evlan_ingress_size: per-port ingress Extended VLAN block size
++ * @evlan_egress_size:  per-port egress Extended VLAN block size
++ * @vf_block_size:      per-port VLAN Filter block size
+  */
+ struct mxl862xx_priv {
+       struct dsa_switch *ds;
+@@ -146,6 +225,9 @@ struct mxl862xx_priv {
+       u16 drop_meter;
+       struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
+       u16 bridges[MXL862XX_MAX_BRIDGES + 1];
++      u16 evlan_ingress_size;
++      u16 evlan_egress_size;
++      u16 vf_block_size;
+ };
+ #endif /* __MXL862XX_H */
diff --git a/target/linux/generic/pending-6.12/760-06-net-dsa-mxl862xx-add-ethtool-statistics-support.patch b/target/linux/generic/pending-6.12/760-06-net-dsa-mxl862xx-add-ethtool-statistics-support.patch
new file mode 100644 (file)
index 0000000..a1517cc
--- /dev/null
@@ -0,0 +1,383 @@
+From 03b583e774835f771dd7c3c265be5903f008e8e5 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 22 Mar 2026 00:57:33 +0000
+Subject: [PATCH 15/35] net: dsa: mxl862xx: add ethtool statistics support
+
+The MxL862xx firmware exposes per-port RMON counters through the
+RMON_PORT_GET command, covering standard IEEE 802.3 MAC statistics
+(unicast/multicast/broadcast packet and byte counts, collision
+counters, pause frames) as well as hardware-specific counters such
+as extended VLAN discard and MTU exceed events.
+
+Add the RMON counter firmware API structures and command definitions.
+Implement .get_strings, .get_sset_count, and .get_ethtool_stats for
+legacy ethtool -S support. Implement .get_eth_mac_stats,
+.get_eth_ctrl_stats, and .get_pause_stats for the standardized
+IEEE 802.3 statistics interface.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h | 142 ++++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h |   3 +
+ drivers/net/dsa/mxl862xx/mxl862xx.c     | 168 ++++++++++++++++++++++++
+ 3 files changed, 313 insertions(+)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+@@ -1224,4 +1224,146 @@ struct mxl862xx_sys_fw_image_version {
+       __le32 iv_build_num;
+ } __packed;
++/**
++ * enum mxl862xx_port_type - Port Type
++ * @MXL862XX_LOGICAL_PORT: Logical Port
++ * @MXL862XX_PHYSICAL_PORT: Physical Port
++ * @MXL862XX_CTP_PORT: Connectivity Termination Port (CTP)
++ * @MXL862XX_BRIDGE_PORT: Bridge Port
++ */
++enum mxl862xx_port_type {
++      MXL862XX_LOGICAL_PORT = 0,
++      MXL862XX_PHYSICAL_PORT,
++      MXL862XX_CTP_PORT,
++      MXL862XX_BRIDGE_PORT,
++};
++
++/**
++ * enum mxl862xx_rmon_port_type - RMON counter table type
++ * @MXL862XX_RMON_CTP_PORT_RX: CTP RX counters
++ * @MXL862XX_RMON_CTP_PORT_TX: CTP TX counters
++ * @MXL862XX_RMON_BRIDGE_PORT_RX: Bridge port RX counters
++ * @MXL862XX_RMON_BRIDGE_PORT_TX: Bridge port TX counters
++ * @MXL862XX_RMON_CTP_PORT_PCE_BYPASS: CTP PCE bypass counters
++ * @MXL862XX_RMON_TFLOW_RX: TFLOW RX counters
++ * @MXL862XX_RMON_TFLOW_TX: TFLOW TX counters
++ * @MXL862XX_RMON_QMAP: QMAP counters
++ * @MXL862XX_RMON_METER: Meter counters
++ * @MXL862XX_RMON_PMAC: PMAC counters
++ */
++enum mxl862xx_rmon_port_type {
++      MXL862XX_RMON_CTP_PORT_RX = 0,
++      MXL862XX_RMON_CTP_PORT_TX,
++      MXL862XX_RMON_BRIDGE_PORT_RX,
++      MXL862XX_RMON_BRIDGE_PORT_TX,
++      MXL862XX_RMON_CTP_PORT_PCE_BYPASS,
++      MXL862XX_RMON_TFLOW_RX,
++      MXL862XX_RMON_TFLOW_TX,
++      MXL862XX_RMON_QMAP = 0x0e,
++      MXL862XX_RMON_METER = 0x19,
++      MXL862XX_RMON_PMAC = 0x1c,
++};
++
++/**
++ * struct mxl862xx_rmon_port_cnt - RMON counters for a port
++ * @port_type: Port type for counter retrieval (see &enum mxl862xx_port_type)
++ * @port_id: Ethernet port number (zero-based)
++ * @sub_if_id_group: Sub-interface ID group
++ * @pce_bypass: Separate CTP Tx counters when PCE is bypassed
++ * @rx_extended_vlan_discard_pkts: Discarded at extended VLAN operation
++ * @mtu_exceed_discard_pkts: Discarded due to MTU exceeded
++ * @tx_under_size_good_pkts: Tx undersize (<64) packet count
++ * @tx_oversize_good_pkts: Tx oversize (>1518) packet count
++ * @rx_good_pkts: Received good packet count
++ * @rx_unicast_pkts: Received unicast packet count
++ * @rx_broadcast_pkts: Received broadcast packet count
++ * @rx_multicast_pkts: Received multicast packet count
++ * @rx_fcserror_pkts: Received FCS error packet count
++ * @rx_under_size_good_pkts: Received undersize good packet count
++ * @rx_oversize_good_pkts: Received oversize good packet count
++ * @rx_under_size_error_pkts: Received undersize error packet count
++ * @rx_good_pause_pkts: Received good pause packet count
++ * @rx_oversize_error_pkts: Received oversize error packet count
++ * @rx_align_error_pkts: Received alignment error packet count
++ * @rx_filtered_pkts: Filtered packet count
++ * @rx64byte_pkts: Received 64-byte packet count
++ * @rx127byte_pkts: Received 65-127 byte packet count
++ * @rx255byte_pkts: Received 128-255 byte packet count
++ * @rx511byte_pkts: Received 256-511 byte packet count
++ * @rx1023byte_pkts: Received 512-1023 byte packet count
++ * @rx_max_byte_pkts: Received 1024-max byte packet count
++ * @tx_good_pkts: Transmitted good packet count
++ * @tx_unicast_pkts: Transmitted unicast packet count
++ * @tx_broadcast_pkts: Transmitted broadcast packet count
++ * @tx_multicast_pkts: Transmitted multicast packet count
++ * @tx_single_coll_count: Transmit single collision count
++ * @tx_mult_coll_count: Transmit multiple collision count
++ * @tx_late_coll_count: Transmit late collision count
++ * @tx_excess_coll_count: Transmit excessive collision count
++ * @tx_coll_count: Transmit collision count
++ * @tx_pause_count: Transmit pause packet count
++ * @tx64byte_pkts: Transmitted 64-byte packet count
++ * @tx127byte_pkts: Transmitted 65-127 byte packet count
++ * @tx255byte_pkts: Transmitted 128-255 byte packet count
++ * @tx511byte_pkts: Transmitted 256-511 byte packet count
++ * @tx1023byte_pkts: Transmitted 512-1023 byte packet count
++ * @tx_max_byte_pkts: Transmitted 1024-max byte packet count
++ * @tx_dropped_pkts: Transmit dropped packet count
++ * @tx_acm_dropped_pkts: Transmit ACM dropped packet count
++ * @rx_dropped_pkts: Received dropped packet count
++ * @rx_good_bytes: Received good byte count (64-bit)
++ * @rx_bad_bytes: Received bad byte count (64-bit)
++ * @tx_good_bytes: Transmitted good byte count (64-bit)
++ */
++struct mxl862xx_rmon_port_cnt {
++      enum mxl862xx_port_type port_type;
++      __le16 port_id;
++      __le16 sub_if_id_group;
++      u8 pce_bypass;
++      __le32 rx_extended_vlan_discard_pkts;
++      __le32 mtu_exceed_discard_pkts;
++      __le32 tx_under_size_good_pkts;
++      __le32 tx_oversize_good_pkts;
++      __le32 rx_good_pkts;
++      __le32 rx_unicast_pkts;
++      __le32 rx_broadcast_pkts;
++      __le32 rx_multicast_pkts;
++      __le32 rx_fcserror_pkts;
++      __le32 rx_under_size_good_pkts;
++      __le32 rx_oversize_good_pkts;
++      __le32 rx_under_size_error_pkts;
++      __le32 rx_good_pause_pkts;
++      __le32 rx_oversize_error_pkts;
++      __le32 rx_align_error_pkts;
++      __le32 rx_filtered_pkts;
++      __le32 rx64byte_pkts;
++      __le32 rx127byte_pkts;
++      __le32 rx255byte_pkts;
++      __le32 rx511byte_pkts;
++      __le32 rx1023byte_pkts;
++      __le32 rx_max_byte_pkts;
++      __le32 tx_good_pkts;
++      __le32 tx_unicast_pkts;
++      __le32 tx_broadcast_pkts;
++      __le32 tx_multicast_pkts;
++      __le32 tx_single_coll_count;
++      __le32 tx_mult_coll_count;
++      __le32 tx_late_coll_count;
++      __le32 tx_excess_coll_count;
++      __le32 tx_coll_count;
++      __le32 tx_pause_count;
++      __le32 tx64byte_pkts;
++      __le32 tx127byte_pkts;
++      __le32 tx255byte_pkts;
++      __le32 tx511byte_pkts;
++      __le32 tx1023byte_pkts;
++      __le32 tx_max_byte_pkts;
++      __le32 tx_dropped_pkts;
++      __le32 tx_acm_dropped_pkts;
++      __le32 rx_dropped_pkts;
++      __le64 rx_good_bytes;
++      __le64 rx_bad_bytes;
++      __le64 tx_good_bytes;
++} __packed;
++
+ #endif /* __MXL862XX_API_H */
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+@@ -16,6 +16,7 @@
+ #define MXL862XX_BRDGPORT_MAGIC               0x400
+ #define MXL862XX_CTP_MAGIC            0x500
+ #define MXL862XX_QOS_MAGIC            0x600
++#define MXL862XX_RMON_MAGIC           0x700
+ #define MXL862XX_SWMAC_MAGIC          0xa00
+ #define MXL862XX_EXTVLAN_MAGIC                0xb00
+ #define MXL862XX_VLANFILTER_MAGIC     0xc00
+@@ -43,6 +44,8 @@
+ #define MXL862XX_QOS_METERCFGSET      (MXL862XX_QOS_MAGIC + 0x2)
+ #define MXL862XX_QOS_METERALLOC               (MXL862XX_QOS_MAGIC + 0x2a)
++#define MXL862XX_RMON_PORT_GET                (MXL862XX_RMON_MAGIC + 0x1)
++
+ #define MXL862XX_MAC_TABLEENTRYADD    (MXL862XX_SWMAC_MAGIC + 0x2)
+ #define MXL862XX_MAC_TABLEENTRYREAD   (MXL862XX_SWMAC_MAGIC + 0x3)
+ #define MXL862XX_MAC_TABLEENTRYQUERY  (MXL862XX_SWMAC_MAGIC + 0x4)
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -30,6 +30,64 @@
+ #define MXL862XX_API_READ_QUIET(dev, cmd, data) \
+       mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
++struct mxl862xx_mib_desc {
++      unsigned int size;
++      unsigned int offset;
++      const char *name;
++};
++
++#define MIB_DESC(_size, _name, _element)                                      \
++{                                                                     \
++      .size = _size,                                                  \
++      .name = _name,                                                  \
++      .offset = offsetof(struct mxl862xx_rmon_port_cnt, _element)     \
++}
++
++static const struct mxl862xx_mib_desc mxl862xx_mib[] = {
++      MIB_DESC(1, "TxGoodPkts", tx_good_pkts),
++      MIB_DESC(1, "TxUnicastPkts", tx_unicast_pkts),
++      MIB_DESC(1, "TxBroadcastPkts", tx_broadcast_pkts),
++      MIB_DESC(1, "TxMulticastPkts", tx_multicast_pkts),
++      MIB_DESC(1, "Tx64BytePkts", tx64byte_pkts),
++      MIB_DESC(1, "Tx127BytePkts", tx127byte_pkts),
++      MIB_DESC(1, "Tx255BytePkts", tx255byte_pkts),
++      MIB_DESC(1, "Tx511BytePkts", tx511byte_pkts),
++      MIB_DESC(1, "Tx1023BytePkts", tx1023byte_pkts),
++      MIB_DESC(1, "TxMaxBytePkts", tx_max_byte_pkts),
++      MIB_DESC(1, "TxDroppedPkts", tx_dropped_pkts),
++      MIB_DESC(1, "TxAcmDroppedPkts", tx_acm_dropped_pkts),
++      MIB_DESC(2, "TxGoodBytes", tx_good_bytes),
++      MIB_DESC(1, "TxSingleCollCount", tx_single_coll_count),
++      MIB_DESC(1, "TxMultCollCount", tx_mult_coll_count),
++      MIB_DESC(1, "TxLateCollCount", tx_late_coll_count),
++      MIB_DESC(1, "TxExcessCollCount", tx_excess_coll_count),
++      MIB_DESC(1, "TxCollCount", tx_coll_count),
++      MIB_DESC(1, "TxPauseCount", tx_pause_count),
++      MIB_DESC(1, "RxGoodPkts", rx_good_pkts),
++      MIB_DESC(1, "RxUnicastPkts", rx_unicast_pkts),
++      MIB_DESC(1, "RxBroadcastPkts", rx_broadcast_pkts),
++      MIB_DESC(1, "RxMulticastPkts", rx_multicast_pkts),
++      MIB_DESC(1, "RxFCSErrorPkts", rx_fcserror_pkts),
++      MIB_DESC(1, "RxUnderSizeGoodPkts", rx_under_size_good_pkts),
++      MIB_DESC(1, "RxOversizeGoodPkts", rx_oversize_good_pkts),
++      MIB_DESC(1, "RxUnderSizeErrorPkts", rx_under_size_error_pkts),
++      MIB_DESC(1, "RxOversizeErrorPkts", rx_oversize_error_pkts),
++      MIB_DESC(1, "RxFilteredPkts", rx_filtered_pkts),
++      MIB_DESC(1, "Rx64BytePkts", rx64byte_pkts),
++      MIB_DESC(1, "Rx127BytePkts", rx127byte_pkts),
++      MIB_DESC(1, "Rx255BytePkts", rx255byte_pkts),
++      MIB_DESC(1, "Rx511BytePkts", rx511byte_pkts),
++      MIB_DESC(1, "Rx1023BytePkts", rx1023byte_pkts),
++      MIB_DESC(1, "RxMaxBytePkts", rx_max_byte_pkts),
++      MIB_DESC(1, "RxDroppedPkts", rx_dropped_pkts),
++      MIB_DESC(1, "RxExtendedVlanDiscardPkts", rx_extended_vlan_discard_pkts),
++      MIB_DESC(1, "MtuExceedDiscardPkts", mtu_exceed_discard_pkts),
++      MIB_DESC(2, "RxGoodBytes", rx_good_bytes),
++      MIB_DESC(2, "RxBadBytes", rx_bad_bytes),
++      MIB_DESC(1, "RxGoodPausePkts", rx_good_pause_pkts),
++      MIB_DESC(1, "RxAlignErrorPkts", rx_align_error_pkts),
++};
++
+ #define MXL862XX_SDMA_PCTRLP(p)               (0xbc0 + ((p) * 0x6))
+ #define MXL862XX_SDMA_PCTRL_EN                BIT(0)
+@@ -1940,6 +1998,110 @@ static int mxl862xx_port_bridge_flags(st
+       return 0;
+ }
++static void mxl862xx_get_strings(struct dsa_switch *ds, int port,
++                              u32 stringset, u8 *data)
++{
++      int i;
++
++      if (stringset != ETH_SS_STATS)
++              return;
++
++      for (i = 0; i < ARRAY_SIZE(mxl862xx_mib); i++)
++              ethtool_puts(&data, mxl862xx_mib[i].name);
++}
++
++static int mxl862xx_get_sset_count(struct dsa_switch *ds, int port, int sset)
++{
++      if (sset != ETH_SS_STATS)
++              return 0;
++
++      return ARRAY_SIZE(mxl862xx_mib);
++}
++
++static int mxl862xx_read_rmon(struct dsa_switch *ds, int port,
++                            struct mxl862xx_rmon_port_cnt *cnt)
++{
++      memset(cnt, 0, sizeof(*cnt));
++      cnt->port_type = MXL862XX_CTP_PORT;
++      cnt->port_id = cpu_to_le16(port);
++
++      return MXL862XX_API_READ(ds->priv, MXL862XX_RMON_PORT_GET, *cnt);
++}
++
++static void mxl862xx_get_ethtool_stats(struct dsa_switch *ds, int port,
++                                     u64 *data)
++{
++      const struct mxl862xx_mib_desc *mib;
++      struct mxl862xx_rmon_port_cnt cnt;
++      int ret, i;
++      void *field;
++
++      ret = mxl862xx_read_rmon(ds, port, &cnt);
++      if (ret) {
++              dev_err(ds->dev, "failed to read RMON stats on port %d\n", port);
++              return;
++      }
++
++      for (i = 0; i < ARRAY_SIZE(mxl862xx_mib); i++) {
++              mib = &mxl862xx_mib[i];
++              field = (u8 *)&cnt + mib->offset;
++
++              if (mib->size == 1)
++                      *data++ = le32_to_cpu(*(__le32 *)field);
++              else
++                      *data++ = le64_to_cpu(*(__le64 *)field);
++      }
++}
++
++static void mxl862xx_get_eth_mac_stats(struct dsa_switch *ds, int port,
++                                     struct ethtool_eth_mac_stats *mac_stats)
++{
++      struct mxl862xx_rmon_port_cnt cnt;
++
++      if (mxl862xx_read_rmon(ds, port, &cnt))
++              return;
++
++      mac_stats->FramesTransmittedOK = le32_to_cpu(cnt.tx_good_pkts);
++      mac_stats->SingleCollisionFrames = le32_to_cpu(cnt.tx_single_coll_count);
++      mac_stats->MultipleCollisionFrames = le32_to_cpu(cnt.tx_mult_coll_count);
++      mac_stats->FramesReceivedOK = le32_to_cpu(cnt.rx_good_pkts);
++      mac_stats->FrameCheckSequenceErrors = le32_to_cpu(cnt.rx_fcserror_pkts);
++      mac_stats->AlignmentErrors = le32_to_cpu(cnt.rx_align_error_pkts);
++      mac_stats->OctetsTransmittedOK = le64_to_cpu(cnt.tx_good_bytes);
++      mac_stats->LateCollisions = le32_to_cpu(cnt.tx_late_coll_count);
++      mac_stats->FramesAbortedDueToXSColls = le32_to_cpu(cnt.tx_excess_coll_count);
++      mac_stats->OctetsReceivedOK = le64_to_cpu(cnt.rx_good_bytes);
++      mac_stats->MulticastFramesXmittedOK = le32_to_cpu(cnt.tx_multicast_pkts);
++      mac_stats->BroadcastFramesXmittedOK = le32_to_cpu(cnt.tx_broadcast_pkts);
++      mac_stats->MulticastFramesReceivedOK = le32_to_cpu(cnt.rx_multicast_pkts);
++      mac_stats->BroadcastFramesReceivedOK = le32_to_cpu(cnt.rx_broadcast_pkts);
++      mac_stats->FrameTooLongErrors = le32_to_cpu(cnt.rx_oversize_error_pkts);
++}
++
++static void mxl862xx_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
++                                      struct ethtool_eth_ctrl_stats *ctrl_stats)
++{
++      struct mxl862xx_rmon_port_cnt cnt;
++
++      if (mxl862xx_read_rmon(ds, port, &cnt))
++              return;
++
++      ctrl_stats->MACControlFramesTransmitted = le32_to_cpu(cnt.tx_pause_count);
++      ctrl_stats->MACControlFramesReceived = le32_to_cpu(cnt.rx_good_pause_pkts);
++}
++
++static void mxl862xx_get_pause_stats(struct dsa_switch *ds, int port,
++                                   struct ethtool_pause_stats *pause_stats)
++{
++      struct mxl862xx_rmon_port_cnt cnt;
++
++      if (mxl862xx_read_rmon(ds, port, &cnt))
++              return;
++
++      pause_stats->tx_pause_frames = le32_to_cpu(cnt.tx_pause_count);
++      pause_stats->rx_pause_frames = le32_to_cpu(cnt.rx_good_pause_pkts);
++}
++
+ static const struct dsa_switch_ops mxl862xx_switch_ops = {
+       .get_tag_protocol = mxl862xx_get_tag_protocol,
+       .setup = mxl862xx_setup,
+@@ -1964,6 +2126,12 @@ static const struct dsa_switch_ops mxl86
+       .port_vlan_filtering = mxl862xx_port_vlan_filtering,
+       .port_vlan_add = mxl862xx_port_vlan_add,
+       .port_vlan_del = mxl862xx_port_vlan_del,
++      .get_strings = mxl862xx_get_strings,
++      .get_sset_count = mxl862xx_get_sset_count,
++      .get_ethtool_stats = mxl862xx_get_ethtool_stats,
++      .get_eth_mac_stats = mxl862xx_get_eth_mac_stats,
++      .get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,
++      .get_pause_stats = mxl862xx_get_pause_stats,
+ };
+ static void mxl862xx_phylink_mac_config(struct phylink_config *config,
diff --git a/target/linux/generic/pending-6.12/760-07-net-dsa-mxl862xx-implement-.get_stats64.patch b/target/linux/generic/pending-6.12/760-07-net-dsa-mxl862xx-implement-.get_stats64.patch
new file mode 100644 (file)
index 0000000..9d4d93b
--- /dev/null
@@ -0,0 +1,325 @@
+From 8b66d20f7e5226f4854a39cfb9f25a0591a5bb83 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 04:14:38 +0000
+Subject: [PATCH 16/35] net: dsa: mxl862xx: implement .get_stats64
+
+Poll free-running firmware RMON counters every 2 seconds and accumulate
+deltas into 64-bit per-port statistics. 32-bit packet counters wrap
+in ~880s at 2.5 Gbps line rate; the 2s polling interval provides a
+comfortable margin. The .get_stats64 callback forces a fresh poll so
+that counters are always up to date when queried.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 167 ++++++++++++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx.h |  51 +++++++++
+ 2 files changed, 218 insertions(+)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -30,6 +30,12 @@
+ #define MXL862XX_API_READ_QUIET(dev, cmd, data) \
+       mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
++/* Polling interval for RMON counter accumulation. At 2.5 Gbps with
++ * minimum-size (64-byte) frames, a 32-bit packet counter wraps in ~880s.
++ * 2s gives a comfortable margin.
++ */
++#define MXL862XX_STATS_POLL_INTERVAL  (2 * HZ)
++
+ struct mxl862xx_mib_desc {
+       unsigned int size;
+       unsigned int offset;
+@@ -784,6 +790,9 @@ static int mxl862xx_setup(struct dsa_swi
+       if (ret)
+               return ret;
++      schedule_delayed_work(&priv->stats_work,
++                            MXL862XX_STATS_POLL_INTERVAL);
++
+       return mxl862xx_setup_mdio(ds);
+ }
+@@ -2102,6 +2111,156 @@ static void mxl862xx_get_pause_stats(str
+       pause_stats->rx_pause_frames = le32_to_cpu(cnt.rx_good_pause_pkts);
+ }
++/* Compute the delta between two 32-bit free-running counter snapshots,
++ * handling a single wrap-around correctly via unsigned subtraction.
++ */
++static u64 mxl862xx_delta32(u32 cur, u32 prev)
++{
++      return (u32)(cur - prev);
++}
++
++/**
++ * mxl862xx_stats_poll - Read RMON counters and accumulate into 64-bit stats
++ * @ds: DSA switch
++ * @port: port index
++ *
++ * The firmware RMON counters are free-running 32-bit values (64-bit for
++ * byte counters). This function reads the hardware via MDIO (may sleep),
++ * computes deltas from the previous snapshot, and accumulates them into
++ * 64-bit per-port stats under a spinlock.
++ *
++ * Called only from the stats polling workqueue -- serialized by the
++ * single-threaded delayed_work, so no MDIO locking is needed here.
++ */
++static void mxl862xx_stats_poll(struct dsa_switch *ds, int port)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port_stats *s = &priv->ports[port].stats;
++      u32 rx_fcserr, rx_under, rx_over, rx_align, tx_drop;
++      u32 rx_drop, rx_evlan, mtu_exc, tx_acm;
++      struct mxl862xx_rmon_port_cnt cnt;
++      u64 rx_bytes, tx_bytes;
++      u32 rx_mcast, tx_coll;
++      u32 rx_pkts, tx_pkts;
++
++      /* MDIO read -- may sleep, done outside the spinlock. */
++      if (mxl862xx_read_rmon(ds, port, &cnt))
++              return;
++
++      rx_pkts   = le32_to_cpu(cnt.rx_good_pkts);
++      tx_pkts   = le32_to_cpu(cnt.tx_good_pkts);
++      rx_bytes  = le64_to_cpu(cnt.rx_good_bytes);
++      tx_bytes  = le64_to_cpu(cnt.tx_good_bytes);
++      rx_fcserr = le32_to_cpu(cnt.rx_fcserror_pkts);
++      rx_under  = le32_to_cpu(cnt.rx_under_size_error_pkts);
++      rx_over   = le32_to_cpu(cnt.rx_oversize_error_pkts);
++      rx_align  = le32_to_cpu(cnt.rx_align_error_pkts);
++      tx_drop   = le32_to_cpu(cnt.tx_dropped_pkts);
++      rx_drop   = le32_to_cpu(cnt.rx_dropped_pkts);
++      rx_evlan  = le32_to_cpu(cnt.rx_extended_vlan_discard_pkts);
++      mtu_exc   = le32_to_cpu(cnt.mtu_exceed_discard_pkts);
++      tx_acm    = le32_to_cpu(cnt.tx_acm_dropped_pkts);
++      rx_mcast  = le32_to_cpu(cnt.rx_multicast_pkts);
++      tx_coll   = le32_to_cpu(cnt.tx_coll_count);
++
++      /* Accumulate deltas under spinlock -- .get_stats64 reads these. */
++      spin_lock_bh(&priv->ports[port].stats_lock);
++
++      s->rx_packets += mxl862xx_delta32(rx_pkts, s->prev_rx_good_pkts);
++      s->tx_packets += mxl862xx_delta32(tx_pkts, s->prev_tx_good_pkts);
++      s->rx_bytes   += rx_bytes - s->prev_rx_good_bytes;
++      s->tx_bytes   += tx_bytes - s->prev_tx_good_bytes;
++
++      s->rx_errors +=
++              mxl862xx_delta32(rx_fcserr, s->prev_rx_fcserror_pkts) +
++              mxl862xx_delta32(rx_under, s->prev_rx_under_size_error_pkts) +
++              mxl862xx_delta32(rx_over, s->prev_rx_oversize_error_pkts) +
++              mxl862xx_delta32(rx_align, s->prev_rx_align_error_pkts);
++      s->tx_errors +=
++              mxl862xx_delta32(tx_drop, s->prev_tx_dropped_pkts);
++
++      s->rx_dropped +=
++              mxl862xx_delta32(rx_drop, s->prev_rx_dropped_pkts) +
++              mxl862xx_delta32(rx_evlan, s->prev_rx_evlan_discard_pkts) +
++              mxl862xx_delta32(mtu_exc, s->prev_mtu_exceed_discard_pkts);
++      s->tx_dropped +=
++              mxl862xx_delta32(tx_drop, s->prev_tx_dropped_pkts) +
++              mxl862xx_delta32(tx_acm, s->prev_tx_acm_dropped_pkts);
++
++      s->multicast  += mxl862xx_delta32(rx_mcast, s->prev_rx_multicast_pkts);
++      s->collisions += mxl862xx_delta32(tx_coll, s->prev_tx_coll_count);
++
++      s->rx_length_errors +=
++              mxl862xx_delta32(rx_under, s->prev_rx_under_size_error_pkts) +
++              mxl862xx_delta32(rx_over, s->prev_rx_oversize_error_pkts);
++      s->rx_crc_errors +=
++              mxl862xx_delta32(rx_fcserr, s->prev_rx_fcserror_pkts);
++      s->rx_frame_errors +=
++              mxl862xx_delta32(rx_align, s->prev_rx_align_error_pkts);
++
++      s->prev_rx_good_pkts             = rx_pkts;
++      s->prev_tx_good_pkts             = tx_pkts;
++      s->prev_rx_good_bytes            = rx_bytes;
++      s->prev_tx_good_bytes            = tx_bytes;
++      s->prev_rx_fcserror_pkts         = rx_fcserr;
++      s->prev_rx_under_size_error_pkts = rx_under;
++      s->prev_rx_oversize_error_pkts   = rx_over;
++      s->prev_rx_align_error_pkts      = rx_align;
++      s->prev_tx_dropped_pkts          = tx_drop;
++      s->prev_rx_dropped_pkts          = rx_drop;
++      s->prev_rx_evlan_discard_pkts    = rx_evlan;
++      s->prev_mtu_exceed_discard_pkts  = mtu_exc;
++      s->prev_tx_acm_dropped_pkts      = tx_acm;
++      s->prev_rx_multicast_pkts        = rx_mcast;
++      s->prev_tx_coll_count            = tx_coll;
++
++      spin_unlock_bh(&priv->ports[port].stats_lock);
++}
++
++static void mxl862xx_stats_work_fn(struct work_struct *work)
++{
++      struct mxl862xx_priv *priv =
++              container_of(work, struct mxl862xx_priv, stats_work.work);
++      struct dsa_switch *ds = priv->ds;
++      struct dsa_port *dp;
++
++      dsa_switch_for_each_available_port(dp, ds)
++              mxl862xx_stats_poll(ds, dp->index);
++
++      schedule_delayed_work(&priv->stats_work,
++                            MXL862XX_STATS_POLL_INTERVAL);
++}
++
++static void mxl862xx_get_stats64(struct dsa_switch *ds, int port,
++                               struct rtnl_link_stats64 *s)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port_stats *ps = &priv->ports[port].stats;
++
++      spin_lock_bh(&priv->ports[port].stats_lock);
++
++      s->rx_packets = ps->rx_packets;
++      s->tx_packets = ps->tx_packets;
++      s->rx_bytes = ps->rx_bytes;
++      s->tx_bytes = ps->tx_bytes;
++      s->rx_errors = ps->rx_errors;
++      s->tx_errors = ps->tx_errors;
++      s->rx_dropped = ps->rx_dropped;
++      s->tx_dropped = ps->tx_dropped;
++      s->multicast = ps->multicast;
++      s->collisions = ps->collisions;
++      s->rx_length_errors = ps->rx_length_errors;
++      s->rx_crc_errors = ps->rx_crc_errors;
++      s->rx_frame_errors = ps->rx_frame_errors;
++
++      spin_unlock_bh(&priv->ports[port].stats_lock);
++
++      /* Trigger a fresh poll so the next read sees up-to-date counters.
++       * No-op if the work is already pending or running.
++       */
++      schedule_delayed_work(&priv->stats_work, 0);
++}
++
+ static const struct dsa_switch_ops mxl862xx_switch_ops = {
+       .get_tag_protocol = mxl862xx_get_tag_protocol,
+       .setup = mxl862xx_setup,
+@@ -2132,6 +2291,7 @@ static const struct dsa_switch_ops mxl86
+       .get_eth_mac_stats = mxl862xx_get_eth_mac_stats,
+       .get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,
+       .get_pause_stats = mxl862xx_get_pause_stats,
++      .get_stats64 = mxl862xx_get_stats64,
+ };
+ static void mxl862xx_phylink_mac_config(struct phylink_config *config,
+@@ -2193,8 +2353,11 @@ static int mxl862xx_probe(struct mdio_de
+               priv->ports[i].priv = priv;
+               INIT_WORK(&priv->ports[i].host_flood_work,
+                         mxl862xx_host_flood_work_fn);
++              spin_lock_init(&priv->ports[i].stats_lock);
+       }
++      INIT_DELAYED_WORK(&priv->stats_work, mxl862xx_stats_work_fn);
++
+       dev_set_drvdata(dev, ds);
+       return dsa_register_switch(ds);
+@@ -2213,6 +2376,8 @@ static void mxl862xx_remove(struct mdio_
+       dsa_unregister_switch(ds);
++      cancel_delayed_work_sync(&priv->stats_work);
++
+       mxl862xx_host_shutdown(priv);
+       /* Cancel any pending host flood work.  dsa_unregister_switch()
+@@ -2237,6 +2402,8 @@ static void mxl862xx_shutdown(struct mdi
+       dsa_switch_shutdown(ds);
++      cancel_delayed_work_sync(&priv->stats_work);
++
+       mxl862xx_host_shutdown(priv);
+       for (i = 0; i < MXL862XX_MAX_PORTS; i++)
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -148,6 +148,47 @@ struct mxl862xx_evlan_block {
+ };
+ /**
++ * struct mxl862xx_port_stats - 64-bit accumulated hardware port statistics
++ *
++ * The firmware RMON counters are 32-bit free-running (64-bit for byte
++ * counters). This structure holds 64-bit accumulators alongside the
++ * previous raw snapshot so that deltas can be computed across polls,
++ * handling 32-bit wrap correctly via unsigned subtraction.
++ */
++struct mxl862xx_port_stats {
++      /* 64-bit accumulators */
++      u64 rx_packets;
++      u64 tx_packets;
++      u64 rx_bytes;
++      u64 tx_bytes;
++      u64 rx_errors;
++      u64 tx_errors;
++      u64 rx_dropped;
++      u64 tx_dropped;
++      u64 multicast;
++      u64 collisions;
++      u64 rx_length_errors;
++      u64 rx_crc_errors;
++      u64 rx_frame_errors;
++      /* Previous raw RMON values for delta computation */
++      u32 prev_rx_good_pkts;
++      u32 prev_tx_good_pkts;
++      u64 prev_rx_good_bytes;
++      u64 prev_tx_good_bytes;
++      u32 prev_rx_fcserror_pkts;
++      u32 prev_rx_under_size_error_pkts;
++      u32 prev_rx_oversize_error_pkts;
++      u32 prev_rx_align_error_pkts;
++      u32 prev_tx_dropped_pkts;
++      u32 prev_rx_dropped_pkts;
++      u32 prev_rx_evlan_discard_pkts;
++      u32 prev_mtu_exceed_discard_pkts;
++      u32 prev_tx_acm_dropped_pkts;
++      u32 prev_rx_multicast_pkts;
++      u32 prev_tx_coll_count;
++};
++
++/**
+  * struct mxl862xx_port - per-port state tracked by the driver
+  * @priv:                back-pointer to switch private data; needed by
+  *                       deferred work handlers to access ds and priv
+@@ -178,6 +219,10 @@ struct mxl862xx_evlan_block {
+  *                       The worker acquires rtnl_lock() to serialize with
+  *                       DSA callbacks and checks @setup_done to avoid
+  *                       acting on torn-down ports.
++ * @stats:               64-bit accumulated hardware statistics; updated
++ *                       periodically by the stats polling work
++ * @stats_lock:          protects accumulator reads in .get_stats64 against
++ *                       concurrent updates from the polling work
+  */
+ struct mxl862xx_port {
+       struct mxl862xx_priv *priv;
+@@ -195,6 +240,9 @@ struct mxl862xx_port {
+       bool host_flood_uc;
+       bool host_flood_mc;
+       struct work_struct host_flood_work;
++      /* Hardware stats accumulation */
++      struct mxl862xx_port_stats stats;
++      spinlock_t stats_lock;
+ };
+ /**
+@@ -216,6 +264,8 @@ struct mxl862xx_port {
+  * @evlan_ingress_size: per-port ingress Extended VLAN block size
+  * @evlan_egress_size:  per-port egress Extended VLAN block size
+  * @vf_block_size:      per-port VLAN Filter block size
++ * @stats_work:         periodic work item that polls RMON hardware counters
++ *                      and accumulates them into 64-bit per-port stats
+  */
+ struct mxl862xx_priv {
+       struct dsa_switch *ds;
+@@ -228,6 +278,7 @@ struct mxl862xx_priv {
+       u16 evlan_ingress_size;
+       u16 evlan_egress_size;
+       u16 vf_block_size;
++      struct delayed_work stats_work;
+ };
+ #endif /* __MXL862XX_H */
diff --git a/target/linux/generic/pending-6.12/760-08-net-dsa-mxl862xx-store-firmware-version-for-feature-.patch b/target/linux/generic/pending-6.12/760-08-net-dsa-mxl862xx-store-firmware-version-for-feature-.patch
new file mode 100644 (file)
index 0000000..d62b466
--- /dev/null
@@ -0,0 +1,98 @@
+From fecfbea928cd762b19ff17aa16fb1ab143d73db1 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 17:56:35 +0000
+Subject: [PATCH 17/35] net: dsa: mxl862xx: store firmware version for feature
+ gating
+
+Query the firmware version at init (already done in wait_ready),
+cache it in priv->fw_version, and provide MXL862XX_FW_VER_MIN()
+for version-gated code paths throughout the driver.
+
+The union mxl862xx_fw_version lays out major/minor/revision so
+that the u32 raw field compares with natural version ordering on
+both big- and little-endian machines.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx.c |  3 +++
+ drivers/net/dsa/mxl862xx/mxl862xx.h | 36 +++++++++++++++++++++++++++++
+ 2 files changed, 39 insertions(+)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -286,6 +286,9 @@ static int mxl862xx_wait_ready(struct ds
+                        ver.iv_major, ver.iv_minor,
+                        le16_to_cpu(ver.iv_revision),
+                        le32_to_cpu(ver.iv_build_num));
++              priv->fw_version.major = ver.iv_major;
++              priv->fw_version.minor = ver.iv_minor;
++              priv->fw_version.revision = le16_to_cpu(ver.iv_revision);
+               return 0;
+ not_ready_yet:
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -3,6 +3,7 @@
+ #ifndef __MXL862XX_H
+ #define __MXL862XX_H
++#include <asm/byteorder.h>
+ #include <linux/mdio.h>
+ #include <linux/workqueue.h>
+ #include <net/dsa.h>
+@@ -246,6 +247,38 @@ struct mxl862xx_port {
+ };
+ /**
++ * union mxl862xx_fw_version - firmware version for comparison and display
++ * @major: firmware major version
++ * @minor: firmware minor version
++ * @revision: firmware revision number
++ * @raw: combined u32 for direct >= comparison (major most significant)
++ *
++ * The struct layout places major in the most-significant byte of the
++ * u32 on both big- and little-endian machines, so raw values compare
++ * with the natural major > minor > revision ordering.
++ */
++union mxl862xx_fw_version {
++      struct {
++#if defined(__BIG_ENDIAN)
++              u8 major;
++              u8 minor;
++              u16 revision;
++#elif defined(__LITTLE_ENDIAN)
++              u16 revision;
++              u8 minor;
++              u8 major;
++#endif
++      };
++      u32 raw;
++};
++
++#define MXL862XX_FW_VER(maj, min, rev) \
++      ((union mxl862xx_fw_version){ .major = (maj), .minor = (min), \
++                                    .revision = (rev) }).raw
++#define MXL862XX_FW_VER_MIN(priv, maj, min, rev) \
++      ((priv)->fw_version.raw >= MXL862XX_FW_VER(maj, min, rev))
++
++/**
+  * struct mxl862xx_priv - driver private data for an MxL862xx switch
+  * @ds:                 pointer to the DSA switch instance
+  * @mdiodev:            MDIO device used to communicate with the switch firmware
+@@ -256,6 +289,8 @@ struct mxl862xx_port {
+  * @drop_meter:         index of the single shared zero-rate firmware meter
+  *                      used to unconditionally drop traffic (used to block
+  *                      flooding)
++ * @fw_version:         cached firmware version, populated at probe and
++ *                      compared with MXL862XX_FW_VER_MIN()
+  * @ports:              per-port state, indexed by switch port number
+  * @bridges:            maps DSA bridge number to firmware bridge ID;
+  *                      zero means no firmware bridge allocated for that
+@@ -273,6 +308,7 @@ struct mxl862xx_priv {
+       struct work_struct crc_err_work;
+       unsigned long crc_err;
+       u16 drop_meter;
++      union mxl862xx_fw_version fw_version;
+       struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
+       u16 bridges[MXL862XX_MAX_BRIDGES + 1];
+       u16 evlan_ingress_size;
diff --git a/target/linux/generic/pending-6.12/760-09-net-dsa-mxl862xx-move-phylink-stubs-to-mxl862xx-phyl.patch b/target/linux/generic/pending-6.12/760-09-net-dsa-mxl862xx-move-phylink-stubs-to-mxl862xx-phyl.patch
new file mode 100644 (file)
index 0000000..833ec06
--- /dev/null
@@ -0,0 +1,163 @@
+From 3cb224514226928df80e43ca2280c7dca654bdfe Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 25 Mar 2026 21:39:30 +0000
+Subject: [PATCH 18/35] net: dsa: mxl862xx: move phylink stubs to
+ mxl862xx-phylink.c
+
+Move the phylink MAC operations and get_caps callback from mxl862xx.c
+into a dedicated mxl862xx-phylink.c file. This prepares for the SerDes
+PCS implementation which adds substantial phylink/PCS code -- keeping
+it in a separate file avoids function-position churn in the main
+driver file.
+
+No functional change.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/Makefile           |  2 +-
+ drivers/net/dsa/mxl862xx/mxl862xx-phylink.c | 51 +++++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-phylink.h | 14 ++++++
+ drivers/net/dsa/mxl862xx/mxl862xx.c         | 38 +--------------
+ 4 files changed, 67 insertions(+), 38 deletions(-)
+ create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
+ create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
+
+--- a/drivers/net/dsa/mxl862xx/Makefile
++++ b/drivers/net/dsa/mxl862xx/Makefile
+@@ -1,3 +1,3 @@
+ # SPDX-License-Identifier: GPL-2.0
+ obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o
+-mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o
++mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o
+--- /dev/null
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
+@@ -0,0 +1,51 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Phylink and PCS support for MaxLinear MxL862xx switch family
++ *
++ * Copyright (C) 2024 MaxLinear Inc.
++ * Copyright (C) 2025 John Crispin <john@phrozen.org>
++ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
++ */
++
++#include <linux/phylink.h>
++#include <net/dsa.h>
++
++#include "mxl862xx.h"
++#include "mxl862xx-phylink.h"
++
++void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
++                             struct phylink_config *config)
++{
++      config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 |
++                                 MAC_100 | MAC_1000 | MAC_2500FD;
++
++      __set_bit(PHY_INTERFACE_MODE_INTERNAL,
++                config->supported_interfaces);
++}
++
++static void mxl862xx_phylink_mac_config(struct phylink_config *config,
++                                      unsigned int mode,
++                                      const struct phylink_link_state *state)
++{
++}
++
++static void mxl862xx_phylink_mac_link_down(struct phylink_config *config,
++                                         unsigned int mode,
++                                         phy_interface_t interface)
++{
++}
++
++static void mxl862xx_phylink_mac_link_up(struct phylink_config *config,
++                                       struct phy_device *phydev,
++                                       unsigned int mode,
++                                       phy_interface_t interface,
++                                       int speed, int duplex,
++                                       bool tx_pause, bool rx_pause)
++{
++}
++
++const struct phylink_mac_ops mxl862xx_phylink_mac_ops = {
++      .mac_config = mxl862xx_phylink_mac_config,
++      .mac_link_down = mxl862xx_phylink_mac_link_down,
++      .mac_link_up = mxl862xx_phylink_mac_link_up,
++};
+--- /dev/null
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
+@@ -0,0 +1,14 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++
++#ifndef __MXL862XX_PHYLINK_H
++#define __MXL862XX_PHYLINK_H
++
++#include <linux/phylink.h>
++
++#include "mxl862xx.h"
++
++extern const struct phylink_mac_ops mxl862xx_phylink_mac_ops;
++void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
++                             struct phylink_config *config);
++
++#endif /* __MXL862XX_PHYLINK_H */
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -22,6 +22,7 @@
+ #include "mxl862xx-api.h"
+ #include "mxl862xx-cmd.h"
+ #include "mxl862xx-host.h"
++#include "mxl862xx-phylink.h"
+ #define MXL862XX_API_WRITE(dev, cmd, data) \
+       mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false)
+@@ -1642,16 +1643,6 @@ static void mxl862xx_port_teardown(struc
+       priv->ports[port].setup_done = false;
+ }
+-static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
+-                                    struct phylink_config *config)
+-{
+-      config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 |
+-                                 MAC_100 | MAC_1000 | MAC_2500FD;
+-
+-      __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+-                config->supported_interfaces);
+-}
+-
+ static int mxl862xx_get_fid(struct dsa_switch *ds, struct dsa_db db)
+ {
+       struct mxl862xx_priv *priv = ds->priv;
+@@ -2297,33 +2288,6 @@ static const struct dsa_switch_ops mxl86
+       .get_stats64 = mxl862xx_get_stats64,
+ };
+-static void mxl862xx_phylink_mac_config(struct phylink_config *config,
+-                                      unsigned int mode,
+-                                      const struct phylink_link_state *state)
+-{
+-}
+-
+-static void mxl862xx_phylink_mac_link_down(struct phylink_config *config,
+-                                         unsigned int mode,
+-                                         phy_interface_t interface)
+-{
+-}
+-
+-static void mxl862xx_phylink_mac_link_up(struct phylink_config *config,
+-                                       struct phy_device *phydev,
+-                                       unsigned int mode,
+-                                       phy_interface_t interface,
+-                                       int speed, int duplex,
+-                                       bool tx_pause, bool rx_pause)
+-{
+-}
+-
+-static const struct phylink_mac_ops mxl862xx_phylink_mac_ops = {
+-      .mac_config = mxl862xx_phylink_mac_config,
+-      .mac_link_down = mxl862xx_phylink_mac_link_down,
+-      .mac_link_up = mxl862xx_phylink_mac_link_up,
+-};
+-
+ static int mxl862xx_probe(struct mdio_device *mdiodev)
+ {
+       struct device *dev = &mdiodev->dev;
diff --git a/target/linux/generic/pending-6.12/760-10-net-dsa-mxl862xx-move-API-macros-to-mxl862xx-host.h.patch b/target/linux/generic/pending-6.12/760-10-net-dsa-mxl862xx-move-API-macros-to-mxl862xx-host.h.patch
new file mode 100644 (file)
index 0000000..01013ee
--- /dev/null
@@ -0,0 +1,53 @@
+From de41d438c4e90876449715a307dd03fa37338742 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 26 Mar 2026 01:50:00 +0000
+Subject: [PATCH 19/35] net: dsa: mxl862xx: move API macros to mxl862xx-host.h
+
+Move the MXL862XX_API_WRITE, MXL862XX_API_READ and
+MXL862XX_API_READ_QUIET convenience macros from mxl862xx.c to
+mxl862xx-host.h next to the mxl862xx_api_wrap() prototype they wrap.
+This makes them available to other compilation units that include
+mxl862xx-host.h, which is needed once the SerDes PCS code in
+mxl862xx-phylink.c also calls firmware commands.
+
+No functional change.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-host.h | 8 ++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx.c      | 7 -------
+ 2 files changed, 8 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h
+@@ -9,6 +9,14 @@ void mxl862xx_host_init(struct mxl862xx_
+ void mxl862xx_host_shutdown(struct mxl862xx_priv *priv);
+ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size,
+                     bool read, bool quiet);
++
++#define MXL862XX_API_WRITE(dev, cmd, data) \
++      mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false)
++#define MXL862XX_API_READ(dev, cmd, data) \
++      mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, false)
++#define MXL862XX_API_READ_QUIET(dev, cmd, data) \
++      mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
++
+ int mxl862xx_reset(struct mxl862xx_priv *priv);
+ #endif /* __MXL862XX_HOST_H */
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -24,13 +24,6 @@
+ #include "mxl862xx-host.h"
+ #include "mxl862xx-phylink.h"
+-#define MXL862XX_API_WRITE(dev, cmd, data) \
+-      mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false)
+-#define MXL862XX_API_READ(dev, cmd, data) \
+-      mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, false)
+-#define MXL862XX_API_READ_QUIET(dev, cmd, data) \
+-      mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
+-
+ /* Polling interval for RMON counter accumulation. At 2.5 Gbps with
+  * minimum-size (64-byte) frames, a 32-bit packet counter wraps in ~880s.
+  * 2s gives a comfortable margin.
diff --git a/target/linux/generic/pending-6.12/760-11-net-dsa-mxl862xx-add-support-for-SerDes-ports.patch b/target/linux/generic/pending-6.12/760-11-net-dsa-mxl862xx-add-support-for-SerDes-ports.patch
new file mode 100644 (file)
index 0000000..790f50c
--- /dev/null
@@ -0,0 +1,1053 @@
+From 88f46eb32d1aed296af2005c3ed8f23a6eea64c3 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 22 Mar 2026 00:57:44 +0000
+Subject: [PATCH 20/35] net: dsa: mxl862xx: add support for SerDes ports
+
+The MxL862xx has two XPCS/SerDes interfaces (XPCS0 for ports 9-12,
+XPCS1 for ports 13-16). Each can operate in various single-lane
+modes (SGMII, 1000BASE-X, 2500BASE-X, 10GBASE-R, 10GBASE-KR,
+USXGMII) or as QSGMII providing four sub-ports.
+
+Implement phylink PCS operations using the firmware's XPCS API:
+
+  - pcs_pre_config: power-sequence the SerDes (hard reset if already
+    running, then PCS_ENABLE with the target interface mode), polling
+    SIGNAL_DETECT until the XPCS exits reset.
+  - pcs_config: configure negotiation mode and CL37/SGMII advertising.
+  - pcs_get_state: read link/speed/duplex/LPA from firmware and decode
+    using phylink's standard CL37, SGMII, and USXGMII decoders, with
+    firmware-resolved speed/duplex override for downshift detection.
+  - pcs_an_restart: restart CL37 or CL73 auto-negotiation.
+  - pcs_link_up: force speed/duplex for SGMII.
+  - pcs_inband_caps: report per-mode in-band status capabilities.
+
+Register a PCS instance for each SerDes port and QSGMII sub-port
+during setup. Advertise the supported interface modes in
+phylink_get_caps based on port number.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h     | 474 +++++++++++++++++++-
+ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h     |  13 +
+ drivers/net/dsa/mxl862xx/mxl862xx-phylink.c | 411 ++++++++++++++++-
+ drivers/net/dsa/mxl862xx/mxl862xx-phylink.h |   2 +
+ drivers/net/dsa/mxl862xx/mxl862xx.c         |   5 +-
+ drivers/net/dsa/mxl862xx/mxl862xx.h         |  19 +
+ 6 files changed, 907 insertions(+), 17 deletions(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+@@ -1185,6 +1185,242 @@ struct mxl862xx_ctp_port_assignment {
+ } __packed;
+ /**
++ * enum mxl862xx_port_duplex - Ethernet port duplex status
++ * @MXL862XX_DUPLEX_FULL: Port operates in full-duplex mode
++ * @MXL862XX_DUPLEX_HALF: Port operates in half-duplex mode
++ * @MXL862XX_DUPLEX_AUTO: Port operates in Auto mode
++ */
++enum mxl862xx_port_duplex {
++      MXL862XX_DUPLEX_FULL = 0,
++      MXL862XX_DUPLEX_HALF,
++      MXL862XX_DUPLEX_AUTO,
++};
++
++/**
++ * enum mxl862xx_port_type - Port Type
++ * @MXL862XX_LOGICAL_PORT: Logical Port
++ * @MXL862XX_PHYSICAL_PORT: Physical Port
++ * @MXL862XX_CTP_PORT: Connectivity Termination Port (CTP)
++ * @MXL862XX_BRIDGE_PORT: Bridge Port
++ */
++enum mxl862xx_port_type {
++      MXL862XX_LOGICAL_PORT = 0,
++      MXL862XX_PHYSICAL_PORT,
++      MXL862XX_CTP_PORT,
++      MXL862XX_BRIDGE_PORT,
++};
++
++/**
++ * enum mxl862xx_port_enable - port enable type selection.
++ * @MXL862XX_PORT_DISABLE: the port is disabled in both directions
++ * @MXL862XX_PORT_ENABLE_RXTX: the port is enabled in both directions
++ * @MXL862XX_PORT_ENABLE_RX: the port is enabled in the receive direction
++ * @MXL862XX_PORT_ENABLE_TX: the port is enabled in the transmit direction
++ */
++enum mxl862xx_port_enable{
++      MXL862XX_PORT_DISABLE = 0,
++      MXL862XX_PORT_ENABLE_RXTX,
++      MXL862XX_PORT_ENABLE_RX,
++      MXL862XX_PORT_ENABLE_TX,
++};
++
++/**
++ * enum mxl862xx_port_flow - ethernet flow control status
++ * @MXL862XX_FLOW_AUTO: automatic flow control
++ * @MXL862XX_FLOW_RX: receive flow control only
++ * @MXL862XX_FLOW_TX: transmit flow control only
++ * @MXL862XX_FLOW_RXTX: receive and transmit flow control
++ * @MXL862XX_FLOW_OFF: no flow control
++ */
++enum mxl862xx_port_flow {
++      MXL862XX_FLOW_AUTO = 0,
++      MXL862XX_FLOW_RX,
++      MXL862XX_FLOW_TX,
++      MXL862XX_FLOW_RXTX,
++      MXL862XX_FLOW_OFF,
++};
++
++/**
++ * enum mxl862xx_port_monitor - port mirror options
++ * @MXL862XX_PORT_MONITOR_NONE: mirroring is disabled
++ * @MXL862XX_PORT_MONITOR_RX: ingress packets are mirrored
++ * @MXL862XX_PORT_MONITOR_TX: egress packets are mirrored
++ * @MXL862XX_PORT_MONITOR_RXTX: ingress and egress packets are mirrored
++ * @MXL862XX_PORT_MONITOR_VLAN_UNKNOWN: mirroring of 'unknown VLAN violation' frames
++ * @MXL862XX_PORT_MONITOR_VLAN_MEMBERSHIP: mirroring of 'VLAN ingress or egress membership
++      violation' frames
++ * @MXL862XX_PORT_MONITOR_PORT_STATE: mirroring of 'port state violation' frames
++ * @MXL862XX_PORT_MONITOR_LEARNING_LIMIT: mirroring of 'MAC learning limit violation' frames
++ * @MXL862XX_PORT_MONITOR_PORT_LOCK: mirroring of 'port lock violation' frames
++ */
++enum mxl862xx_port_monitor {
++      MXL862XX_PORT_MONITOR_NONE = 0,
++      MXL862XX_PORT_MONITOR_RX,
++      MXL862XX_PORT_MONITOR_TX,
++      MXL862XX_PORT_MONITOR_RXTX,
++      MXL862XX_PORT_MONITOR_VLAN_UNKNOWN,
++      MXL862XX_PORT_MONITOR_VLAN_MEMBERSHIP = 16,
++      MXL862XX_PORT_MONITOR_PORT_STATE = 32,
++      MXL862XX_PORT_MONITOR_LEARNING_LIMIT = 64,
++      MXL862XX_PORT_MONITOR_PORT_LOCK = 128,
++};
++
++/**
++ * enum mxl862xx_if_rmon_mode - interface RMON counter mode
++ * @MXL862XX_IF_RMON_FID: FID based RMON counters
++ * @MXL862XX_IF_RMON_SUBID: sub-interface ID based
++ * @MXL862XX_IF_RMON_FLOWID_LSB: flow ID based (bits 3:0)
++ * @MXL862XX_IF_RMON_FLOWID_MSB: flow ID based (bits 7:4)
++ */
++enum mxl862xx_if_rmon_mode {
++      MXL862XX_IF_RMON_FID = 0,
++      MXL862XX_IF_RMON_SUBID,
++      MXL862XX_IF_RMON_FLOWID_LSB,
++      MXL862XX_IF_RMON_FLOWID_MSB,
++};
++
++/**
++ * struct mxl862xx_port_cfg - Port Configuration Parameters
++ * @port_type: See &enum mxl862xx_port_type
++ * @port_id: Ethernet Port number (zero-based counting)
++ * @enable: See &enum mxl862xx_port_enable
++ * @unicast_unknown_drop: Drop unknown unicast packets
++ * @multicast_unknown_drop: Drop unknown multicast packets
++ * @reserved_packet_drop: Drop reserved packet types
++ * @broadcast_drop: Drop broadcast packets
++ * @aging: Enables MAC address table aging.
++ * @learning: MAC address table learning
++ * @learning_mac_port_lock: Automatic MAC address table learning locking on the port
++ * @learning_limit: Automatic MAC address table learning limitation on this port
++ * @mac_spoofing_detection: MAC spoofing detection. Identifies ingress packets that carry
++ *      a MAC source address which was previously learned on a different ingress port
++ * @flow_ctrl: See &enum mxl862xx_port_flow
++ * @port_monitor: See &enum mxl862xx_port_monitor
++ * @if_counters: Assign Interface RMON Counters for this Port
++ * @if_count_start_idx: Interface RMON Counters Start Index
++ * @if_rmonmode: See &enum mxl862xx_if_rmon_mode
++ */
++struct mxl862xx_port_cfg {
++      __le32 port_type; /* enum mxl862xx_port_type */
++      __le16 port_id;
++      __le32 enable; /* enum mxl862xx_port_enable */
++      u8 unicast_unknown_drop;
++      u8 multicast_unknown_drop;
++      u8 reserved_packet_drop;
++      u8 broadcast_drop;
++      u8 aging;
++      u8 learning;
++      u8 learning_mac_port_lock;
++      __le16 learning_limit;
++      u8 mac_spoofing_detection;
++      __le32 flow_ctrl; /* enum mxl862xx_port_flow */
++      __le32 port_monitor; /* enum mxl862xx_port_monitor */
++      u8 if_counters;
++      __le32 if_count_start_idx;
++      __le32 if_rmonmode; /* enum mxl862xx_if_rmon_mode */
++} __packed;
++
++/**
++ * enum mxl862xx_port_speed -  Ethernet port speed mode
++ * @MXL862XX_PORT_SPEED_10: 10 Mbit/s
++ * @MXL862XX_PORT_SPEED_100: 100 Mbit/s
++ * @MXL862XX_PORT_SPEED_200: 200 Mbit/s
++ * @MXL862XX_PORT_SPEED_1000: 1000 Mbit/s
++ * @MXL862XX_PORT_SPEED_2500: 2.5 Gbit/s
++ * @MXL862XX_PORT_SPEED_5000: 5 Gbit/s
++ * @MXL862XX_PORT_SPEED_10000: 10 Gbit/s
++ * @MXL862XX_PORT_SPEED_AUTO: Auto speed for XGMAC
++ */
++enum mxl862xx_port_speed {
++      MXL862XX_PORT_SPEED_10 = 0,
++      MXL862XX_PORT_SPEED_100,
++      MXL862XX_PORT_SPEED_200,
++      MXL862XX_PORT_SPEED_1000,
++      MXL862XX_PORT_SPEED_2500,
++      MXL862XX_PORT_SPEED_5000,
++      MXL862XX_PORT_SPEED_10000,
++      MXL862XX_PORT_SPEED_AUTO,
++};
++
++/**
++ * enum mxl862xx_port_link - Force the MAC and PHY link modus
++ * @MXL862XX_PORT_LINK_UP: Link up
++ * @MXL862XX_PORT_LINK_DOWN: Link down
++ * @MXL862XX_PORT_LINK_AUTO: Link Auto
++ */
++enum mxl862xx_port_link {
++      MXL862XX_PORT_LINK_UP = 0,
++      MXL862XX_PORT_LINK_DOWN,
++      MXL862XX_PORT_LINK_AUTO,
++};
++
++/**
++ * enum mxl862xx_mii_mode - Ethernet port interface mode
++ * @MXL862XX_PORT_HW_MII: Normal PHY interface
++ * @MXL862XX_PORT_HW_RMII: Reduced MII interface in normal mode
++ * @MXL862XX_PORT_HW_GMII: GMII or MII, depending upon the speed
++ * @MXL862XX_PORT_HW_RGMII: RGMII mode
++ * @MXL862XX_PORT_HW_XGMII: XGMII mode
++ */
++enum mxl862xx_mii_mode {
++      MXL862XX_PORT_HW_MII = 0,
++      MXL862XX_PORT_HW_RMII,
++      MXL862XX_PORT_HW_GMII,
++      MXL862XX_PORT_HW_RGMII,
++      MXL862XX_PORT_HW_XGMII,
++};
++
++/**
++ * enum mxl862xx_mii_type - Ethernet port configuration for PHY or MAC mode
++ * @MXL862XX_PORT_MAC: The Ethernet port is configured to work in MAC mode
++ * @MXL862XX_PORT_PHY: The Ethernet port is configured to work in PHY mode
++ */
++enum mxl862xx_mii_type {
++      MXL862XX_PORT_MAC = 0,
++      MXL862XX_PORT_PHY,
++};
++
++/**
++ * enum mxl862xx_clk_mode - Ethernet port clock source configuration
++ * @MXL862XX_PORT_CLK_NA: Clock Mode not applicable
++ * @MXL862XX_PORT_CLK_MASTER: Clock Master Mode. The port is configured to provide the clock as output signal
++ * @MXL862XX_PORT_CLK_SLAVE: Clock Slave Mode. The port is configured to use the input clock signal
++ */
++enum mxl862xx_clk_mode {
++      MXL862XX_PORT_CLK_NA = 0,
++      MXL862XX_PORT_CLK_MASTER,
++      MXL862XX_PORT_CLK_SLAVE,
++};
++
++/**
++ * struct mxl862xx_port_link_cfg - Ethernet port link, speed status and flow control status
++ * @port_id: Ethernet Port number
++ * @duplex_force: Force Port Duplex Mode
++ * @duplex: See &enum mxl862xx_port_duplex
++ * @speed_force: Force Link Speed
++ * @speed: See &enum mxl862xx_port_speed
++ * @link_force: Force Link
++ * @link: See &enum mxl862xx_port_link
++ * @mii_mode: See &enum mxl862xx_mii_mode
++ * @mii_type: See &enum mxl862xx_mii_type
++ * @clk_mode: See &enum mxl862xx_clk_mode
++ * @lpi: 'Low Power Idle' Support for 'Energy Efficient Ethernet'
++ */
++struct mxl862xx_port_link_cfg {
++      __le16 port_id;
++      u8 duplex_force;
++      __le32  duplex; /* enum mxl862xx_port_duplex */
++      u8 speed_force;
++      __le32 speed; /* enum mxl862xx_port_speed */
++      u8 link_force;
++      __le32 link; /* enum mxl862xx_port_link */
++      __le32 mii_mode; /* enum mxl862xx_mii_mode */
++      __le32 mii_type; /* enum mxl862xx_mii_type */
++      __le32 clk_mode; /* enum mxl862xx_clk_mode */
++      u8 lpi;
++} __packed;
++
++/**
+  * enum mxl862xx_stp_port_state - Spanning Tree Protocol port states
+  * @MXL862XX_STP_PORT_STATE_FORWARD: Forwarding state
+  * @MXL862XX_STP_PORT_STATE_DISABLE: Disabled/Discarding state
+@@ -1225,20 +1461,6 @@ struct mxl862xx_sys_fw_image_version {
+ } __packed;
+ /**
+- * enum mxl862xx_port_type - Port Type
+- * @MXL862XX_LOGICAL_PORT: Logical Port
+- * @MXL862XX_PHYSICAL_PORT: Physical Port
+- * @MXL862XX_CTP_PORT: Connectivity Termination Port (CTP)
+- * @MXL862XX_BRIDGE_PORT: Bridge Port
+- */
+-enum mxl862xx_port_type {
+-      MXL862XX_LOGICAL_PORT = 0,
+-      MXL862XX_PHYSICAL_PORT,
+-      MXL862XX_CTP_PORT,
+-      MXL862XX_BRIDGE_PORT,
+-};
+-
+-/**
+  * enum mxl862xx_rmon_port_type - RMON counter table type
+  * @MXL862XX_RMON_CTP_PORT_RX: CTP RX counters
+  * @MXL862XX_RMON_CTP_PORT_TX: CTP TX counters
+@@ -1366,4 +1588,228 @@ struct mxl862xx_rmon_port_cnt {
+       __le64 tx_good_bytes;
+ } __packed;
++/**
++ * enum mxl862xx_xpcs_if_mode - XPCS interface mode
++ * @MXL862XX_XPCS_IF_SGMII: SGMII
++ * @MXL862XX_XPCS_IF_1000BASEX: 1000BASE-X
++ * @MXL862XX_XPCS_IF_2500BASEX: 2500BASE-X
++ * @MXL862XX_XPCS_IF_USXGMII: USXGMII
++ * @MXL862XX_XPCS_IF_10GBASER: 10GBASE-R
++ * @MXL862XX_XPCS_IF_10GKR: 10GBASE-KR
++ * @MXL862XX_XPCS_IF_5GBASER: 5GBASE-R
++ * @MXL862XX_XPCS_IF_QSGMII: QSGMII
++ */
++enum mxl862xx_xpcs_if_mode {
++      MXL862XX_XPCS_IF_SGMII = 0,
++      MXL862XX_XPCS_IF_1000BASEX = 1,
++      MXL862XX_XPCS_IF_2500BASEX = 2,
++      MXL862XX_XPCS_IF_USXGMII = 3,
++      MXL862XX_XPCS_IF_10GBASER = 4,
++      MXL862XX_XPCS_IF_10GKR = 5,
++      MXL862XX_XPCS_IF_5GBASER = 6,
++      MXL862XX_XPCS_IF_QSGMII = 7,
++};
++
++/**
++ * enum mxl862xx_xpcs_neg_mode - PCS negotiation mode
++ * @MXL862XX_XPCS_NEG_NONE: no inband negotiation
++ * @MXL862XX_XPCS_NEG_INBAND_AN_OFF: inband selected but AN disabled
++ * @MXL862XX_XPCS_NEG_INBAND_AN_ON: inband with AN enabled
++ */
++enum mxl862xx_xpcs_neg_mode {
++      MXL862XX_XPCS_NEG_NONE = 0,
++      MXL862XX_XPCS_NEG_INBAND_AN_OFF = 1,
++      MXL862XX_XPCS_NEG_INBAND_AN_ON = 2,
++};
++
++/**
++ * enum mxl862xx_xpcs_speed - PCS speed values
++ * @MXL862XX_XPCS_SPEED_UNKNOWN: unknown speed
++ * @MXL862XX_XPCS_SPEED_10: 10 Mbps
++ * @MXL862XX_XPCS_SPEED_100: 100 Mbps
++ * @MXL862XX_XPCS_SPEED_1000: 1000 Mbps
++ * @MXL862XX_XPCS_SPEED_2500: 2500 Mbps
++ * @MXL862XX_XPCS_SPEED_5000: 5000 Mbps
++ * @MXL862XX_XPCS_SPEED_10000: 10000 Mbps
++ */
++enum mxl862xx_xpcs_speed {
++      MXL862XX_XPCS_SPEED_UNKNOWN = 0,
++      MXL862XX_XPCS_SPEED_10 = 10,
++      MXL862XX_XPCS_SPEED_100 = 100,
++      MXL862XX_XPCS_SPEED_1000 = 1000,
++      MXL862XX_XPCS_SPEED_2500 = 2500,
++      MXL862XX_XPCS_SPEED_5000 = 5000,
++      MXL862XX_XPCS_SPEED_10000 = 10000,
++};
++
++/**
++ * enum mxl862xx_xpcs_duplex - PCS duplex mode
++ * @MXL862XX_XPCS_DUPLEX_HALF: half duplex
++ * @MXL862XX_XPCS_DUPLEX_FULL: full duplex
++ */
++enum mxl862xx_xpcs_duplex {
++      MXL862XX_XPCS_DUPLEX_HALF = 0,
++      MXL862XX_XPCS_DUPLEX_FULL = 1,
++};
++
++/**
++ * enum mxl862xx_xpcs_loopback_mode - XPCS loopback mode
++ * @MXL862XX_XPCS_LB_DISABLE: disable all loopback
++ * @MXL862XX_XPCS_LB_PCS_SERIAL: PCS TX-to-RX serial loopback
++ * @MXL862XX_XPCS_LB_PCS_PARALLEL: PCS RX-to-TX parallel loopback
++ * @MXL862XX_XPCS_LB_PMA_SERIAL: PMA TX-to-RX serial loopback
++ * @MXL862XX_XPCS_LB_PMA_PARALLEL: PMA RX-to-TX parallel loopback
++ */
++enum mxl862xx_xpcs_loopback_mode {
++      MXL862XX_XPCS_LB_DISABLE = 0,
++      MXL862XX_XPCS_LB_PCS_SERIAL = 1,
++      MXL862XX_XPCS_LB_PCS_PARALLEL = 2,
++      MXL862XX_XPCS_LB_PMA_SERIAL = 3,
++      MXL862XX_XPCS_LB_PMA_PARALLEL = 4,
++};
++
++/**
++ * enum mxl862xx_xpcs_reset_type - XPCS reset type
++ * @MXL862XX_XPCS_RESET_VR: vendor-specific reset (fast)
++ * @MXL862XX_XPCS_RESET_SOFT: PCS soft reset
++ * @MXL862XX_XPCS_RESET_HARD: full hardware reset
++ */
++enum mxl862xx_xpcs_reset_type {
++      MXL862XX_XPCS_RESET_VR = 0,
++      MXL862XX_XPCS_RESET_SOFT = 1,
++      MXL862XX_XPCS_RESET_HARD = 2,
++};
++
++/**
++ * struct mxl862xx_xpcs_pcs_cfg - PCS configuration
++ * @port_id: XPCS port index (0 or 1)
++ * @interface: interface mode (enum mxl862xx_xpcs_if_mode)
++ * @neg_mode: negotiation mode (enum mxl862xx_xpcs_neg_mode)
++ * @permit_pause: allow pause to MAC
++ * @usx_lane_mode: USXGMII lane mode (0=single, 1=quad)
++ * @phy_side: PHY side (1) or MAC side (0)
++ * @advertising: CL37 advertisement word
++ * @result: firmware result (>0 AN restart needed, 0 no change, <0 error)
++ */
++struct mxl862xx_xpcs_pcs_cfg {
++      u8 port_id:2;
++      u8 interface:6;
++      u8 neg_mode:2;
++      u8 permit_pause:1;
++      u8 usx_lane_mode:2;
++      u8 phy_side:1;
++      u8 __rsv:2;
++      __le16 advertising;
++      __le16 result;
++} __packed;
++
++/**
++ * struct mxl862xx_xpcs_pcs_state - PCS link state
++ * @port_id: XPCS port index (0 or 1)
++ * @interface: interface mode (enum mxl862xx_xpcs_if_mode)
++ * @usx_lane_mode: USXGMII lane mode
++ * @usx_subport: USXGMII sub-port index (0-3)
++ * @link: link up
++ * @an_complete: auto-negotiation complete
++ * @duplex: duplex mode (enum mxl862xx_xpcs_duplex)
++ * @pcs_fault: PCS fault detected
++ * @pause: pause flags (bit 0 = symmetric, bit 1 = asymmetric)
++ * @speed: link speed in Mbps (enum mxl862xx_xpcs_speed)
++ * @lpa: raw link partner advertisement word
++ */
++struct mxl862xx_xpcs_pcs_state {
++      u8 port_id:2;
++      u8 interface:6;
++      u8 usx_lane_mode:2;
++      u8 usx_subport:2;
++      u8 link:1;
++      u8 an_complete:1;
++      u8 duplex:1;
++      u8 pcs_fault:1;
++      u8 pause:2;
++      u8 __rsv:6;
++      u8 __pad;
++      __le16 speed;
++      __le16 lpa;
++} __packed;
++
++/**
++ * struct mxl862xx_xpcs_pcs_power - PCS enable/disable
++ * @port_id: XPCS port index (0 or 1)
++ * @interface: interface mode (enum mxl862xx_xpcs_if_mode)
++ * @phy_side: PHY side (1) or MAC side (0)
++ * @result: firmware result
++ */
++struct mxl862xx_xpcs_pcs_power {
++      u8 port_id:2;
++      u8 interface:6;
++      u8 phy_side:1;
++      u8 __rsv:7;
++      __le16 result;
++} __packed;
++
++/**
++ * struct mxl862xx_xpcs_an_restart - AN restart parameters
++ * @port_id: XPCS port index (0 or 1)
++ * @interface: interface mode (enum mxl862xx_xpcs_if_mode)
++ * @usx_lane_mode: USXGMII lane mode
++ * @result: firmware result
++ */
++struct mxl862xx_xpcs_an_restart {
++      u8 port_id:2;
++      u8 interface:6;
++      u8 usx_lane_mode:2;
++      u8 __rsv:6;
++      __le16 result;
++} __packed;
++
++/**
++ * struct mxl862xx_xpcs_an_disable - AN disable parameters
++ * @port_id: XPCS port index
++ * @result: firmware result
++ */
++struct mxl862xx_xpcs_an_disable {
++      u8 port_id;
++      u8 __pad;
++      __le16 result;
++} __packed;
++
++/**
++ * struct mxl862xx_xpcs_force_speed - force PCS speed and duplex
++ * @port_id: XPCS port index
++ * @duplex: duplex mode (enum mxl862xx_xpcs_duplex)
++ * @speed: speed in Mbps (enum mxl862xx_xpcs_speed)
++ * @result: firmware result
++ */
++struct mxl862xx_xpcs_force_speed {
++      u8 port_id;
++      u8 duplex;
++      __le16 speed;
++      __le16 result;
++} __packed;
++
++/**
++ * struct mxl862xx_xpcs_loopback_cfg - loopback control
++ * @port_id: XPCS port index
++ * @mode: loopback mode (enum mxl862xx_xpcs_loopback_mode)
++ * @result: firmware result
++ */
++struct mxl862xx_xpcs_loopback_cfg {
++      u8 port_id;
++      u8 mode;
++      __le16 result;
++} __packed;
++
++/**
++ * struct mxl862xx_xpcs_reset_cfg - XPCS reset
++ * @port_id: XPCS port index
++ * @reset_type: reset type (enum mxl862xx_xpcs_reset_type)
++ * @result: firmware result
++ */
++struct mxl862xx_xpcs_reset_cfg {
++      u8 port_id;
++      u8 reset_type;
++      __le16 result;
++} __packed;
++
+ #endif /* __MXL862XX_API_H */
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+@@ -25,6 +25,8 @@
+ #define GPY_GPY2XX_MAGIC              0x1800
+ #define SYS_MISC_MAGIC                        0x1900
++#define MXL862XX_COMMON_PORTLINKCFGGET        (MXL862XX_COMMON_MAGIC + 0x5)
++#define MXL862XX_COMMON_PORTCFGGET    (MXL862XX_COMMON_MAGIC + 0x7)
+ #define MXL862XX_COMMON_CFGGET                (MXL862XX_COMMON_MAGIC + 0x9)
+ #define MXL862XX_COMMON_CFGSET                (MXL862XX_COMMON_MAGIC + 0xa)
+ #define MXL862XX_COMMON_REGISTERMOD   (MXL862XX_COMMON_MAGIC + 0x11)
+@@ -71,6 +73,17 @@
+ #define SYS_MISC_FW_VERSION           (SYS_MISC_MAGIC + 0x2)
++#define MXL862XX_XPCS_MAGIC           0x1a00
++#define MXL862XX_XPCS_PCS_CONFIG      (MXL862XX_XPCS_MAGIC + 0x1)
++#define MXL862XX_XPCS_PCS_GET_STATE   (MXL862XX_XPCS_MAGIC + 0x2)
++#define MXL862XX_XPCS_PCS_ENABLE      (MXL862XX_XPCS_MAGIC + 0x3)
++#define MXL862XX_XPCS_PCS_DISABLE     (MXL862XX_XPCS_MAGIC + 0x4)
++#define MXL862XX_XPCS_AN_RESTART      (MXL862XX_XPCS_MAGIC + 0x5)
++#define MXL862XX_XPCS_AN_DISABLE      (MXL862XX_XPCS_MAGIC + 0x6)
++#define MXL862XX_XPCS_FORCE_SPEED     (MXL862XX_XPCS_MAGIC + 0x7)
++#define MXL862XX_XPCS_LOOPBACK                (MXL862XX_XPCS_MAGIC + 0x8)
++#define MXL862XX_XPCS_RESET           (MXL862XX_XPCS_MAGIC + 0x9)
++
+ #define MMD_API_MAXIMUM_ID            0x7fff
+ #endif /* __MXL862XX_CMD_H */
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
+@@ -7,10 +7,14 @@
+  * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
+  */
++#include <linux/iopoll.h>
+ #include <linux/phylink.h>
+ #include <net/dsa.h>
+ #include "mxl862xx.h"
++#include "mxl862xx-api.h"
++#include "mxl862xx-cmd.h"
++#include "mxl862xx-host.h"
+ #include "mxl862xx-phylink.h"
+ void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
+@@ -19,8 +23,393 @@ void mxl862xx_phylink_get_caps(struct ds
+       config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 |
+                                  MAC_100 | MAC_1000 | MAC_2500FD;
+-      __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+-                config->supported_interfaces);
++      switch (port) {
++      case 1 ... 8:
++              __set_bit(PHY_INTERFACE_MODE_INTERNAL,
++                        config->supported_interfaces);
++              break;
++      case 9:
++      case 13:
++              __set_bit(PHY_INTERFACE_MODE_SGMII, config->supported_interfaces);
++              __set_bit(PHY_INTERFACE_MODE_1000BASEX, config->supported_interfaces);
++              __set_bit(PHY_INTERFACE_MODE_2500BASEX, config->supported_interfaces);
++              __set_bit(PHY_INTERFACE_MODE_10GBASER, config->supported_interfaces);
++              __set_bit(PHY_INTERFACE_MODE_10GKR, config->supported_interfaces);
++              __set_bit(PHY_INTERFACE_MODE_USXGMII, config->supported_interfaces);
++              config->mac_capabilities |= MAC_10000FD | MAC_5000FD;
++              fallthrough;
++      case 10 ... 12:
++      case 14 ... 16:
++              __set_bit(PHY_INTERFACE_MODE_QSGMII, config->supported_interfaces);
++              break;
++      default:
++              break;
++      }
++}
++
++static struct mxl862xx_pcs *pcs_to_mxl862xx_pcs(struct phylink_pcs *pcs)
++{
++      return container_of(pcs, struct mxl862xx_pcs, pcs);
++}
++
++static int mxl862xx_xpcs_port_id(int port)
++{
++      return port >= 13;
++}
++
++static int mxl862xx_xpcs_if_mode(phy_interface_t interface)
++{
++      switch (interface) {
++      case PHY_INTERFACE_MODE_SGMII:
++              return MXL862XX_XPCS_IF_SGMII;
++      case PHY_INTERFACE_MODE_QSGMII:
++              return MXL862XX_XPCS_IF_QSGMII;
++      case PHY_INTERFACE_MODE_1000BASEX:
++              return MXL862XX_XPCS_IF_1000BASEX;
++      case PHY_INTERFACE_MODE_2500BASEX:
++              return MXL862XX_XPCS_IF_2500BASEX;
++      case PHY_INTERFACE_MODE_USXGMII:
++              return MXL862XX_XPCS_IF_USXGMII;
++      case PHY_INTERFACE_MODE_10GBASER:
++              return MXL862XX_XPCS_IF_10GBASER;
++      case PHY_INTERFACE_MODE_10GKR:
++              return MXL862XX_XPCS_IF_10GKR;
++      default:
++              return -EINVAL;
++      }
++}
++
++static int mxl862xx_xpcs_neg_mode(unsigned int neg_mode)
++{
++      if (!(neg_mode & PHYLINK_PCS_NEG_INBAND))
++              return MXL862XX_XPCS_NEG_NONE;
++      if (neg_mode & PHYLINK_PCS_NEG_ENABLED)
++              return MXL862XX_XPCS_NEG_INBAND_AN_ON;
++      return MXL862XX_XPCS_NEG_INBAND_AN_OFF;
++}
++
++static struct mxl862xx_xpcs_signal_detect
++mxl862xx_xpcs_signal_detect(struct mxl862xx_priv *priv, int port_id)
++{
++      struct mxl862xx_xpcs_signal_detect sd = { .port_id = port_id };
++
++      MXL862XX_API_READ(priv, MXL862XX_XPCS_SIGNAL_DETECT, sd);
++
++      return sd;
++}
++
++static int mxl862xx_xpcs_poll_ready(struct mxl862xx_priv *priv, int port_id)
++{
++      struct mxl862xx_xpcs_signal_detect sd;
++      int ret;
++
++      ret = read_poll_timeout(mxl862xx_xpcs_signal_detect, sd,
++                              !sd.in_reset, 50000, 1000000,
++                              false, priv, port_id);
++      if (ret)
++              dev_warn(priv->ds->dev, "XPCS%d ready timeout\n", port_id);
++
++      return ret;
++}
++
++static void mxl862xx_pcs_disable(struct phylink_pcs *pcs)
++{
++      struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
++      struct mxl862xx_priv *priv = mpcs->priv;
++      int port = mpcs->port;
++      struct mxl862xx_xpcs_pcs_power pwr = {};
++
++      if (port != 9 && port != 13)
++              return;
++
++      pwr.port_id = mxl862xx_xpcs_port_id(port);
++
++      MXL862XX_API_WRITE(priv, MXL862XX_XPCS_PCS_DISABLE, pwr);
++      mpcs->enabled = false;
++}
++
++static void mxl862xx_pcs_pre_config(struct phylink_pcs *pcs,
++                                  phy_interface_t interface)
++{
++      struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
++      struct mxl862xx_priv *priv = mpcs->priv;
++      int port = mpcs->port;
++      struct mxl862xx_xpcs_pcs_power pwr = {};
++      struct mxl862xx_xpcs_reset_cfg rst = {};
++      int port_id, if_mode;
++
++      if (port != 9 && port != 13)
++              return;
++
++      if_mode = mxl862xx_xpcs_if_mode(interface);
++      if (if_mode < 0)
++              return;
++
++      port_id = mxl862xx_xpcs_port_id(port);
++
++      /* Full reset only if PCS is already running (not after a clean disable,
++       * which already asserts hardware reset via XPCS_PCS_DISABLE).
++       */
++      if (mpcs->enabled) {
++              rst.port_id = port_id;
++              rst.reset_type = MXL862XX_XPCS_RESET_HARD;
++              MXL862XX_API_WRITE(priv, MXL862XX_XPCS_RESET, rst);
++              mxl862xx_xpcs_poll_ready(priv, port_id);
++      }
++
++      pwr.port_id = port_id;
++      pwr.interface = if_mode;
++      MXL862XX_API_WRITE(priv, MXL862XX_XPCS_PCS_ENABLE, pwr);
++      mxl862xx_xpcs_poll_ready(priv, port_id);
++      mpcs->enabled = true;
++}
++
++static int mxl862xx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
++                             phy_interface_t interface,
++                             const unsigned long *advertising,
++                             bool permit_pause_to_mac)
++{
++      struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
++      struct mxl862xx_priv *priv = mpcs->priv;
++      int port = mpcs->port;
++      struct mxl862xx_xpcs_pcs_cfg cfg = {};
++      int if_mode, ret;
++
++      /* Sub-interfaces are set up implicitly by the main interface */
++      if (port != 9 && port != 13)
++              return 0;
++
++      if_mode = mxl862xx_xpcs_if_mode(interface);
++      if (if_mode < 0) {
++              dev_err(priv->ds->dev, "unsupported interface: %s\n",
++                      phy_modes(interface));
++              return if_mode;
++      }
++
++      mpcs->if_mode = if_mode;
++
++      cfg.port_id = mxl862xx_xpcs_port_id(port);
++      cfg.interface = if_mode;
++      cfg.neg_mode = mxl862xx_xpcs_neg_mode(neg_mode);
++      cfg.permit_pause = permit_pause_to_mac ? 1 : 0;
++
++      if (neg_mode & PHYLINK_PCS_NEG_INBAND) {
++              switch (interface) {
++              case PHY_INTERFACE_MODE_1000BASEX:
++              case PHY_INTERFACE_MODE_2500BASEX:
++                      cfg.advertising = cpu_to_le16(
++                              linkmode_adv_to_mii_adv_x(advertising,
++                                      ETHTOOL_LINK_MODE_1000baseX_Full_BIT));
++                      break;
++              case PHY_INTERFACE_MODE_SGMII:
++              case PHY_INTERFACE_MODE_QSGMII:
++                      cfg.advertising = cpu_to_le16(ADVERTISE_SGMII);
++                      break;
++              default:
++                      break;
++              }
++      }
++
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_XPCS_PCS_CONFIG, cfg);
++      if (ret)
++              return ret;
++
++      /* result > 0 means AN restart is needed */
++      return le16_to_cpu(cfg.result) > 0 ? 1 : 0;
++}
++
++static void mxl862xx_xpcs_decode_speed(u16 fw_speed,
++                                      struct phylink_link_state *state)
++{
++      switch (fw_speed) {
++      case MXL862XX_XPCS_SPEED_10:
++              state->speed = SPEED_10;
++              break;
++      case MXL862XX_XPCS_SPEED_100:
++              state->speed = SPEED_100;
++              break;
++      case MXL862XX_XPCS_SPEED_1000:
++              state->speed = SPEED_1000;
++              break;
++      case MXL862XX_XPCS_SPEED_2500:
++              state->speed = SPEED_2500;
++              break;
++      case MXL862XX_XPCS_SPEED_5000:
++              state->speed = SPEED_5000;
++              break;
++      case MXL862XX_XPCS_SPEED_10000:
++              state->speed = SPEED_10000;
++              break;
++      default:
++              state->speed = SPEED_UNKNOWN;
++              break;
++      }
++
++      state->duplex = DUPLEX_FULL;
++}
++
++static void mxl862xx_pcs_get_state(struct phylink_pcs *pcs,
++                                 struct phylink_link_state *state)
++{
++      struct mxl862xx_priv *priv = pcs_to_mxl862xx_pcs(pcs)->priv;
++      int port = pcs_to_mxl862xx_pcs(pcs)->port;
++      struct mxl862xx_xpcs_pcs_state st = {};
++      int if_mode, ret;
++      u16 fw_speed, lpa, bmsr;
++
++      if_mode = mxl862xx_xpcs_if_mode(state->interface);
++      if (if_mode < 0)
++              return;
++
++      st.port_id = mxl862xx_xpcs_port_id(port);
++      st.interface = if_mode;
++
++      ret = MXL862XX_API_READ(priv, MXL862XX_XPCS_PCS_GET_STATE, st);
++      if (ret)
++              return;
++
++      fw_speed = le16_to_cpu(st.speed);
++      lpa = le16_to_cpu(st.lpa);
++
++      state->link = st.link && !st.pcs_fault;
++      if (!state->link)
++              return;
++
++      switch (state->interface) {
++      case PHY_INTERFACE_MODE_1000BASEX:
++      case PHY_INTERFACE_MODE_2500BASEX:
++      case PHY_INTERFACE_MODE_SGMII:
++      case PHY_INTERFACE_MODE_QSGMII:
++              /* Synthesize BMSR from firmware state and use phylink's
++               * standard CL37/SGMII decoders for LPA, pause, and speed.
++               */
++              bmsr = BMSR_LSTATUS;
++              if (st.an_complete)
++                      bmsr |= BMSR_ANEGCOMPLETE;
++              phylink_mii_c22_pcs_decode_state(state, bmsr, lpa);
++
++              /* Override speed/duplex with firmware's resolved values
++               * for downshift detection.
++               */
++              mxl862xx_xpcs_decode_speed(fw_speed, state);
++              state->duplex = st.duplex ? DUPLEX_FULL : DUPLEX_HALF;
++              break;
++
++      case PHY_INTERFACE_MODE_USXGMII:
++              state->an_complete = st.an_complete;
++              phylink_decode_usxgmii_word(state, lpa);
++
++              /* Override with firmware's resolved values */
++              mxl862xx_xpcs_decode_speed(fw_speed, state);
++              state->duplex = st.duplex ? DUPLEX_FULL : DUPLEX_HALF;
++              break;
++
++      case PHY_INTERFACE_MODE_10GBASER:
++      case PHY_INTERFACE_MODE_10GKR:
++              mxl862xx_xpcs_decode_speed(fw_speed, state);
++              break;
++
++      default:
++              state->link = false;
++              break;
++      }
++}
++
++static void mxl862xx_pcs_an_restart(struct phylink_pcs *pcs)
++{
++      struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
++      struct mxl862xx_priv *priv = mpcs->priv;
++      int port = mpcs->port;
++      struct mxl862xx_xpcs_an_restart an = {};
++
++      if (port != 9 && port != 13)
++              return;
++
++      an.port_id = mxl862xx_xpcs_port_id(port);
++      an.interface = mpcs->if_mode;
++
++      MXL862XX_API_WRITE(priv, MXL862XX_XPCS_AN_RESTART, an);
++}
++
++static void mxl862xx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
++                               phy_interface_t interface, int speed,
++                               int duplex)
++{
++      struct mxl862xx_priv *priv = pcs_to_mxl862xx_pcs(pcs)->priv;
++      int port = pcs_to_mxl862xx_pcs(pcs)->port;
++      struct mxl862xx_xpcs_force_speed fs = {};
++
++      /* Only SGMII needs explicit speed forcing */
++      if (interface != PHY_INTERFACE_MODE_SGMII)
++              return;
++
++      if (port != 9 && port != 13)
++              return;
++
++      fs.port_id = mxl862xx_xpcs_port_id(port);
++      fs.duplex = (duplex == DUPLEX_FULL) ? MXL862XX_XPCS_DUPLEX_FULL :
++                                            MXL862XX_XPCS_DUPLEX_HALF;
++      fs.speed = cpu_to_le16(speed);
++
++      MXL862XX_API_WRITE(priv, MXL862XX_XPCS_FORCE_SPEED, fs);
++}
++
++static unsigned int mxl862xx_pcs_inband_caps(struct phylink_pcs *pcs,
++                                           phy_interface_t interface)
++{
++      switch (interface) {
++      case PHY_INTERFACE_MODE_SGMII:
++      case PHY_INTERFACE_MODE_QSGMII:
++      case PHY_INTERFACE_MODE_USXGMII:
++      case PHY_INTERFACE_MODE_1000BASEX:
++      case PHY_INTERFACE_MODE_2500BASEX:
++      case PHY_INTERFACE_MODE_10GBASER:
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE |
++                     LINK_INBAND_BYPASS;
++      case PHY_INTERFACE_MODE_10GKR:
++              return LINK_INBAND_ENABLE | LINK_INBAND_BYPASS;
++      default:
++              return 0;
++      }
++}
++
++static const struct phylink_pcs_ops mxl862xx_pcs_ops = {
++      .pcs_disable = mxl862xx_pcs_disable,
++      .pcs_pre_config = mxl862xx_pcs_pre_config,
++      .pcs_config = mxl862xx_pcs_config,
++      .pcs_get_state = mxl862xx_pcs_get_state,
++      .pcs_an_restart = mxl862xx_pcs_an_restart,
++      .pcs_link_up = mxl862xx_pcs_link_up,
++      .pcs_inband_caps = mxl862xx_pcs_inband_caps,
++};
++
++void mxl862xx_setup_pcs(struct mxl862xx_priv *priv, struct mxl862xx_pcs *pcs,
++                      int port)
++{
++      pcs->priv = priv;
++      pcs->port = port;
++
++      pcs->pcs.ops = &mxl862xx_pcs_ops;
++      pcs->pcs.poll = true;
++}
++
++static struct phylink_pcs *
++mxl862xx_phylink_mac_select_pcs(struct phylink_config *config,
++                              phy_interface_t interface)
++{
++      struct dsa_port *dp = dsa_phylink_to_port(config);
++      struct mxl862xx_priv *priv = dp->ds->priv;
++      int port = dp->index;
++
++      if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 80))
++              return NULL;
++
++      switch (port) {
++      case 9 ... 16:
++              return &priv->serdes_ports[port - 9].pcs;
++      default:
++              return NULL;
++      }
+ }
+ static void mxl862xx_phylink_mac_config(struct phylink_config *config,
+@@ -48,4 +437,5 @@ const struct phylink_mac_ops mxl862xx_ph
+       .mac_config = mxl862xx_phylink_mac_config,
+       .mac_link_down = mxl862xx_phylink_mac_link_down,
+       .mac_link_up = mxl862xx_phylink_mac_link_up,
++      .mac_select_pcs = mxl862xx_phylink_mac_select_pcs,
+ };
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
+@@ -10,5 +10,7 @@
+ extern const struct phylink_mac_ops mxl862xx_phylink_mac_ops;
+ void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
+                              struct phylink_config *config);
++void mxl862xx_setup_pcs(struct mxl862xx_priv *priv, struct mxl862xx_pcs *pcs,
++                      int port);
+ #endif /* __MXL862XX_PHYLINK_H */
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -729,7 +729,7 @@ static int mxl862xx_setup(struct dsa_swi
+       int n_user_ports = 0, max_vlans;
+       int ingress_finals, vid_rules;
+       struct dsa_port *dp;
+-      int ret;
++      int ret, i;
+       ret = mxl862xx_reset(priv);
+       if (ret)
+@@ -739,6 +739,9 @@ static int mxl862xx_setup(struct dsa_swi
+       if (ret)
+               return ret;
++      for (i = 0; i < 8; i++)
++              mxl862xx_setup_pcs(priv, &priv->serdes_ports[i], i + 9);
++
+       /* Calculate Extended VLAN block sizes.
+        * With VLAN Filter handling VID membership checks:
+        *   Ingress: only final catchall rules (PVID insertion, 802.1Q
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -247,6 +247,22 @@ struct mxl862xx_port {
+ };
+ /**
++ * struct mxl862xx_pcs - link SerDes interfaces to bridge ports
++ * @pcs:     &struct phylink_pcs instance
++ * @priv:    pointer to &struct mxl862xx_priv
++ * @port:    bridge port index
++ * @if_mode: cached firmware interface mode (enum mxl862xx_xpcs_if_mode)
++ * @enabled: true if the PCS/SerDes is currently powered up
++ */
++struct mxl862xx_pcs {
++      struct phylink_pcs pcs;
++      struct mxl862xx_priv *priv;
++      int port;
++      int if_mode;
++      bool enabled;
++};
++
++/**
+  * union mxl862xx_fw_version - firmware version for comparison and display
+  * @major: firmware major version
+  * @minor: firmware minor version
+@@ -291,6 +307,8 @@ union mxl862xx_fw_version {
+  *                      flooding)
+  * @fw_version:         cached firmware version, populated at probe and
+  *                      compared with MXL862XX_FW_VER_MIN()
++ * @serdes_ports:       SerDes interfaces incl. sub-interfaces in case of
++ *                      10G_QXGMII
+  * @ports:              per-port state, indexed by switch port number
+  * @bridges:            maps DSA bridge number to firmware bridge ID;
+  *                      zero means no firmware bridge allocated for that
+@@ -309,6 +327,7 @@ struct mxl862xx_priv {
+       unsigned long crc_err;
+       u16 drop_meter;
+       union mxl862xx_fw_version fw_version;
++      struct mxl862xx_pcs serdes_ports[8];
+       struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
+       u16 bridges[MXL862XX_MAX_BRIDGES + 1];
+       u16 evlan_ingress_size;
diff --git a/target/linux/generic/pending-6.12/760-12-net-dsa-mxl862xx-add-SerDes-ethtool-statistics.patch b/target/linux/generic/pending-6.12/760-12-net-dsa-mxl862xx-add-SerDes-ethtool-statistics.patch
new file mode 100644 (file)
index 0000000..5f1e1bb
--- /dev/null
@@ -0,0 +1,268 @@
+From d40565e2e00fc2c8f04b9c571fcbea2f146db844 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 18:14:33 +0000
+Subject: [PATCH 21/35] net: dsa: mxl862xx: add SerDes ethtool statistics
+
+Expose SerDes equalization and signal detect parameters as ethtool
+statistics on ports 9-16 (XPCS-backed ports). Uses the XPCS EQ_GET
+and SIGNAL_DETECT firmware commands to read TX/RX equalization
+coefficients, DFE taps, and link-level signal status.
+
+The 19 additional stats (serdes_tx_*, serdes_rx_*, serdes_pma_link,
+serdes_link_fault, serdes_in_reset) are appended after the standard
+RMON counters and gated on firmware >= 1.0.80.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h     | 88 +++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h     |  2 +
+ drivers/net/dsa/mxl862xx/mxl862xx-phylink.c | 93 +++++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-phylink.h |  3 +
+ drivers/net/dsa/mxl862xx/mxl862xx.c         |  6 +-
+ 5 files changed, 191 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+@@ -1812,4 +1812,92 @@ struct mxl862xx_xpcs_reset_cfg {
+       __le16 result;
+ } __packed;
++/**
++ * struct mxl862xx_xpcs_eq_item - single equalization parameter
++ * @value: current initial value
++ * @ovrd: override value
++ * @ovrd_en: override enable flag
++ */
++struct mxl862xx_xpcs_eq_item {
++      u8 value;
++      u8 ovrd;
++      u8 ovrd_en;
++} __packed;
++
++/**
++ * struct mxl862xx_xpcs_tx_eq_info - TX equalization status
++ * @main: TX main cursor (0-63)
++ * @pre: TX pre-cursor (0-63)
++ * @post: TX post-cursor (0-63)
++ * @iboost_lvl: TX iboost level (0-15)
++ * @vboost_lvl: TX vboost level (0-7)
++ * @vboost_en: TX vboost enable (0-1)
++ */
++struct mxl862xx_xpcs_tx_eq_info {
++      struct mxl862xx_xpcs_eq_item main;
++      struct mxl862xx_xpcs_eq_item pre;
++      struct mxl862xx_xpcs_eq_item post;
++      struct mxl862xx_xpcs_eq_item iboost_lvl;
++      struct mxl862xx_xpcs_eq_item vboost_lvl;
++      struct mxl862xx_xpcs_eq_item vboost_en;
++} __packed;
++
++/**
++ * struct mxl862xx_xpcs_rx_eq_info - RX equalization status
++ * @att_lvl: RX attenuation level (0-7)
++ * @vga1_gain: RX VGA1 gain (0-7)
++ * @vga2_gain: RX VGA2 gain (0-7)
++ * @ctle_boost: RX CTLE boost (0-31)
++ * @ctle_pole: RX CTLE pole (0-3)
++ * @dfe_tap1: RX DFE tap1 (0-255)
++ * @dfe_bypass: RX DFE bypass (0-1)
++ * @adapt_mode: RX adapt mode (0-3)
++ * @adapt_sel: RX adapt select (0-1)
++ */
++struct mxl862xx_xpcs_rx_eq_info {
++      struct mxl862xx_xpcs_eq_item att_lvl;
++      struct mxl862xx_xpcs_eq_item vga1_gain;
++      struct mxl862xx_xpcs_eq_item vga2_gain;
++      struct mxl862xx_xpcs_eq_item ctle_boost;
++      struct mxl862xx_xpcs_eq_item ctle_pole;
++      struct mxl862xx_xpcs_eq_item dfe_tap1;
++      struct mxl862xx_xpcs_eq_item dfe_bypass;
++      struct mxl862xx_xpcs_eq_item adapt_mode;
++      struct mxl862xx_xpcs_eq_item adapt_sel;
++} __packed;
++
++/**
++ * struct mxl862xx_xpcs_eq_get - EQ get request/response
++ * @port_id: XPCS port index (0 or 1)
++ * @result: firmware result
++ * @tx: TX equalization info
++ * @rx: RX equalization info
++ */
++struct mxl862xx_xpcs_eq_get {
++      u8 port_id;
++      __le16 result;
++      struct mxl862xx_xpcs_tx_eq_info tx;
++      struct mxl862xx_xpcs_rx_eq_info rx;
++} __packed;
++
++/**
++ * struct mxl862xx_xpcs_signal_detect - signal detect status
++ * @port_id: XPCS port index (0 or 1)
++ * @rx_signal: RX signal detected
++ * @pma_link: PMA link up
++ * @link_fault: PCS link fault
++ * @in_reset: XPCS in reset
++ * @result: firmware result
++ */
++struct mxl862xx_xpcs_signal_detect {
++      u8 port_id:2;
++      u8 rx_signal:1;
++      u8 pma_link:1;
++      u8 link_fault:1;
++      u8 in_reset:1;
++      u8 __rsv:2;
++      u8 __pad;
++      __le16 result;
++} __packed;
++
+ #endif /* __MXL862XX_API_H */
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+@@ -83,6 +83,8 @@
+ #define MXL862XX_XPCS_FORCE_SPEED     (MXL862XX_XPCS_MAGIC + 0x7)
+ #define MXL862XX_XPCS_LOOPBACK                (MXL862XX_XPCS_MAGIC + 0x8)
+ #define MXL862XX_XPCS_RESET           (MXL862XX_XPCS_MAGIC + 0x9)
++#define MXL862XX_XPCS_EQ_GET          (MXL862XX_XPCS_MAGIC + 0xc)
++#define MXL862XX_XPCS_SIGNAL_DETECT   (MXL862XX_XPCS_MAGIC + 0xd)
+ #define MMD_API_MAXIMUM_ID            0x7fff
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
+@@ -439,3 +439,96 @@ const struct phylink_mac_ops mxl862xx_ph
+       .mac_link_up = mxl862xx_phylink_mac_link_up,
+       .mac_select_pcs = mxl862xx_phylink_mac_select_pcs,
+ };
++
++/* --- SerDes ethtool statistics --- */
++
++static const char mxl862xx_serdes_stats[][ETH_GSTRING_LEN] = {
++      "serdes_tx_main",
++      "serdes_tx_pre",
++      "serdes_tx_post",
++      "serdes_tx_iboost",
++      "serdes_tx_vboost",
++      "serdes_tx_vboost_en",
++      "serdes_rx_att",
++      "serdes_rx_vga1",
++      "serdes_rx_vga2",
++      "serdes_rx_ctle_boost",
++      "serdes_rx_ctle_pole",
++      "serdes_rx_dfe_tap1",
++      "serdes_rx_dfe_bypass",
++      "serdes_rx_adapt_mode",
++      "serdes_rx_adapt_sel",
++      "serdes_rx_signal",
++      "serdes_pma_link",
++      "serdes_link_fault",
++      "serdes_in_reset",
++};
++
++static bool mxl862xx_port_has_serdes_stats(struct dsa_switch *ds, int port)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++
++      return port >= 9 && port <= 16 &&
++             MXL862XX_FW_VER_MIN(priv, 1, 0, 80);
++}
++
++int mxl862xx_serdes_stats_count(struct dsa_switch *ds, int port)
++{
++      if (mxl862xx_port_has_serdes_stats(ds, port))
++              return ARRAY_SIZE(mxl862xx_serdes_stats);
++
++      return 0;
++}
++
++void mxl862xx_serdes_get_strings(struct dsa_switch *ds, int port, u8 *data)
++{
++      int i;
++
++      if (!mxl862xx_port_has_serdes_stats(ds, port))
++              return;
++
++      for (i = 0; i < ARRAY_SIZE(mxl862xx_serdes_stats); i++)
++              ethtool_puts(&data, mxl862xx_serdes_stats[i]);
++}
++
++void mxl862xx_serdes_get_stats(struct dsa_switch *ds, int port, u64 *data)
++{
++      struct mxl862xx_xpcs_eq_get eq = {
++              .port_id = mxl862xx_xpcs_port_id(port),
++      };
++      struct mxl862xx_xpcs_signal_detect sig = {};
++
++      if (!mxl862xx_port_has_serdes_stats(ds, port))
++              return;
++
++      sig.port_id = mxl862xx_xpcs_port_id(port);
++
++      if (!MXL862XX_API_READ(ds->priv, MXL862XX_XPCS_EQ_GET, eq)) {
++              *data++ = eq.tx.main.value;
++              *data++ = eq.tx.pre.value;
++              *data++ = eq.tx.post.value;
++              *data++ = eq.tx.iboost_lvl.value;
++              *data++ = eq.tx.vboost_lvl.value;
++              *data++ = eq.tx.vboost_en.value;
++              *data++ = eq.rx.att_lvl.value;
++              *data++ = eq.rx.vga1_gain.value;
++              *data++ = eq.rx.vga2_gain.value;
++              *data++ = eq.rx.ctle_boost.value;
++              *data++ = eq.rx.ctle_pole.value;
++              *data++ = eq.rx.dfe_tap1.value;
++              *data++ = eq.rx.dfe_bypass.value;
++              *data++ = eq.rx.adapt_mode.value;
++              *data++ = eq.rx.adapt_sel.value;
++      } else {
++              data += 15;
++      }
++
++      if (!MXL862XX_API_READ(ds->priv, MXL862XX_XPCS_SIGNAL_DETECT, sig)) {
++              *data++ = sig.rx_signal;
++              *data++ = sig.pma_link;
++              *data++ = sig.link_fault;
++              *data++ = sig.in_reset;
++      } else {
++              data += 4;
++      }
++}
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
+@@ -12,5 +12,8 @@ void mxl862xx_phylink_get_caps(struct ds
+                              struct phylink_config *config);
+ void mxl862xx_setup_pcs(struct mxl862xx_priv *priv, struct mxl862xx_pcs *pcs,
+                       int port);
++int mxl862xx_serdes_stats_count(struct dsa_switch *ds, int port);
++void mxl862xx_serdes_get_strings(struct dsa_switch *ds, int port, u8 *data);
++void mxl862xx_serdes_get_stats(struct dsa_switch *ds, int port, u64 *data);
+ #endif /* __MXL862XX_PHYLINK_H */
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -2007,6 +2007,8 @@ static void mxl862xx_get_strings(struct
+       for (i = 0; i < ARRAY_SIZE(mxl862xx_mib); i++)
+               ethtool_puts(&data, mxl862xx_mib[i].name);
++
++      mxl862xx_serdes_get_strings(ds, port, data);
+ }
+ static int mxl862xx_get_sset_count(struct dsa_switch *ds, int port, int sset)
+@@ -2014,7 +2016,7 @@ static int mxl862xx_get_sset_count(struc
+       if (sset != ETH_SS_STATS)
+               return 0;
+-      return ARRAY_SIZE(mxl862xx_mib);
++      return ARRAY_SIZE(mxl862xx_mib) + mxl862xx_serdes_stats_count(ds, port);
+ }
+ static int mxl862xx_read_rmon(struct dsa_switch *ds, int port,
+@@ -2050,6 +2052,8 @@ static void mxl862xx_get_ethtool_stats(s
+               else
+                       *data++ = le64_to_cpu(*(__le64 *)field);
+       }
++
++      mxl862xx_serdes_get_stats(ds, port, data);
+ }
+ static void mxl862xx_get_eth_mac_stats(struct dsa_switch *ds, int port,
diff --git a/target/linux/generic/pending-6.12/760-13-net-dsa-mxl862xx-add-SerDes-self-test-via-PRBS-and-B.patch b/target/linux/generic/pending-6.12/760-13-net-dsa-mxl862xx-add-SerDes-self-test-via-PRBS-and-B.patch
new file mode 100644 (file)
index 0000000..1da11ae
--- /dev/null
@@ -0,0 +1,208 @@
+From 54dd5fabc543f8538202367a863eb0e9161bacab Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 18:15:32 +0000
+Subject: [PATCH 22/35] net: dsa: mxl862xx: add SerDes self-test via PRBS and
+ BERT
+
+Implement the dsa_switch_ops.self_test callback for SerDes ports
+(9-16). Two loopback tests are run:
+
+  1. PCS-level PRBS31: enables TX/RX PRBS31 pattern at the PCS layer,
+     waits 100ms, then reads the error counter.
+  2. SerDes-level BERT PRBS31: enables TX/RX BERT with PRBS31 pattern
+     at the SerDes layer, waits 100ms, then reads the error counter.
+
+Both tests clean up (disable pattern generators) regardless of outcome.
+Gated on firmware >= 1.0.80 via mxl862xx_port_has_serdes_stats().
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h     | 45 +++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h     |  2 +
+ drivers/net/dsa/mxl862xx/mxl862xx-phylink.c | 85 +++++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-phylink.h |  3 +
+ drivers/net/dsa/mxl862xx/mxl862xx.c         |  1 +
+ 5 files changed, 136 insertions(+)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+@@ -1900,4 +1900,49 @@ struct mxl862xx_xpcs_signal_detect {
+       __le16 result;
+ } __packed;
++/**
++ * struct mxl862xx_xpcs_prbs_cfg - PCS-level PRBS31 test pattern
++ * @port_id: XPCS port index (0 or 1)
++ * @tx_en: TX PRBS31 enable
++ * @rx_en: RX PRBS31 enable
++ * @read_err: read error count
++ * @rx_err_cnt: RX PRBS31 error count (valid when read_err=1)
++ * @result: firmware result
++ */
++struct mxl862xx_xpcs_prbs_cfg {
++      u8 port_id:2;
++      u8 tx_en:1;
++      u8 rx_en:1;
++      u8 read_err:1;
++      u8 __rsv:3;
++      u8 __pad;
++      __le16 rx_err_cnt;
++      __le16 result;
++} __packed;
++
++/**
++ * struct mxl862xx_xpcs_bert_cfg - SerDes-level BERT test pattern
++ * @port_id: XPCS port index (0 or 1)
++ * @tx_en: TX BERT enable
++ * @rx_en: RX BERT enable
++ * @read_err: read RX error count
++ * @clear_err: clear RX error counter
++ * @insert_err: insert one TX error
++ * @pattern: PRBS pattern type (1-7; 0 = disable)
++ * @rx_err_cnt: RX BERT error count (valid when read_err=1)
++ * @result: firmware result
++ */
++struct mxl862xx_xpcs_bert_cfg {
++      u8 port_id:2;
++      u8 tx_en:1;
++      u8 rx_en:1;
++      u8 read_err:1;
++      u8 clear_err:1;
++      u8 insert_err:1;
++      u8 __rsv:1;
++      u8 pattern;
++      __le16 rx_err_cnt;
++      __le16 result;
++} __packed;
++
+ #endif /* __MXL862XX_API_H */
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+@@ -83,6 +83,8 @@
+ #define MXL862XX_XPCS_FORCE_SPEED     (MXL862XX_XPCS_MAGIC + 0x7)
+ #define MXL862XX_XPCS_LOOPBACK                (MXL862XX_XPCS_MAGIC + 0x8)
+ #define MXL862XX_XPCS_RESET           (MXL862XX_XPCS_MAGIC + 0x9)
++#define MXL862XX_XPCS_PRBS_CFG                (MXL862XX_XPCS_MAGIC + 0xa)
++#define MXL862XX_XPCS_BERT_CFG                (MXL862XX_XPCS_MAGIC + 0xb)
+ #define MXL862XX_XPCS_EQ_GET          (MXL862XX_XPCS_MAGIC + 0xc)
+ #define MXL862XX_XPCS_SIGNAL_DETECT   (MXL862XX_XPCS_MAGIC + 0xd)
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
+@@ -532,3 +532,88 @@ void mxl862xx_serdes_get_stats(struct ds
+               data += 4;
+       }
+ }
++
++void mxl862xx_serdes_self_test(struct dsa_switch *ds, int port,
++                      struct ethtool_test *etest, u64 *data)
++{
++      struct mxl862xx_xpcs_prbs_cfg prbs = {};
++      struct mxl862xx_xpcs_bert_cfg bert = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      int xpcs_id = mxl862xx_xpcs_port_id(port);
++      int i = 0;
++      int ret;
++
++      if (!mxl862xx_port_has_serdes_stats(ds, port))
++              return;
++
++      /* Test 1: PCS PRBS31 */
++      prbs.port_id = xpcs_id;
++      prbs.tx_en = 1;
++      prbs.rx_en = 1;
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_XPCS_PRBS_CFG, prbs);
++      if (ret) {
++              data[i++] = 1;
++              goto skip_prbs;
++      }
++
++      msleep(100);
++
++      memset(&prbs, 0, sizeof(prbs));
++      prbs.port_id = xpcs_id;
++      prbs.read_err = 1;
++      ret = MXL862XX_API_READ(priv, MXL862XX_XPCS_PRBS_CFG, prbs);
++
++      /* Disable PRBS */
++      memset(&prbs, 0, sizeof(prbs));
++      prbs.port_id = xpcs_id;
++      MXL862XX_API_WRITE(priv, MXL862XX_XPCS_PRBS_CFG, prbs);
++
++      if (ret) {
++              data[i++] = 1;
++      } else {
++              data[i] = le16_to_cpu(prbs.rx_err_cnt) ? 1 : 0;
++              if (data[i])
++                      etest->flags |= ETH_TEST_FL_FAILED;
++              i++;
++      }
++
++skip_prbs:
++      /* Test 2: SerDes BERT PRBS31 -- clear error counter first */
++      bert.port_id = xpcs_id;
++      bert.clear_err = 1;
++      MXL862XX_API_WRITE(priv, MXL862XX_XPCS_BERT_CFG, bert);
++
++      /* Enable BERT with PRBS31 pattern */
++      memset(&bert, 0, sizeof(bert));
++      bert.port_id = xpcs_id;
++      bert.tx_en = 1;
++      bert.rx_en = 1;
++      bert.pattern = 6; /* PRBS31 */
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_XPCS_BERT_CFG, bert);
++      if (ret) {
++              data[i++] = 1;
++              return;
++      }
++
++      msleep(100);
++
++      /* Read error count */
++      memset(&bert, 0, sizeof(bert));
++      bert.port_id = xpcs_id;
++      bert.read_err = 1;
++      ret = MXL862XX_API_READ(priv, MXL862XX_XPCS_BERT_CFG, bert);
++
++      /* Disable BERT */
++      memset(&bert, 0, sizeof(bert));
++      bert.port_id = xpcs_id;
++      MXL862XX_API_WRITE(priv, MXL862XX_XPCS_BERT_CFG, bert);
++
++      if (ret) {
++              data[i++] = 1;
++      } else {
++              data[i] = le16_to_cpu(bert.rx_err_cnt) ? 1 : 0;
++              if (data[i])
++                      etest->flags |= ETH_TEST_FL_FAILED;
++              i++;
++      }
++}
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
+@@ -3,6 +3,7 @@
+ #ifndef __MXL862XX_PHYLINK_H
+ #define __MXL862XX_PHYLINK_H
++#include <linux/ethtool.h>
+ #include <linux/phylink.h>
+ #include "mxl862xx.h"
+@@ -15,5 +16,7 @@ void mxl862xx_setup_pcs(struct mxl862xx_
+ int mxl862xx_serdes_stats_count(struct dsa_switch *ds, int port);
+ void mxl862xx_serdes_get_strings(struct dsa_switch *ds, int port, u8 *data);
+ void mxl862xx_serdes_get_stats(struct dsa_switch *ds, int port, u64 *data);
++void mxl862xx_serdes_self_test(struct dsa_switch *ds, int port,
++                      struct ethtool_test *etest, u64 *data);
+ #endif /* __MXL862XX_PHYLINK_H */
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -2286,6 +2286,7 @@ static const struct dsa_switch_ops mxl86
+       .get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,
+       .get_pause_stats = mxl862xx_get_pause_stats,
+       .get_stats64 = mxl862xx_get_stats64,
++      .self_test = mxl862xx_serdes_self_test,
+ };
+ static int mxl862xx_probe(struct mdio_device *mdiodev)
diff --git a/target/linux/generic/pending-6.12/760-14-net-dsa-mxl862xx-trap-link-local-frames-to-the-CPU-p.patch b/target/linux/generic/pending-6.12/760-14-net-dsa-mxl862xx-trap-link-local-frames-to-the-CPU-p.patch
new file mode 100644 (file)
index 0000000..385e44f
--- /dev/null
@@ -0,0 +1,831 @@
+From dd62e68cd0bd29934c3efbce687d5e103cc4b331 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 18:51:13 +0000
+Subject: [PATCH 23/35] net: dsa: mxl862xx: trap link-local frames to the CPU
+ port
+
+Install per-CTP PCE rules on each user port that trap IEEE 802.1D
+link-local frames (01:80:c2:00:00:0x) to the CPU port via an
+explicit forwarding portmap with cross-state enabled, ensuring the
+frames reach the host even when the bridge port is in BLOCKING or
+LEARNING state.
+
+Add the PCE rule firmware API structures, command definitions, and
+the rule block allocation interface.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h | 684 ++++++++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h |   5 +
+ drivers/net/dsa/mxl862xx/mxl862xx.c     |  69 +++
+ 3 files changed, 758 insertions(+)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+@@ -1420,6 +1420,690 @@ struct mxl862xx_port_link_cfg {
+       u8 lpi;
+ } __packed;
++/* PCE (Packet Classification Engine) rule structures.
++ *
++ * Binary layout must exactly match the firmware's GSW_PCE_rule_t
++ * (gsw_flow.h, packed little-endian). The firmware deserializes
++ * the structure directly from the MDIO data buffer produced by
++ * mxl862xx_api_wrap().
++ */
++
++/**
++ * union mxl862xx_ip - IPv4 or IPv6 address
++ * @ipv4: IPv4 address in little-endian
++ * @ipv6: IPv6 address as array of little-endian 16-bit words
++ */
++union mxl862xx_ip {
++      __le32 ipv4;
++      __le16 ipv6[8];
++} __packed;
++
++/**
++ * struct mxl862xx_pce_pattern - PCE rule match pattern
++ *
++ * Every field must remain in the order shown; the firmware
++ * interprets the buffer positionally.
++ *
++ * @index: PCE rule index (0..511)
++ * @dst_ip_mask: Destination IP nibble mask (outer)
++ * @inner_dst_ip_mask: Inner destination IP nibble mask
++ * @src_ip_mask: Source IP nibble mask (outer)
++ * @inner_src_ip_mask: Inner source IP nibble mask
++ * @sub_if_id: Incoming sub-interface ID value
++ * @pkt_lng: Packet length in bytes
++ * @pkt_lng_range: Packet length range upper bound
++ * @mac_dst_mask: Destination MAC nibble mask
++ * @mac_src_mask: Source MAC nibble mask
++ * @app_data_msb: MSB application field (first 2 bytes after IP header,
++ *                typically TCP/UDP source port)
++ * @app_mask_range_msb: MSB application mask or range value
++ * @ether_type: EtherType value to match
++ * @ether_type_mask: EtherType nibble mask
++ * @session_id: PPPoE session ID
++ * @ppp_protocol: PPP protocol value
++ * @ppp_protocol_mask: PPP protocol bit mask
++ * @vid: CTAG VLAN ID (inner VLAN)
++ * @slan_vid: STAG VLAN ID (outer VLAN)
++ * @flex_field4_value: Flexible field 4 match value
++ * @flex_field4_mask_range: Flexible field 4 mask or range
++ * @flex_field3_value: Flexible field 3 match value
++ * @flex_field3_mask_range: Flexible field 3 mask or range
++ * @flex_field1_value: Flexible field 1 match value
++ * @flex_field1_mask_range: Flexible field 1 mask or range
++ * @outer_vid_range: Outer VLAN ID range
++ * @payload1: Payload-1 value (16-bit)
++ * @payload1_mask: Payload-1 bit mask
++ * @payload2: Payload-2 value (16-bit)
++ * @payload2_mask: Payload-2 bit mask
++ * @parser_flag_lsb: Parser flag LSW value (bits 15:0)
++ * @parser_flag_lsb_mask: Parser flag LSW mask (1 = masked out)
++ * @parser_flag_msb: Parser flag MSW value (bits 31:16)
++ * @parser_flag_msb_mask: Parser flag MSW mask (1 = masked out)
++ * @parser_flag1_lsb: Parser flag1 LSW value (bits 47:32)
++ * @parser_flag1_lsb_mask: Parser flag1 LSW mask
++ * @parser_flag1_msb: Parser flag1 MSW value (bits 63:48)
++ * @parser_flag1_msb_mask: Parser flag1 MSW mask
++ * @app_data_lsb: LSB application field (next 2 bytes after @app_data_msb,
++ *                typically TCP/UDP destination port)
++ * @app_mask_range_lsb: LSB application mask or range value
++ * @insertion_flag: CPU-inserted packet flag
++ * @vid_range: CTAG VLAN ID range (used as mask when @vid_range_select is 0)
++ * @flex_field2_mask_range: Flexible field 2 mask or range
++ * @flex_field2_value: Flexible field 2 match value
++ * @port_id: Ingress port ID for classification
++ * @dscp: Outer DSCP value
++ * @inner_dscp: Inner DSCP value
++ * @pcp: CTAG VLAN PCP (bits 2:0) and DEI (bit 3)
++ * @stag_pcp_dei: STAG VLAN PCP (bits 2:0) and DEI (bit 3)
++ * @mac_dst: Destination MAC address
++ * @mac_src: Source MAC address
++ * @protocol: Outer IP protocol value
++ * @protocol_mask: Outer IP protocol nibble mask
++ * @inner_protocol: Inner IP protocol value
++ * @inner_protocol_mask: Inner IP protocol bit mask
++ * @flex_field4_parser_index: Flexible field 4 parser output index (0..127)
++ * @flex_field3_parser_index: Flexible field 3 parser output index (0..127)
++ * @flex_field1_parser_index: Flexible field 1 parser output index (0..127)
++ * @flex_field2_parser_index: Flexible field 2 parser output index (0..127)
++ * @enable: Rule is enabled (used) or disabled (unused)
++ * @port_id_enable: Enable ingress port ID matching
++ * @port_id_exclude: Exclude (negate) port ID match
++ * @sub_if_id_type: Sub-interface ID field mode selector
++ * @sub_if_id_enable: Enable sub-interface ID matching
++ * @sub_if_id_exclude: Exclude sub-interface ID match
++ * @dscp_enable: Enable outer DSCP matching
++ * @dscp_exclude: Exclude outer DSCP match
++ * @inner_dscp_enable: Enable inner DSCP matching
++ * @inner_dscp_exclude: Exclude inner DSCP match
++ * @pcp_enable: Enable CTAG PCP/DEI matching
++ * @ctag_pcp_dei_exclude: Exclude CTAG PCP/DEI match
++ * @stag_pcp_dei_enable: Enable STAG PCP/DEI matching
++ * @stag_pcp_dei_exclude: Exclude STAG PCP/DEI match
++ * @pkt_lng_enable: Enable packet length matching
++ * @pkt_lng_exclude: Exclude packet length match
++ * @mac_dst_enable: Enable destination MAC matching
++ * @dst_mac_exclude: Exclude destination MAC match
++ * @mac_src_enable: Enable source MAC matching
++ * @src_mac_exclude: Exclude source MAC match
++ * @app_data_msb_enable: Enable MSB application field matching
++ * @app_mask_range_msb_select: MSB application mask/range selection
++ *                             (0 = nibble mask, 1 = range)
++ * @app_msb_exclude: Exclude MSB application match
++ * @app_data_lsb_enable: Enable LSB application field matching
++ * @app_mask_range_lsb_select: LSB application mask/range selection
++ *                             (0 = nibble mask, 1 = range)
++ * @app_lsb_exclude: Exclude LSB application match
++ * @dst_ip_select: Outer destination IP selection
++ *                 (0 = disabled, 1 = IPv4, 2 = IPv6)
++ * @dst_ip: Outer destination IP address
++ * @dst_ip_exclude: Exclude outer destination IP match
++ * @inner_dst_ip_select: Inner destination IP selection
++ * @inner_dst_ip: Inner destination IP address
++ * @inner_dst_ip_exclude: Exclude inner destination IP match
++ * @src_ip_select: Outer source IP selection
++ *                 (0 = disabled, 1 = IPv4, 2 = IPv6)
++ * @src_ip: Outer source IP address
++ * @src_ip_exclude: Exclude outer source IP match
++ * @inner_src_ip_select: Inner source IP selection
++ * @inner_src_ip: Inner source IP address
++ * @inner_src_ip_exclude: Exclude inner source IP match
++ * @ether_type_enable: Enable EtherType matching
++ * @ether_type_exclude: Exclude EtherType match
++ * @protocol_enable: Enable outer IP protocol matching
++ * @protocol_exclude: Exclude outer IP protocol match
++ * @inner_protocol_enable: Enable inner IP protocol matching
++ * @inner_protocol_exclude: Exclude inner IP protocol match
++ * @session_id_enable: Enable PPPoE session ID matching
++ * @session_id_exclude: Exclude PPPoE session ID match
++ * @ppp_protocol_enable: Enable PPP protocol matching
++ * @ppp_protocol_exclude: Exclude PPP protocol match
++ * @vid_used: Enable CTAG VLAN ID matching
++ * @vid_range_select: CVLAN mask/range selection (0 = mask, 1 = range)
++ * @vid_exclude: Exclude CTAG VLAN ID match
++ * @vid_original: Use original VLAN ID as key even if modified earlier
++ * @slan_vid_used: Enable STAG VLAN ID matching
++ * @slan_vid_exclude: Exclude STAG VLAN ID match
++ * @svid_range_select: SVLAN mask/range selection (0 = mask, 1 = range)
++ * @outer_vid_original: Use original outer VLAN ID as key even if modified
++ * @payload1_src_enable: Enable payload-1 matching
++ * @payload1_mask_range_select: Payload-1 mask/range selection
++ *                              (0 = bit mask, 1 = range)
++ * @payload1_exclude: Exclude payload-1 match
++ * @payload2_src_enable: Enable payload-2 matching
++ * @payload2_mask_range_select: Payload-2 mask/range selection
++ *                              (0 = bit mask, 1 = range)
++ * @payload2_exclude: Exclude payload-2 match
++ * @parser_flag_lsb_enable: Enable parser flag LSW matching
++ * @parser_flag_lsb_exclude: Exclude parser flag LSW match
++ * @parser_flag_msb_enable: Enable parser flag MSW matching
++ * @parser_flag_msb_exclude: Exclude parser flag MSW match
++ * @parser_flag1_lsb_enable: Enable parser flag1 LSW matching
++ * @parser_flag1_lsb_exclude: Exclude parser flag1 LSW match
++ * @parser_flag1_msb_enable: Enable parser flag1 MSW matching
++ * @parser_flag1_msb_exclude: Exclude parser flag1 MSW match
++ * @insertion_flag_enable: Enable insertion flag matching
++ * @flex_field4_enable: Enable flexible field 4 matching
++ * @flex_field4_exclude_enable: Exclude flexible field 4 match
++ * @flex_field4_range_enable: Flexible field 4 range mode
++ *                            (0 = mask, 1 = range)
++ * @flex_field3_enable: Enable flexible field 3 matching
++ * @flex_field3_exclude_enable: Exclude flexible field 3 match
++ * @flex_field3_range_enable: Flexible field 3 range mode
++ * @flex_field2_enable: Enable flexible field 2 matching
++ * @flex_field2_exclude_enable: Exclude flexible field 2 match
++ * @flex_field2_range_enable: Flexible field 2 range mode
++ * @flex_field1_enable: Enable flexible field 1 matching
++ * @flex_field1_exclude_enable: Exclude flexible field 1 match
++ * @flex_field1_range_enable: Flexible field 1 range mode
++ */
++struct mxl862xx_pce_pattern {
++      __le16 index;
++      __le32 dst_ip_mask;
++      __le32 inner_dst_ip_mask;
++      __le32 src_ip_mask;
++      __le32 inner_src_ip_mask;
++      __le16 sub_if_id;
++      __le16 pkt_lng;
++      __le16 pkt_lng_range;
++      __le16 mac_dst_mask;
++      __le16 mac_src_mask;
++      __le16 app_data_msb;
++      __le16 app_mask_range_msb;
++      __le16 ether_type;
++      __le16 ether_type_mask;
++      __le16 session_id;
++      __le16 ppp_protocol;
++      __le16 ppp_protocol_mask;
++      __le16 vid;
++      __le16 slan_vid;
++      __le16 flex_field4_value;
++      __le16 flex_field4_mask_range;
++      __le16 flex_field3_value;
++      __le16 flex_field3_mask_range;
++      __le16 flex_field1_value;
++      __le16 flex_field1_mask_range;
++      __le16 outer_vid_range;
++      __le16 payload1;
++      __le16 payload1_mask;
++      __le16 payload2;
++      __le16 payload2_mask;
++      __le16 parser_flag_lsb;
++      __le16 parser_flag_lsb_mask;
++      __le16 parser_flag_msb;
++      __le16 parser_flag_msb_mask;
++      __le16 parser_flag1_lsb;
++      __le16 parser_flag1_lsb_mask;
++      __le16 parser_flag1_msb;
++      __le16 parser_flag1_msb_mask;
++      __le16 app_data_lsb;
++      __le16 app_mask_range_lsb;
++      __le16 insertion_flag;
++      __le16 vid_range;
++      __le16 flex_field2_mask_range;
++      __le16 flex_field2_value;
++      u8 port_id;
++      u8 dscp;
++      u8 inner_dscp;
++      u8 pcp;
++      u8 stag_pcp_dei;
++      u8 mac_dst[ETH_ALEN];
++      u8 mac_src[ETH_ALEN];
++      u8 protocol;
++      u8 protocol_mask;
++      u8 inner_protocol;
++      u8 inner_protocol_mask;
++      u8 flex_field4_parser_index;
++      u8 flex_field3_parser_index;
++      u8 flex_field1_parser_index;
++      u8 flex_field2_parser_index;
++      u8 enable;
++      u8 port_id_enable;
++      u8 port_id_exclude;
++      __le32 sub_if_id_type;
++      u8 sub_if_id_enable;
++      u8 sub_if_id_exclude;
++      u8 dscp_enable;
++      u8 dscp_exclude;
++      u8 inner_dscp_enable;
++      u8 inner_dscp_exclude;
++      u8 pcp_enable;
++      u8 ctag_pcp_dei_exclude;
++      u8 stag_pcp_dei_enable;
++      u8 stag_pcp_dei_exclude;
++      u8 pkt_lng_enable;
++      u8 pkt_lng_exclude;
++      u8 mac_dst_enable;
++      u8 dst_mac_exclude;
++      u8 mac_src_enable;
++      u8 src_mac_exclude;
++      u8 app_data_msb_enable;
++      u8 app_mask_range_msb_select;
++      u8 app_msb_exclude;
++      u8 app_data_lsb_enable;
++      u8 app_mask_range_lsb_select;
++      u8 app_lsb_exclude;
++      __le32 dst_ip_select;
++      union mxl862xx_ip dst_ip;
++      u8 dst_ip_exclude;
++      __le32 inner_dst_ip_select;
++      union mxl862xx_ip inner_dst_ip;
++      u8 inner_dst_ip_exclude;
++      __le32 src_ip_select;
++      union mxl862xx_ip src_ip;
++      u8 src_ip_exclude;
++      __le32 inner_src_ip_select;
++      union mxl862xx_ip inner_src_ip;
++      u8 inner_src_ip_exclude;
++      u8 ether_type_enable;
++      u8 ether_type_exclude;
++      u8 protocol_enable;
++      u8 protocol_exclude;
++      u8 inner_protocol_enable;
++      u8 inner_protocol_exclude;
++      u8 session_id_enable;
++      u8 session_id_exclude;
++      u8 ppp_protocol_enable;
++      u8 ppp_protocol_exclude;
++      u8 vid_used;
++      u8 vid_range_select;
++      u8 vid_exclude;
++      u8 vid_original;
++      u8 slan_vid_used;
++      u8 slan_vid_exclude;
++      u8 svid_range_select;
++      u8 outer_vid_original;
++      u8 payload1_src_enable;
++      u8 payload1_mask_range_select;
++      u8 payload1_exclude;
++      u8 payload2_src_enable;
++      u8 payload2_mask_range_select;
++      u8 payload2_exclude;
++      u8 parser_flag_lsb_enable;
++      u8 parser_flag_lsb_exclude;
++      u8 parser_flag_msb_enable;
++      u8 parser_flag_msb_exclude;
++      u8 parser_flag1_lsb_enable;
++      u8 parser_flag1_lsb_exclude;
++      u8 parser_flag1_msb_enable;
++      u8 parser_flag1_msb_exclude;
++      u8 insertion_flag_enable;
++      u8 flex_field4_enable;
++      u8 flex_field4_exclude_enable;
++      u8 flex_field4_range_enable;
++      u8 flex_field3_enable;
++      u8 flex_field3_exclude_enable;
++      u8 flex_field3_range_enable;
++      u8 flex_field2_enable;
++      u8 flex_field2_exclude_enable;
++      u8 flex_field2_range_enable;
++      u8 flex_field1_enable;
++      u8 flex_field1_exclude_enable;
++      u8 flex_field1_range_enable;
++} __packed;
++
++static_assert(sizeof(struct mxl862xx_pce_pattern) == 279);
++
++/**
++ * struct mxl862xx_pce_action_pbb - Provider Backbone Bridging (Mac-in-Mac)
++ *                                  action configuration
++ *
++ * @tunnel_id_known_traffic: Tunnel template index for I-Header known traffic
++ * @tunnel_id_unknown_traffic: Tunnel template index for I-Header unknown
++ *                             traffic
++ * @process_id_known_traffic: Tunnel template index for B-TAG known traffic
++ * @process_id_unknown_traffic: Tunnel template index for B-TAG unknown
++ *                              traffic
++ * @iheader_op_mode: I-Header operation mode (0 = no change, 1 = insert,
++ *                   2 = remove, 3 = replace)
++ * @btag_op_mode: B-TAG operation mode (0 = no change, 1 = insert,
++ *               2 = remove, 3 = replace)
++ * @mac_table_macinmac_select: MAC table Mac-in-Mac selection
++ *                             (0 = outer MAC, 1 = inner MAC)
++ * @iheader_action_enable: Enable Mac-in-Mac I-Header action
++ * @tunnel_id_known_traffic_enable: Enable tunnel ID for known traffic
++ * @tunnel_id_unknown_traffic_enable: Enable tunnel ID for unknown traffic
++ * @b_dst_mac_from_mac_table_enable: Use B-DA from MAC table instead of
++ *                                   tunnel template (I-Header insertion
++ *                                   mode only)
++ * @replace_b_src_mac_enable: Replace B-SA from tunnel template
++ * @replace_b_dst_mac_enable: Replace B-DA from tunnel template
++ * @replace_i_tag_res_enable: Replace I-Tag Res from tunnel template
++ * @replace_i_tag_uac_enable: Replace I-Tag UAC from tunnel template
++ * @replace_i_tag_dei_enable: Replace I-Tag DEI from tunnel template
++ * @replace_i_tag_pcp_enable: Replace I-Tag PCP from tunnel template
++ * @replace_i_tag_sid_enable: Replace I-Tag SID from tunnel template
++ * @replace_i_tag_tpid_enable: Replace I-Tag TPID from tunnel template
++ * @btag_action_enable: Enable B-TAG action
++ * @process_id_known_traffic_enable: Enable process ID for B-TAG known
++ *                                   traffic
++ * @process_id_unknown_traffic_enable: Enable process ID for B-TAG unknown
++ *                                     traffic
++ * @replace_b_tag_dei_enable: Replace B-Tag DEI from tunnel template
++ * @replace_b_tag_pcp_enable: Replace B-Tag PCP from tunnel template
++ * @replace_b_tag_vid_enable: Replace B-Tag VID from tunnel template
++ * @replace_b_tag_tpid_enable: Replace B-Tag TPID from tunnel template
++ * @mac_table_macinmac_action_enable: Enable MAC table Mac-in-Mac action
++ */
++struct mxl862xx_pce_action_pbb {
++      u8 tunnel_id_known_traffic;
++      u8 tunnel_id_unknown_traffic;
++      u8 process_id_known_traffic;
++      u8 process_id_unknown_traffic;
++      __le32 iheader_op_mode;
++      __le32 btag_op_mode;
++      __le32 mac_table_macinmac_select;
++      u8 iheader_action_enable;
++      u8 tunnel_id_known_traffic_enable;
++      u8 tunnel_id_unknown_traffic_enable;
++      u8 b_dst_mac_from_mac_table_enable;
++      u8 replace_b_src_mac_enable;
++      u8 replace_b_dst_mac_enable;
++      u8 replace_i_tag_res_enable;
++      u8 replace_i_tag_uac_enable;
++      u8 replace_i_tag_dei_enable;
++      u8 replace_i_tag_pcp_enable;
++      u8 replace_i_tag_sid_enable;
++      u8 replace_i_tag_tpid_enable;
++      u8 btag_action_enable;
++      u8 process_id_known_traffic_enable;
++      u8 process_id_unknown_traffic_enable;
++      u8 replace_b_tag_dei_enable;
++      u8 replace_b_tag_pcp_enable;
++      u8 replace_b_tag_vid_enable;
++      u8 replace_b_tag_tpid_enable;
++      u8 mac_table_macinmac_action_enable;
++} __packed;
++
++static_assert(sizeof(struct mxl862xx_pce_action_pbb) == 36);
++
++/**
++ * struct mxl862xx_pce_action_dest_subif - Destination sub-interface ID
++ *                                         action configuration
++ *
++ * @dest_subifid_action_enable: Destination sub-interface ID group field
++ *                              action enable
++ * @dest_subifid_assignment_enable: Destination sub-interface ID group field
++ *                                  assignment enable
++ * @dest_subifgrp_field: Destination sub-interface ID group field value,
++ *                       or LAG index when trunking action is enabled
++ */
++struct mxl862xx_pce_action_dest_subif {
++      u8 dest_subifid_action_enable;
++      u8 dest_subifid_assignment_enable;
++      __le16 dest_subifgrp_field;
++} __packed;
++
++static_assert(sizeof(struct mxl862xx_pce_action_dest_subif) == 4);
++
++/**
++ * enum mxl862xx_pce_action_portmap - Forwarding group action selector
++ *
++ * Selects how the packet is forwarded. Mutually exclusive with
++ * the flow_id_action (bFlowID_Action in vendor API).
++ *
++ * @MXL862XX_PCE_ACTION_PORTMAP_DISABLE: Forwarding action is disabled
++ * @MXL862XX_PCE_ACTION_PORTMAP_REGULAR: Use default port-map from
++ *     forwarding classification
++ * @MXL862XX_PCE_ACTION_PORTMAP_DISCARD: Discard the packet
++ * @MXL862XX_PCE_ACTION_PORTMAP_CPU: Forward to the CPU port
++ *     (as configured by GSW_CPU_PortCfgSet, typically the on-die
++ *     microcontroller -- not the DSA CPU port)
++ * @MXL862XX_PCE_ACTION_PORTMAP_ALTERNATIVE: Forward to an explicit
++ *     portmap given by forward_port_map[]
++ */
++enum mxl862xx_pce_action_portmap {
++      MXL862XX_PCE_ACTION_PORTMAP_DISABLE             = 0,
++      MXL862XX_PCE_ACTION_PORTMAP_REGULAR             = 1,
++      MXL862XX_PCE_ACTION_PORTMAP_DISCARD             = 2,
++      MXL862XX_PCE_ACTION_PORTMAP_CPU                 = 3,
++      MXL862XX_PCE_ACTION_PORTMAP_ALTERNATIVE         = 4,
++};
++
++/**
++ * enum mxl862xx_pce_action_cross_state - Cross state action selector
++ *
++ * Controls whether the packet ignores STP port-state filtering.
++ *
++ * @MXL862XX_PCE_ACTION_CROSS_STATE_DISABLE: Cross state action is disabled
++ * @MXL862XX_PCE_ACTION_CROSS_STATE_REGULAR: Enabled; packet is treated
++ *     as non-cross-state (does not ignore port-state filtering)
++ * @MXL862XX_PCE_ACTION_CROSS_STATE_CROSS: Enabled; packet ignores
++ *     port-state filtering rules (e.g. passes through BLOCKING state)
++ */
++enum mxl862xx_pce_action_cross_state {
++      MXL862XX_PCE_ACTION_CROSS_STATE_DISABLE         = 0,
++      MXL862XX_PCE_ACTION_CROSS_STATE_REGULAR         = 1,
++      MXL862XX_PCE_ACTION_CROSS_STATE_CROSS           = 2,
++};
++
++/**
++ * struct mxl862xx_pce_action - PCE rule action configuration
++ *
++ * Defines the actions applied to packets matching a PCE rule pattern.
++ *
++ * @time_comp: Signed time compensation value for OAM delay measurement
++ * @extended_vlan_block_id: Extended VLAN block allocated for this flow
++ *                          entry (valid when @extended_vlan_enable is set)
++ * @ins_ext_point: Insertion/extraction point
++ * @ptp_seq_id: PTP sequence ID for PTP application
++ * @pkt_update_offset: Byte offset (2..255) for counter/timestamp
++ *                     insertion (used when @no_pkt_update and
++ *                     @append_to_pkt are both false)
++ * @oam_flow_id: Traffic flow counter ID for OAM loss measurement
++ * @record_id: Record ID used by extraction and/or OAM process
++ * @forward_port_map: Target portmap for forwarded packets. Each bit
++ *                    represents one bridge port. Used when
++ *                    @port_map_action is %MXL862XX_PCE_ACTION_PORTMAP_ALTERNATIVE.
++ * @rmon_id: RMON counter ID (index starts from zero)
++ * @svlan_id: Alternative STAG VLAN ID
++ * @flow_id: Flow ID
++ * @rout_ext_id: Routing extension ID value (8-bit range)
++ * @traffic_class_alternate: Alternative traffic class (used when
++ *                           @traffic_class_action selects alternate)
++ * @meter_id: Meter ID
++ * @vlan_id: Alternative CTAG VLAN ID
++ * @fid: Alternative Filtering Identifier (FID)
++ * @traffic_class_action: Traffic class action selector
++ *                        (0 = disable, 1 = regular CoS, 2 = alternative)
++ * @snooping_type_action: IGMP snooping control selector
++ * @learning_action: MAC learning action selector
++ *                   (0 = disable, 1 = regular, 2 = force no learn,
++ *                   3 = force learn)
++ * @irq_action: Interrupt action selector
++ *              (0 = disable, 1 = regular, 2 = generate interrupt)
++ * @cross_state_action: Cross state action selector.
++ *     See &enum mxl862xx_pce_action_cross_state
++ * @crit_frame_action: Critical frame action selector
++ *                     (0 = disable, 1 = regular, 2 = critical)
++ * @color_frame_action: Color frame action selector (replaces
++ *                      @crit_frame_action in GSWIP-3.1)
++ * @timestamp_action: Timestamp action selector
++ *                    (0 = disable, 1 = regular, 2 = store timestamps)
++ * @port_map_action: Forwarding portmap action selector.
++ *     See &enum mxl862xx_pce_action_portmap
++ * @meter_action: Meter action selector
++ *                (0 = disable, 1 = regular, 2 = meter 1,
++ *                3 = meter 1 and 2)
++ * @vlan_action: CTAG VLAN action selector
++ *              (0 = disable, 1 = regular, 2 = alternative)
++ * @svlan_action: STAG VLAN action selector
++ *               (0 = disable, 1 = regular, 2 = alternative)
++ * @vlan_cross_action: Cross-VLAN action selector
++ *                     (0 = disable, 1 = regular, 2 = cross-VLAN)
++ * @process_path_action: MPE processing path assignment
++ *                       (0 = unused, 1 = path-1, 2 = path-2, 3 = both)
++ * @port_filter_type_action: Port filter action type (0..6)
++ * @pbb_action: Provider Backbone Bridging (Mac-in-Mac) action.
++ *     See &struct mxl862xx_pce_action_pbb
++ * @dest_subif_action: Destination sub-interface ID action.
++ *     See &struct mxl862xx_pce_action_dest_subif
++ * @remark_action: Enable remarking action
++ * @remark_pcp: Enable CTAG VLAN PCP remarking
++ * @remark_stag_pcp: Enable STAG VLAN PCP remarking
++ * @remark_stag_dei: Enable STAG VLAN DEI remarking
++ * @remark_dscp: Enable DSCP remarking
++ * @remark_class: Enable class remarking
++ * @rmon_action: Enable RMON counter action
++ * @fid_enable: Enable alternative FID
++ * @extended_vlan_enable: Enable extended VLAN operation for matching
++ *                        traffic
++ * @cvlan_ignore_control: CVLAN ignore control
++ * @port_bitmap_mux_control: Port bitmap mux control
++ * @port_trunk_action: Enable trunking action
++ * @port_link_selection: Port link selection control
++ * @flow_id_action: Enable flow ID action (mutually exclusive with
++ *                  @port_map_action)
++ * @rout_ext_id_action: Enable routing extension ID action
++ * @rt_dst_port_mask_cmp_action: Routing destination port mask comparison
++ * @rt_src_port_mask_cmp_action: Routing source port mask comparison
++ * @rt_dst_ip_mask_cmp_action: Routing destination IP mask comparison
++ * @rt_src_ip_mask_cmp_action: Routing source IP mask comparison
++ * @rt_inner_ip_as_key_action: Use inner IP in tunneled header as
++ *                             routing key
++ * @rt_accel_ena_action: Routing acceleration enable
++ * @rt_ctrl_ena_action: Routing control enable (selects routing
++ *                      accelerate action)
++ * @extract_enable: Enable packet extraction at point defined by
++ *                  @record_id
++ * @oam_enable: Enable OAM processing for matching packets
++ * @pce_bypass_path: Update packet in PCE bypass path (after QoS queue)
++ * @tx_flow_cnt: Use TX flow counter (otherwise RX flow counter)
++ * @time_format: Timestamp format (0 = digital 10B, 1 = binary 10B,
++ *               2 = digital 8B, 3 = binary 8B)
++ * @no_pkt_update: Do not update packet
++ * @append_to_pkt: Append counter/timestamp to end of packet (when
++ *                 @no_pkt_update is false)
++ * @pbb_action_enable: Enable PBB action. See &struct mxl862xx_pce_action_pbb
++ * @dest_subif_action_enable: Enable destination sub-interface ID action.
++ *     See &struct mxl862xx_pce_action_dest_subif
++ */
++struct mxl862xx_pce_action {
++      __le64 time_comp;
++      __le16 extended_vlan_block_id;
++      u8 ins_ext_point;
++      u8 ptp_seq_id;
++      __le16 pkt_update_offset;
++      __le16 oam_flow_id;
++      __le16 record_id;
++      __le16 forward_port_map[8];
++      __le16 rmon_id;
++      __le16 svlan_id;
++      __le16 flow_id;
++      __le16 rout_ext_id;
++      u8 traffic_class_alternate;
++      u8 meter_id;
++      u8 vlan_id;
++      u8 fid;
++      __le32 traffic_class_action;
++      __le32 snooping_type_action;
++      __le32 learning_action;
++      __le32 irq_action;
++      __le32 cross_state_action;
++      __le32 crit_frame_action;
++      __le32 color_frame_action;
++      __le32 timestamp_action;
++      __le32 port_map_action;
++      __le32 meter_action;
++      __le32 vlan_action;
++      __le32 svlan_action;
++      __le32 vlan_cross_action;
++      __le32 process_path_action;
++      __le32 port_filter_type_action;
++      struct mxl862xx_pce_action_pbb pbb_action;
++      struct mxl862xx_pce_action_dest_subif dest_subif_action;
++      u8 remark_action;
++      u8 remark_pcp;
++      u8 remark_stag_pcp;
++      u8 remark_stag_dei;
++      u8 remark_dscp;
++      u8 remark_class;
++      u8 rmon_action;
++      u8 fid_enable;
++      u8 extended_vlan_enable;
++      u8 cvlan_ignore_control;
++      u8 port_bitmap_mux_control;
++      u8 port_trunk_action;
++      u8 port_link_selection;
++      u8 flow_id_action;
++      u8 rout_ext_id_action;
++      u8 rt_dst_port_mask_cmp_action;
++      u8 rt_src_port_mask_cmp_action;
++      u8 rt_dst_ip_mask_cmp_action;
++      u8 rt_src_ip_mask_cmp_action;
++      u8 rt_inner_ip_as_key_action;
++      u8 rt_accel_ena_action;
++      u8 rt_ctrl_ena_action;
++      u8 extract_enable;
++      u8 oam_enable;
++      u8 pce_bypass_path;
++      u8 tx_flow_cnt;
++      __le32 time_format;
++      u8 no_pkt_update;
++      u8 append_to_pkt;
++      u8 pbb_action_enable;
++      u8 dest_subif_action_enable;
++} __packed;
++
++static_assert(sizeof(struct mxl862xx_pce_action) == 180);
++
++/**
++ * enum mxl862xx_pce_rule_region - PCE rule table region selector
++ *
++ * Selects which region of the traffic flow table the rule belongs to.
++ *
++ * @MXL862XX_PCE_RULE_COMMON: Common region shared by all CTPs
++ *     (global rules, indices 0..63)
++ * @MXL862XX_PCE_RULE_CTP: Per-CTP region. The rule index is relative
++ *     to the CTP block identified by logicalportid; the firmware
++ *     translates it to an absolute hardware index.
++ * @MXL862XX_PCE_RULE_DEBUG: Debug region with direct HW index mapping
++ */
++enum mxl862xx_pce_rule_region {
++      MXL862XX_PCE_RULE_COMMON        = 0,
++      MXL862XX_PCE_RULE_CTP           = 1,
++      MXL862XX_PCE_RULE_DEBUG         = 2,
++};
++
++/**
++ * struct mxl862xx_pce_rule - PCE rule configuration
++ * @logicalportid: Logical Port Id
++ * @subifidgroup: Sub-interface ID group
++ * @region: PCE rule region (common or per-CTP)
++ * @pattern: Match pattern (destination MAC, EtherType, etc.)
++ * @action: Forwarding action (portmap, cross-state, etc.)
++ *
++ * This structure is passed to the firmware via the MDIO data
++ * buffer using the %MXL862XX_TFLOW_PCERULEWRITE command.
++ * The binary layout must exactly match the firmware's
++ * GSW_PCE_rule_t (466 bytes, packed, little-endian scalars).
++ */
++struct mxl862xx_pce_rule {
++      u8 logicalportid;
++      __le16 subifidgroup;
++      __le32 region;
++      struct mxl862xx_pce_pattern pattern;
++      struct mxl862xx_pce_action action;
++} __packed;
++
++static_assert(sizeof(struct mxl862xx_pce_rule) == 466);
++
++/**
++ * struct mxl862xx_pce_rule_alloc - PCE rule block allocation
++ * @num_of_rules: Number of rules to allocate (input) / allocated (output).
++ *   The firmware rounds up to a multiple of four consecutive entries.
++ * @blockid: Starting rule index of the allocated block (output on alloc,
++ *   input on free).
++ *
++ * Used with %MXL862XX_TFLOW_PCERULEALLOC and %MXL862XX_TFLOW_PCERULEFREE.
++ * Maps to the firmware's ``GSW_PCE_rule_alloc_t``.
++ */
++struct mxl862xx_pce_rule_alloc {
++      __le16 num_of_rules;
++      __le16 blockid;
++} __packed;
++
++static_assert(sizeof(struct mxl862xx_pce_rule_alloc) == 4);
++
+ /**
+  * enum mxl862xx_stp_port_state - Spanning Tree Protocol port states
+  * @MXL862XX_STP_PORT_STATE_FORWARD: Forwarding state
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+@@ -12,6 +12,7 @@
+       (MXL862XX_MMD_REG_DATA_LAST - MXL862XX_MMD_REG_DATA_FIRST + 1)
+ #define MXL862XX_COMMON_MAGIC         0x100
++#define MXL862XX_TFLOW_MAGIC          0x200
+ #define MXL862XX_BRDG_MAGIC           0x300
+ #define MXL862XX_BRDGPORT_MAGIC               0x400
+ #define MXL862XX_CTP_MAGIC            0x500
+@@ -31,6 +32,10 @@
+ #define MXL862XX_COMMON_CFGSET                (MXL862XX_COMMON_MAGIC + 0xa)
+ #define MXL862XX_COMMON_REGISTERMOD   (MXL862XX_COMMON_MAGIC + 0x11)
++#define MXL862XX_TFLOW_PCERULEWRITE   (MXL862XX_TFLOW_MAGIC + 0x2)
++#define MXL862XX_TFLOW_PCERULEALLOC   (MXL862XX_TFLOW_MAGIC + 0x4)
++#define MXL862XX_TFLOW_PCERULEFREE    (MXL862XX_TFLOW_MAGIC + 0x5)
++
+ #define MXL862XX_BRIDGE_ALLOC         (MXL862XX_BRDG_MAGIC + 0x1)
+ #define MXL862XX_BRIDGE_CONFIGSET     (MXL862XX_BRDG_MAGIC + 0x2)
+ #define MXL862XX_BRIDGE_CONFIGGET     (MXL862XX_BRDG_MAGIC + 0x3)
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -280,9 +280,11 @@ static int mxl862xx_wait_ready(struct ds
+                        ver.iv_major, ver.iv_minor,
+                        le16_to_cpu(ver.iv_revision),
+                        le32_to_cpu(ver.iv_build_num));
++
+               priv->fw_version.major = ver.iv_major;
+               priv->fw_version.minor = ver.iv_minor;
+               priv->fw_version.revision = le16_to_cpu(ver.iv_revision);
++
+               return 0;
+ not_ready_yet:
+@@ -410,6 +412,68 @@ static int mxl862xx_setup_drop_meter(str
+       return MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
+ }
++
++/* Per-CTP offset used for the link-local trap rule. Each port's CTP
++ * flow-table block is pre-allocated by the firmware during init (44
++ * entries per port on a 10-port SKU, of which offset 0 is reserved
++ * for flow-control marking). Offset 1 is the first unused slot.
++ */
++#define MXL862XX_LINK_LOCAL_CTP_OFFSET                1
++
++/* Install a PCE rule that traps IEEE 802.1D link-local frames
++ * (01:80:c2:00:00:0x) to the CPU port for a single user port,
++ * preventing the hardware bridge from flooding them to other ports.
++ * The firmware does not install this rule by default because its own
++ * STP module is not used when DSA manages STP.
++ *
++ * The rule is written into the port's per-CTP flow table at offset 1.
++ * The firmware already allocates a 44-entry block for every CTP during
++ * init (8 entries exposed initially, expandable), so no dynamic
++ * allocation via PCERULEALLOC is needed. Using region=CTP causes the
++ * firmware to translate the CTP-relative offset into an absolute
++ * hardware index.
++ *
++ * Cross-state is enabled so that link-local frames reach the CPU even
++ * when the bridge port is in BLOCKING or LEARNING state.
++ */
++static int mxl862xx_setup_link_local_trap(struct dsa_switch *ds, int port)
++{
++      DECLARE_BITMAP(portmap, MXL862XX_MAX_BRIDGE_PORTS);
++      struct dsa_port *dp = dsa_to_port(ds, port);
++      struct mxl862xx_pce_rule rule = {};
++      int cpu_port = dp->cpu_dp->index;
++      int i;
++
++      /* Address this port's CTP flow-table block */
++      rule.logicalportid = port;
++      rule.subifidgroup = 0;
++      rule.region = cpu_to_le32(MXL862XX_PCE_RULE_CTP);
++
++      /* Pattern: link-local MAC on this specific ingress port */
++      rule.pattern.index = cpu_to_le16(MXL862XX_LINK_LOCAL_CTP_OFFSET);
++      rule.pattern.enable = 1;
++      rule.pattern.mac_dst_enable = 1;
++      memcpy(rule.pattern.mac_dst, eth_reserved_addr_base, ETH_ALEN);
++      rule.pattern.mac_dst_mask = cpu_to_le16(0x0001);
++
++      /* Action: forward to the CPU port via explicit portmap */
++      rule.action.port_map_action =
++              cpu_to_le32(MXL862XX_PCE_ACTION_PORTMAP_ALTERNATIVE);
++
++      bitmap_zero(portmap, MXL862XX_MAX_BRIDGE_PORTS);
++      __set_bit(cpu_port, portmap);
++      for (i = 0; i < ARRAY_SIZE(rule.action.forward_port_map); i++)
++              rule.action.forward_port_map[i] =
++                      cpu_to_le16(bitmap_read(portmap, i * 16, 16));
++
++      /* Bypass STP port state */
++      rule.action.cross_state_action =
++              cpu_to_le32(MXL862XX_PCE_ACTION_CROSS_STATE_CROSS);
++
++      return MXL862XX_API_WRITE(ds->priv, MXL862XX_TFLOW_PCERULEWRITE,
++                                rule);
++}
++
+ static int mxl862xx_set_bridge_port(struct dsa_switch *ds, int port)
+ {
+       struct mxl862xx_bridge_port_config br_port_cfg = {};
+@@ -1594,6 +1658,11 @@ static int mxl862xx_port_setup(struct ds
+       if (ret)
+               return ret;
++      /* install link-local trap for this user port */
++      ret = mxl862xx_setup_link_local_trap(ds, port);
++      if (ret)
++              return ret;
++
+       /* Initialize and pre-allocate per-port EVLAN and VF blocks for
+        * user ports. CPU ports do not use EVLAN or VF -- frames pass
+        * through without processing. Pre-allocation avoids firmware
diff --git a/target/linux/generic/pending-6.12/760-15-net-dsa-mxl862xx-warn-about-old-firmware-default-PCE.patch b/target/linux/generic/pending-6.12/760-15-net-dsa-mxl862xx-warn-about-old-firmware-default-PCE.patch
new file mode 100644 (file)
index 0000000..38dc45c
--- /dev/null
@@ -0,0 +1,32 @@
+From 3bba25f7ba35e3bca8230bd37ffb612944dbf301 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 18:51:21 +0000
+Subject: [PATCH 24/35] net: dsa: mxl862xx: warn about old firmware default PCE
+ rules
+
+Firmware versions older than 1.0.80 install global PCE rules at
+boot that redirect link-local frames (BPDUs, LLDP, LACP) to port 0
+(the on-chip microcontroller) instead of the DSA CPU port.  With
+port 0 disabled under DSA, these rules silently drop matching
+traffic.
+
+Log a warning when old firmware is detected so users know to update.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -854,6 +854,10 @@ static int mxl862xx_setup(struct dsa_swi
+       if (ret)
+               return ret;
++      if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 80))
++              dev_warn(ds->dev, "firmware < 1.0.80 installs global PCE rules "
++                       "that interfere with DSA operation, please update\n");
++
+       schedule_delayed_work(&priv->stats_work,
+                             MXL862XX_STATS_POLL_INTERVAL);
diff --git a/target/linux/generic/pending-6.12/760-16-net-dsa-add-802.1Q-VLAN-based-tag-driver-for-MxL862x.patch b/target/linux/generic/pending-6.12/760-16-net-dsa-add-802.1Q-VLAN-based-tag-driver-for-MxL862x.patch
new file mode 100644 (file)
index 0000000..8663883
--- /dev/null
@@ -0,0 +1,2466 @@
+From 1687c5632dfd80461b12425b943e30555faa3dd4 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Sun, 22 Mar 2026 00:58:04 +0000
+Subject: [PATCH 25/35] net: dsa: add 802.1Q VLAN-based tag driver for MxL862xx
+
+The MxL862xx native 8-byte special tag (SpTag) requires firmware
+support on the switch CPU and is not compatible with all SoC Ethernet
+controllers. An 802.1Q VLAN-based tagging alternative allows the
+switch to operate with any standard Ethernet MAC that supports VLAN
+tag insertion and stripping.
+
+Add a DSA tag driver that uses the tag_8021q framework to encode
+source and destination port information in standard 802.1Q VLAN
+tags. Register the DSA_TAG_PROTO_MXL862_8021Q protocol in the DSA
+header. Map TX queue priority to PCP in the xmit path and extract
+switch_id and source port from the management VID in the receive
+path. Set promisc_on_conduit so the conduit interface accepts
+frames with management VIDs that are not in its own VLAN filter.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/Kconfig        |    1 +
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h |  221 +++
+ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h |    2 +
+ drivers/net/dsa/mxl862xx/mxl862xx.c     | 1626 ++++++++++++++++++++---
+ drivers/net/dsa/mxl862xx/mxl862xx.h     |   21 +-
+ include/net/dsa.h                       |    2 +
+ net/dsa/Kconfig                         |    7 +
+ net/dsa/Makefile                        |    1 +
+ net/dsa/tag_mxl862xx_8021q.c            |   59 +
+ 9 files changed, 1738 insertions(+), 202 deletions(-)
+ create mode 100644 net/dsa/tag_mxl862xx_8021q.c
+
+--- a/drivers/net/dsa/mxl862xx/Kconfig
++++ b/drivers/net/dsa/mxl862xx/Kconfig
+@@ -4,6 +4,7 @@ config NET_DSA_MXL862
+       depends on NET_DSA
+       select CRC16
+       select NET_DSA_TAG_MXL_862XX
++      select NET_DSA_TAG_MXL_862XX_8021Q
+       help
+         This enables support for the MaxLinear MxL862xx switch family.
+         These switches have two 10GE SerDes interfaces, one typically
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+@@ -1185,6 +1185,227 @@ struct mxl862xx_ctp_port_assignment {
+ } __packed;
+ /**
++ * enum mxl862xx_ctp_port_config_mask - CTP Port Configuration Mask
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_BRIDGE_PORT_ID:
++ *     Mask for bridge_port_id.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_FORCE_TRAFFIC_CLASS:
++ *     Mask for forced_traffic_class and default_traffic_class.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_INGRESS_VLAN:
++ *     Mask for ingress_extended_vlan_enable and
++ *     ingress_extended_vlan_block_id.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_INGRESS_VLAN_IGMP:
++ *     Mask for ingress_extended_vlan_igmp_enable and
++ *     ingress_extended_vlan_block_id_igmp.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_VLAN:
++ *     Mask for egress_extended_vlan_enable and
++ *     egress_extended_vlan_block_id.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_VLAN_IGMP:
++ *     Mask for egress_extended_vlan_igmp_enable and
++ *     egress_extended_vlan_block_id_igmp.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_INGRESS_NTO1_VLAN:
++ *     Mask for ingress_nto1vlan_enable.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_NTO1_VLAN:
++ *     Mask for egress_nto1vlan_enable.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_INGRESS_MARKING:
++ *     Mask for ingress_marking_mode.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_MARKING:
++ *     Mask for egress_marking_mode.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_MARKING_OVERRIDE:
++ *     Mask for egress_marking_override_enable and
++ *     egress_marking_mode_override.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_REMARKING:
++ *     Mask for egress_remarking_mode.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_INGRESS_METER:
++ *     Mask for ingress_metering_enable and ingress_traffic_meter_id.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_METER:
++ *     Mask for egress_metering_enable and egress_traffic_meter_id.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_BRIDGING_BYPASS:
++ *     Mask for bridging_bypass, dest_logical_port_id, pmapper_enable,
++ *     dest_sub_if_id_group, pmapper_mapping_mode, pmapper_id_valid and
++ *     pmapper_dest_sub_if_id_group.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_FLOW_ENTRY:
++ *     Mask for first_flow_entry_index and number_of_flow_entries.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_LOOPBACK_AND_MIRROR:
++ *     Mask for ingress_loopback_enable, ingress_da_sa_swap_enable,
++ *     egress_loopback_enable, egress_da_sa_swap_enable,
++ *     ingress_mirror_enable and egress_mirror_enable.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_ALL: Enable all fields.
++ * @MXL862XX_CTP_PORT_CONFIG_MASK_FORCE: Bypass any check for debug purpose.
++ */
++enum mxl862xx_ctp_port_config_mask {
++      MXL862XX_CTP_PORT_CONFIG_MASK_BRIDGE_PORT_ID = BIT(0),
++      MXL862XX_CTP_PORT_CONFIG_MASK_FORCE_TRAFFIC_CLASS = BIT(1),
++      MXL862XX_CTP_PORT_CONFIG_MASK_INGRESS_VLAN = BIT(2),
++      MXL862XX_CTP_PORT_CONFIG_MASK_INGRESS_VLAN_IGMP = BIT(3),
++      MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_VLAN = BIT(4),
++      MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_VLAN_IGMP = BIT(5),
++      MXL862XX_CTP_PORT_CONFIG_MASK_INGRESS_NTO1_VLAN = BIT(6),
++      MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_NTO1_VLAN = BIT(7),
++      MXL862XX_CTP_PORT_CONFIG_MASK_INGRESS_MARKING = BIT(8),
++      MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_MARKING = BIT(9),
++      MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_MARKING_OVERRIDE = BIT(10),
++      MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_REMARKING = BIT(11),
++      MXL862XX_CTP_PORT_CONFIG_MASK_INGRESS_METER = BIT(12),
++      MXL862XX_CTP_PORT_CONFIG_MASK_EGRESS_METER = BIT(13),
++      MXL862XX_CTP_PORT_CONFIG_MASK_BRIDGING_BYPASS = BIT(14),
++      MXL862XX_CTP_PORT_CONFIG_MASK_FLOW_ENTRY = BIT(15),
++      MXL862XX_CTP_PORT_CONFIG_MASK_LOOPBACK_AND_MIRROR = BIT(16),
++      MXL862XX_CTP_PORT_CONFIG_MASK_ALL = 0x7FFFFFFF,
++      MXL862XX_CTP_PORT_CONFIG_MASK_FORCE = BIT(31),
++};
++
++/**
++ * struct mxl862xx_ctp_port_config - CTP Port Configuration
++ * @logical_port_id: Logical Port Id. The valid range is hardware dependent.
++ *                   Ignored when mask has
++ *                   %MXL862XX_CTP_PORT_CONFIG_MASK_FORCE.
++ * @n_sub_if_id_group: Sub interface ID group. The valid range is
++ *                     hardware/protocol dependent. When mask has
++ *                     %MXL862XX_CTP_PORT_CONFIG_MASK_FORCE, this is the
++ *                     absolute CTP index in hardware (debug only).
++ * @mask: See &enum mxl862xx_ctp_port_config_mask
++ * @bridge_port_id: Ingress Bridge Port ID to which this CTP port is
++ *                  associated for ingress traffic
++ * @forced_traffic_class: Default traffic class cannot be overridden by other
++ *                        rules (except traffic flow table and special tag)
++ * @default_traffic_class: Default traffic class for all ingress traffic from
++ *                         this CTP port
++ * @ingress_extended_vlan_enable: Enable extended VLAN processing for ingress
++ *                                non-IGMP traffic
++ * @ingress_extended_vlan_block_id: Extended VLAN block allocated for ingress
++ *                                  non-IGMP traffic. Valid when
++ *                                  ingress_extended_vlan_enable is set.
++ * @ingress_extended_vlan_block_size: Extended VLAN block size for ingress
++ *                                    non-IGMP traffic. If 0, the block size of
++ *                                    ingress_extended_vlan_block_id is used.
++ * @ingress_extended_vlan_igmp_enable: Enable extended VLAN processing for
++ *                                     ingress IGMP traffic
++ * @ingress_extended_vlan_block_id_igmp: Extended VLAN block allocated for
++ *                                       ingress IGMP traffic. Valid when
++ *                                       ingress_extended_vlan_igmp_enable is
++ *                                       set.
++ * @ingress_extended_vlan_block_size_igmp: Extended VLAN block size for ingress
++ *                                         IGMP traffic. If 0, the block size of
++ *                                         ingress_extended_vlan_block_id_igmp
++ *                                         is used.
++ * @egress_extended_vlan_enable: Enable extended VLAN processing for egress
++ *                               non-IGMP traffic
++ * @egress_extended_vlan_block_id: Extended VLAN block allocated for egress
++ *                                 non-IGMP traffic. Valid when
++ *                                 egress_extended_vlan_enable is set.
++ * @egress_extended_vlan_block_size: Extended VLAN block size for egress
++ *                                   non-IGMP traffic. If 0, the block size of
++ *                                   egress_extended_vlan_block_id is used.
++ * @egress_extended_vlan_igmp_enable: Enable extended VLAN processing for
++ *                                    egress IGMP traffic
++ * @egress_extended_vlan_block_id_igmp: Extended VLAN block allocated for
++ *                                      egress IGMP traffic. Valid when
++ *                                      egress_extended_vlan_igmp_enable is set.
++ * @egress_extended_vlan_block_size_igmp: Extended VLAN block size for egress
++ *                                        IGMP traffic. If 0, the block size of
++ *                                        egress_extended_vlan_block_id_igmp is
++ *                                        used.
++ * @ingress_nto1vlan_enable: If enabled and ingress packet is VLAN tagged,
++ *                           outer VLAN ID is used for nSubIfId field in MAC
++ *                           table; otherwise 0 is used
++ * @egress_nto1vlan_enable: If enabled and egress packet is known unicast,
++ *                          outer VLAN ID is from nSubIfId field in MAC table
++ * @ingress_marking_mode: Ingress color marking mode for ingress traffic
++ * @egress_marking_mode: Egress color marking mode for ingress traffic at
++ *                       egress priority queue color marking stage
++ * @egress_marking_override_enable: Override color marking mode from last stage
++ * @egress_marking_mode_override: Egress color marking mode for egress traffic.
++ *                                Valid only when
++ *                                egress_marking_override_enable is set.
++ * @egress_remarking_mode: Color remarking for egress traffic
++ * @ingress_metering_enable: Traffic metering on ingress traffic applies
++ * @ingress_traffic_meter_id: Meter for ingress CTP process
++ * @egress_metering_enable: Traffic metering on egress traffic applies
++ * @egress_traffic_meter_id: Meter for egress CTP process
++ * @bridging_bypass: Ingress traffic bypasses bridging/multicast processing.
++ *                   Traffic flow table is not bypassed.
++ * @dest_logical_port_id: Destination logical port when bridging_bypass is set
++ * @pmapper_enable: When bridging_bypass is set, selects whether to use
++ *                  dest_sub_if_id_group or P-mapper for sub interface
++ * @dest_sub_if_id_group: Destination sub interface ID group when
++ *                        bridging_bypass is set and pmapper_enable is false
++ * @pmapper_mapping_mode: When bridging_bypass and pmapper_enable are set,
++ *                        selects DSCP or PCP to derive sub interface ID
++ * @pmapper_id_valid: When set, P-mapper is re-used and no new allocation or
++ *                    value change occurs. When false, allocation is handled
++ *                    by the API.
++ * @pmapper_id: P-mapper ID. Valid when pmapper_id_valid is set.
++ * @pmapper_dest_sub_if_id_group: P-mapper destination sub interface ID group
++ *                                entries (73 bytes, firmware layout)
++ * @first_flow_entry_index: First traffic flow table entry associated to this
++ *                          CTP port. Should be a multiple of 4.
++ * @number_of_flow_entries: Number of traffic flow table entries associated to
++ *                          this CTP port. Should be a multiple of 4.
++ * @ingress_loopback_enable: Ingress traffic is redirected to ingress logical
++ *                           port of this CTP with source sub interface ID as
++ *                           destination. Bypasses processing except flow table.
++ * @ingress_da_sa_swap_enable: Destination/Source MAC address of ingress traffic
++ *                             is swapped before transmitted
++ * @egress_loopback_enable: Egress traffic to this CTP port is redirected to
++ *                          ingress logical port with same sub interface ID
++ * @egress_da_sa_swap_enable: Destination/Source MAC address of egress traffic
++ *                            is swapped before transmitted
++ * @ingress_mirror_enable: If enabled, ingress traffic is mirrored to the
++ *                         monitoring port. Mutually exclusive with
++ *                         ingress_loopback_enable.
++ * @egress_mirror_enable: If enabled, egress traffic is mirrored to the
++ *                        monitoring port. Mutually exclusive with
++ *                        egress_loopback_enable.
++ */
++struct mxl862xx_ctp_port_config {
++      u8 logical_port_id;
++      __le16 n_sub_if_id_group;
++      __le32 mask;
++      __le16 bridge_port_id;
++      u8 forced_traffic_class;
++      u8 default_traffic_class;
++      u8 ingress_extended_vlan_enable;
++      __le16 ingress_extended_vlan_block_id;
++      __le16 ingress_extended_vlan_block_size;
++      u8 ingress_extended_vlan_igmp_enable;
++      __le16 ingress_extended_vlan_block_id_igmp;
++      __le16 ingress_extended_vlan_block_size_igmp;
++      u8 egress_extended_vlan_enable;
++      __le16 egress_extended_vlan_block_id;
++      __le16 egress_extended_vlan_block_size;
++      u8 egress_extended_vlan_igmp_enable;
++      __le16 egress_extended_vlan_block_id_igmp;
++      __le16 egress_extended_vlan_block_size_igmp;
++      u8 ingress_nto1vlan_enable;
++      u8 egress_nto1vlan_enable;
++      __le32 ingress_marking_mode;
++      __le32 egress_marking_mode;
++      u8 egress_marking_override_enable;
++      __le32 egress_marking_mode_override;
++      __le32 egress_remarking_mode;
++      u8 ingress_metering_enable;
++      __le16 ingress_traffic_meter_id;
++      u8 egress_metering_enable;
++      __le16 egress_traffic_meter_id;
++      u8 bridging_bypass;
++      u8 dest_logical_port_id;
++      u8 pmapper_enable;
++      __le16 dest_sub_if_id_group;
++      __le32 pmapper_mapping_mode;
++      u8 pmapper_id_valid;
++      __le16 pmapper_id;
++      u8 pmapper_dest_sub_if_id_group[73];
++      __le16 first_flow_entry_index;
++      __le16 number_of_flow_entries;
++      u8 ingress_loopback_enable;
++      u8 ingress_da_sa_swap_enable;
++      u8 egress_loopback_enable;
++      u8 egress_da_sa_swap_enable;
++      u8 ingress_mirror_enable;
++      u8 egress_mirror_enable;
++} __packed;
++
++/**
+  * enum mxl862xx_port_duplex - Ethernet port duplex status
+  * @MXL862XX_DUPLEX_FULL: Port operates in full-duplex mode
+  * @MXL862XX_DUPLEX_HALF: Port operates in half-duplex mode
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+@@ -47,12 +47,14 @@
+ #define MXL862XX_BRIDGEPORT_FREE      (MXL862XX_BRDGPORT_MAGIC + 0x4)
+ #define MXL862XX_CTP_PORTASSIGNMENTSET        (MXL862XX_CTP_MAGIC + 0x3)
++#define MXL862XX_CTP_PORTCONFIGSET    (MXL862XX_CTP_MAGIC + 0x5)
+ #define MXL862XX_QOS_METERCFGSET      (MXL862XX_QOS_MAGIC + 0x2)
+ #define MXL862XX_QOS_METERALLOC               (MXL862XX_QOS_MAGIC + 0x2a)
+ #define MXL862XX_RMON_PORT_GET                (MXL862XX_RMON_MAGIC + 0x1)
++#define MXL862XX_MAC_TABLECLEAR               (MXL862XX_SWMAC_MAGIC + 0x1)
+ #define MXL862XX_MAC_TABLEENTRYADD    (MXL862XX_SWMAC_MAGIC + 0x2)
+ #define MXL862XX_MAC_TABLEENTRYREAD   (MXL862XX_SWMAC_MAGIC + 0x3)
+ #define MXL862XX_MAC_TABLEENTRYQUERY  (MXL862XX_SWMAC_MAGIC + 0x4)
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -16,6 +16,7 @@
+ #include <linux/of_mdio.h>
+ #include <linux/phy.h>
+ #include <linux/phylink.h>
++#include <linux/dsa/8021q.h>
+ #include <net/dsa.h>
+ #include "mxl862xx.h"
+@@ -115,6 +116,9 @@ enum mxl862xx_evlan_action {
+       EVLAN_PVID_OR_DISCARD,          /* insert PVID tag or discard if no PVID */
+       EVLAN_PVID_OR_PASS,             /* insert PVID tag or pass-through */
+       EVLAN_STRIP1_AND_PVID_OR_DISCARD,/* strip 1 tag + insert PVID, or discard */
++      EVLAN_INSERT_OUTER,             /* insert outer tag with mgmt_vid */
++      EVLAN_STRIP1,                   /* strip 1 tag unconditionally */
++      EVLAN_REASSIGN,                 /* reassign bridge port (keep tags) */
+ };
+ struct mxl862xx_evlan_rule_desc {
+@@ -124,6 +128,7 @@ struct mxl862xx_evlan_rule_desc {
+       u8 inner_tpid;          /* enum mxl862xx_extended_vlan_filter_tpid */
+       bool match_vid;         /* true: match on VID from the vid parameter */
+       u8 action;              /* enum mxl862xx_evlan_action */
++      u16 bridge_port_id;     /* for EVLAN_REASSIGN */
+ };
+ /* Shorthand constants for readability */
+@@ -190,11 +195,69 @@ static const struct mxl862xx_evlan_rule_
+       { FT_NO_FILTER, FT_NO_TAG, TP_NONE, TP_NONE,  true, EVLAN_STRIP_IF_UNTAGGED },
+ };
++/*
++ * tag_8021q: virtual bridge port egress rules.
++ *
++ * Inserts the management VID as an outer 802.1Q tag on all frames
++ * exiting toward the CPU via a virtual bridge port. Covers every
++ * possible frame type (untagged, single-tagged, double-tagged).
++ *
++ * 802.1Q ACCEPT rules must precede NO_FILTER catchalls to prevent
++ * NO_FILTER from matching standard 802.1Q frames first.
++ */
++static const struct mxl862xx_evlan_rule_desc cpu_egress_tag_8021q[] = {
++      /* 802.1Q outer + inner present */
++      { FT_NORMAL,    FT_NORMAL,    TP_8021Q, TP_8021Q, false, EVLAN_INSERT_OUTER },
++      /* 802.1Q outer, no inner */
++      { FT_NORMAL,    FT_NO_TAG,    TP_8021Q, TP_NONE,  false, EVLAN_INSERT_OUTER },
++      /* Non-8021Q outer + inner present */
++      { FT_NO_FILTER, FT_NO_FILTER, TP_NONE,  TP_NONE,  false, EVLAN_INSERT_OUTER },
++      /* Non-8021Q outer only */
++      { FT_NO_FILTER, FT_NO_TAG,    TP_NONE,  TP_NONE,  false, EVLAN_INSERT_OUTER },
++      /* Untagged */
++      { FT_NO_TAG,    FT_NO_TAG,    TP_NONE,  TP_NONE,  false, EVLAN_INSERT_OUTER },
++};
++
++/*
++ * tag_8021q: CPU port ingress reassignment rules.
++ *
++ * Each user port with a management VID gets these rules on the CPU port's
++ * ingress EVLAN block. They match the management VID as outer 802.1Q tag
++ * and reassign the frame to the user port's virtual bridge port.
++ *
++ * NO_FILTER is used for the inner position so that frames with any inner
++ * TPID (including non-802.1Q TPIDs like 802.1ad 0x88A8) are routed
++ * correctly. The management VID tag is kept and stripped later by the
++ * user port's egress EVLAN catchall rules.
++ *
++ * The bridge_port_id is overridden per-port at programming time.
++ */
++static const struct mxl862xx_evlan_rule_desc cpu_ingress_reassign[] = {
++      /* Mgmt VID outer + any inner tag present */
++      { FT_NORMAL,    FT_NO_FILTER, TP_8021Q, TP_NONE,  true, EVLAN_REASSIGN },
++      /* Mgmt VID outer, no inner */
++      { FT_NORMAL,    FT_NO_TAG,    TP_8021Q, TP_NONE,  true, EVLAN_REASSIGN },
++};
++
++/* User port egress catchall rules for tag_8021q mode.
++ * Strip the outer management VID tag from CPU->user frames that were
++ * not matched by any per-VID egress rule. Appended to the user port
++ * egress EVLAN block when tag_8021q is active.
++ */
++static const struct mxl862xx_evlan_rule_desc tag_8021q_egress_strip[] = {
++      /* Any outer tag + inner present: strip outer (mgmt VID) */
++      { FT_NO_FILTER, FT_NO_FILTER, TP_NONE,  TP_NONE,  false, EVLAN_STRIP1 },
++      /* Any outer tag, no inner: strip it */
++      { FT_NO_FILTER, FT_NO_TAG,    TP_NONE,  TP_NONE,  false, EVLAN_STRIP1 },
++};
++
+ static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds,
+                                                      int port,
+                                                      enum dsa_tag_protocol m)
+ {
+-      return DSA_TAG_PROTO_MXL862;
++      struct mxl862xx_priv *priv = ds->priv;
++
++      return priv->tag_proto;
+ }
+ /* PHY access via firmware relay */
+@@ -420,6 +483,78 @@ static int mxl862xx_setup_drop_meter(str
+  */
+ #define MXL862XX_LINK_LOCAL_CTP_OFFSET                1
++/**
++ * mxl862xx_cpu_bridge_port_id - Get the bridge port ID for CPU-side forwarding
++ * @ds: DSA switch
++ * @port: user port number
++ *
++ * In tag_8021q mode, returns the virtual bridge port ID so that frames
++ * destined for the CPU pass through the virtual bridge port's egress
++ * EVLAN (which inserts the management VID). In native SpTag mode,
++ * returns the physical CPU port index.
++ */
++static int mxl862xx_cpu_bridge_port_id(struct dsa_switch *ds, int port)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
++
++      if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q && p->bridge_port_cpu)
++              return p->bridge_port_cpu;
++
++      return dsa_to_port(ds, port)->cpu_dp->index;
++}
++
++/**
++ * mxl862xx_tag_8021q_disable_cpu_egress - Disable virtual bridge port egress EVLAN
++ * @ds: DSA switch
++ * @port: user port whose virtual bridge port egress EVLAN to disable
++ */
++static void mxl862xx_tag_8021q_disable_cpu_egress(struct dsa_switch *ds,
++                                                 int port)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
++      struct mxl862xx_bridge_port_config bp_cfg = {};
++
++      if (!p->bridge_port_cpu || !p->cpu_egress_evlan.allocated)
++              return;
++
++      /* Disable egress EVLAN on the virtual bridge port */
++      bp_cfg.bridge_port_id = cpu_to_le16(p->bridge_port_cpu);
++      bp_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN);
++      bp_cfg.egress_extended_vlan_enable = 0;
++      MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, bp_cfg);
++
++      p->cpu_egress_evlan.in_use = false;
++}
++
++/**
++ * mxl862xx_set_cpu_ctp_ingress_evlan - Assign ingress EVLAN to the CPU
++ *                                      port's CTP
++ * @ds: DSA switch
++ * @cpu: CPU port index
++ *
++ * Both the reference and legacy drivers assign the CPU port's ingress
++ * EVLAN at the CTP level (via CTP_PORTCONFIGSET) rather than the
++ * bridge port level (BRIDGEPORT_CONFIGSET). The GSWIP ingress
++ * pipeline evaluates Bridge Port EVLAN first, then CTP EVLAN; the
++ * bridge port reassignment treatment used by tag_8021q only works
++ * reliably from the CTP level.
++ */
++static int mxl862xx_set_cpu_ctp_ingress_evlan(struct dsa_switch *ds, int cpu)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_evlan_block *blk = &priv->ports[cpu].ingress_evlan;
++      struct mxl862xx_ctp_port_config ctp = {};
++
++      ctp.logical_port_id = cpu;
++      ctp.mask = cpu_to_le32(MXL862XX_CTP_PORT_CONFIG_MASK_INGRESS_VLAN);
++      ctp.ingress_extended_vlan_enable = blk->in_use;
++      ctp.ingress_extended_vlan_block_id = cpu_to_le16(blk->block_id);
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_CTP_PORTCONFIGSET, ctp);
++}
++
+ /* Install a PCE rule that traps IEEE 802.1D link-local frames
+  * (01:80:c2:00:00:0x) to the CPU port for a single user port,
+  * preventing the hardware bridge from flooding them to other ports.
+@@ -440,10 +575,14 @@ static int mxl862xx_setup_link_local_tra
+ {
+       DECLARE_BITMAP(portmap, MXL862XX_MAX_BRIDGE_PORTS);
+       struct dsa_port *dp = dsa_to_port(ds, port);
++      struct mxl862xx_priv *priv = ds->priv;
+       struct mxl862xx_pce_rule rule = {};
+       int cpu_port = dp->cpu_dp->index;
++      struct mxl862xx_port *p;
+       int i;
++      p = &priv->ports[port];
++
+       /* Address this port's CTP flow-table block */
+       rule.logicalportid = port;
+       rule.subifidgroup = 0;
+@@ -466,11 +605,18 @@ static int mxl862xx_setup_link_local_tra
+               rule.action.forward_port_map[i] =
+                       cpu_to_le16(bitmap_read(portmap, i * 16, 16));
++      if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q &&
++          p->cpu_egress_evlan.in_use) {
++              rule.action.extended_vlan_enable = 1;
++              rule.action.extended_vlan_block_id =
++                      cpu_to_le16(p->cpu_egress_evlan.block_id);
++      }
++
+       /* Bypass STP port state */
+       rule.action.cross_state_action =
+               cpu_to_le32(MXL862XX_PCE_ACTION_CROSS_STATE_CROSS);
+-      return MXL862XX_API_WRITE(ds->priv, MXL862XX_TFLOW_PCERULEWRITE,
++      return MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE,
+                                 rule);
+ }
+@@ -587,7 +733,8 @@ static int mxl862xx_sync_bridge_members(
+                               __set_bit(member_dp->index,
+                                         priv->ports[port].portmap);
+               }
+-              __set_bit(dp->cpu_dp->index, priv->ports[port].portmap);
++              __set_bit(mxl862xx_cpu_bridge_port_id(ds, port),
++                        priv->ports[port].portmap);
+               err = mxl862xx_set_bridge_port(ds, port);
+               if (err)
+@@ -762,7 +909,6 @@ static void mxl862xx_free_bridge(struct
+ static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
+ {
+-      struct dsa_port *dp = dsa_to_port(ds, port);
+       struct mxl862xx_priv *priv = ds->priv;
+       int ret;
+@@ -774,15 +920,27 @@ static int mxl862xx_add_single_port_brid
+       priv->ports[port].learning = false;
+       bitmap_zero(priv->ports[port].portmap, MXL862XX_MAX_BRIDGE_PORTS);
+-      __set_bit(dp->cpu_dp->index, priv->ports[port].portmap);
++      __set_bit(mxl862xx_cpu_bridge_port_id(ds, port),
++                priv->ports[port].portmap);
+       ret = mxl862xx_set_bridge_port(ds, port);
+       if (ret)
+               return ret;
+-      /* Standalone ports should not flood unknown unicast or multicast
+-       * towards the CPU by default; only broadcast is needed initially.
+-       */
++      /* In tag_8021q mode the TX path goes through the bridge engine
++       * (CTP ingress EVLAN reassigns to a virtual bridge port which
++       * then forwards via the bridge). With learning disabled on
++       * standalone ports, unknown unicast must be flooded so that
++       * frames from the host can reach the user port.
++       *
++       * In native SpTag mode, TX bypasses the bridge engine entirely
++       * (the special tag selects the egress port directly), so flood
++       * control only affects CPU-bound traffic and can be restrictive.
++       */
++      if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q)
++              return mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
++                                                true, true, true);
++
+       return mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
+                                        false, false, true);
+ }
+@@ -790,10 +948,12 @@ static int mxl862xx_add_single_port_brid
+ static int mxl862xx_setup(struct dsa_switch *ds)
+ {
+       struct mxl862xx_priv *priv = ds->priv;
+-      int n_user_ports = 0, max_vlans;
++      int n_user_ports = 0, n_cpu_ports = 0, max_vlans;
++      int cpu_egress_rules, cpu_ingress_per_port;
+       int ingress_finals, vid_rules;
++      int egress_catchalls, evlan_reserved;
+       struct dsa_port *dp;
+-      int ret, i;
++      int ret, i, port;
+       ret = mxl862xx_reset(priv);
+       if (ret)
+@@ -806,7 +966,7 @@ static int mxl862xx_setup(struct dsa_swi
+       for (i = 0; i < 8; i++)
+               mxl862xx_setup_pcs(priv, &priv->serdes_ports[i], i + 9);
+-      /* Calculate Extended VLAN block sizes.
++      /* Calculate Extended VLAN and VLAN Filter block sizes.
+        * With VLAN Filter handling VID membership checks:
+        *   Ingress: only final catchall rules (PVID insertion, 802.1Q
+        *            accept, non-8021Q TPID handling, discard).
+@@ -814,40 +974,67 @@ static int mxl862xx_setup(struct dsa_swi
+        *            ingress EVLAN rules are needed. (7 entries.)
+        *   Egress:  2 rules per VID that needs tag stripping (untagged VIDs).
+        *            No egress final catchalls -- VLAN Filter does the discard.
+-       *   CPU:     EVLAN is left disabled on CPU ports -- frames pass
+-       *            through without EVLAN processing.
++       *
++       * tag_8021q mode reserves additional resources from the global
++       * pools for management VID handling:
++       *   EVLAN: 5 egress rules per user port (on virtual bridge ports)
++       *          + dynamically-sized CPU ingress EVLAN (2 per user port,
++       *            budgeted here to guarantee space).
++       *   VF:    CPU port needs its own VF block for management VIDs.
+        *
+        * Total EVLAN budget:
+-       *   n_user_ports * (ingress + egress) ≤ 1024.
+-       * Ingress blocks are small (7 entries), so almost all capacity
+-       * goes to egress VID rules.
++       *   n_user_ports * (ingress + egress + cpu_egress + cpu_ingress_share)
++       *   <= 1024.
++       * Total VF budget:
++       *   (n_user_ports + n_cpu_ports) * vf_block_size <= 1024.
+        */
+       dsa_switch_for_each_user_port(dp, ds)
+               n_user_ports++;
++      dsa_switch_for_each_cpu_port(dp, ds)
++              n_cpu_ports++;
+       if (n_user_ports) {
+               ingress_finals = ARRAY_SIZE(ingress_aware_final);
+               vid_rules = ARRAY_SIZE(vid_accept_standard);
++              cpu_egress_rules = ARRAY_SIZE(cpu_egress_tag_8021q);
++              cpu_ingress_per_port = ARRAY_SIZE(cpu_ingress_reassign);
++              egress_catchalls = ARRAY_SIZE(tag_8021q_egress_strip);
+               /* Ingress block: fixed at finals count (7 entries) */
+               priv->evlan_ingress_size = ingress_finals;
++              /* CPU port ingress EVLAN: reassign rules per user port */
++              priv->cpu_evlan_ingress_size =
++                      cpu_ingress_per_port * n_user_ports;
++
++              /* Reserve EVLAN entries for tag_8021q:
++               *  - virtual bridge port egress blocks (cpu_egress_rules each)
++               *  - CPU port ingress EVLAN (cpu_ingress_per_port each)
++               *  - user port egress catchalls for mgmt VID stripping
++               */
++              evlan_reserved = n_user_ports * (ingress_finals +
++                                               cpu_egress_rules +
++                                               cpu_ingress_per_port +
++                                               egress_catchalls);
++
+               /* Egress block: remaining budget divided equally among
+                * user ports. Each untagged VID needs vid_rules (2)
+                * EVLAN entries for tag stripping. Tagged-only VIDs
+-               * need no EVLAN rules at all.
++               * need no EVLAN rules at all. The block also includes
++               * space for the tag_8021q egress catchall rules.
+                */
+-              max_vlans = (MXL862XX_TOTAL_EVLAN_ENTRIES -
+-                           n_user_ports * ingress_finals) /
++              max_vlans = (MXL862XX_TOTAL_EVLAN_ENTRIES - evlan_reserved) /
+                           (n_user_ports * vid_rules);
+-              priv->evlan_egress_size = vid_rules * max_vlans;
++              priv->evlan_egress_size = vid_rules * max_vlans +
++                                        egress_catchalls;
+-              /* VLAN Filter block: one per user port. The 1024-entry
+-               * table is divided equally among user ports. Each port
+-               * gets its own VF block for per-port VID membership --
+-               * discard_unmatched_tagged handles the rest.
++              /* VLAN Filter block: one per user port plus one per CPU
++               * port (used in tag_8021q mode for management VIDs).
++               * The 1024-entry table is divided equally among all
++               * consumers.
+                */
+-              priv->vf_block_size = MXL862XX_TOTAL_VF_ENTRIES / n_user_ports;
++              priv->vf_block_size = MXL862XX_TOTAL_VF_ENTRIES /
++                                    (n_user_ports + n_cpu_ports);
+       }
+       ret = mxl862xx_setup_drop_meter(ds);
+@@ -858,6 +1045,68 @@ static int mxl862xx_setup(struct dsa_swi
+               dev_warn(ds->dev, "firmware < 1.0.80 installs global PCE rules "
+                        "that interfere with DSA operation, please update\n");
++      /* Pre-allocate firmware resources for all ports. The DSA core
++       * calls change_tag_protocol() between setup() and port_setup(),
++       * and in tag_8021q mode that triggers dsa_tag_8021q_register()
++       * which fires tag_8021q_vlan_add callbacks that need EVLAN and
++       * VF blocks. complete_tag_8021q_setup() also needs per-port
++       * FIDs from add_single_port_bridge().
++       *
++       * Per-port configuration (SpTag, CTP, portmaps, link-local
++       * traps) is deferred to port_setup().
++       */
++      dsa_switch_for_each_cpu_port(dp, ds) {
++              port = dp->index;
++
++              mxl862xx_vf_init(&priv->ports[port].vf,
++                               priv->vf_block_size);
++              mxl862xx_evlan_block_init(&priv->ports[port].ingress_evlan,
++                                        priv->cpu_evlan_ingress_size);
++              ret = mxl862xx_evlan_block_alloc(priv,
++                                               &priv->ports[port].ingress_evlan);
++              if (ret)
++                      return ret;
++
++              ret = mxl862xx_vf_alloc(priv, &priv->ports[port].vf);
++              if (ret)
++                      return ret;
++      }
++
++      dsa_switch_for_each_user_port(dp, ds) {
++              port = dp->index;
++
++              mxl862xx_evlan_block_init(&priv->ports[port].ingress_evlan,
++                                        priv->evlan_ingress_size);
++              ret = mxl862xx_evlan_block_alloc(priv,
++                                               &priv->ports[port].ingress_evlan);
++              if (ret)
++                      return ret;
++
++              mxl862xx_evlan_block_init(&priv->ports[port].egress_evlan,
++                                        priv->evlan_egress_size);
++              ret = mxl862xx_evlan_block_alloc(priv,
++                                               &priv->ports[port].egress_evlan);
++              if (ret)
++                      return ret;
++
++              mxl862xx_vf_init(&priv->ports[port].vf,
++                               priv->vf_block_size);
++              ret = mxl862xx_vf_alloc(priv, &priv->ports[port].vf);
++              if (ret)
++                      return ret;
++
++              mxl862xx_evlan_block_init(&priv->ports[port].cpu_egress_evlan,
++                                        ARRAY_SIZE(cpu_egress_tag_8021q));
++              ret = mxl862xx_evlan_block_alloc(priv,
++                                               &priv->ports[port].cpu_egress_evlan);
++              if (ret)
++                      return ret;
++
++              ret = mxl862xx_add_single_port_bridge(ds, port);
++              if (ret)
++                      return ret;
++      }
++
+       schedule_delayed_work(&priv->stats_work,
+                             MXL862XX_STATS_POLL_INTERVAL);
+@@ -939,6 +1188,52 @@ static int mxl862xx_configure_sp_tag_pro
+ }
+ /**
++ * mxl862xx_set_cpu_vbp - Push CPU-side virtual bridge port config to firmware
++ * @ds: DSA switch
++ * @port: user port index whose VBP to configure
++ *
++ * Each user port in tag_8021q mode has a virtual bridge port (VBP) that
++ * sits on the CPU RX path. The VBP lives in the user port's permanent
++ * per-port FID so host FDB/MDB entries in that FID can target it directly.
++ * Per-port host flood control is implemented via egress sub-meters on
++ * the VBP.
++ *
++ * This is intentionally separate from mxl862xx_set_bridge_port() because
++ * the VBP and the physical bridge port are independent firmware entities:
++ * host flood changes (deferred from atomic context) only need the VBP
++ * update, and VLAN/STP changes only need the physical bridge port update.
++ */
++static int mxl862xx_set_cpu_vbp(struct dsa_switch *ds, int port)
++{
++      struct mxl862xx_bridge_port_config vbp_cfg = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
++      bool enable;
++      int i, idx;
++
++      if (!p->bridge_port_cpu)
++              return 0;
++
++      vbp_cfg.bridge_port_id = cpu_to_le16(p->bridge_port_cpu);
++      vbp_cfg.mask = cpu_to_le32(
++              MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
++              MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER);
++      vbp_cfg.bridge_id = cpu_to_le16(p->fid);
++
++      for (i = 0; i < ARRAY_SIZE(mxl862xx_flood_meters); i++) {
++              idx = mxl862xx_flood_meters[i];
++              enable = !!(p->host_flood_block & BIT(idx));
++
++              vbp_cfg.egress_traffic_sub_meter_id[idx] =
++                      enable ? cpu_to_le16(priv->drop_meter) : 0;
++              vbp_cfg.egress_sub_metering_enable[idx] = enable;
++      }
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET,
++                                vbp_cfg);
++}
++
++/**
+  * mxl862xx_evlan_write_rule - Write a single Extended VLAN rule to hardware
+  * @priv: driver private data
+  * @block_id: HW Extended VLAN block ID
+@@ -947,6 +1242,7 @@ static int mxl862xx_configure_sp_tag_pro
+  * @vid: VLAN ID for VID-specific rules (ignored when !desc->match_vid)
+  * @untagged: strip tag on egress for EVLAN_STRIP_IF_UNTAGGED action
+  * @pvid: port VLAN ID for PVID insertion rules (0 = no PVID)
++ * @mgmt_vid: tag_8021q management VID for outer tag insertion (0 = unused)
+  *
+  * Translates a compact rule descriptor into a full firmware
+  * mxl862xx_extendedvlan_config struct and writes it via the API.
+@@ -954,7 +1250,8 @@ static int mxl862xx_configure_sp_tag_pro
+ static int mxl862xx_evlan_write_rule(struct mxl862xx_priv *priv,
+                                    u16 block_id, u16 entry_index,
+                                    const struct mxl862xx_evlan_rule_desc *desc,
+-                                   u16 vid, bool untagged, u16 pvid)
++                                   u16 vid, bool untagged, u16 pvid,
++                                   u16 mgmt_vid)
+ {
+       struct mxl862xx_extendedvlan_config cfg = {};
+       struct mxl862xx_extendedvlan_filter_vlan *fv;
+@@ -1044,6 +1341,31 @@ static int mxl862xx_evlan_write_rule(str
+                               cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_DISCARD_UPSTREAM);
+               }
+               break;
++
++      case EVLAN_INSERT_OUTER:
++              cfg.treatment.remove_tag =
++                      cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
++              cfg.treatment.add_outer_vlan = 1;
++              cfg.treatment.outer_vlan.vid_mode =
++                      cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_VID_VAL);
++              cfg.treatment.outer_vlan.vid_val = cpu_to_le32(mgmt_vid);
++              cfg.treatment.outer_vlan.tpid =
++                      cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_8021Q);
++              break;
++
++      case EVLAN_STRIP1:
++              cfg.treatment.remove_tag =
++                      cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_REMOVE_1_TAG);
++              break;
++
++      case EVLAN_REASSIGN:
++              cfg.treatment.remove_tag =
++                      cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
++              cfg.treatment.reassign_bridge_port = 1;
++              cfg.treatment.new_bridge_port_id =
++                      cpu_to_le16(desc->bridge_port_id);
++              break;
++
+       }
+       return MXL862XX_API_WRITE(priv, MXL862XX_EXTENDEDVLAN_SET, cfg);
+@@ -1104,7 +1426,7 @@ static int mxl862xx_evlan_write_final_ru
+       for (i = 0; i < n_rules; i++) {
+               ret = mxl862xx_evlan_write_rule(priv, blk->block_id,
+                                               start_idx + i, &rules[i],
+-                                              0, false, pvid);
++                                              0, false, pvid, 0);
+               if (ret)
+                       return ret;
+       }
+@@ -1273,6 +1595,27 @@ static int mxl862xx_vf_del_vid(struct mx
+ }
+ /**
++ * mxl862xx_vf_clear_vids - Remove all VID entries without freeing the HW block
++ * @priv: driver private data
++ * @vf: VLAN Filter block
++ *
++ * Frees the software VID list and resets active_count, but keeps the
++ * HW block allocated to avoid firmware table fragmentation.
++ */
++static void mxl862xx_vf_clear_vids(struct mxl862xx_priv *priv,
++                                 struct mxl862xx_vf_block *vf)
++{
++      struct mxl862xx_vf_vid *ve, *tmp;
++
++      list_for_each_entry_safe(ve, tmp, &vf->vids, list) {
++              list_del(&ve->list);
++              kfree(ve);
++      }
++
++      vf->active_count = 0;
++}
++
++/**
+  * mxl862xx_evlan_program_ingress - Write the fixed ingress catchall rules
+  * @priv: driver private data
+  * @port: port number
+@@ -1323,8 +1666,8 @@ static int mxl862xx_evlan_program_egress
+       const struct mxl862xx_evlan_rule_desc *vid_rules;
+       struct mxl862xx_vf_vid *vfv;
+       u16 old_active = blk->n_active;
++      int n_vid, n_catch, ret;
+       u16 idx = 0, i;
+-      int n_vid, ret;
+       if (p->vlan_filtering) {
+               vid_rules = vid_accept_standard;
+@@ -1341,13 +1684,23 @@ static int mxl862xx_evlan_program_egress
+               if (p->vlan_filtering && !vfv->untagged)
+                       continue;
++              /* Skip the tag_8021q management VID -- it must NOT get
++               * per-VID egress rules. The management VID arrives as
++               * the outer tag on CPU->user frames and is stripped by
++               * the catchall rules appended below. A per-VID rule
++               * here would match first (NO_FILTER outer) and prevent
++               * the catchall from stripping the tag.
++               */
++              if (p->tag_8021q_vid && vfv->vid == p->tag_8021q_vid)
++                      continue;
++
+               if (idx + n_vid > blk->block_size)
+                       return -ENOSPC;
+               ret = mxl862xx_evlan_write_rule(priv, blk->block_id,
+                                               idx++, &vid_rules[0],
+                                               vfv->vid, vfv->untagged,
+-                                              p->pvid);
++                                              p->pvid, 0);
+               if (ret)
+                       return ret;
+@@ -1356,7 +1709,29 @@ static int mxl862xx_evlan_program_egress
+                                                       idx++, &vid_rules[1],
+                                                       vfv->vid,
+                                                       vfv->untagged,
+-                                                      p->pvid);
++                                                      p->pvid, 0);
++                      if (ret)
++                              return ret;
++              }
++      }
++
++      /* In tag_8021q mode, append catchall rules that strip the outer
++       * management VID tag from CPU->user frames. The management VID
++       * is kept through the forwarding pipeline (CPU ingress EVLAN
++       * only reassigns the bridge port, without stripping) and must
++       * be removed here before the frame exits the user port.
++       */
++      if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q) {
++              n_catch = ARRAY_SIZE(tag_8021q_egress_strip);
++
++              if (idx + n_catch > blk->block_size)
++                      return -ENOSPC;
++
++              for (i = 0; i < n_catch; i++) {
++                      ret = mxl862xx_evlan_write_rule(priv, blk->block_id,
++                                                      idx++,
++                                                      &tag_8021q_egress_strip[i],
++                                                      0, false, 0, 0);
+                       if (ret)
+                               return ret;
+               }
+@@ -1368,8 +1743,7 @@ static int mxl862xx_evlan_program_egress
+        */
+       for (i = idx; i < old_active; i++) {
+               ret = mxl862xx_evlan_deactivate_entry(priv,
+-                                                    blk->block_id,
+-                                                    i);
++                                                    blk->block_id, i);
+               if (ret)
+                       return ret;
+       }
+@@ -1393,13 +1767,16 @@ static int mxl862xx_port_vlan_filtering(
+       /* Reprogram Extended VLAN rules if filtering mode changed */
+       if (changed) {
+-              /* When leaving VLAN-aware mode, release the ingress HW
+-               * block. The firmware passes frames through unchanged
+-               * when no ingress EVLAN block is assigned, so the block
+-               * is unnecessary in unaware mode.
++              /* When leaving VLAN-aware mode, disable the ingress
++               * EVLAN engine. The block stays allocated to avoid
++               * firmware EVLAN table fragmentation.
+                */
+-              if (!vlan_filtering)
++              if (!vlan_filtering) {
+                       p->ingress_evlan.in_use = false;
++                      ret = mxl862xx_set_bridge_port(ds, port);
++                      if (ret)
++                              return ret;
++              }
+               ret = mxl862xx_evlan_program_ingress(priv, port);
+               if (ret)
+@@ -1536,18 +1913,19 @@ static int mxl862xx_setup_cpu_bridge(str
+       /* include all assigned user ports in the CPU portmap */
+       bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
+-      dsa_switch_for_each_user_port(dp, ds) {
+-              /* it's safe to rely on cpu_dp being valid for user ports */
+-              if (dp->cpu_dp->index != port)
+-                      continue;
++      if (priv->tag_proto != DSA_TAG_PROTO_MXL862_8021Q) {
++              dsa_switch_for_each_user_port(dp, ds) {
++                      /* it's safe to rely on cpu_dp being valid for user ports */
++                      if (dp->cpu_dp->index != port)
++                              continue;
+-              __set_bit(dp->index, p->portmap);
++                      __set_bit(dp->index, p->portmap);
++              }
+       }
+       return mxl862xx_set_bridge_port(ds, port);
+ }
+-
+ static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
+                                    const struct dsa_bridge bridge,
+                                    bool *tx_fwd_offload,
+@@ -1580,7 +1958,6 @@ static int mxl862xx_port_bridge_join(str
+ static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
+                                      const struct dsa_bridge bridge)
+ {
+-      struct dsa_port *dp = dsa_to_port(ds, port);
+       struct mxl862xx_priv *priv = ds->priv;
+       struct mxl862xx_port *p = &priv->ports[port];
+       int err;
+@@ -1595,34 +1972,587 @@ static void mxl862xx_port_bridge_leave(s
+        * single-port bridge
+        */
+       bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
+-      __set_bit(dp->cpu_dp->index, p->portmap);
++      __set_bit(mxl862xx_cpu_bridge_port_id(ds, port), p->portmap);
+       p->flood_block = 0;
++      p->host_flood_block = 0;
+-      /* Detach EVLAN and VF blocks from the bridge port BEFORE freeing
+-       * them. The firmware tracks a usage count per block and rejects
+-       * FREE while the count is non-zero.
++      /* Reset VLAN state for standalone mode. Ingress EVLAN is not
++       * needed outside a VLAN-aware bridge. Egress EVLAN is
++       * reprogrammed below -- in tag_8021q mode it gets the
++       * management VID strip catchalls, in SpTag mode it is cleared.
+        *
+-       * For EVLAN: setting in_use=false makes set_bridge_port send
+-       * enable=false, which decrements the firmware refcount.
+-       *
+-       * For VF: set_bridge_port sees dp->bridge == NULL (DSA already
+-       * cleared it) and sends vlan_filter_enable=0, which decrements
+-       * the firmware VF refcount.
++       * Do NOT clear the VF VID list here. Bridge VLANs are already
++       * removed by port_vlan_del during the switchdev replay in
++       * dsa_port_pre_bridge_leave. The remaining VIDs (e.g. the
++       * tag_8021q management VID) must survive bridge leave.
+        */
+       p->pvid = 0;
+       p->ingress_evlan.in_use = false;
+-      p->egress_evlan.in_use = false;
++      err = mxl862xx_evlan_program_egress(priv, port);
++      if (err)
++              dev_err(ds->dev,
++                      "failed to restore egress EVLAN on port %d: %pe\n",
++                      port, ERR_PTR(err));
++
++      /* Push the complete standalone port state to firmware. The
++       * firmware compares old vs new EVLAN/VF enable flags and adjusts
++       * block refcounts accordingly, so a single call suffices.
++       */
+       err = mxl862xx_set_bridge_port(ds, port);
+       if (err)
+               dev_err(ds->dev,
+                       "failed to update bridge port %d state: %pe\n", port,
+                       ERR_PTR(err));
++      err = mxl862xx_set_cpu_vbp(ds, port);
++      if (err)
++              dev_err(ds->dev,
++                      "failed to update CPU VBP for port %d: %pe\n", port,
++                      ERR_PTR(err));
++
+       if (!dsa_bridge_ports(ds, bridge.dev))
+               mxl862xx_free_bridge(ds, &bridge);
+ }
++static int mxl862xx_setup_virtual_bridge_port(struct dsa_switch *ds, int port)
++{
++      struct mxl862xx_bridge_port_alloc bp_alloc = {};
++      struct mxl862xx_bridge_port_config bp_cfg = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      struct dsa_port *cpu_dp;
++      int ret;
++
++      cpu_dp = dsa_to_port(ds, port)->cpu_dp;
++
++      ret = MXL862XX_API_READ(priv, MXL862XX_BRIDGEPORT_ALLOC, bp_alloc);
++      if (ret) {
++              dev_err(ds->dev,
++                      "failed to allocate virtual bridge port for port %d: %pe\n",
++                      port, ERR_PTR(ret));
++              return ret;
++      }
++
++      priv->ports[port].bridge_port_cpu = le16_to_cpu(bp_alloc.bridge_port_id);
++
++      bp_cfg.bridge_port_id = bp_alloc.bridge_port_id;
++      bp_cfg.mask = cpu_to_le32(
++              MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
++              MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
++              MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
++              MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING);
++      bp_cfg.bridge_id = cpu_to_le16(priv->ports[port].fid);
++      bp_cfg.src_mac_learning_disable = 1;
++      bp_cfg.dest_logical_port_id = cpu_dp->index;
++      mxl862xx_fw_portmap_set_bit(bp_cfg.bridge_port_map, port);
++
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, bp_cfg);
++      if (ret)
++              dev_err(ds->dev,
++                      "failed to configure virtual bridge port %u for port %d: %pe\n",
++                      priv->ports[port].bridge_port_cpu, port, ERR_PTR(ret));
++
++      return ret;
++}
++
++static void mxl862xx_free_virtual_bridge_port(struct dsa_switch *ds, int port)
++{
++      struct mxl862xx_bridge_port_alloc bp_alloc = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      int ret;
++
++      if (!priv->ports[port].bridge_port_cpu)
++              return;
++
++      mxl862xx_tag_8021q_disable_cpu_egress(ds, port);
++
++      bp_alloc.bridge_port_id = cpu_to_le16(priv->ports[port].bridge_port_cpu);
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_FREE, bp_alloc);
++      if (ret)
++              dev_err(ds->dev,
++                      "failed to free virtual bridge port %u for port %d: %pe\n",
++                      priv->ports[port].bridge_port_cpu, port, ERR_PTR(ret));
++      else
++              priv->ports[port].bridge_port_cpu = 0;
++}
++
++static int mxl862xx_setup_tag_8021q(struct dsa_switch *ds)
++{
++      struct dsa_port *dp;
++      int ret;
++
++      dsa_switch_for_each_user_port(dp, ds) {
++              ret = mxl862xx_setup_virtual_bridge_port(ds, dp->index);
++              if (ret)
++                      return ret;
++      }
++
++      return 0;
++}
++
++static void mxl862xx_teardown_tag_8021q(struct dsa_switch *ds)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct dsa_port *dp;
++      int cpu;
++
++      dsa_switch_for_each_user_port(dp, ds) {
++              mxl862xx_free_virtual_bridge_port(ds, dp->index);
++              priv->ports[dp->index].tag_8021q_vid = 0;
++      }
++
++      /* Disable CPU port EVLAN engine and clear VF VID entries.
++       * The HW blocks stay allocated (freed in port_teardown).
++       */
++      dsa_switch_for_each_cpu_port(dp, ds) {
++              cpu = dp->index;
++
++              priv->ports[cpu].ingress_evlan.in_use = false;
++              mxl862xx_set_cpu_ctp_ingress_evlan(ds, cpu);
++              mxl862xx_vf_clear_vids(priv, &priv->ports[cpu].vf);
++      }
++
++}
++
++/**
++ * mxl862xx_tag_8021q_program_cpu_egress - Program virtual bridge port egress EVLAN
++ * @ds: DSA switch
++ * @port: user port whose virtual bridge port needs programming
++ *
++ * Programs the egress EVLAN block on the virtual bridge port associated
++ * with @port. The block is pre-allocated in port_setup. The rules insert the
++ * port's tag_8021q management VID as an outer 802.1Q tag on all
++ * frames exiting toward the CPU through this virtual bridge port.
++ */
++static int mxl862xx_tag_8021q_program_cpu_egress(struct dsa_switch *ds,
++                                                int port)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
++      struct mxl862xx_evlan_block *blk = &p->cpu_egress_evlan;
++      struct mxl862xx_bridge_port_config bp_cfg = {};
++      int n_rules = ARRAY_SIZE(cpu_egress_tag_8021q);
++      int i, ret;
++
++      if (!p->bridge_port_cpu || !p->tag_8021q_vid)
++              return 0;
++
++      for (i = 0; i < n_rules; i++) {
++              ret = mxl862xx_evlan_write_rule(priv, blk->block_id,
++                                              i, &cpu_egress_tag_8021q[i],
++                                              0, false, 0,
++                                              p->tag_8021q_vid);
++              if (ret)
++                      return ret;
++      }
++
++      blk->n_active = n_rules;
++      blk->in_use = true;
++
++      /* Enable egress EVLAN on the virtual bridge port */
++      bp_cfg.bridge_port_id = cpu_to_le16(p->bridge_port_cpu);
++      bp_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN);
++      bp_cfg.egress_extended_vlan_enable = 1;
++      bp_cfg.egress_extended_vlan_block_id = cpu_to_le16(blk->block_id);
++      bp_cfg.egress_extended_vlan_block_size = cpu_to_le16(n_rules);
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, bp_cfg);
++}
++
++/**
++ * mxl862xx_tag_8021q_cpu_vlan_program - Reprogram CPU port ingress EVLAN
++ * @ds: DSA switch
++ *
++ * Rebuilds the CPU port ingress EVLAN block with reassign rules for
++ * every tag_8021q VID currently in use. Called whenever a tag_8021q
++ * VID is added or removed.
++ *
++ * Each user port with a non-zero tag_8021q_vid gets 2 rules:
++ *   - outer VID match + inner present: reassign to virtual bridge port
++ *   - outer VID match + no inner:      reassign to virtual bridge port
++ *
++ * The EVLAN block is assigned to the CPU port's CTP (not its bridge
++ * port) via CTP_PORTCONFIGSET, matching the reference and legacy
++ * driver architecture.
++ */
++static int mxl862xx_tag_8021q_cpu_vlan_program(struct dsa_switch *ds)
++{
++      struct mxl862xx_evlan_rule_desc rule;
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_evlan_block *blk;
++      struct dsa_port *cpu_dp, *dp;
++      struct mxl862xx_port *p;
++      u16 idx, old_active, vid;
++      int cpu, ret, i;
++
++      dsa_switch_for_each_cpu_port(cpu_dp, ds)
++              break;
++
++      cpu = cpu_dp->index;
++      blk = &priv->ports[cpu].ingress_evlan;
++
++      old_active = blk->n_active;
++      idx = 0;
++
++      dsa_switch_for_each_user_port(dp, ds) {
++              p = &priv->ports[dp->index];
++              vid = p->tag_8021q_vid;
++
++              if (!vid)
++                      continue;
++
++              for (i = 0; i < ARRAY_SIZE(cpu_ingress_reassign); i++) {
++                      rule = cpu_ingress_reassign[i];
++
++                      rule.bridge_port_id = p->bridge_port_cpu;
++                      ret = mxl862xx_evlan_write_rule(priv, blk->block_id,
++                                                      idx++, &rule, vid,
++                                                      false, 0, 0);
++                      if (ret)
++                              return ret;
++              }
++      }
++
++      blk->n_active = idx;
++
++      /* Deactivate stale entries beyond the new active range */
++      for (; idx < old_active; idx++) {
++              ret = mxl862xx_evlan_deactivate_entry(priv, blk->block_id,
++                                                    idx);
++              if (ret)
++                      return ret;
++      }
++      blk->in_use = blk->n_active > 0;
++
++      return mxl862xx_set_cpu_ctp_ingress_evlan(ds, cpu);
++}
++
++static int mxl862xx_tag_8021q_cpu_vlan_add(struct dsa_switch *ds, int port,
++                                         u16 vid)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      int ret;
++
++      /* Add VID to CPU port's VF block so firmware accepts frames
++       * tagged with this VID on CPU port ingress.
++       */
++      ret = mxl862xx_vf_add_vid(priv, &priv->ports[port].vf, vid, false);
++      if (ret)
++              return ret;
++
++      return mxl862xx_tag_8021q_cpu_vlan_program(ds);
++}
++
++static int mxl862xx_tag_8021q_cpu_vlan_del(struct dsa_switch *ds, int port,
++                                         u16 vid)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      int ret;
++
++      ret = mxl862xx_vf_del_vid(priv, &priv->ports[port].vf, vid);
++      if (ret)
++              return ret;
++
++      return mxl862xx_tag_8021q_cpu_vlan_program(ds);
++}
++
++static int mxl862xx_tag_8021q_vlan_add(struct dsa_switch *ds, int port,
++                                     u16 vid, u16 flags)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      int ret;
++
++      if (dsa_is_cpu_port(ds, port))
++              return mxl862xx_tag_8021q_cpu_vlan_add(ds, port, vid);
++
++      /* User port: store the tag_8021q VID and add to VF block */
++      priv->ports[port].tag_8021q_vid = vid;
++
++      ret = mxl862xx_vf_add_vid(priv, &priv->ports[port].vf, vid, false);
++      if (ret)
++              return ret;
++
++      ret = mxl862xx_tag_8021q_program_cpu_egress(ds, port);
++      if (ret)
++              return ret;
++
++      /* Rebuild CPU ingress EVLAN to include this port's management VID.
++       * The DSA framework may call the CPU port's tag_8021q_vlan_add
++       * before this user port's callback (ports iterate in index order),
++       * so the CPU ingress EVLAN rebuild triggered by the CPU callback
++       * might have run before tag_8021q_vid was set. Rebuild now to
++       * ensure this port's reassignment rule is present.
++       */
++      return mxl862xx_tag_8021q_cpu_vlan_program(ds);
++}
++
++static int mxl862xx_tag_8021q_vlan_del(struct dsa_switch *ds, int port,
++                                     u16 vid)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++
++      if (dsa_is_cpu_port(ds, port))
++              return mxl862xx_tag_8021q_cpu_vlan_del(ds, port, vid);
++
++      if (priv->ports[port].tag_8021q_vid == vid) {
++              priv->ports[port].tag_8021q_vid = 0;
++              mxl862xx_tag_8021q_disable_cpu_egress(ds, port);
++      }
++
++      return mxl862xx_vf_del_vid(priv, &priv->ports[port].vf, vid);
++}
++
++/**
++ * mxl862xx_refresh_cpu_targets - Update portmaps and traps for new CPU target
++ * @ds: DSA switch
++ *
++ * After switching between SpTag and tag_8021q, the CPU-side target in
++ * each user port's portmap changes (physical CPU port vs. virtual
++ * bridge port). This rebuilds every user port's portmap with the
++ * correct CPU target and reinstalls the link-local PCE trap.
++ */
++static int mxl862xx_refresh_cpu_targets(struct dsa_switch *ds)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct dsa_port *dp, *member_dp;
++      struct mxl862xx_port *p;
++      int ret, port;
++
++      dsa_switch_for_each_user_port(dp, ds) {
++              port = dp->index;
++              p = &priv->ports[port];
++
++              bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
++              if (dp->bridge) {
++
++                      dsa_switch_for_each_bridge_member(member_dp, ds, dp->bridge->dev) {
++                              if (member_dp->index != port)
++                                      __set_bit(member_dp->index, p->portmap);
++                      }
++              }
++              __set_bit(mxl862xx_cpu_bridge_port_id(ds, port), p->portmap);
++
++              /* Reprogram user port egress EVLAN to add or remove the
++               * tag_8021q management VID strip catchalls.
++               */
++              ret = mxl862xx_evlan_program_egress(priv, port);
++              if (ret)
++                      return ret;
++
++              ret = mxl862xx_set_bridge_port(ds, port);
++              if (ret)
++                      return ret;
++
++              ret = mxl862xx_setup_link_local_trap(ds, port);
++              if (ret)
++                      return ret;
++      }
++
++      return 0;
++}
++
++/**
++ * mxl862xx_complete_tag_8021q_setup - Finish deferred tag_8021q initialization
++ * @ds: DSA switch
++ *
++ * Called from change_tag_protocol() to configure the firmware for
++ * tag_8021q mode. Requires each user port to already have an FID
++ * (from add_single_port_bridge in setup()). Reconfigures CPU ports,
++ * allocates virtual bridge ports and enables flooding on standalone
++ * bridges. Link-local traps are refreshed separately after
++ * dsa_tag_8021q_register() has set cpu_egress_evlan.in_use.
++ */
++static int mxl862xx_complete_tag_8021q_setup(struct dsa_switch *ds)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct dsa_port *dp;
++      int ret, port;
++
++      /* Disable SpTag and reduce to a single CTP on CPU ports for
++       * 8021q mode. Without a special tag the PMAC cannot select a
++       * sub-CTP, so only CTP 0 must exist.
++       */
++      dsa_switch_for_each_cpu_port(dp, ds) {
++              ret = mxl862xx_configure_sp_tag_proto(ds, dp->index, false);
++              if (ret)
++                      return ret;
++
++              ret = mxl862xx_configure_ctp_port(ds, dp->index,
++                                                dp->index, 1);
++              if (ret)
++                      return ret;
++
++              ret = mxl862xx_setup_cpu_bridge(ds, dp->index);
++              if (ret)
++                      return ret;
++      }
++
++      ret = mxl862xx_setup_tag_8021q(ds);
++      if (ret)
++              return ret;
++
++      /* In tag_8021q mode TX goes through the bridge engine (CTP
++       * ingress EVLAN reassigns to a virtual bridge port), so
++       * unknown unicast and multicast must be flooded at the bridge
++       * level for frames from the CPU to reach user ports. The
++       * per-port bridges may have been created with flooding
++       * disabled (SpTag mode default), so update them now.
++       *
++       * Block unknown UC and MC on the VBP egress meters so frames
++       * to unknown destinations are not flooded to the host. DSA
++       * core will selectively enable host flooding via
++       * port_set_host_flood when needed (e.g. promisc mode).
++       */
++      dsa_switch_for_each_user_port(dp, ds) {
++              port = dp->index;
++
++              if (dp->bridge)
++                      continue;
++
++              ret = mxl862xx_bridge_config_fwd(ds,
++                                               priv->ports[port].fid,
++                                               true, true, true);
++              if (ret)
++                      return ret;
++
++              priv->ports[port].host_flood_block =
++                      BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC) |
++                      BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP) |
++                      BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP);
++
++              ret = mxl862xx_set_cpu_vbp(ds, port);
++              if (ret)
++                      return ret;
++      }
++
++      return 0;
++}
++
++static int mxl862xx_change_tag_protocol(struct dsa_switch *ds,
++                                      enum dsa_tag_protocol proto)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      enum dsa_tag_protocol old_proto = priv->tag_proto;
++      struct dsa_port *dp;
++      int ret, port;
++
++      /* Flush all MAC entries on tag protocol change. Host entries
++       * installed via portmap (tag_8021q VBP-based) vs single port_id
++       * (SpTag) are not compatible across modes.
++       */
++      if (ds->setup)
++              mxl862xx_api_wrap(priv, MXL862XX_MAC_TABLECLEAR,
++                                NULL, 0, false, false);
++
++      /* Set tag_proto early so that helpers called below (e.g.
++       * mxl862xx_setup_cpu_bridge) see the target protocol.
++       * Restored on failure.
++       */
++      priv->tag_proto = proto;
++
++      switch (proto) {
++      case DSA_TAG_PROTO_MXL862:
++              if (ds->tag_8021q_ctx) {
++                      dsa_tag_8021q_unregister(ds);
++                      mxl862xx_teardown_tag_8021q(ds);
++
++                      /* Virtual bridge ports are gone; revert portmaps
++                       * and traps to target the physical CPU port.
++                       */
++                      ret = mxl862xx_refresh_cpu_targets(ds);
++                      if (ret)
++                              goto err_restore;
++
++                      /* Revert standalone bridges to SpTag mode
++                       * defaults: discard unknown UC/MC (SpTag TX
++                       * bypasses bridge engine) while keeping
++                       * broadcast flooding.
++                       */
++                      dsa_switch_for_each_user_port(dp, ds) {
++                              port = dp->index;
++
++                              if (dp->bridge)
++                                      continue;
++
++                              mxl862xx_bridge_config_fwd(ds,
++                                                        priv->ports[port].fid,
++                                                        false, false, true);
++                      }
++              }
++              dsa_switch_for_each_cpu_port(dp, ds) {
++                      ret = mxl862xx_configure_sp_tag_proto(ds, dp->index,
++                                                            true);
++                      if (ret)
++                              goto err_restore;
++
++                      /* Restore multiple CTPs so the special tag's
++                       * sub_if_id can select per-port sub-CTPs.
++                       */
++                      ret = mxl862xx_configure_ctp_port(ds, dp->index,
++                                                        dp->index,
++                                                        32 - dp->index);
++                      if (ret)
++                              goto err_restore;
++
++                      /* Restore CPU portmap: SpTag mode needs all user
++                       * ports in the CPU's bridge_port_map. tag_8021q
++                       * mode clears it to prevent FID 0 flooding.
++                       */
++                      ret = mxl862xx_setup_cpu_bridge(ds, dp->index);
++                      if (ret)
++                              goto err_restore;
++              }
++              break;
++
++      case DSA_TAG_PROTO_MXL862_8021Q:
++              ret = mxl862xx_complete_tag_8021q_setup(ds);
++              if (ret)
++                      goto err_restore;
++
++              /* RTNL is held by the DSA core when calling
++               * change_tag_protocol(), both during initial setup
++               * and at runtime.
++               */
++              ret = dsa_tag_8021q_register(ds, htons(ETH_P_8021Q));
++              if (ret) {
++                      mxl862xx_teardown_tag_8021q(ds);
++                      goto err_restore;
++              }
++
++              /* Refresh link-local traps now that tag_8021q_vlan_add
++               * callbacks have set cpu_egress_evlan.in_use, so the
++               * PCE rules get the correct EVLAN treatment.
++               */
++              ret = mxl862xx_refresh_cpu_targets(ds);
++              if (ret) {
++                      dsa_tag_8021q_unregister(ds);
++                      mxl862xx_teardown_tag_8021q(ds);
++                      goto err_restore;
++              }
++              break;
++
++      default:
++              ret = -EPROTONOSUPPORT;
++              goto err_restore;
++      }
++
++      return 0;
++
++err_restore:
++      priv->tag_proto = old_proto;
++      return ret;
++}
++
++static void mxl862xx_teardown(struct dsa_switch *ds)
++{
++      /* tag_8021q teardown is handled in mxl862xx_remove() under
++       * RTNL, before dsa_unregister_switch() takes dsa2_mutex.
++       * dsa_tag_8021q_unregister() needs RTNL for vlan_vid_del(),
++       * and acquiring RTNL inside teardown() (which runs under
++       * dsa2_mutex) would invert the RTNL -> dsa2_mutex lock order.
++       */
++}
++
+ static int mxl862xx_port_setup(struct dsa_switch *ds, int port)
+ {
+       struct mxl862xx_priv *priv = ds->priv;
+@@ -1642,55 +2572,30 @@ static int mxl862xx_port_setup(struct ds
+           dsa_port_is_dsa(dp))
+               return 0;
+-      /* configure tag protocol */
+-      ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);
++      /* configure tag protocol: SpTag for native, disable for 8021q */
++      ret = mxl862xx_configure_sp_tag_proto(ds, port,
++                                            is_cpu_port &&
++                                            priv->tag_proto == DSA_TAG_PROTO_MXL862);
+       if (ret)
+               return ret;
+       /* assign CTP port IDs */
+       ret = mxl862xx_configure_ctp_port(ds, port, port,
+-                                        is_cpu_port ? 32 - port : 1);
++                                        (is_cpu_port &&
++                                         priv->tag_proto == DSA_TAG_PROTO_MXL862) ?
++                                        32 - port : 1);
+       if (ret)
+               return ret;
+       if (is_cpu_port)
+-              /* assign user ports to CPU port bridge */
+               return mxl862xx_setup_cpu_bridge(ds, port);
+-      /* setup single-port bridge for user ports */
+-      ret = mxl862xx_add_single_port_bridge(ds, port);
+-      if (ret)
+-              return ret;
+-
+       /* install link-local trap for this user port */
+       ret = mxl862xx_setup_link_local_trap(ds, port);
+       if (ret)
+               return ret;
+-      /* Initialize and pre-allocate per-port EVLAN and VF blocks for
+-       * user ports. CPU ports do not use EVLAN or VF -- frames pass
+-       * through without processing. Pre-allocation avoids firmware
+-       * EVLAN table fragmentation and simplifies control flow.
+-       */
+-      mxl862xx_evlan_block_init(&priv->ports[port].ingress_evlan,
+-                                priv->evlan_ingress_size);
+-      ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].ingress_evlan);
+-      if (ret)
+-              return ret;
+-
+-      mxl862xx_evlan_block_init(&priv->ports[port].egress_evlan,
+-                                priv->evlan_egress_size);
+-      ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].egress_evlan);
+-      if (ret)
+-              return ret;
+-
+-      mxl862xx_vf_init(&priv->ports[port].vf, priv->vf_block_size);
+-      ret = mxl862xx_vf_alloc(priv, &priv->ports[port].vf);
+-      if (ret)
+-              return ret;
+-
+       priv->ports[port].setup_done = true;
+-
+       return 0;
+ }
+@@ -1712,7 +2617,7 @@ static void mxl862xx_port_teardown(struc
+       priv->ports[port].setup_done = false;
+ }
+-static int mxl862xx_get_fid(struct dsa_switch *ds, struct dsa_db db)
++static int mxl862xx_get_fid(struct dsa_switch *ds, const struct dsa_db db)
+ {
+       struct mxl862xx_priv *priv = ds->priv;
+@@ -1730,23 +2635,244 @@ static int mxl862xx_get_fid(struct dsa_s
+       }
+ }
+-static int mxl862xx_port_fdb_add(struct dsa_switch *ds, int port,
+-                               const unsigned char *addr, u16 vid, struct dsa_db db)
++/**
++ * mxl862xx_fdb_bridge_port - Translate port for MAC table in tag_8021q mode
++ * @ds: DSA switch
++ * @port: port number passed by DSA (usually the CPU port for host entries)
++ * @db: database context identifying the user port or bridge
++ *
++ * In tag_8021q mode, host FDB/MDB entries for standalone ports must use
++ * the virtual bridge port (bridge_port_cpu) as the MAC table destination
++ * so that known-unicast and known-multicast frames exit through the
++ * virtual bridge port's egress EVLAN, which inserts the management VID.
++ * Without this, the firmware forwards known traffic directly to the
++ * physical CPU bridge port, bypassing management VID insertion, and DSA
++ * drops the untagged frame.
++ */
++static int mxl862xx_fdb_bridge_port(struct dsa_switch *ds, int port,
++                                  const struct dsa_db db)
+ {
+-      struct mxl862xx_mac_table_add param = {};
+-      int fid = mxl862xx_get_fid(ds, db), ret;
+       struct mxl862xx_priv *priv = ds->priv;
++      u16 bp_cpu;
+-      if (fid < 0)
+-              return fid;
++      if (dsa_is_cpu_port(ds, port) && priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q &&
++          db.type == DSA_DB_PORT) {
++              bp_cpu = priv->ports[db.dp->index].bridge_port_cpu;
++
++              if (bp_cpu)
++                      return bp_cpu;
++      }
++
++      return port;
++}
++
++/**
++ * mxl862xx_fdb_add_per_fid - Install a unicast FDB entry in one FID
++ */
++static int mxl862xx_fdb_add_per_fid(struct dsa_switch *ds,
++                                   const unsigned char *addr, u16 vid,
++                                   u16 fid, int port_id)
++{
++      struct mxl862xx_mac_table_add param = {};
++      struct mxl862xx_priv *priv = ds->priv;
+-      param.port_id = cpu_to_le32(port);
++      param.port_id = cpu_to_le32(port_id);
+       param.static_entry = true;
+       param.fid = cpu_to_le16(fid);
+       param.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid));
+       ether_addr_copy(param.mac, addr);
+-      ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, param);
++      return MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, param);
++}
++
++/**
++ * mxl862xx_fdb_del_per_fid - Remove a unicast FDB entry from one FID
++ */
++static int mxl862xx_fdb_del_per_fid(struct dsa_switch *ds,
++                                   const unsigned char *addr, u16 vid,
++                                   u16 fid)
++{
++      struct mxl862xx_mac_table_remove param = {};
++      struct mxl862xx_priv *priv = ds->priv;
++
++      param.fid = cpu_to_le16(fid);
++      param.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid));
++      ether_addr_copy(param.mac, addr);
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE, param);
++}
++
++/**
++ * mxl862xx_mac_portmap_add - Set port bits in a MAC table entry's portmap
++ * @priv: driver private data
++ * @addr: MAC address
++ * @fid: firmware FID
++ * @vid: VLAN ID
++ * @add_map: firmware-format portmap of bits to set
++ *
++ * Queries the existing MAC table entry by {addr, fid, vid}. If found,
++ * the existing portmap is preserved and @add_map bits are OR'd in.
++ * The entry is then written back as a static portmap-mode entry.
++ */
++static int mxl862xx_mac_portmap_add(struct mxl862xx_priv *priv,
++                                  const unsigned char *addr,
++                                  u16 fid, u16 vid,
++                                  const __le16 *add_map)
++{
++      struct mxl862xx_mac_table_query qparam = {};
++      struct mxl862xx_mac_table_add aparam = {};
++      int i, ret;
++
++      ether_addr_copy(qparam.mac, addr);
++      qparam.fid = cpu_to_le16(fid);
++      qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid));
++
++      ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);
++      if (ret)
++              return ret;
++
++      ether_addr_copy(aparam.mac, addr);
++      aparam.fid = cpu_to_le16(fid);
++      aparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid));
++      aparam.static_entry = true;
++      aparam.port_id = cpu_to_le32(MXL862XX_PORTMAP_FLAG);
++
++      if (qparam.found)
++              memcpy(aparam.port_map, qparam.port_map,
++                     sizeof(aparam.port_map));
++
++      for (i = 0; i < ARRAY_SIZE(aparam.port_map); i++)
++              aparam.port_map[i] |= add_map[i];
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, aparam);
++}
++
++/**
++ * mxl862xx_mac_portmap_del - Clear port bits from a MAC table entry's portmap
++ * @priv: driver private data
++ * @addr: MAC address
++ * @fid: firmware FID
++ * @vid: VLAN ID
++ * @del_map: firmware-format portmap of bits to clear
++ *
++ * Queries the existing MAC table entry. If not found, returns 0.
++ * Clears all @del_map bits from the portmap. If the portmap becomes
++ * empty, the entry is removed entirely; otherwise it is updated.
++ */
++static int mxl862xx_mac_portmap_del(struct mxl862xx_priv *priv,
++                                  const unsigned char *addr,
++                                  u16 fid, u16 vid,
++                                  const __le16 *del_map)
++{
++      struct mxl862xx_mac_table_remove rparam = {};
++      struct mxl862xx_mac_table_query qparam = {};
++      struct mxl862xx_mac_table_add aparam = {};
++      int i, ret;
++
++      ether_addr_copy(qparam.mac, addr);
++      qparam.fid = cpu_to_le16(fid);
++      qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid));
++
++      ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);
++      if (ret)
++              return ret;
++
++      if (!qparam.found)
++              return 0;
++
++      for (i = 0; i < ARRAY_SIZE(qparam.port_map); i++)
++              qparam.port_map[i] &= ~del_map[i];
++
++      if (mxl862xx_fw_portmap_is_empty(qparam.port_map)) {
++              ether_addr_copy(rparam.mac, addr);
++              rparam.fid = cpu_to_le16(fid);
++              rparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID,
++                                                   vid));
++              return MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE,
++                                        rparam);
++      }
++
++      ether_addr_copy(aparam.mac, addr);
++      aparam.fid = cpu_to_le16(fid);
++      aparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid));
++      aparam.static_entry = true;
++      aparam.port_id = cpu_to_le32(MXL862XX_PORTMAP_FLAG);
++      memcpy(aparam.port_map, qparam.port_map, sizeof(aparam.port_map));
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, aparam);
++}
++
++/**
++ * mxl862xx_mac_add_host_bridge - Install a host FDB/MDB entry with VBP portmap
++ * @ds: DSA switch
++ * @addr: MAC address
++ * @vid: VLAN ID
++ * @bridge: bridge whose members' VBPs to include
++ *
++ * In tag_8021q mode, host FDB/MDB entries in a shared bridge FID must use
++ * portmap mode targeting ALL bridge members' virtual bridge ports (VBPs).
++ * The firmware ANDs the entry's portmap with each ingress port's
++ * bridge_port_map, which contains only that port's own VBP. This
++ * selects the correct VBP per ingress port, ensuring frames exit
++ * through the right egress EVLAN (which inserts the per-port management
++ * VID that identifies the source port to DSA on the CPU side).
++ */
++static int mxl862xx_mac_add_host_bridge(struct dsa_switch *ds,
++                                      const unsigned char *addr, u16 vid,
++                                      const struct dsa_bridge *bridge)
++{
++      __le16 add_map[MXL862XX_FW_PORTMAP_WORDS] = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      u16 fid = priv->bridges[bridge->num];
++      struct dsa_port *member_dp;
++
++      dsa_switch_for_each_bridge_member(member_dp, ds, bridge->dev)
++              mxl862xx_fw_portmap_set_bit(add_map,
++                                          priv->ports[member_dp->index].bridge_port_cpu);
++
++      return mxl862xx_mac_portmap_add(priv, addr, fid, vid, add_map);
++}
++
++static int mxl862xx_port_fdb_add(struct dsa_switch *ds, int port,
++                               const unsigned char *addr, u16 vid, const struct dsa_db db)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct dsa_port *target_dp;
++      int fid, ret;
++
++      /* tag_8021q host FDB for bridged ports: portmap with all VBPs */
++      if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q && dsa_is_cpu_port(ds, port) &&
++          db.type == DSA_DB_BRIDGE) {
++              if (!priv->bridges[db.bridge.num])
++                      return -ENOENT;
++
++              return mxl862xx_mac_add_host_bridge(ds, addr, vid, &db.bridge);
++      }
++
++      /* tag_8021q standalone host FDB for bridged ports: also mirror
++       * into the bridge FID. DSA installs VID-specific host entries
++       * via the standalone path (DSA_DB_PORT), but with IVL enabled
++       * the firmware needs matching entries in the bridge FID for
++       * VID-keyed lookups to succeed.
++       */
++      if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q && dsa_is_cpu_port(ds, port) &&
++          db.type == DSA_DB_PORT && vid > 0) {
++              target_dp = dsa_to_port(ds, db.dp->index);
++
++              if (target_dp->bridge) {
++                      ret = mxl862xx_mac_add_host_bridge(ds, addr, vid,
++                                                         target_dp->bridge);
++                      if (ret)
++                              return ret;
++              }
++      }
++
++      fid = mxl862xx_get_fid(ds, db);
++      if (fid < 0)
++              return fid;
++
++      ret = mxl862xx_fdb_add_per_fid(ds, addr, vid, fid,
++                                     mxl862xx_fdb_bridge_port(ds, port, db));
+       if (ret)
+               dev_err(ds->dev, "failed to add FDB entry on port %d\n", port);
+@@ -1756,18 +2882,25 @@ static int mxl862xx_port_fdb_add(struct
+ static int mxl862xx_port_fdb_del(struct dsa_switch *ds, int port,
+                                const unsigned char *addr, u16 vid, const struct dsa_db db)
+ {
+-      struct mxl862xx_mac_table_remove param = {};
+-      int fid = mxl862xx_get_fid(ds, db), ret;
+       struct mxl862xx_priv *priv = ds->priv;
++      struct dsa_port *target_dp;
++      int fid, ret;
++      /* Mirror of the standalone->bridge FID path in fdb_add */
++      if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q && dsa_is_cpu_port(ds, port) &&
++          db.type == DSA_DB_PORT && vid > 0) {
++              target_dp = dsa_to_port(ds, db.dp->index);
++
++              if (target_dp->bridge && priv->bridges[target_dp->bridge->num])
++                      mxl862xx_fdb_del_per_fid(ds, addr, vid,
++                                               priv->bridges[target_dp->bridge->num]);
++      }
++
++      fid = mxl862xx_get_fid(ds, db);
+       if (fid < 0)
+               return fid;
+-      param.fid = cpu_to_le16(fid);
+-      param.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid));
+-      ether_addr_copy(param.mac, addr);
+-
+-      ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE, param);
++      ret = mxl862xx_fdb_del_per_fid(ds, addr, vid, fid);
+       if (ret)
+               dev_err(ds->dev, "failed to remove FDB entry on port %d\n", port);
+@@ -1806,88 +2939,147 @@ static int mxl862xx_port_fdb_dump(struct
+       return 0;
+ }
++/**
++ * mxl862xx_mdb_add_to_fid - Add a port bit to an MDB entry in one FID
++ * @ds: DSA switch
++ * @mdb: multicast group address and VID
++ * @fid: firmware FID to operate on
++ * @port_bit: port index to set in the portmap
++ * @vid: VLAN ID for the MAC table entry
++ */
++static int mxl862xx_mdb_add_to_fid(struct dsa_switch *ds,
++                                  const struct switchdev_obj_port_mdb *mdb,
++                                  u16 fid, int port_bit, u16 vid)
++{
++      __le16 add_map[MXL862XX_FW_PORTMAP_WORDS] = {};
++
++      mxl862xx_fw_portmap_set_bit(add_map, port_bit);
++
++      return mxl862xx_mac_portmap_add(ds->priv, mdb->addr, fid, vid,
++                                      add_map);
++}
++
++/**
++ * mxl862xx_mdb_del_from_fid - Remove a port bit from an MDB entry in one FID
++ * @ds: DSA switch
++ * @mdb: multicast group address
++ * @fid: firmware FID to operate on
++ * @port_bit: port index to clear from the portmap
++ * @vid: VLAN ID for the MAC table entry (0 for SVL/tag_8021q mode)
++ */
++static int mxl862xx_mdb_del_from_fid(struct dsa_switch *ds,
++                                    const struct switchdev_obj_port_mdb *mdb,
++                                    u16 fid, int port_bit, u16 vid)
++{
++      __le16 del_map[MXL862XX_FW_PORTMAP_WORDS] = {};
++
++      mxl862xx_fw_portmap_set_bit(del_map, port_bit);
++
++      return mxl862xx_mac_portmap_del(ds->priv, mdb->addr, fid, vid,
++                                      del_map);
++}
++
+ static int mxl862xx_port_mdb_add(struct dsa_switch *ds, int port,
+                                const struct switchdev_obj_port_mdb *mdb,
+                                const struct dsa_db db)
+ {
+-      struct mxl862xx_mac_table_query qparam = {};
+-      struct mxl862xx_mac_table_add aparam = {};
+       struct mxl862xx_priv *priv = ds->priv;
+       int fid, ret;
++      /* tag_8021q host MDB for bridged ports: portmap with all VBPs */
++      if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q && dsa_is_cpu_port(ds, port) &&
++          db.type == DSA_DB_BRIDGE) {
++              if (!priv->bridges[db.bridge.num])
++                      return -ENOENT;
++
++              return mxl862xx_mac_add_host_bridge(ds, mdb->addr,
++                                                  mdb->vid, &db.bridge);
++      }
++
+       fid = mxl862xx_get_fid(ds, db);
+       if (fid < 0)
+               return fid;
+-      /* Look up existing entry by {MAC, FID, TCI} */
+-      ether_addr_copy(qparam.mac, mdb->addr);
+-      qparam.fid = cpu_to_le16(fid);
+-      qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+-
+-      ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);
++      ret = mxl862xx_mdb_add_to_fid(ds, mdb, fid,
++                                     mxl862xx_fdb_bridge_port(ds, port, db),
++                                     mdb->vid);
+       if (ret)
+               return ret;
+-      /* Build the ADD command using portmap mode */
+-      ether_addr_copy(aparam.mac, mdb->addr);
+-      aparam.fid = cpu_to_le16(fid);
+-      aparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+-      aparam.static_entry = true;
+-      aparam.port_id = cpu_to_le32(MXL862XX_PORTMAP_FLAG);
++      /* In tag_8021q mode, standalone host MDB entries need both the VBP
++       * and the physical port in the portmap. The TX path goes through
++       * the bridge engine (CPU -> VBP -> MAC lookup), so source-port
++       * filtering would remove the sole VBP entry, dropping the frame.
++       * With both bits set:
++       *   TX: VBP source-filtered -> physical port remains -> frame exits
++       *   RX: physical port source-filtered -> VBP remains -> CPU receives
++       */
++      if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q && db.type == DSA_DB_PORT)
++              ret = mxl862xx_mdb_add_to_fid(ds, mdb, fid, db.dp->index,
++                                             mdb->vid);
+-      /* Merge with existing portmap if entry already exists */
+-      if (qparam.found)
+-              memcpy(aparam.port_map, qparam.port_map,
+-                     sizeof(aparam.port_map));
++      return ret;
++}
+-      mxl862xx_fw_portmap_set_bit(aparam.port_map, port);
++/**
++ * mxl862xx_mac_del_host_bridge - Remove VBP bits from a host FDB/MDB entry
++ * @ds: DSA switch
++ * @addr: MAC address
++ * @vid: VLAN ID
++ * @bridge: bridge whose members' VBPs to clear
++ *
++ * Clears all bridge member VBP bits from the portmap. If the portmap
++ * becomes empty (no user-port bits remain), removes the entry entirely.
++ */
++static int mxl862xx_mac_del_host_bridge(struct dsa_switch *ds,
++                                      const unsigned char *addr, u16 vid,
++                                      const struct dsa_bridge *bridge)
++{
++      __le16 del_map[MXL862XX_FW_PORTMAP_WORDS] = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      u16 fid = priv->bridges[bridge->num];
++      struct dsa_port *member_dp;
+-      return MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, aparam);
++      dsa_switch_for_each_bridge_member(member_dp, ds, bridge->dev)
++              mxl862xx_fw_portmap_set_bit(del_map,
++                                          priv->ports[member_dp->index].bridge_port_cpu);
++
++      return mxl862xx_mac_portmap_del(priv, addr, fid, vid, del_map);
+ }
+ static int mxl862xx_port_mdb_del(struct dsa_switch *ds, int port,
+                                const struct switchdev_obj_port_mdb *mdb,
+                                const struct dsa_db db)
+ {
+-      struct mxl862xx_mac_table_remove rparam = {};
+-      struct mxl862xx_mac_table_query qparam = {};
+-      struct mxl862xx_mac_table_add aparam = {};
+-      int fid = mxl862xx_get_fid(ds, db), ret;
+       struct mxl862xx_priv *priv = ds->priv;
++      int fid, ret;
++
++      /* tag_8021q host MDB for bridged ports: clear all VBP bits */
++      if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q && dsa_is_cpu_port(ds, port) &&
++          db.type == DSA_DB_BRIDGE) {
++              if (!priv->bridges[db.bridge.num])
++                      return -ENOENT;
++
++              return mxl862xx_mac_del_host_bridge(ds, mdb->addr,
++                                                  mdb->vid, &db.bridge);
++      }
++      fid = mxl862xx_get_fid(ds, db);
+       if (fid < 0)
+               return fid;
+-      /* Look up existing entry */
+-      qparam.fid = cpu_to_le16(fid);
+-      qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+-      ether_addr_copy(qparam.mac, mdb->addr);
+-
+-      ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);
++      ret = mxl862xx_mdb_del_from_fid(ds, mdb, fid,
++                                       mxl862xx_fdb_bridge_port(ds, port, db),
++                                       mdb->vid);
+       if (ret)
+               return ret;
+-      if (!qparam.found)
+-              return 0;
+-
+-      mxl862xx_fw_portmap_clear_bit(qparam.port_map, port);
+-
+-      if (mxl862xx_fw_portmap_is_empty(qparam.port_map)) {
+-              /* No ports left -- remove the entry entirely */
+-              rparam.fid = cpu_to_le16(fid);
+-              rparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+-              ether_addr_copy(rparam.mac, mdb->addr);
+-              ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE, rparam);
+-      } else {
+-              /* Write back with reduced portmap */
+-              aparam.fid = cpu_to_le16(fid);
+-              aparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+-              ether_addr_copy(aparam.mac, mdb->addr);
+-              aparam.static_entry = true;
+-              aparam.port_id = cpu_to_le32(MXL862XX_PORTMAP_FLAG);
+-              memcpy(aparam.port_map, qparam.port_map, sizeof(aparam.port_map));
+-              ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, aparam);
+-      }
++      /* In tag_8021q mode, standalone host MDB entries have both the VBP
++       * and the physical port in the portmap -- remove both bits.
++       */
++      if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q && db.type == DSA_DB_PORT)
++              ret = mxl862xx_mdb_del_from_fid(ds, mdb, fid, db.dp->index,
++                                               mdb->vid);
+       return ret;
+ }
+@@ -1975,7 +3167,9 @@ static void mxl862xx_host_flood_work_fn(
+       struct mxl862xx_priv *priv = p->priv;
+       struct dsa_switch *ds = priv->ds;
+       int port = p - priv->ports;
++      unsigned long block;
+       bool uc, mc;
++      int ret;
+       rtnl_lock();
+@@ -1988,14 +3182,31 @@ static void mxl862xx_host_flood_work_fn(
+       uc = p->host_flood_uc;
+       mc = p->host_flood_mc;
+-      /* The hardware controls unknown-unicast/multicast forwarding per FID
+-       * (bridge), not per source port. For bridged ports all members share
+-       * one FID, so we cannot selectively suppress flooding to the CPU for
+-       * one source port while allowing it for another. Silently ignore the
+-       * request -- the excess flooding towards the CPU is harmless.
+-       */
+-      if (!dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
+-              mxl862xx_bridge_config_fwd(ds, p->fid, uc, mc, true);
++      if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q) {
++              block = 0;
++
++              if (!uc)
++                      block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC);
++              if (!mc) {
++                      block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP);
++                      block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP);
++              }
++
++              if (block != p->host_flood_block) {
++                      p->host_flood_block = block;
++                      ret = mxl862xx_set_cpu_vbp(ds, port);
++                      if (ret)
++                              dev_err(ds->dev,
++                                      "failed to set host flood on port %d: %pe\n",
++                                      port, ERR_PTR(ret));
++              }
++      } else {
++              /* SpTag mode: per-FID forwarding, only works for
++               * standalone ports (each has its own FID).
++               */
++              if (!dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
++                      mxl862xx_bridge_config_fwd(ds, p->fid, uc, mc, true);
++      }
+       rtnl_unlock();
+ }
+@@ -2330,7 +3541,9 @@ static void mxl862xx_get_stats64(struct
+ static const struct dsa_switch_ops mxl862xx_switch_ops = {
+       .get_tag_protocol = mxl862xx_get_tag_protocol,
++      .change_tag_protocol = mxl862xx_change_tag_protocol,
+       .setup = mxl862xx_setup,
++      .teardown = mxl862xx_teardown,
+       .port_setup = mxl862xx_port_setup,
+       .port_teardown = mxl862xx_port_teardown,
+       .phylink_get_caps = mxl862xx_phylink_get_caps,
+@@ -2352,6 +3565,8 @@ static const struct dsa_switch_ops mxl86
+       .port_vlan_filtering = mxl862xx_port_vlan_filtering,
+       .port_vlan_add = mxl862xx_port_vlan_add,
+       .port_vlan_del = mxl862xx_port_vlan_del,
++      .tag_8021q_vlan_add = mxl862xx_tag_8021q_vlan_add,
++      .tag_8021q_vlan_del = mxl862xx_tag_8021q_vlan_del,
+       .get_strings = mxl862xx_get_strings,
+       .get_sset_count = mxl862xx_get_sset_count,
+       .get_ethtool_stats = mxl862xx_get_ethtool_stats,
+@@ -2399,6 +3614,8 @@ static int mxl862xx_probe(struct mdio_de
+       INIT_DELAYED_WORK(&priv->stats_work, mxl862xx_stats_work_fn);
++      priv->tag_proto = DSA_TAG_PROTO_MXL862;
++
+       dev_set_drvdata(dev, ds);
+       return dsa_register_switch(ds);
+@@ -2415,16 +3632,29 @@ static void mxl862xx_remove(struct mdio_
+       priv = ds->priv;
++      /* Tear down tag_8021q under RTNL before dsa_unregister_switch().
++       * dsa_tag_8021q_unregister() calls vlan_vid_del() which needs
++       * RTNL. dsa_unregister_switch() takes dsa2_mutex, and other
++       * paths take RTNL -> dsa2_mutex, so RTNL must be acquired
++       * before dsa2_mutex to avoid lock inversion.
++       */
++      if (ds->tag_8021q_ctx) {
++              rtnl_lock();
++              dsa_tag_8021q_unregister(ds);
++              mxl862xx_teardown_tag_8021q(ds);
++              rtnl_unlock();
++      }
++
+       dsa_unregister_switch(ds);
+       cancel_delayed_work_sync(&priv->stats_work);
+       mxl862xx_host_shutdown(priv);
+-      /* Cancel any pending host flood work.  dsa_unregister_switch()
++      /* Cancel any pending host flood work. dsa_unregister_switch()
+        * has already called port_teardown (which sets setup_done=false),
+        * but a worker could still be blocked on rtnl_lock(). Since we
+-       * are now outside RTNL, cancel_work_sync() will not deadlock.
++       * are now outside RTNL, cancel_work_sync() won't deadlock.
+        */
+       for (i = 0; i < MXL862XX_MAX_PORTS; i++)
+               cancel_work_sync(&priv->ports[i].host_flood_work);
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -8,8 +8,6 @@
+ #include <linux/workqueue.h>
+ #include <net/dsa.h>
+-struct mxl862xx_priv;
+-
+ #define MXL862XX_MAX_PORTS            17
+ #define MXL862XX_DEFAULT_BRIDGE               0
+ #define MXL862XX_MAX_BRIDGES          48
+@@ -20,6 +18,8 @@ struct mxl862xx_priv;
+ /* Number of __le16 words in a firmware portmap (128-bit bitmap). */
+ #define MXL862XX_FW_PORTMAP_WORDS     (MXL862XX_MAX_BRIDGE_PORTS / 16)
++struct mxl862xx_priv;
++
+ /**
+  * mxl862xx_fw_portmap_from_bitmap - convert a kernel bitmap to a firmware
+  *                                   portmap (__le16[8])
+@@ -210,6 +210,9 @@ struct mxl862xx_port_stats {
+  * @vf:                  per-port VLAN Filter block state
+  * @ingress_evlan:       ingress extended VLAN block state
+  * @egress_evlan:        egress extended VLAN block state
++ * @bridge_port_cpu:     virtual bridge port ID for tag_8021q CPU-side CTP
++ * @host_flood_block:    bitmask of firmware meter indices used to block
++ *                       host flooding on the virtual bridge port (tag_8021q)
+  * @host_flood_uc:       desired host unicast flood state (true = flood);
+  *                       updated atomically by port_set_host_flood, consumed
+  *                       by the deferred host_flood_work
+@@ -224,6 +227,7 @@ struct mxl862xx_port_stats {
+  *                       periodically by the stats polling work
+  * @stats_lock:          protects accumulator reads in .get_stats64 against
+  *                       concurrent updates from the polling work
++ * @tag_8021q_vid:       currently assigned tag_8021q management VID
+  */
+ struct mxl862xx_port {
+       struct mxl862xx_priv *priv;
+@@ -238,9 +242,14 @@ struct mxl862xx_port {
+       struct mxl862xx_vf_block vf;
+       struct mxl862xx_evlan_block ingress_evlan;
+       struct mxl862xx_evlan_block egress_evlan;
++      /* tag_8021q state */
++      u16 bridge_port_cpu;
++      unsigned long host_flood_block;
+       bool host_flood_uc;
+       bool host_flood_mc;
+       struct work_struct host_flood_work;
++      u16 tag_8021q_vid;
++      struct mxl862xx_evlan_block cpu_egress_evlan;
+       /* Hardware stats accumulation */
+       struct mxl862xx_port_stats stats;
+       spinlock_t stats_lock;
+@@ -302,6 +311,7 @@ union mxl862xx_fw_version {
+  *                      errors
+  * @crc_err:            set atomically before CRC-triggered shutdown, cleared
+  *                      after
++ * @tag_proto:          active DSA tag protocol (native or 8021q)
+  * @drop_meter:         index of the single shared zero-rate firmware meter
+  *                      used to unconditionally drop traffic (used to block
+  *                      flooding)
+@@ -310,12 +320,13 @@ union mxl862xx_fw_version {
+  * @serdes_ports:       SerDes interfaces incl. sub-interfaces in case of
+  *                      10G_QXGMII
+  * @ports:              per-port state, indexed by switch port number
++ * @evlan_ingress_size: per-port ingress Extended VLAN block size
++ * @evlan_egress_size:  per-port egress Extended VLAN block size
++ * @cpu_evlan_ingress_size: CPU port ingress EVLAN block size (tag_8021q)
+  * @bridges:            maps DSA bridge number to firmware bridge ID;
+  *                      zero means no firmware bridge allocated for that
+  *                      DSA bridge number. Indexed by dsa_bridge.num
+  *                      (0 .. ds->max_num_bridges).
+- * @evlan_ingress_size: per-port ingress Extended VLAN block size
+- * @evlan_egress_size:  per-port egress Extended VLAN block size
+  * @vf_block_size:      per-port VLAN Filter block size
+  * @stats_work:         periodic work item that polls RMON hardware counters
+  *                      and accumulates them into 64-bit per-port stats
+@@ -325,6 +336,7 @@ struct mxl862xx_priv {
+       struct mdio_device *mdiodev;
+       struct work_struct crc_err_work;
+       unsigned long crc_err;
++      enum dsa_tag_protocol tag_proto;
+       u16 drop_meter;
+       union mxl862xx_fw_version fw_version;
+       struct mxl862xx_pcs serdes_ports[8];
+@@ -332,6 +344,7 @@ struct mxl862xx_priv {
+       u16 bridges[MXL862XX_MAX_BRIDGES + 1];
+       u16 evlan_ingress_size;
+       u16 evlan_egress_size;
++      u16 cpu_evlan_ingress_size;
+       u16 vf_block_size;
+       struct delayed_work stats_work;
+ };
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -56,6 +56,8 @@ struct tc_action;
+ #define DSA_TAG_PROTO_VSC73XX_8021Q_VALUE     28
+ #define DSA_TAG_PROTO_BRCM_LEGACY_FCS_VALUE   29
+ #define DSA_TAG_PROTO_MXL862_VALUE            30
++#define DSA_TAG_PROTO_MXL862_8021Q_VALUE      31
++
+ enum dsa_tag_protocol {
+       DSA_TAG_PROTO_NONE              = DSA_TAG_PROTO_NONE_VALUE,
+@@ -89,6 +91,7 @@ enum dsa_tag_protocol {
+       DSA_TAG_PROTO_LAN937X           = DSA_TAG_PROTO_LAN937X_VALUE,
+       DSA_TAG_PROTO_VSC73XX_8021Q     = DSA_TAG_PROTO_VSC73XX_8021Q_VALUE,
+       DSA_TAG_PROTO_MXL862            = DSA_TAG_PROTO_MXL862_VALUE,
++      DSA_TAG_PROTO_MXL862_8021Q      = DSA_TAG_PROTO_MXL862_8021Q_VALUE,
+ };
+ struct dsa_switch;
+--- a/net/dsa/Kconfig
++++ b/net/dsa/Kconfig
+@@ -111,6 +111,13 @@ config NET_DSA_TAG_MXL_862XX
+         MaxLinear MxL86252 and MxL86282 switches using their native 8-byte
+         tagging protocol.
++config NET_DSA_TAG_MXL_862XX_8021Q
++      tristate "Tag driver for MaxLinear MxL862xx switches, using VLAN"
++      help
++        Say Y or M if you want to enable support for tagging frames for the
++        MaxLinear MxL86252 and MxL86282 switches using 802.1Q VLAN-based
++        tagging instead of their native 8-byte tagging protocol.
++
+ config NET_DSA_TAG_KSZ
+       tristate "Tag driver for Microchip 8795/937x/9477/9893 families of switches"
+       help
+--- a/net/dsa/Makefile
++++ b/net/dsa/Makefile
+@@ -29,6 +29,7 @@ obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz
+ obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
+ obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o
+ obj-$(CONFIG_NET_DSA_TAG_MXL_862XX) += tag_mxl862xx.o
++obj-$(CONFIG_NET_DSA_TAG_MXL_862XX_8021Q) += tag_mxl862xx_8021q.o
+ obj-$(CONFIG_NET_DSA_TAG_NONE) += tag_none.o
+ obj-$(CONFIG_NET_DSA_TAG_OCELOT) += tag_ocelot.o
+ obj-$(CONFIG_NET_DSA_TAG_OCELOT_8021Q) += tag_ocelot_8021q.o
+--- /dev/null
++++ b/net/dsa/tag_mxl862xx_8021q.c
+@@ -0,0 +1,59 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * DSA 802.1Q-based tag driver for MaxLinear MxL862xx switches
++ *
++ * Uses the DSA tag_8021q framework to encode port information in
++ * 802.1Q VLAN tags instead of the native 8-byte MxL862xx special tag.
++ *
++ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
++ */
++
++#include <linux/dsa/8021q.h>
++
++#include "tag.h"
++#include "tag_8021q.h"
++
++#define MXL862_8021Q_NAME "mxl862xx-8021q"
++
++static struct sk_buff *mxl862_8021q_xmit(struct sk_buff *skb,
++                                       struct net_device *netdev)
++{
++      struct dsa_port *dp = dsa_user_to_port(netdev);
++      u16 tx_vid = dsa_tag_8021q_standalone_vid(dp);
++      u16 queue_mapping = skb_get_queue_mapping(skb);
++      u8 pcp = netdev_txq_to_tc(netdev, queue_mapping);
++
++      return dsa_8021q_xmit(skb, netdev, ETH_P_8021Q,
++                            (pcp << VLAN_PRIO_SHIFT) | tx_vid);
++}
++
++static struct sk_buff *mxl862_8021q_rcv(struct sk_buff *skb,
++                                      struct net_device *netdev)
++{
++      int src_port = -1, switch_id = -1;
++
++      dsa_8021q_rcv(skb, &src_port, &switch_id, NULL, NULL);
++
++      skb->dev = dsa_conduit_find_user(netdev, switch_id, src_port);
++      if (!skb->dev)
++              return NULL;
++
++      dsa_default_offload_fwd_mark(skb);
++
++      return skb;
++}
++
++static const struct dsa_device_ops mxl862_8021q_netdev_ops = {
++      .name                   = MXL862_8021Q_NAME,
++      .proto                  = DSA_TAG_PROTO_MXL862_8021Q,
++      .xmit                   = mxl862_8021q_xmit,
++      .rcv                    = mxl862_8021q_rcv,
++      .needed_headroom        = VLAN_HLEN,
++      .promisc_on_conduit     = true,
++};
++
++MODULE_DESCRIPTION("DSA tag driver for MaxLinear MxL862xx switches, using VLAN");
++MODULE_LICENSE("GPL");
++MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_MXL862_8021Q, MXL862_8021Q_NAME);
++
++module_dsa_tag_driver(mxl862_8021q_netdev_ops);
diff --git a/target/linux/generic/pending-6.12/760-17-net-dsa-mxl862xx-add-link-aggregation-support.patch b/target/linux/generic/pending-6.12/760-17-net-dsa-mxl862xx-add-link-aggregation-support.patch
new file mode 100644 (file)
index 0000000..d373f65
--- /dev/null
@@ -0,0 +1,862 @@
+From 31359e8b7673e656d0591a9eb5014b45911383ae Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 03:44:41 +0000
+Subject: [PATCH 26/35] net: dsa: mxl862xx: add link aggregation support
+
+Implement LAG offloading via the firmware's trunking engine.  A
+dedicated firmware bridge port is allocated per LAG and remains
+stable for the LAG's lifetime.  All member CTPs redirect to it,
+and FDB/MDB entries target it, so no entry migration is needed
+when the LAG master (lowest-numbered member port) changes.
+
+The firmware provides three cooperating mechanisms:
+
+  - PCE_TRUNK_CONF register: global 6-bit hash field selection
+    (SA, DA, SIP, DIP, sport, dport)
+  - CTP redirection: all member CTPs point bridge_port_id to the
+    LAG's dedicated bridge port
+  - P-mapper on the LAG bridge port: 64 hash-indexed entries
+    (indices 9..72) filled round-robin with active member ports
+
+Hash and active-backup bond modes are supported.  The global hash
+register is widened monotonically on LAG join and recomputed from
+stored per-port requirements on LAG leave.
+
+The LAG master's full bridge port configuration (bridge_id, EVLAN,
+VLAN filter, learning, portmap, flood metering) is pushed to the
+LAG bridge port via __mxl862xx_set_bridge_port() whenever it
+changes.  Bridge portmaps use the LAG bridge port ID instead of
+individual member port indices, ensuring correct cross-LAG
+forwarding.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h |  22 +
+ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h |   4 +
+ drivers/net/dsa/mxl862xx/mxl862xx.c     | 587 +++++++++++++++++++++++-
+ drivers/net/dsa/mxl862xx/mxl862xx.h     |  34 ++
+ 4 files changed, 630 insertions(+), 17 deletions(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+@@ -564,6 +564,28 @@ struct mxl862xx_pmapper {
+ } __packed;
+ /**
++ * struct mxl862xx_trunking_cfg - LAG hash algorithm configuration
++ * @ip_src:   Include source IP address in trunk hash (1 = include)
++ * @ip_dst:   Include destination IP address in trunk hash
++ * @mac_src:  Include source MAC address in trunk hash
++ * @mac_dst:  Include destination MAC address in trunk hash
++ * @src_port: Include TCP/UDP source port in trunk hash
++ * @dst_port: Include TCP/UDP destination port in trunk hash
++ *
++ * The firmware inverts the boolean sense when writing the hardware
++ * register (PCE_TRUNK_CONF): bit=0 means include, bit=1 means exclude.
++ * This struct uses the logical sense (1 = include).
++ */
++struct mxl862xx_trunking_cfg {
++      u8 ip_src;
++      u8 ip_dst;
++      u8 mac_src;
++      u8 mac_dst;
++      u8 src_port;
++      u8 dst_port;
++} __packed;
++
++/**
+  * struct mxl862xx_bridge_port_config - Bridge Port Configuration
+  * @bridge_port_id: Bridge Port ID allocated by bridge port allocation
+  * @mask: See &enum mxl862xx_bridge_port_config_mask
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+@@ -51,6 +51,7 @@
+ #define MXL862XX_QOS_METERCFGSET      (MXL862XX_QOS_MAGIC + 0x2)
+ #define MXL862XX_QOS_METERALLOC               (MXL862XX_QOS_MAGIC + 0x2a)
++#define MXL862XX_QOS_PMAPPERTABLESET  (MXL862XX_QOS_MAGIC + 0x2e)
+ #define MXL862XX_RMON_PORT_GET                (MXL862XX_RMON_MAGIC + 0x1)
+@@ -73,6 +74,9 @@
+ #define MXL862XX_SS_SPTAG_SET         (MXL862XX_SS_MAGIC + 0x2)
++#define MXL862XX_TRUNKING_MAGIC               0xe00
++#define MXL862XX_TRUNKING_CFGSET      (MXL862XX_TRUNKING_MAGIC + 0x2)
++
+ #define MXL862XX_STP_PORTCFGSET               (MXL862XX_STP_MAGIC + 0x2)
+ #define INT_GPHY_READ                 (GPY_GPY2XX_MAGIC + 0x1)
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -620,7 +620,42 @@ static int mxl862xx_setup_link_local_tra
+                                 rule);
+ }
+-static int mxl862xx_set_bridge_port(struct dsa_switch *ds, int port)
++static bool mxl862xx_is_lag_master(const struct mxl862xx_priv *priv, int port)
++{
++      struct dsa_lag *lag = priv->ports[port].lag;
++      int i;
++
++      if (!lag)
++              return true;
++
++      for (i = 0; i < port; i++) {
++              if (priv->ports[i].lag == lag)
++                      return false;
++      }
++
++      return true;
++}
++
++/**
++ * mxl862xx_lag_bridge_port - Get the effective bridge port ID for a port
++ * @priv: driver private data
++ * @port: port index
++ *
++ * If @port is a member of a LAG, returns the LAG's dedicated firmware
++ * bridge port ID. Otherwise returns @port itself.
++ */
++static u16 mxl862xx_lag_bridge_port(const struct mxl862xx_priv *priv, int port)
++{
++      struct dsa_lag *lag = priv->ports[port].lag;
++
++      if (lag && priv->lag_bridge_ports[lag->id])
++              return priv->lag_bridge_ports[lag->id];
++
++      return port;
++}
++
++static int __mxl862xx_set_bridge_port(struct dsa_switch *ds, int port,
++                                    u16 bp_id)
+ {
+       struct mxl862xx_bridge_port_config br_port_cfg = {};
+       struct dsa_port *dp = dsa_to_port(ds, port);
+@@ -632,7 +667,7 @@ static int mxl862xx_set_bridge_port(stru
+       bool enable;
+       int i, idx;
+-      br_port_cfg.bridge_port_id = cpu_to_le16(port);
++      br_port_cfg.bridge_port_id = cpu_to_le16(bp_id);
+       br_port_cfg.bridge_id = cpu_to_le16(bridge_id);
+       br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
+                                      MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
+@@ -715,12 +750,38 @@ static int mxl862xx_set_bridge_port(stru
+                                 br_port_cfg);
+ }
++static int mxl862xx_set_bridge_port(struct dsa_switch *ds, int port)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
++      u16 lag_bp;
++      int ret;
++
++      ret = __mxl862xx_set_bridge_port(ds, port, port);
++      if (ret)
++              return ret;
++
++      /* If this port is a LAG master, also push its config to the
++       * LAG's dedicated bridge port (which is the actual target of
++       * all member CTP redirections).
++       */
++      if (p->lag && mxl862xx_is_lag_master(priv, port)) {
++              lag_bp = priv->lag_bridge_ports[p->lag->id];
++              if (lag_bp)
++                      ret = __mxl862xx_set_bridge_port(ds, port, lag_bp);
++      }
++
++      return ret;
++}
++
+ static int mxl862xx_sync_bridge_members(struct dsa_switch *ds,
+                                       const struct dsa_bridge *bridge)
+ {
+       struct mxl862xx_priv *priv = ds->priv;
+       struct dsa_port *dp, *member_dp;
+-      int port, err, ret = 0;
++      struct mxl862xx_port *p;
++      int port, member, err, ret = 0;
++      u16 lag_bp, bp;
+       dsa_switch_for_each_bridge_member(dp, ds, bridge->dev) {
+               port = dp->index;
+@@ -729,9 +790,21 @@ static int mxl862xx_sync_bridge_members(
+                           MXL862XX_MAX_BRIDGE_PORTS);
+               dsa_switch_for_each_bridge_member(member_dp, ds, bridge->dev) {
+-                      if (member_dp->index != port)
+-                              __set_bit(member_dp->index,
+-                                        priv->ports[port].portmap);
++                      member = member_dp->index;
++
++                      /* For LAG members, only include the LAG's
++                       * dedicated bridge port in the portmap.
++                       * Non-master members are skipped to avoid
++                       * duplicates (they share the same LAG bridge
++                       * port).
++                       */
++                      if (!mxl862xx_is_lag_master(priv, member))
++                              continue;
++                      if (member != port) {
++                              bp = mxl862xx_lag_bridge_port(priv,
++                                                           member);
++                              __set_bit(bp, priv->ports[port].portmap);
++                      }
+               }
+               __set_bit(mxl862xx_cpu_bridge_port_id(ds, port),
+                         priv->ports[port].portmap);
+@@ -741,6 +814,25 @@ static int mxl862xx_sync_bridge_members(
+                       ret = err;
+       }
++      /* Push updated portmaps to LAG bridge ports. Each LAG master's
++       * portmap (which excludes itself) is used for the LAG bridge
++       * port -- this naturally avoids self-forwarding.
++       */
++      dsa_switch_for_each_bridge_member(dp, ds, bridge->dev) {
++              p = &priv->ports[dp->index];
++
++              if (!p->lag || !mxl862xx_is_lag_master(priv, dp->index))
++                      continue;
++
++              lag_bp = priv->lag_bridge_ports[p->lag->id];
++              if (!lag_bp)
++                      continue;
++
++              err = __mxl862xx_set_bridge_port(ds, dp->index, lag_bp);
++              if (err)
++                      ret = err;
++      }
++
+       return ret;
+ }
+@@ -1926,6 +2018,408 @@ static int mxl862xx_setup_cpu_bridge(str
+       return mxl862xx_set_bridge_port(ds, port);
+ }
++/**
++ * mxl862xx_lag_master_port - Find the LAG master (lowest-numbered member)
++ * @ds: DSA switch
++ * @lag: LAG to search
++ *
++ * The master's bridge port hosts the P-mapper and receives all ingress
++ * traffic via CTP redirection from other members.
++ *
++ * Return: port index of the master, or -ENOENT if no members.
++ */
++static int mxl862xx_lag_master_port(struct dsa_switch *ds,
++                                  const struct dsa_lag *lag)
++{
++      struct dsa_port *dp;
++      int master = -ENOENT;
++
++      dsa_lag_foreach_port(dp, ds->dst, lag) {
++              if (dp->ds != ds)
++                      continue;
++              if (master < 0 || dp->index < master)
++                      master = dp->index;
++      }
++
++      return master;
++}
++
++/**
++ * mxl862xx_lag_hash_bits - Translate Linux hash mode to firmware hash bitmask
++ * @info: bonding upper info (tx_type + hash_type)
++ *
++ * Return: 6-bit hash field bitmask (MXL862XX_TRUNK_HASH_*), or negative
++ *         errno if the mode is unsupported.
++ */
++static int mxl862xx_lag_hash_bits(const struct netdev_lag_upper_info *info)
++{
++      if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
++              return 0;
++
++      switch (info->hash_type) {
++      case NETDEV_LAG_HASH_L2:
++              return MXL862XX_TRUNK_HASH_SA | MXL862XX_TRUNK_HASH_DA;
++      case NETDEV_LAG_HASH_L34:
++              return MXL862XX_TRUNK_HASH_SIP | MXL862XX_TRUNK_HASH_DIP |
++                     MXL862XX_TRUNK_HASH_SPORT | MXL862XX_TRUNK_HASH_DPORT;
++      case NETDEV_LAG_HASH_L23:
++      case NETDEV_LAG_HASH_E23:
++              return MXL862XX_TRUNK_HASH_SA | MXL862XX_TRUNK_HASH_DA |
++                     MXL862XX_TRUNK_HASH_SIP | MXL862XX_TRUNK_HASH_DIP;
++      case NETDEV_LAG_HASH_E34:
++              return MXL862XX_TRUNK_HASH_SA | MXL862XX_TRUNK_HASH_DA |
++                     MXL862XX_TRUNK_HASH_SIP | MXL862XX_TRUNK_HASH_DIP |
++                     MXL862XX_TRUNK_HASH_SPORT | MXL862XX_TRUNK_HASH_DPORT;
++      default:
++              return -EOPNOTSUPP;
++      }
++}
++
++/**
++ * mxl862xx_lag_set_hash - Push trunk hash configuration to firmware
++ * @priv: driver private data
++ * @hash_bits: 6-bit hash field bitmask (MXL862XX_TRUNK_HASH_*)
++ *
++ * Only issues a firmware command when @hash_bits differs from the
++ * currently active configuration.
++ */
++static int mxl862xx_lag_set_hash(struct mxl862xx_priv *priv, u8 hash_bits)
++{
++      struct mxl862xx_trunking_cfg cfg = {};
++
++      if (priv->trunk_hash == hash_bits)
++              return 0;
++
++      cfg.mac_src  = !!(hash_bits & MXL862XX_TRUNK_HASH_SA);
++      cfg.mac_dst  = !!(hash_bits & MXL862XX_TRUNK_HASH_DA);
++      cfg.ip_src   = !!(hash_bits & MXL862XX_TRUNK_HASH_SIP);
++      cfg.ip_dst   = !!(hash_bits & MXL862XX_TRUNK_HASH_DIP);
++      cfg.src_port = !!(hash_bits & MXL862XX_TRUNK_HASH_SPORT);
++      cfg.dst_port = !!(hash_bits & MXL862XX_TRUNK_HASH_DPORT);
++
++      priv->trunk_hash = hash_bits;
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_TRUNKING_CFGSET, cfg);
++}
++
++/**
++ * mxl862xx_lag_recompute_hash - Recompute global hash from all active LAGs
++ * @ds: DSA switch
++ *
++ * Scans all ports and ORs together the stored hash requirements of every
++ * active LAG member. Used after a LAG is destroyed to potentially narrow
++ * the global hash configuration.
++ *
++ * Return: union of all active LAGs' hash field bitmasks.
++ */
++static u8 mxl862xx_lag_recompute_hash(struct dsa_switch *ds)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      u8 hash = 0;
++      int port;
++
++      for (port = 0; port < ds->num_ports; port++) {
++              if (priv->ports[port].lag)
++                      hash |= priv->ports[port].lag_hash_bits;
++      }
++
++      return hash;
++}
++
++/**
++ * mxl862xx_lag_build_pmapper - Fill P-mapper with round-robin LAG distribution
++ * @ds: DSA switch
++ * @lag: LAG group
++ * @pm: P-mapper struct to fill (entries 9..72)
++ *
++ * Only ports with lag_tx_enabled are included. Falls back to the
++ * master port if no members are active.
++ */
++static void mxl862xx_lag_build_pmapper(struct dsa_switch *ds,
++                                     const struct dsa_lag *lag,
++                                     struct mxl862xx_pmapper *pm)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      int active_ports[MXL862XX_MAX_PORTS];
++      int n_active = 0, master, port;
++      struct dsa_port *dp;
++      int i;
++
++      dsa_lag_foreach_port(dp, ds->dst, lag) {
++              if (dp->ds != ds)
++                      continue;
++              if (priv->ports[dp->index].lag_tx_enabled)
++                      active_ports[n_active++] = dp->index;
++      }
++
++      /* Fallback: if no members are active, use the master port */
++      if (!n_active) {
++              master = mxl862xx_lag_master_port(ds, lag);
++
++              if (master >= 0) {
++                      active_ports[0] = master;
++                      n_active = 1;
++              }
++      }
++
++      if (!n_active)
++              return;
++
++      for (i = 0; i < MXL862XX_PMAPPER_LAG_COUNT; i++) {
++              port = active_ports[i % n_active];
++
++              pm->dest_sub_if_id_group[MXL862XX_PMAPPER_LAG_FIRST + i] =
++                      (port << 4) & 0xff;
++      }
++}
++
++/**
++ * mxl862xx_lag_redirect_ctp - Redirect a port's CTP to the LAG master
++ * @priv: driver private data
++ * @port: port whose CTP to redirect
++ * @master_port: LAG master port index
++ */
++static int mxl862xx_lag_redirect_ctp(struct mxl862xx_priv *priv,
++                                   int port, int master_port)
++{
++      struct mxl862xx_ctp_port_config ctp = {};
++
++      ctp.logical_port_id = port;
++      ctp.mask = cpu_to_le32(MXL862XX_CTP_PORT_CONFIG_MASK_BRIDGE_PORT_ID);
++      ctp.bridge_port_id = cpu_to_le16(master_port);
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_CTP_PORTCONFIGSET, ctp);
++}
++
++/**
++ * mxl862xx_lag_restore_ctp - Restore a port's CTP to point to itself
++ * @priv: driver private data
++ * @port: port whose CTP to restore
++ */
++static int mxl862xx_lag_restore_ctp(struct mxl862xx_priv *priv, int port)
++{
++      return mxl862xx_lag_redirect_ctp(priv, port, port);
++}
++
++/**
++ * mxl862xx_lag_disable_pmapper - Disable P-mapper on a bridge port
++ * @ds: DSA switch
++ * @bp_id: firmware bridge port ID to reconfigure
++ */
++static int mxl862xx_lag_disable_pmapper(struct dsa_switch *ds, u16 bp_id)
++{
++      struct mxl862xx_bridge_port_config bp_cfg = {};
++      struct mxl862xx_priv *priv = ds->priv;
++
++      bp_cfg.bridge_port_id = cpu_to_le16(bp_id);
++      bp_cfg.mask = cpu_to_le32(
++              MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING);
++      bp_cfg.dest_logical_port_id = bp_id;
++      bp_cfg.pmapper_enable = 0;
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, bp_cfg);
++}
++
++/**
++ * mxl862xx_lag_sync - Synchronize LAG hardware state for a LAG group
++ * @ds: DSA switch
++ * @lag: LAG group to synchronize
++ *
++ * Finds the master (lowest-numbered member), redirects all member CTPs
++ * to the LAG's dedicated firmware bridge port, configures the P-mapper
++ * for hash distribution, and pushes the master's full bridge port
++ * configuration (EVLAN, VF, portmap, learning) to the LAG bridge port.
++ */
++static int mxl862xx_lag_sync(struct dsa_switch *ds, const struct dsa_lag *lag)
++{
++      struct mxl862xx_bridge_port_config bp_cfg = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_pmapper pm = {};
++      struct dsa_port *dp;
++      int master, ret;
++      u16 lag_bp;
++
++      lag_bp = priv->lag_bridge_ports[lag->id];
++      if (!lag_bp)
++              return -ENOENT;
++
++      master = mxl862xx_lag_master_port(ds, lag);
++      if (master < 0)
++              return master;
++
++      /* Redirect all member CTPs to the LAG bridge port */
++      dsa_lag_foreach_port(dp, ds->dst, lag) {
++              if (dp->ds != ds)
++                      continue;
++              ret = mxl862xx_lag_redirect_ctp(priv, dp->index, lag_bp);
++              if (ret)
++                      return ret;
++      }
++
++      /* Push the master's full config to the LAG bridge port so it
++       * inherits the current bridge_id, EVLAN/VF blocks, portmap,
++       * learning and flood settings.
++       */
++      ret = __mxl862xx_set_bridge_port(ds, master, lag_bp);
++      if (ret)
++              return ret;
++
++      /* Build P-mapper with active members */
++      mxl862xx_lag_build_pmapper(ds, lag, &pm);
++
++      /* Enable P-mapper in LAG mode on the LAG bridge port */
++      bp_cfg.bridge_port_id = cpu_to_le16(lag_bp);
++      bp_cfg.mask = cpu_to_le32(
++              MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING);
++      bp_cfg.dest_logical_port_id = master;
++      bp_cfg.pmapper_enable = 1;
++      bp_cfg.pmapper_mapping_mode =
++              cpu_to_le32(MXL862XX_PMAPPER_MAPPING_LAG);
++      bp_cfg.pmapper = pm;
++
++      return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, bp_cfg);
++}
++
++static int mxl862xx_port_lag_join(struct dsa_switch *ds, int port,
++                                const struct dsa_lag lag,
++                                struct netdev_lag_upper_info *info,
++                                struct netlink_ext_ack *extack)
++{
++      struct mxl862xx_bridge_port_alloc bp_alloc = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      struct dsa_port *dp = dsa_to_port(ds, port);
++      int hash_bits;
++      u8 new_hash;
++      int ret;
++
++      if (dsa_is_cpu_port(ds, port)) {
++              NL_SET_ERR_MSG_MOD(extack, "CPU port LAG not supported");
++              return -EOPNOTSUPP;
++      }
++
++      if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH &&
++          info->tx_type != NETDEV_LAG_TX_TYPE_ACTIVEBACKUP) {
++              NL_SET_ERR_MSG_MOD(extack, "Only hash and active-backup LAG modes supported");
++              return -EOPNOTSUPP;
++      }
++
++      hash_bits = mxl862xx_lag_hash_bits(info);
++      if (hash_bits < 0) {
++              NL_SET_ERR_MSG_MOD(extack, "Unsupported LAG hash mode");
++              return hash_bits;
++      }
++
++      /* Allocate a dedicated firmware bridge port for this LAG on
++       * first member join. This bridge port is stable for the
++       * LAG's lifetime -- all CTP redirections, FDB and MDB entries
++       * target it, so no migration is needed on membership changes.
++       */
++      if (!priv->lag_bridge_ports[lag.id]) {
++              ret = MXL862XX_API_READ(priv, MXL862XX_BRIDGEPORT_ALLOC,
++                                      bp_alloc);
++              if (ret) {
++                      NL_SET_ERR_MSG_MOD(extack,
++                                         "Failed to allocate LAG bridge port");
++                      return ret;
++              }
++              priv->lag_bridge_ports[lag.id] =
++                      le16_to_cpu(bp_alloc.bridge_port_id);
++      }
++
++      priv->ports[port].lag = dp->lag;
++      priv->ports[port].lag_tx_enabled = dp->lag_tx_enabled;
++      priv->ports[port].lag_hash_bits = hash_bits;
++
++      /* Widen global hash to include this LAG's requirements */
++      new_hash = priv->trunk_hash | hash_bits;
++      ret = mxl862xx_lag_set_hash(priv, new_hash);
++      if (ret)
++              goto err_undo;
++
++      ret = mxl862xx_lag_sync(ds, dp->lag);
++      if (ret)
++              goto err_undo;
++
++      return 0;
++
++err_undo:
++      priv->ports[port].lag = NULL;
++      priv->ports[port].lag_tx_enabled = false;
++      priv->ports[port].lag_hash_bits = 0;
++      return ret;
++}
++
++static int mxl862xx_port_lag_leave(struct dsa_switch *ds, int port,
++                                 const struct dsa_lag lag)
++{
++      struct mxl862xx_bridge_port_alloc bp_alloc = {};
++      struct mxl862xx_priv *priv = ds->priv;
++      u8 new_hash;
++      int ret;
++
++      /* Restore this port's CTP to point to itself */
++      ret = mxl862xx_lag_restore_ctp(priv, port);
++      if (ret)
++              dev_err(ds->dev, "failed to restore CTP for port %d: %pe\n",
++                      port, ERR_PTR(ret));
++
++      priv->ports[port].lag = NULL;
++      priv->ports[port].lag_tx_enabled = false;
++      priv->ports[port].lag_hash_bits = 0;
++
++      /* If other members remain, re-sync the LAG */
++      if (mxl862xx_lag_master_port(ds, &lag) >= 0) {
++              ret = mxl862xx_lag_sync(ds, &lag);
++              if (ret)
++                      dev_err(ds->dev,
++                              "failed to re-sync LAG after port %d left: %pe\n",
++                              port, ERR_PTR(ret));
++      } else if (priv->lag_bridge_ports[lag.id]) {
++              /* Last member left -- disable P-mapper and free the
++               * LAG's dedicated bridge port.
++               */
++              ret = mxl862xx_lag_disable_pmapper(ds,
++                                                 priv->lag_bridge_ports[lag.id]);
++              if (ret)
++                      dev_err(ds->dev,
++                              "failed to disable P-mapper on LAG bridge port %u: %pe\n",
++                              priv->lag_bridge_ports[lag.id], ERR_PTR(ret));
++
++              bp_alloc.bridge_port_id =
++                      cpu_to_le16(priv->lag_bridge_ports[lag.id]);
++              ret = MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_FREE,
++                                       bp_alloc);
++              if (ret)
++                      dev_err(ds->dev,
++                              "failed to free LAG bridge port %u: %pe\n",
++                              priv->lag_bridge_ports[lag.id], ERR_PTR(ret));
++
++              priv->lag_bridge_ports[lag.id] = 0;
++      }
++
++      /* Recompute global hash from remaining LAGs */
++      new_hash = mxl862xx_lag_recompute_hash(ds);
++      ret = mxl862xx_lag_set_hash(priv, new_hash);
++      if (ret)
++              dev_err(ds->dev, "failed to update trunk hash: %pe\n",
++                      ERR_PTR(ret));
++
++      return 0;
++}
++
++static int mxl862xx_port_lag_change(struct dsa_switch *ds, int port)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct dsa_port *dp = dsa_to_port(ds, port);
++
++      if (!priv->ports[port].lag)
++              return 0;
++
++      priv->ports[port].lag_tx_enabled = dp->lag_tx_enabled;
++
++      return mxl862xx_lag_sync(ds, priv->ports[port].lag);
++}
++
+ static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
+                                    const struct dsa_bridge bridge,
+                                    bool *tx_fwd_offload,
+@@ -1952,7 +2446,18 @@ static int mxl862xx_port_bridge_join(str
+               return 0;
+       }
+-      return mxl862xx_sync_bridge_members(ds, &bridge);
++      ret = mxl862xx_sync_bridge_members(ds, &bridge);
++      if (ret)
++              return ret;
++
++      /* If this port is in a LAG, re-sync the LAG bridge port so it
++       * picks up the new bridge_id (switching from standalone FID to
++       * the shared bridge FID).
++       */
++      if (priv->ports[port].lag)
++              ret = mxl862xx_lag_sync(ds, priv->ports[port].lag);
++
++      return ret;
+ }
+ static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
+@@ -2011,6 +2516,17 @@ static void mxl862xx_port_bridge_leave(s
+                       "failed to update CPU VBP for port %d: %pe\n", port,
+                       ERR_PTR(err));
++      /* If this port is in a LAG, re-sync the LAG bridge port so it
++       * reverts to the standalone FID.
++       */
++      if (p->lag) {
++              err = mxl862xx_lag_sync(ds, p->lag);
++              if (err)
++                      dev_err(ds->dev,
++                              "failed to re-sync LAG after port %d left bridge: %pe\n",
++                              port, ERR_PTR(err));
++      }
++
+       if (!dsa_bridge_ports(ds, bridge.dev))
+               mxl862xx_free_bridge(ds, &bridge);
+ }
+@@ -2636,18 +3152,17 @@ static int mxl862xx_get_fid(struct dsa_s
+ }
+ /**
+- * mxl862xx_fdb_bridge_port - Translate port for MAC table in tag_8021q mode
++ * mxl862xx_fdb_bridge_port - Translate port to effective bridge port ID
+  * @ds: DSA switch
+  * @port: port number passed by DSA (usually the CPU port for host entries)
+  * @db: database context identifying the user port or bridge
+  *
+- * In tag_8021q mode, host FDB/MDB entries for standalone ports must use
+- * the virtual bridge port (bridge_port_cpu) as the MAC table destination
+- * so that known-unicast and known-multicast frames exit through the
+- * virtual bridge port's egress EVLAN, which inserts the management VID.
+- * Without this, the firmware forwards known traffic directly to the
+- * physical CPU bridge port, bypassing management VID insertion, and DSA
+- * drops the untagged frame.
++ * Returns the firmware bridge port ID that should be used for MAC table
++ * entries targeting @port:
++ *  - CPU port in tag_8021q standalone mode: the virtual bridge port
++ *    (bridge_port_cpu) so known traffic exits through egress EVLAN
++ *  - User port in a LAG: the LAG's dedicated firmware bridge port
++ *  - Otherwise: the port index itself
+  */
+ static int mxl862xx_fdb_bridge_port(struct dsa_switch *ds, int port,
+                                   const struct dsa_db db)
+@@ -2663,7 +3178,7 @@ static int mxl862xx_fdb_bridge_port(stru
+                       return bp_cpu;
+       }
+-      return port;
++      return mxl862xx_lag_bridge_port(priv, port);
+ }
+ /**
+@@ -2907,11 +3422,43 @@ static int mxl862xx_port_fdb_del(struct
+       return ret;
+ }
++static int mxl862xx_lag_fdb_add(struct dsa_switch *ds, const struct dsa_lag lag,
++                              const unsigned char *addr, u16 vid,
++                              const struct dsa_db db)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      u16 lag_bp = priv->lag_bridge_ports[lag.id];
++      int fid;
++
++      if (!lag_bp)
++              return -ENOENT;
++
++      fid = mxl862xx_get_fid(ds, db);
++      if (fid < 0)
++              return fid;
++
++      return mxl862xx_fdb_add_per_fid(ds, addr, vid, fid, lag_bp);
++}
++
++static int mxl862xx_lag_fdb_del(struct dsa_switch *ds, const struct dsa_lag lag,
++                              const unsigned char *addr, u16 vid,
++                              const struct dsa_db db)
++{
++      int fid;
++
++      fid = mxl862xx_get_fid(ds, db);
++      if (fid < 0)
++              return fid;
++
++      return mxl862xx_fdb_del_per_fid(ds, addr, vid, fid);
++}
++
+ static int mxl862xx_port_fdb_dump(struct dsa_switch *ds, int port,
+                                 dsa_fdb_dump_cb_t *cb, void *data)
+ {
+       struct mxl862xx_mac_table_read param = { .initial = 1 };
+       struct mxl862xx_priv *priv = ds->priv;
++      u16 lag_bp = mxl862xx_lag_bridge_port(priv, port);
+       u32 entry_port_id;
+       int ret;
+@@ -2925,7 +3472,7 @@ static int mxl862xx_port_fdb_dump(struct
+               entry_port_id = le32_to_cpu(param.port_id);
+-              if (entry_port_id == port) {
++              if (entry_port_id == port || entry_port_id == lag_bp) {
+                       ret = cb(param.mac, FIELD_GET(MXL862XX_TCI_VLAN_ID,
+                                                     le16_to_cpu(param.tci)),
+                                param.static_entry, data);
+@@ -3562,6 +4109,11 @@ static const struct dsa_switch_ops mxl86
+       .port_fdb_dump = mxl862xx_port_fdb_dump,
+       .port_mdb_add = mxl862xx_port_mdb_add,
+       .port_mdb_del = mxl862xx_port_mdb_del,
++      .port_lag_join = mxl862xx_port_lag_join,
++      .port_lag_leave = mxl862xx_port_lag_leave,
++      .port_lag_change = mxl862xx_port_lag_change,
++      .lag_fdb_add = mxl862xx_lag_fdb_add,
++      .lag_fdb_del = mxl862xx_lag_fdb_del,
+       .port_vlan_filtering = mxl862xx_port_vlan_filtering,
+       .port_vlan_add = mxl862xx_port_vlan_add,
+       .port_vlan_del = mxl862xx_port_vlan_del,
+@@ -3602,6 +4154,7 @@ static int mxl862xx_probe(struct mdio_de
+       ds->num_ports = MXL862XX_MAX_PORTS;
+       ds->fdb_isolation = true;
+       ds->max_num_bridges = MXL862XX_MAX_BRIDGES;
++      ds->num_lag_ids = MXL862XX_MAX_LAG_IDS;
+       mxl862xx_host_init(priv);
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -14,6 +14,19 @@
+ #define MXL862XX_MAX_BRIDGE_PORTS     128
+ #define MXL862XX_TOTAL_EVLAN_ENTRIES  1024
+ #define MXL862XX_TOTAL_VF_ENTRIES     1024
++#define MXL862XX_MAX_LAG_IDS          16
++
++/* Trunk hash field bitmask (matches PCE_TRUNK_CONF layout) */
++#define MXL862XX_TRUNK_HASH_SA                BIT(0)
++#define MXL862XX_TRUNK_HASH_DA                BIT(1)
++#define MXL862XX_TRUNK_HASH_SIP       BIT(2)
++#define MXL862XX_TRUNK_HASH_DIP       BIT(3)
++#define MXL862XX_TRUNK_HASH_SPORT     BIT(4)
++#define MXL862XX_TRUNK_HASH_DPORT     BIT(5)
++
++/* P-mapper LAG entries occupy indices 9..72 (64 entries) */
++#define MXL862XX_PMAPPER_LAG_FIRST    9
++#define MXL862XX_PMAPPER_LAG_COUNT    64
+ /* Number of __le16 words in a firmware portmap (128-bit bitmap). */
+ #define MXL862XX_FW_PORTMAP_WORDS     (MXL862XX_MAX_BRIDGE_PORTS / 16)
+@@ -228,6 +241,12 @@ struct mxl862xx_port_stats {
+  * @stats_lock:          protects accumulator reads in .get_stats64 against
+  *                       concurrent updates from the polling work
+  * @tag_8021q_vid:       currently assigned tag_8021q management VID
++ * @lag:                 non-NULL when port is member of a LAG group;
++ *                       points to the DSA LAG structure
++ * @lag_tx_enabled:      true when this port is active for TX in its LAG
++ * @lag_hash_bits:       hash field bitmask (MXL862XX_TRUNK_HASH_*) requested
++ *                       when this port joined its LAG; used to recompute the
++ *                       global trunk_hash when a LAG is destroyed
+  */
+ struct mxl862xx_port {
+       struct mxl862xx_priv *priv;
+@@ -250,6 +269,10 @@ struct mxl862xx_port {
+       struct work_struct host_flood_work;
+       u16 tag_8021q_vid;
+       struct mxl862xx_evlan_block cpu_egress_evlan;
++      /* LAG state */
++      struct dsa_lag *lag;
++      bool lag_tx_enabled;
++      u8 lag_hash_bits;
+       /* Hardware stats accumulation */
+       struct mxl862xx_port_stats stats;
+       spinlock_t stats_lock;
+@@ -328,6 +351,15 @@ union mxl862xx_fw_version {
+  *                      DSA bridge number. Indexed by dsa_bridge.num
+  *                      (0 .. ds->max_num_bridges).
+  * @vf_block_size:      per-port VLAN Filter block size
++ * @lag_bridge_ports:   maps DSA LAG ID to firmware bridge port ID;
++ *                      zero means no bridge port allocated for that LAG.
++ *                      Indexed by lag->id (entry 0 is unused).
++ *                      The bridge port is stable for the LAG's lifetime
++ *                      so FDB/MDB entries never need migration on
++ *                      membership changes.
++ * @trunk_hash:         current global hash field bitmask (6 bits,
++ *                      MXL862XX_TRUNK_HASH_*); union of all active LAGs'
++ *                      hash requirements
+  * @stats_work:         periodic work item that polls RMON hardware counters
+  *                      and accumulates them into 64-bit per-port stats
+  */
+@@ -346,6 +378,8 @@ struct mxl862xx_priv {
+       u16 evlan_egress_size;
+       u16 cpu_evlan_ingress_size;
+       u16 vf_block_size;
++      u16 lag_bridge_ports[MXL862XX_MAX_LAG_IDS + 1];
++      u8 trunk_hash;
+       struct delayed_work stats_work;
+ };
diff --git a/target/linux/generic/pending-6.12/760-18-net-dsa-mxl862xx-add-support-for-mirror-port.patch b/target/linux/generic/pending-6.12/760-18-net-dsa-mxl862xx-add-support-for-mirror-port.patch
new file mode 100644 (file)
index 0000000..faf69c0
--- /dev/null
@@ -0,0 +1,229 @@
+From fbfa1b0649c578e0d43e3a61617b53a9a722efad Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 12:05:29 +0000
+Subject: [PATCH 27/35] net: dsa: mxl862xx: add support for mirror port
+
+The MxL862xx hardware supports a single monitor port which can be
+configured to mirror any other port's ingress and/or egress traffic.
+
+Implement support for .port_mirror_add/.port_mirror_del using this
+feature.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h |  12 +++
+ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h |   1 +
+ drivers/net/dsa/mxl862xx/mxl862xx.c     | 118 ++++++++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx.h     |   8 ++
+ 4 files changed, 139 insertions(+)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+@@ -729,6 +729,18 @@ struct mxl862xx_bridge_port_config {
+ } __packed;
+ /**
++ * struct mxl862xx_monitor_port_cfg - Monitor port configuration
++ * @port_id: Destination port for mirrored traffic (zero-based)
++ * @sub_if_id: Monitoring sub-interface ID
++ * @monitor_port: Reserved
++ */
++struct mxl862xx_monitor_port_cfg {
++      u8 port_id;
++      __le16 sub_if_id;
++      u8 monitor_port;
++} __packed;
++
++/**
+  * struct mxl862xx_cfg -  Global Switch configuration Attributes
+  * @mac_table_age_timer: See &enum mxl862xx_age_timer
+  * @age_timer: Custom MAC table aging timer in seconds
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+@@ -30,6 +30,7 @@
+ #define MXL862XX_COMMON_PORTCFGGET    (MXL862XX_COMMON_MAGIC + 0x7)
+ #define MXL862XX_COMMON_CFGGET                (MXL862XX_COMMON_MAGIC + 0x9)
+ #define MXL862XX_COMMON_CFGSET                (MXL862XX_COMMON_MAGIC + 0xa)
++#define MXL862XX_COMMON_MONITORPORTCFGSET (MXL862XX_COMMON_MAGIC + 0xe)
+ #define MXL862XX_COMMON_REGISTERMOD   (MXL862XX_COMMON_MAGIC + 0x11)
+ #define MXL862XX_TFLOW_PCERULEWRITE   (MXL862XX_TFLOW_MAGIC + 0x2)
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -1129,6 +1129,8 @@ static int mxl862xx_setup(struct dsa_swi
+                                     (n_user_ports + n_cpu_ports);
+       }
++      priv->mirror_dest = -1;
++
+       ret = mxl862xx_setup_drop_meter(ds);
+       if (ret)
+               return ret;
+@@ -2018,6 +2020,120 @@ static int mxl862xx_setup_cpu_bridge(str
+       return mxl862xx_set_bridge_port(ds, port);
+ }
++static int mxl862xx_port_mirror_add(struct dsa_switch *ds, int port,
++                                  struct dsa_mall_mirror_tc_entry *mirror,
++                                  bool ingress,
++                                  struct netlink_ext_ack *extack)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
++      struct mxl862xx_monitor_port_cfg mon = {
++              .port_id = mirror->to_local_port,
++      };
++      struct mxl862xx_ctp_port_config ctp = {
++              .logical_port_id = port,
++              .mask = cpu_to_le32(
++                      MXL862XX_CTP_PORT_CONFIG_MASK_LOOPBACK_AND_MIRROR),
++              .ingress_mirror_enable = p->ingress_mirror,
++              .egress_mirror_enable = p->egress_mirror,
++      };
++      int ret;
++
++      /* The hardware has a single global monitor port. Reject if an
++       * existing mirror session targets a different destination.
++       */
++      if (priv->mirror_dest >= 0 &&
++          priv->mirror_dest != mirror->to_local_port) {
++              NL_SET_ERR_MSG_MOD(extack,
++                                 "Only one mirror destination port is supported");
++              return -EBUSY;
++      }
++
++      if (ingress)
++              ctp.ingress_mirror_enable = 1;
++      else
++              ctp.egress_mirror_enable = 1;
++
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_CTP_PORTCONFIGSET, ctp);
++      if (ret) {
++              dev_err(ds->dev, "mirror: CTP write failed for port %d: %pe\n",
++                      port, ERR_PTR(ret));
++              return ret;
++      }
++
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_COMMON_MONITORPORTCFGSET, mon);
++      if (ret) {
++              dev_err(ds->dev,
++                      "mirror: failed to set monitor port %d: %pe\n",
++                      mirror->to_local_port, ERR_PTR(ret));
++              /* Roll back CTP change */
++              ctp.ingress_mirror_enable = p->ingress_mirror;
++              ctp.egress_mirror_enable = p->egress_mirror;
++              MXL862XX_API_WRITE(priv, MXL862XX_CTP_PORTCONFIGSET, ctp);
++              return ret;
++      }
++
++      if (ingress)
++              p->ingress_mirror = true;
++      else
++              p->egress_mirror = true;
++
++      priv->mirror_dest = mirror->to_local_port;
++
++      return 0;
++}
++
++static void mxl862xx_port_mirror_del(struct dsa_switch *ds, int port,
++                                   struct dsa_mall_mirror_tc_entry *mirror)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_port *p = &priv->ports[port];
++      struct mxl862xx_ctp_port_config ctp = {
++              .logical_port_id = port,
++              .mask = cpu_to_le32(
++                      MXL862XX_CTP_PORT_CONFIG_MASK_LOOPBACK_AND_MIRROR),
++              .ingress_mirror_enable = p->ingress_mirror,
++              .egress_mirror_enable = p->egress_mirror,
++      };
++      struct mxl862xx_monitor_port_cfg mon = {};
++      bool active = false;
++      int i, ret;
++
++      if (mirror->ingress)
++              ctp.ingress_mirror_enable = 0;
++      else
++              ctp.egress_mirror_enable = 0;
++
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_CTP_PORTCONFIGSET, ctp);
++      if (ret)
++              dev_err(ds->dev, "mirror: CTP write failed for port %d: %pe\n",
++                      port, ERR_PTR(ret));
++
++      if (mirror->ingress)
++              p->ingress_mirror = false;
++      else
++              p->egress_mirror = false;
++
++      /* If no ports have any mirrors active, clear the monitor port */
++      for (i = 0; i < ds->num_ports; i++) {
++              if (priv->ports[i].ingress_mirror ||
++                  priv->ports[i].egress_mirror) {
++                      active = true;
++                      break;
++              }
++      }
++
++      if (active)
++              return;
++
++      ret = MXL862XX_API_WRITE(priv, MXL862XX_COMMON_MONITORPORTCFGSET, mon);
++      if (ret)
++              dev_err(ds->dev, "mirror: failed to clear monitor port: %pe\n",
++                      ERR_PTR(ret));
++
++      priv->mirror_dest = -1;
++}
++
+ /**
+  * mxl862xx_lag_master_port - Find the LAG master (lowest-numbered member)
+  * @ds: DSA switch
+@@ -4109,6 +4225,8 @@ static const struct dsa_switch_ops mxl86
+       .port_fdb_dump = mxl862xx_port_fdb_dump,
+       .port_mdb_add = mxl862xx_port_mdb_add,
+       .port_mdb_del = mxl862xx_port_mdb_del,
++      .port_mirror_add = mxl862xx_port_mirror_add,
++      .port_mirror_del = mxl862xx_port_mirror_del,
+       .port_lag_join = mxl862xx_port_lag_join,
+       .port_lag_leave = mxl862xx_port_lag_leave,
+       .port_lag_change = mxl862xx_port_lag_change,
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -241,6 +241,8 @@ struct mxl862xx_port_stats {
+  * @stats_lock:          protects accumulator reads in .get_stats64 against
+  *                       concurrent updates from the polling work
+  * @tag_8021q_vid:       currently assigned tag_8021q management VID
++ * @ingress_mirror:      true when ingress mirroring is active on this port
++ * @egress_mirror:       true when egress mirroring is active on this port
+  * @lag:                 non-NULL when port is member of a LAG group;
+  *                       points to the DSA LAG structure
+  * @lag_tx_enabled:      true when this port is active for TX in its LAG
+@@ -269,6 +271,9 @@ struct mxl862xx_port {
+       struct work_struct host_flood_work;
+       u16 tag_8021q_vid;
+       struct mxl862xx_evlan_block cpu_egress_evlan;
++      /* Mirror state */
++      bool ingress_mirror;
++      bool egress_mirror;
+       /* LAG state */
+       struct dsa_lag *lag;
+       bool lag_tx_enabled;
+@@ -360,6 +365,8 @@ union mxl862xx_fw_version {
+  * @trunk_hash:         current global hash field bitmask (6 bits,
+  *                      MXL862XX_TRUNK_HASH_*); union of all active LAGs'
+  *                      hash requirements
++ * @mirror_dest:        current mirror destination port, or -1 if no mirror
++ *                      session is active; used to detect monitor port conflicts
+  * @stats_work:         periodic work item that polls RMON hardware counters
+  *                      and accumulates them into 64-bit per-port stats
+  */
+@@ -380,6 +387,7 @@ struct mxl862xx_priv {
+       u16 vf_block_size;
+       u16 lag_bridge_ports[MXL862XX_MAX_LAG_IDS + 1];
+       u8 trunk_hash;
++      int mirror_dest;
+       struct delayed_work stats_work;
+ };
diff --git a/target/linux/generic/pending-6.12/760-19-net-dsa-wire-flash_update-devlink-callback-to-driver.patch b/target/linux/generic/pending-6.12/760-19-net-dsa-wire-flash_update-devlink-callback-to-driver.patch
new file mode 100644 (file)
index 0000000..19da9a0
--- /dev/null
@@ -0,0 +1,57 @@
+From 67f82834819b71417b58dc1293c20f71b990264f Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 16:30:08 +0000
+Subject: [PATCH 28/35] net: dsa: wire flash_update devlink callback to drivers
+
+Add a devlink_flash_update callback to dsa_switch_ops so that DSA
+drivers can support devlink dev flash without open-coding the devlink
+plumbing. The new trampoline in net/dsa/devlink.c follows the existing
+dsa_devlink_info_get pattern exactly.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ include/net/dsa.h |  3 +++
+ net/dsa/devlink.c | 13 +++++++++++++
+ 2 files changed, 16 insertions(+)
+
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -1185,6 +1185,9 @@ struct dsa_switch_ops {
+       int     (*devlink_info_get)(struct dsa_switch *ds,
+                                   struct devlink_info_req *req,
+                                   struct netlink_ext_ack *extack);
++      int     (*devlink_flash_update)(struct dsa_switch *ds,
++                                     struct devlink_flash_update_params *params,
++                                     struct netlink_ext_ack *extack);
+       int     (*devlink_sb_pool_get)(struct dsa_switch *ds,
+                                      unsigned int sb_index, u16 pool_index,
+                                      struct devlink_sb_pool_info *pool_info);
+--- a/net/dsa/devlink.c
++++ b/net/dsa/devlink.c
+@@ -20,6 +20,18 @@ static int dsa_devlink_info_get(struct d
+       return -EOPNOTSUPP;
+ }
++static int dsa_devlink_flash_update(struct devlink *dl,
++                                  struct devlink_flash_update_params *params,
++                                  struct netlink_ext_ack *extack)
++{
++      struct dsa_switch *ds = dsa_devlink_to_ds(dl);
++
++      if (!ds->ops->devlink_flash_update)
++              return -EOPNOTSUPP;
++
++      return ds->ops->devlink_flash_update(ds, params, extack);
++}
++
+ static int dsa_devlink_sb_pool_get(struct devlink *dl,
+                                  unsigned int sb_index, u16 pool_index,
+                                  struct devlink_sb_pool_info *pool_info)
+@@ -169,6 +181,7 @@ dsa_devlink_sb_occ_tc_port_bind_get(stru
+ static const struct devlink_ops dsa_devlink_ops = {
+       .info_get                       = dsa_devlink_info_get,
++      .flash_update                   = dsa_devlink_flash_update,
+       .sb_pool_get                    = dsa_devlink_sb_pool_get,
+       .sb_pool_set                    = dsa_devlink_sb_pool_set,
+       .sb_port_pool_get               = dsa_devlink_sb_port_pool_get,
diff --git a/target/linux/generic/pending-6.12/760-20-net-dsa-mxl862xx-add-SMDIO-clause-22-register-access.patch b/target/linux/generic/pending-6.12/760-20-net-dsa-mxl862xx-add-SMDIO-clause-22-register-access.patch
new file mode 100644 (file)
index 0000000..17668b9
--- /dev/null
@@ -0,0 +1,72 @@
+From 1a87b829ef3280d646dc480f7b261d9e32896899 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 16:30:17 +0000
+Subject: [PATCH 29/35] net: dsa: mxl862xx: add SMDIO clause-22 register access
+
+Add mxl862xx_smdio_read() and mxl862xx_smdio_write() for clause-22
+SMDIO register access. MCUboot rescue mode only exposes clause-22
+registers; the existing clause-45 MMD interface is unavailable during
+firmware transfer. The MDIO bus lock is held per-transaction (not
+across polls) so that SB PDI polling during flash erase does not
+starve other MDIO users.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-host.c | 35 ++++++++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-host.h |  2 ++
+ 2 files changed, 37 insertions(+)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+@@ -493,6 +493,41 @@ out:
+       return ret;
+ }
++#define MXL862XX_SMDIO_ADDR_REG               0x1f
++#define MXL862XX_SMDIO_PAGE_MASK      0xfff0
++#define MXL862XX_SMDIO_OFF_MASK               0x000f
++
++int mxl862xx_smdio_read(struct mxl862xx_priv *priv, u32 addr)
++{
++      struct mii_bus *bus = priv->mdiodev->bus;
++      int phy = priv->mdiodev->addr;
++      int ret;
++
++      mutex_lock(&bus->mdio_lock);
++      ret = __mdiobus_write(bus, phy, MXL862XX_SMDIO_ADDR_REG,
++                            addr & MXL862XX_SMDIO_PAGE_MASK);
++      if (ret >= 0)
++              ret = __mdiobus_read(bus, phy, addr & MXL862XX_SMDIO_OFF_MASK);
++      mutex_unlock(&bus->mdio_lock);
++      return ret;
++}
++
++int mxl862xx_smdio_write(struct mxl862xx_priv *priv, u32 addr, u16 val)
++{
++      struct mii_bus *bus = priv->mdiodev->bus;
++      int phy = priv->mdiodev->addr;
++      int ret;
++
++      mutex_lock(&bus->mdio_lock);
++      ret = __mdiobus_write(bus, phy, MXL862XX_SMDIO_ADDR_REG,
++                            addr & MXL862XX_SMDIO_PAGE_MASK);
++      if (ret >= 0)
++              ret = __mdiobus_write(bus, phy, addr & MXL862XX_SMDIO_OFF_MASK,
++                                    val);
++      mutex_unlock(&bus->mdio_lock);
++      return ret;
++}
++
+ void mxl862xx_host_init(struct mxl862xx_priv *priv)
+ {
+       INIT_WORK(&priv->crc_err_work, mxl862xx_crc_err_work_fn);
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h
+@@ -18,5 +18,7 @@ int mxl862xx_api_wrap(struct mxl862xx_pr
+       mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
+ int mxl862xx_reset(struct mxl862xx_priv *priv);
++int mxl862xx_smdio_read(struct mxl862xx_priv *priv, u32 addr);
++int mxl862xx_smdio_write(struct mxl862xx_priv *priv, u32 addr, u16 val);
+ #endif /* __MXL862XX_HOST_H */
diff --git a/target/linux/generic/pending-6.12/760-21-net-dsa-mxl862xx-add-devlink-flash_update-and-info_g.patch b/target/linux/generic/pending-6.12/760-21-net-dsa-mxl862xx-add-devlink-flash_update-and-info_g.patch
new file mode 100644 (file)
index 0000000..28b91f8
--- /dev/null
@@ -0,0 +1,569 @@
+From b7e8f8fd4493b255f0f01fe790a73ad61b5e8ce8 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 16:30:31 +0000
+Subject: [PATCH 30/35] net: dsa: mxl862xx: add devlink flash_update and
+ info_get
+
+Implement runtime firmware upgrade via "devlink dev flash" and version
+reporting via "devlink dev info":
+
+  devlink dev info mdio_bus/<bus>/<addr>
+  devlink dev flash mdio_bus/<bus>/<addr> file <firmware.bin>
+
+The driver sends SYS_MISC_FW_UPDATE to enter MCUboot rescue mode,
+transfers the signed image over the SB PDI bulk-transfer protocol
+(clause-22 SMDIO), waits for the switch to reboot, then schedules
+device_reprobe() for a clean remove()+probe() cycle.
+
+Before the transfer begins the driver closes all conduit interfaces
+and marks every netdev (user and conduit) not-present via
+netif_device_detach() so that userspace cannot bring ports back up
+during the ~15 minute flash process. Progress is reported through
+devlink status notifications. Once the FW_UPDATE command has been
+sent the switch is in MCUboot mode and normal operation can only be
+restored by a reprobe, so the driver always schedules one regardless
+of transfer outcome.
+
+The reprobe work item is dynamically allocated (following the iwlwifi
+pattern) because device_reprobe() triggers remove() which frees the
+devm-managed priv while the work is still executing.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/Makefile        |   2 +-
+ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h  |   1 +
+ drivers/net/dsa/mxl862xx/mxl862xx-fw.c   | 434 +++++++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-fw.h   |  15 +
+ drivers/net/dsa/mxl862xx/mxl862xx-host.c |   7 +
+ drivers/net/dsa/mxl862xx/mxl862xx.c      |   4 +
+ drivers/net/dsa/mxl862xx/mxl862xx.h      |   2 +
+ 7 files changed, 464 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.c
+ create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.h
+
+--- a/drivers/net/dsa/mxl862xx/Makefile
++++ b/drivers/net/dsa/mxl862xx/Makefile
+@@ -1,3 +1,3 @@
+ # SPDX-License-Identifier: GPL-2.0
+ obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o
+-mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o
++mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o mxl862xx-fw.o
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+@@ -83,6 +83,7 @@
+ #define INT_GPHY_READ                 (GPY_GPY2XX_MAGIC + 0x1)
+ #define INT_GPHY_WRITE                        (GPY_GPY2XX_MAGIC + 0x2)
++#define SYS_MISC_FW_UPDATE            (SYS_MISC_MAGIC + 0x1)
+ #define SYS_MISC_FW_VERSION           (SYS_MISC_MAGIC + 0x2)
+ #define MXL862XX_XPCS_MAGIC           0x1a00
+--- /dev/null
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
+@@ -0,0 +1,434 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Firmware flash and devlink support for MaxLinear MxL862xx
++ *
++ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
++ *
++ * Usage:
++ *   # Query running firmware version:
++ *   devlink dev info mdio_bus/<bus>/<addr>
++ *
++ *   # Flash new firmware (all ports are taken down automatically):
++ *   devlink dev flash mdio_bus/<bus>/<addr> file <firmware.bin>
++ *
++ * The flash process takes approximately 15 minutes. Progress is
++ * reported via devlink status notifications. After a successful (or
++ * failed) flash the driver reprobes the device automatically.
++ */
++
++#include <linux/crc32.h>
++#include <linux/delay.h>
++#include <linux/device.h>
++#include <linux/module.h>
++#include <linux/netdevice.h>
++#include <linux/rtnetlink.h>
++#include <net/dsa.h>
++
++#include "mxl862xx.h"
++#include "mxl862xx-api.h"
++#include "mxl862xx-cmd.h"
++#include "mxl862xx-fw.h"
++#include "mxl862xx-host.h"
++
++/* SB PDI registers (clause-22 SMDIO address space) */
++#define MXL862XX_SB_PDI_CTRL          0xe100
++#define MXL862XX_SB_PDI_ADDR          0xe101
++#define MXL862XX_SB_PDI_DATA          0xe102
++#define MXL862XX_SB_PDI_STAT          0xe103
++
++/* SB PDI CTRL modes */
++#define MXL862XX_SB_PDI_CTRL_RST      0x00
++#define MXL862XX_SB_PDI_CTRL_WR       0x02
++
++/* SB PDI handshake magic */
++#define MXL862XX_SB_PDI_READY         0xc55c
++#define MXL862XX_SB_PDI_START         0xf48f
++#define MXL862XX_SB_PDI_END           0x3cc3
++
++/* Firmware transfer geometry */
++#define MXL862XX_FW_HDR_SIZE          20
++#define MXL862XX_FW_BANK_HALF         16384   /* words per half-bank */
++#define MXL862XX_FW_BANK_SLICE                32760   /* words per full slice */
++#define MXL862XX_FW_SB1_ADDR          0x7800  /* SB1 word address */
++
++/* Timeouts */
++#define MXL862XX_FW_READY_TIMEOUT_MS  30000
++#define MXL862XX_FW_ACK_TIMEOUT_MS    5000
++#define MXL862XX_FW_ERASE_TIMEOUT_MS  300000  /* flash erase is very slow */
++#define MXL862XX_FW_WRITE_TIMEOUT_MS  120000  /* per-slice program timeout */
++#define MXL862XX_FW_REBOOT_DELAY_MS   5000
++
++static void mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv)
++{
++      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
++                           MXL862XX_SB_PDI_CTRL_RST);
++      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
++                           MXL862XX_SB_PDI_CTRL_RST);
++      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,
++                           MXL862XX_SB_PDI_CTRL_RST);
++}
++
++static int mxl862xx_sb_pdi_poll_stat(struct mxl862xx_priv *priv, u16 expected,
++                                   unsigned long timeout_ms)
++{
++      unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);
++      int ret;
++
++      do {
++              ret = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);
++              if (ret < 0)
++                      return ret;
++              if ((u16)ret == expected)
++                      return 0;
++              usleep_range(10000, 11000);
++      } while (time_before(jiffies, timeout));
++
++      return -ETIMEDOUT;
++}
++
++/* Reprobe work -- dynamically allocated so it survives remove().
++ * device_reprobe() -> remove() frees priv (devm) while work is executing,
++ * so the work struct must not live in mxl862xx_priv.
++ */
++struct mxl862xx_reprobe {
++      struct device *dev;
++      struct delayed_work dwork;
++};
++
++static void mxl862xx_reprobe_work_fn(struct work_struct *work)
++{
++      struct mxl862xx_reprobe *reprobe =
++              container_of(work, struct mxl862xx_reprobe, dwork.work);
++
++      if (device_reprobe(reprobe->dev))
++              dev_err(reprobe->dev, "reprobe failed\n");
++      put_device(reprobe->dev);
++      kfree(reprobe);
++      module_put(THIS_MODULE);
++}
++
++/* MCUboot firmware image header (20 bytes) */
++struct mxl862xx_fw_hdr {
++      __le32 image_type;
++      __le32 image_size_1;
++      __le32 image_checksum_1;
++      __le32 image_size_2;
++      __le32 image_checksum_2;
++} __packed;
++
++static int mxl862xx_flash_firmware(struct mxl862xx_priv *priv,
++                                 const struct firmware *fw,
++                                 struct devlink *dl)
++{
++      const struct mxl862xx_fw_hdr *hdr;
++      u32 word_idx = 0, data_written = 0, idx = 0;
++      unsigned long next_notify = 0;
++      const u8 *payload;
++      u32 payload_size;
++      u16 word, fdata;
++      int ret, i;
++      u32 crc;
++
++      if (fw->size < MXL862XX_FW_HDR_SIZE)
++              return -EINVAL;
++
++      hdr = (const struct mxl862xx_fw_hdr *)fw->data;
++      payload = fw->data + MXL862XX_FW_HDR_SIZE;
++      payload_size = le32_to_cpu(hdr->image_size_1) +
++                     le32_to_cpu(hdr->image_size_2);
++
++      if (payload_size > fw->size - MXL862XX_FW_HDR_SIZE) {
++              dev_err(&priv->mdiodev->dev,
++                      "flash: firmware file too small for declared size\n");
++              return -EINVAL;
++      }
++
++      /* Validate CRC-32 of both image slots before touching hardware */
++      if (le32_to_cpu(hdr->image_size_1)) {
++              crc = ~crc32_le(~0U, payload,
++                              le32_to_cpu(hdr->image_size_1));
++              if (crc != le32_to_cpu(hdr->image_checksum_1)) {
++                      dev_err(&priv->mdiodev->dev,
++                              "flash: image 1 CRC mismatch (got %08x, expected %08x)\n",
++                              crc, le32_to_cpu(hdr->image_checksum_1));
++                      return -EINVAL;
++              }
++      }
++
++      if (le32_to_cpu(hdr->image_size_2)) {
++              crc = ~crc32_le(~0U,
++                              payload + le32_to_cpu(hdr->image_size_1),
++                              le32_to_cpu(hdr->image_size_2));
++              if (crc != le32_to_cpu(hdr->image_checksum_2)) {
++                      dev_err(&priv->mdiodev->dev,
++                              "flash: image 2 CRC mismatch (got %08x, expected %08x)\n",
++                              crc, le32_to_cpu(hdr->image_checksum_2));
++                      return -EINVAL;
++              }
++      }
++
++      /* Step 1: Tell firmware to enter MCUboot rescue mode.
++       * The FW_UPDATE command takes no payload (size 0).
++       */
++      ret = mxl862xx_api_wrap(priv, SYS_MISC_FW_UPDATE, NULL, 0,
++                              false, false);
++      if (ret) {
++              dev_err(&priv->mdiodev->dev,
++                      "flash: FW_UPDATE command failed: %pe\n",
++                      ERR_PTR(ret));
++              return ret;
++      }
++
++      /* From this point on, the switch is in MCUboot rescue mode.
++       * Any failure must go through the end_magic label to tell
++       * MCUboot to reboot rather than leaving it stuck waiting.
++       */
++
++      /* Step 2: Reset PDI and wait for bootloader ready */
++      devlink_flash_update_status_notify(dl, "Waiting for bootloader",
++                                         NULL, 0, 0);
++      mxl862xx_sb_pdi_reset(priv);
++      ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,
++                                      MXL862XX_FW_READY_TIMEOUT_MS);
++      if (ret) {
++              dev_err(&priv->mdiodev->dev,
++                      "flash: bootloader not ready: %pe\n", ERR_PTR(ret));
++              goto end_magic;
++      }
++
++      /* Step 3: Start handshake */
++      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
++                           MXL862XX_SB_PDI_START);
++      ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_START + 1,
++                                      MXL862XX_FW_ACK_TIMEOUT_MS);
++      if (ret) {
++              dev_err(&priv->mdiodev->dev,
++                      "flash: start handshake failed: %pe\n", ERR_PTR(ret));
++              goto end_magic;
++      }
++
++      /* Step 4: Transfer 20-byte header using auto-increment write mode */
++      devlink_flash_update_status_notify(dl, "Erasing flash", NULL, 0, 0);
++      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
++                           MXL862XX_SB_PDI_CTRL_WR);
++      for (i = 0; i < MXL862XX_FW_HDR_SIZE / 2; i++) {
++              word = fw->data[i * 2] |
++                     ((u16)fw->data[i * 2 + 1] << 8);
++              mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, word);
++      }
++      mxl862xx_sb_pdi_reset(priv);
++
++      /* Write header byte count to STAT to trigger erase */
++      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
++                           MXL862XX_FW_HDR_SIZE);
++
++      /* Wait for header ACK (header_size + 1) */
++      ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_FW_HDR_SIZE + 1,
++                                      MXL862XX_FW_ACK_TIMEOUT_MS);
++      if (ret) {
++              dev_err(&priv->mdiodev->dev,
++                      "flash: header ACK failed: %pe\n", ERR_PTR(ret));
++              goto end_magic;
++      }
++
++      /* Step 5: Wait for erase to complete (STAT goes to 0) */
++      ret = mxl862xx_sb_pdi_poll_stat(priv, 0,
++                                      MXL862XX_FW_ERASE_TIMEOUT_MS);
++      if (ret) {
++              dev_err(&priv->mdiodev->dev,
++                      "flash: erase timeout: %pe\n", ERR_PTR(ret));
++              goto end_magic;
++      }
++
++      /* Step 6: Transfer payload using dual-bank auto-increment writes */
++      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
++                           MXL862XX_SB_PDI_CTRL_WR);
++
++      while (idx < payload_size) {
++              if (idx + 1 < payload_size) {
++                      fdata = payload[idx] |
++                              ((u16)payload[idx + 1] << 8);
++                      idx += 2;
++                      data_written += 2;
++              } else {
++                      fdata = payload[idx];
++                      idx++;
++                      data_written++;
++              }
++
++              mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, fdata);
++              word_idx++;
++
++              /* Last byte(s): flush final partial slice */
++              if (idx >= payload_size) {
++                      mxl862xx_sb_pdi_reset(priv);
++                      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
++                                           data_written);
++                      break;
++              }
++
++              /* Half-bank boundary: switch to SB1 address */
++              if (word_idx == MXL862XX_FW_BANK_HALF) {
++                      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
++                                           MXL862XX_SB_PDI_CTRL_RST);
++                      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
++                                           MXL862XX_FW_SB1_ADDR);
++                      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
++                                           MXL862XX_SB_PDI_CTRL_WR);
++              } else if (word_idx >= MXL862XX_FW_BANK_SLICE) {
++                      /* Full slice: flush and wait for program */
++                      mxl862xx_sb_pdi_reset(priv);
++                      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
++                                           data_written);
++                      word_idx = 0;
++                      data_written = 0;
++
++                      ret = mxl862xx_sb_pdi_poll_stat(
++                              priv, 0, MXL862XX_FW_WRITE_TIMEOUT_MS);
++                      if (ret) {
++                              dev_err(&priv->mdiodev->dev,
++                                      "flash: write timeout at %u/%u: %pe\n",
++                                      idx, payload_size, ERR_PTR(ret));
++                              goto end_magic;
++                      }
++                      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
++                                           MXL862XX_SB_PDI_CTRL_WR);
++
++                      if (time_after(jiffies, next_notify)) {
++                              devlink_flash_update_status_notify(
++                                      dl, "Flashing", NULL,
++                                      idx, payload_size);
++                              next_notify = jiffies +
++                                            msecs_to_jiffies(500);
++                      }
++              }
++      }
++
++      /* Wait for final slice to be programmed */
++      ret = mxl862xx_sb_pdi_poll_stat(priv, 0,
++                                      MXL862XX_FW_WRITE_TIMEOUT_MS);
++      if (ret) {
++              dev_err(&priv->mdiodev->dev,
++                      "flash: final write timeout: %pe\n", ERR_PTR(ret));
++              goto end_magic;
++      }
++
++      devlink_flash_update_status_notify(dl, "Flashing", NULL,
++                                         payload_size, payload_size);
++
++end_magic:
++      /* Always send end magic so MCUboot reboots instead of sitting
++       * idle. The hardware reset during reprobe recovers the switch
++       * regardless of whether the transfer succeeded or failed.
++       */
++      mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
++                           MXL862XX_SB_PDI_END);
++      msleep(MXL862XX_FW_REBOOT_DELAY_MS);
++
++      return ret;
++}
++
++int mxl862xx_devlink_info_get(struct dsa_switch *ds,
++                            struct devlink_info_req *req,
++                            struct netlink_ext_ack *extack)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      char ver_str[32];
++
++      snprintf(ver_str, sizeof(ver_str), "%u.%u.%u",
++               priv->fw_version.major, priv->fw_version.minor,
++               priv->fw_version.revision);
++
++      return devlink_info_version_running_put(req, "fw", ver_str);
++}
++
++int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
++                                struct devlink_flash_update_params *params,
++                                struct netlink_ext_ack *extack)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_sys_fw_image_version ver = {};
++      struct mxl862xx_reprobe *reprobe;
++      struct dsa_port *dp;
++      int ret, i;
++
++      if (params->component) {
++              NL_SET_ERR_MSG_MOD(extack, "component is not supported");
++              return -EOPNOTSUPP;
++      }
++
++      dev_info(ds->dev, "flash: running firmware %u.%u.%u\n",
++               priv->fw_version.major, priv->fw_version.minor,
++               priv->fw_version.revision);
++
++      /* Close all user and CPU ports while the firmware is still
++       * alive. dev_close() on user ports triggers multicast group
++       * leave and host MDB/FDB removal on the CPU port through the
++       * normal DSA callbacks so the core's tracking lists are
++       * drained before we enter MCUboot. Then mark user ports
++       * not-present so userspace cannot bring them back up during
++       * the (slow) flash process. The conduit is only closed, not
++       * detached -- it is owned by the Ethernet MAC driver and
++       * dev_open() during reprobe must be able to bring it back.
++       */
++      rtnl_lock();
++      dsa_switch_for_each_user_port(dp, ds) {
++              if (dp->user) {
++                      dev_close(dp->user);
++                      netif_device_detach(dp->user);
++              }
++      }
++      dsa_switch_for_each_cpu_port(dp, ds)
++              dev_close(dp->conduit);
++      rtnl_unlock();
++
++      /* Block all firmware API commands while the switch is being
++       * reflashed. The conduit is intentionally kept open -- it is
++       * owned by the Ethernet MAC driver and would not recover on
++       * reprobe if we closed it here.
++       */
++      priv->block_host = true;
++
++      /* Stop stats polling and pending host-flood work */
++      cancel_delayed_work_sync(&priv->stats_work);
++      for (i = 0; i < ds->num_ports; i++)
++              cancel_work_sync(&priv->ports[i].host_flood_work);
++
++      ret = mxl862xx_flash_firmware(priv, params->fw, ds->devlink);
++      if (ret)
++              NL_SET_ERR_MSG_MOD(extack, "firmware transfer failed");
++
++      if (!ret) {
++              /* Read new firmware version (switch just rebooted).
++               * Temporarily lift the block for this single query.
++               */
++              priv->block_host = false;
++              memset(&ver, 0, sizeof(ver));
++              if (!MXL862XX_API_READ_QUIET(priv, SYS_MISC_FW_VERSION, ver)
++                  && ver.iv_major)
++                      dev_info(ds->dev, "flash: new firmware %u.%u.%u\n",
++                               ver.iv_major, ver.iv_minor,
++                               le16_to_cpu(ver.iv_revision));
++      }
++
++      /* Silently discard all API commands during the teardown that
++       * reprobe triggers -- the switch firmware has been reset and
++       * has no knowledge of the old configuration.
++       */
++      priv->skip_teardown = true;
++
++      reprobe = kzalloc(sizeof(*reprobe), GFP_KERNEL);
++      if (!reprobe)
++              return ret;
++
++      if (!try_module_get(THIS_MODULE)) {
++              kfree(reprobe);
++              return ret;
++      }
++
++      reprobe->dev = get_device(ds->dev);
++      INIT_DELAYED_WORK(&reprobe->dwork, mxl862xx_reprobe_work_fn);
++      schedule_delayed_work(&reprobe->dwork, msecs_to_jiffies(500));
++
++      return ret;
++}
+--- /dev/null
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
+@@ -0,0 +1,15 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++
++#ifndef __MXL862XX_FW_H
++#define __MXL862XX_FW_H
++
++#include <net/dsa.h>
++
++int mxl862xx_devlink_info_get(struct dsa_switch *ds,
++                            struct devlink_info_req *req,
++                            struct netlink_ext_ack *extack);
++int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
++                                struct devlink_flash_update_params *params,
++                                struct netlink_ext_ack *extack);
++
++#endif /* __MXL862XX_FW_H */
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+@@ -14,6 +14,7 @@
+ #include <linux/limits.h>
+ #include <net/dsa.h>
+ #include "mxl862xx.h"
++#include "mxl862xx-cmd.h"
+ #include "mxl862xx-host.h"
+ #define CTRL_BUSY_MASK                        BIT(15)
+@@ -334,6 +335,12 @@ int mxl862xx_api_wrap(struct mxl862xx_pr
+       int ret, cmd_ret;
+       u16 max, crc, i;
++      if (priv->skip_teardown)
++              return 0;
++
++      if (priv->block_host && cmd != SYS_MISC_FW_UPDATE)
++              return -EBUSY;
++
+       dev_dbg(&priv->mdiodev->dev, "CMD %04x DATA %*ph\n", cmd, size, data);
+       mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -22,6 +22,7 @@
+ #include "mxl862xx.h"
+ #include "mxl862xx-api.h"
+ #include "mxl862xx-cmd.h"
++#include "mxl862xx-fw.h"
+ #include "mxl862xx-host.h"
+ #include "mxl862xx-phylink.h"
+@@ -4245,6 +4246,9 @@ static const struct dsa_switch_ops mxl86
+       .get_pause_stats = mxl862xx_get_pause_stats,
+       .get_stats64 = mxl862xx_get_stats64,
+       .self_test = mxl862xx_serdes_self_test,
++      .devlink_info_get = mxl862xx_devlink_info_get,
++      .devlink_flash_update = mxl862xx_devlink_flash_update,
++
+ };
+ static int mxl862xx_probe(struct mdio_device *mdiodev)
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -388,6 +388,8 @@ struct mxl862xx_priv {
+       u16 lag_bridge_ports[MXL862XX_MAX_LAG_IDS + 1];
+       u8 trunk_hash;
+       int mirror_dest;
++      bool block_host;
++      bool skip_teardown;
+       struct delayed_work stats_work;
+ };
diff --git a/target/linux/generic/pending-6.12/760-22-net-dsa-mxl862xx-implement-port-MTU-configuration.patch b/target/linux/generic/pending-6.12/760-22-net-dsa-mxl862xx-implement-port-MTU-configuration.patch
new file mode 100644 (file)
index 0000000..1869a9f
--- /dev/null
@@ -0,0 +1,110 @@
+From 2cb9aeb3a8d7ebac20331e0a533dcfbd73fa4237 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 23:42:18 +0000
+Subject: [PATCH 31/35] net: dsa: mxl862xx: implement port MTU configuration
+
+The firmware exposes a global max_packet_len register via
+MXL862XX_COMMON_CFGSET. Since this is switch-wide rather than
+per-port, cache each port's requested MTU and program the register
+with the maximum across all ports. The firmware call is skipped when
+the effective maximum does not change.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 50 +++++++++++++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx.h |  4 +++
+ 2 files changed, 54 insertions(+)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -11,6 +11,7 @@
+ #include <linux/delay.h>
+ #include <linux/etherdevice.h>
+ #include <linux/if_bridge.h>
++#include <linux/if_vlan.h>
+ #include <linux/module.h>
+ #include <linux/of_device.h>
+ #include <linux/of_mdio.h>
+@@ -3768,6 +3769,53 @@ static int mxl862xx_set_ageing_time(stru
+       return ret;
+ }
++static int mxl862xx_port_change_mtu(struct dsa_switch *ds, int port,
++                                  int new_mtu)
++{
++      struct mxl862xx_priv *priv = ds->priv;
++      struct mxl862xx_cfg param = {};
++      int i, old_max = 0, new_max = 0;
++      int ret;
++
++      for (i = 0; i < ds->num_ports; i++) {
++              if (priv->ports[i].mtu > old_max)
++                      old_max = priv->ports[i].mtu;
++      }
++
++      priv->ports[port].mtu = new_mtu;
++
++      for (i = 0; i < ds->num_ports; i++) {
++              if (priv->ports[i].mtu > new_max)
++                      new_max = priv->ports[i].mtu;
++      }
++
++      if (new_max != old_max) {
++              ret = MXL862XX_API_READ(priv, MXL862XX_COMMON_CFGGET,
++                                      param);
++              if (ret)
++                      return ret;
++
++              param.max_packet_len = cpu_to_le16(new_max +
++                                                 VLAN_ETH_HLEN +
++                                                 ETH_FCS_LEN);
++              ret = MXL862XX_API_WRITE(priv, MXL862XX_COMMON_CFGSET,
++                                       param);
++              if (ret) {
++                      dev_err(ds->dev,
++                              "failed to set MTU to %d: %pe\n",
++                              new_mtu, ERR_PTR(ret));
++                      return ret;
++              }
++      }
++
++      return 0;
++}
++
++static int mxl862xx_port_max_mtu(struct dsa_switch *ds, int port)
++{
++      return U16_MAX - VLAN_ETH_HLEN - ETH_FCS_LEN;
++}
++
+ static void mxl862xx_port_stp_state_set(struct dsa_switch *ds, int port,
+                                       u8 state)
+ {
+@@ -4215,6 +4263,8 @@ static const struct dsa_switch_ops mxl86
+       .port_disable = mxl862xx_port_disable,
+       .port_fast_age = mxl862xx_port_fast_age,
+       .set_ageing_time = mxl862xx_set_ageing_time,
++      .port_change_mtu = mxl862xx_port_change_mtu,
++      .port_max_mtu = mxl862xx_port_max_mtu,
+       .port_bridge_join = mxl862xx_port_bridge_join,
+       .port_bridge_leave = mxl862xx_port_bridge_leave,
+       .port_pre_bridge_flags = mxl862xx_port_pre_bridge_flags,
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -249,6 +249,8 @@ struct mxl862xx_port_stats {
+  * @lag_hash_bits:       hash field bitmask (MXL862XX_TRUNK_HASH_*) requested
+  *                       when this port joined its LAG; used to recompute the
+  *                       global trunk_hash when a LAG is destroyed
++ * @mtu:                 per-port requested MTU; the global switch register
++ *                       is set to the maximum across all ports
+  */
+ struct mxl862xx_port {
+       struct mxl862xx_priv *priv;
+@@ -278,6 +280,8 @@ struct mxl862xx_port {
+       struct dsa_lag *lag;
+       bool lag_tx_enabled;
+       u8 lag_hash_bits;
++      /* MTU */
++      int mtu;
+       /* Hardware stats accumulation */
+       struct mxl862xx_port_stats stats;
+       spinlock_t stats_lock;
diff --git a/target/linux/generic/pending-6.12/760-23-net-dsa-mxl862xx-support-BR_HAIRPIN_MODE-bridge-flag.patch b/target/linux/generic/pending-6.12/760-23-net-dsa-mxl862xx-support-BR_HAIRPIN_MODE-bridge-flag.patch
new file mode 100644 (file)
index 0000000..bf4d2f0
--- /dev/null
@@ -0,0 +1,106 @@
+From d55ca68eb0d20a66c32d531b0a454871b486c1b1 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 25 Mar 2026 01:47:19 +0000
+Subject: [PATCH 32/35] net: dsa: mxl862xx: support BR_HAIRPIN_MODE bridge flag
+
+Implement hairpin mode by including the port's own bridge port ID in
+its forwarding portmap. When hairpin is enabled, bridged frames whose
+destination resolves to the ingress port are allowed to egress there
+instead of being dropped.
+
+For LAG ports, the LAG's dedicated bridge port is added to the
+master's portmap, which naturally propagates to the LAG bridge port
+via the second loop in sync_bridge_members.
+
+The port_bridge_flags handler toggles the bit directly on the cached
+portmap and pushes the update via set_bridge_port, avoiding a full
+bridge member rebuild since only the calling port is affected.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 30 ++++++++++++++++++++++++++++-
+ drivers/net/dsa/mxl862xx/mxl862xx.h |  6 ++++++
+ 2 files changed, 35 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -811,6 +811,15 @@ static int mxl862xx_sync_bridge_members(
+               __set_bit(mxl862xx_cpu_bridge_port_id(ds, port),
+                         priv->ports[port].portmap);
++              /* Hairpin: include the port's own bridge port so bridged
++               * frames can egress the ingress port.
++               * For LAG ports this adds the LAG bridge port, which
++               * propagates to the LAG BP in the second loop below.
++               */
++              if (priv->ports[port].hairpin)
++                      __set_bit(mxl862xx_lag_bridge_port(priv, port),
++                                priv->ports[port].portmap);
++
+               err = mxl862xx_set_bridge_port(ds, port);
+               if (err)
+                       ret = err;
+@@ -3939,7 +3948,7 @@ static int mxl862xx_port_pre_bridge_flag
+                                         struct netlink_ext_ack *extack)
+ {
+       if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD |
+-                         BR_LEARNING))
++                         BR_LEARNING | BR_HAIRPIN_MODE))
+               return -EINVAL;
+       return 0;
+@@ -3954,6 +3963,7 @@ static int mxl862xx_port_bridge_flags(st
+       unsigned long block = old_block;
+       bool need_update = false;
+       int ret;
++      u16 bp;
+       if (flags.mask & BR_FLOOD) {
+               if (flags.val & BR_FLOOD)
+@@ -3988,6 +3998,24 @@ static int mxl862xx_port_bridge_flags(st
+               ret = mxl862xx_set_bridge_port(ds, port);
+               if (ret)
+                       return ret;
++      }
++
++      if (flags.mask & BR_HAIRPIN_MODE) {
++              bp = mxl862xx_lag_bridge_port(priv, port);
++              priv->ports[port].hairpin = !!(flags.val & BR_HAIRPIN_MODE);
++
++              /* Hairpin adds/removes the port's own bridge port from its
++               * cached portmap. Only this port is affected -- push the
++               * updated portmap directly.
++               */
++              if (flags.val & BR_HAIRPIN_MODE)
++                      __set_bit(bp, priv->ports[port].portmap);
++              else
++                      __clear_bit(bp, priv->ports[port].portmap);
++
++              ret = mxl862xx_set_bridge_port(ds, port);
++              if (ret)
++                      return ret;
+       }
+       return 0;
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -241,6 +241,10 @@ struct mxl862xx_port_stats {
+  * @stats_lock:          protects accumulator reads in .get_stats64 against
+  *                       concurrent updates from the polling work
+  * @tag_8021q_vid:       currently assigned tag_8021q management VID
++ * @hairpin:             true when hairpin mode is active (BR_HAIRPIN_MODE);
++ *                       the port's own bridge port is included in its
++ *                       portmap so bridged frames can egress the ingress
++ *                       port
+  * @ingress_mirror:      true when ingress mirroring is active on this port
+  * @egress_mirror:       true when egress mirroring is active on this port
+  * @lag:                 non-NULL when port is member of a LAG group;
+@@ -273,6 +277,8 @@ struct mxl862xx_port {
+       struct work_struct host_flood_work;
+       u16 tag_8021q_vid;
+       struct mxl862xx_evlan_block cpu_egress_evlan;
++      /* Hairpin state */
++      bool hairpin;
+       /* Mirror state */
+       bool ingress_mirror;
+       bool egress_mirror;
diff --git a/target/linux/generic/pending-6.12/760-24-net-dsa-mxl862xx-support-BR_ISOLATED-bridge-flag.patch b/target/linux/generic/pending-6.12/760-24-net-dsa-mxl862xx-support-BR_ISOLATED-bridge-flag.patch
new file mode 100644 (file)
index 0000000..a524d15
--- /dev/null
@@ -0,0 +1,95 @@
+From 74b6654ba74eb142340de4c51b97c0221cfcae37 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 25 Mar 2026 01:51:33 +0000
+Subject: [PATCH 33/35] net: dsa: mxl862xx: support BR_ISOLATED bridge flag
+
+Implement port isolation by excluding isolated ports from each other's
+forwarding portmaps in sync_bridge_members. Non-isolated ports can
+still reach isolated ports and vice versa -- only isolated-to-isolated
+forwarding is blocked.
+
+When the isolation state changes, all bridge members' portmaps are
+rebuilt via sync_bridge_members since multiple ports are affected.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 26 +++++++++++++++++++++++++-
+ drivers/net/dsa/mxl862xx/mxl862xx.h |  4 ++++
+ 2 files changed, 29 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -802,6 +802,14 @@ static int mxl862xx_sync_bridge_members(
+                        */
+                       if (!mxl862xx_is_lag_master(priv, member))
+                               continue;
++
++                      /* Isolated ports cannot forward to each other.
++                       * Non-isolated ports can reach everyone.
++                       */
++                      if (priv->ports[port].isolated &&
++                          priv->ports[member].isolated)
++                              continue;
++
+                       if (member != port) {
+                               bp = mxl862xx_lag_bridge_port(priv,
+                                                            member);
+@@ -3948,7 +3956,7 @@ static int mxl862xx_port_pre_bridge_flag
+                                         struct netlink_ext_ack *extack)
+ {
+       if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD |
+-                         BR_LEARNING | BR_HAIRPIN_MODE))
++                         BR_LEARNING | BR_HAIRPIN_MODE | BR_ISOLATED))
+               return -EINVAL;
+       return 0;
+@@ -3962,6 +3970,7 @@ static int mxl862xx_port_bridge_flags(st
+       unsigned long old_block = priv->ports[port].flood_block;
+       unsigned long block = old_block;
+       bool need_update = false;
++      struct dsa_port *dp;
+       int ret;
+       u16 bp;
+@@ -4018,6 +4027,21 @@ static int mxl862xx_port_bridge_flags(st
+                       return ret;
+       }
++      if (flags.mask & BR_ISOLATED) {
++              dp = dsa_to_port(ds, port);
++              priv->ports[port].isolated = !!(flags.val & BR_ISOLATED);
++
++              /* Isolation affects all bridge members' portmaps:
++               * isolated ports must be removed from each other's
++               * portmaps. Rebuild all portmaps for this bridge.
++               */
++              if (dp->bridge) {
++                      ret = mxl862xx_sync_bridge_members(ds, dp->bridge);
++                      if (ret)
++                              return ret;
++              }
++      }
++
+       return 0;
+ }
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -214,6 +214,9 @@ struct mxl862xx_port_stats {
+  * @flood_block:         bitmask of firmware meter indices that are currently
+  *                       rate-limiting flood traffic on this port (zero-rate
+  *                       meters used to block flooding)
++ * @isolated:            true when port isolation is active (BR_ISOLATED);
++ *                       isolated ports are excluded from each other's
++ *                       forwarding portmaps
+  * @learning:            true when address learning is enabled on this port
+  * @setup_done:          set at end of port_setup, cleared at start of
+  *                       port_teardown; guards deferred work against
+@@ -261,6 +264,7 @@ struct mxl862xx_port {
+       u16 fid;
+       DECLARE_BITMAP(portmap, MXL862XX_MAX_BRIDGE_PORTS);
+       unsigned long flood_block;
++      bool isolated;
+       bool learning;
+       bool setup_done;
+       /* VLAN state */
diff --git a/target/linux/generic/pending-6.12/760-25-DO-NOT-SUBMIT-net-dsa-mxl862xx-re-introduce-PCE-work.patch b/target/linux/generic/pending-6.12/760-25-DO-NOT-SUBMIT-net-dsa-mxl862xx-re-introduce-PCE-work.patch
new file mode 100644 (file)
index 0000000..98cfb44
--- /dev/null
@@ -0,0 +1,81 @@
+From 0902a6790750714445c75a66d60f1bc4897126ce Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 18:17:49 +0000
+Subject: [PATCH 34/35] DO NOT SUBMIT: net: dsa: mxl862xx: re-introduce PCE
+ workaround for old firmware
+
+Re-introduce the mxl862xx_disable_fw_global_rules() function that
+disables firmware default global PCE rules for firmware versions
+older than 1.0.80.  The upstream submission replaced this with a
+dev_warn() since firmware >= 1.0.80 no longer installs these rules,
+but downstream deployments may still run older firmware.
+
+This commit is for downstream use only and must not be submitted
+upstream.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 45 +++++++++++++++++++++++++++--
+ 1 file changed, 42 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -477,6 +477,43 @@ static int mxl862xx_setup_drop_meter(str
+       return MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
+ }
++/* Disable firmware global PCE rules that trap various protocols to the
++ * on-die microcontroller (port 0) via PORTMAP_CPU. Under DSA, these
++ * frames must either reach the host CPU via per-port rules (link-local)
++ * or through the normal bridge forwarding path (ARP broadcast), so the
++ * global firmware rules are not needed. With the microcontroller port
++ * disabled they would silently drop matching traffic.
++ *
++ * Global rules have lower indices than CTP rules, hence higher priority
++ * in the PCE pipeline -- they must be explicitly disabled or they will
++ * shadow the per-CTP traps.
++ *
++ * Indices from gsw_flow_index.h:
++ *   1 -- BPDU (STP/RSTP, dst 01:80:c2:00:00:00)
++ *   3 -- LLDP         (EtherType 0x88cc)
++ *   4 -- OAM/LACP     (EtherType 0x8809)
++ *   6 -- System MAC   (dst 02:e0:92:00:00:01, vendor management MAC)
++ *   7 -- ARP Request  (broadcast + EtherType 0x0806 + TPA 192.0.2.1)
++ */
++static int mxl862xx_disable_fw_global_rules(struct dsa_switch *ds)
++{
++      static const u16 indices[] = { 1, 3, 4, 6, 7 };
++      struct mxl862xx_pce_rule rule;
++      int i, ret;
++
++      for (i = 0; i < ARRAY_SIZE(indices); i++) {
++              memset(&rule, 0, sizeof(rule));
++              rule.pattern.index = cpu_to_le16(indices[i]);
++              /* pattern.enable == 0 -> rule is disabled */
++
++              ret = MXL862XX_API_WRITE(ds->priv,
++                                       MXL862XX_TFLOW_PCERULEWRITE, rule);
++              if (ret)
++                      return ret;
++      }
++
++      return 0;
++}
+ /* Per-CTP offset used for the link-local trap rule. Each port's CTP
+  * flow-table block is pre-allocated by the firmware during init (44
+@@ -1154,9 +1191,11 @@ static int mxl862xx_setup(struct dsa_swi
+       if (ret)
+               return ret;
+-      if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 80))
+-              dev_warn(ds->dev, "firmware < 1.0.80 installs global PCE rules "
+-                       "that interfere with DSA operation, please update\n");
++      if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 80)) {
++              ret = mxl862xx_disable_fw_global_rules(ds);
++              if (ret)
++                      return ret;
++      }
+       /* Pre-allocate firmware resources for all ports. The DSA core
+        * calls change_tag_protocol() between setup() and port_setup(),
diff --git a/target/linux/generic/pending-6.12/760-26-DO-NOT-SUBMIT-net-dsa-mxl862xx-legacy-SFP-API-fallba.patch b/target/linux/generic/pending-6.12/760-26-DO-NOT-SUBMIT-net-dsa-mxl862xx-legacy-SFP-API-fallba.patch
new file mode 100644 (file)
index 0000000..41cd927
--- /dev/null
@@ -0,0 +1,251 @@
+From 0ac876d5b952218ab79ea0a0815cf6fd1290b1d0 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 24 Mar 2026 18:19:56 +0000
+Subject: [PATCH 35/35] DO NOT SUBMIT: net: dsa: mxl862xx: legacy SFP API
+ fallback for old firmware
+
+Re-introduce the SYS_MISC_SFP_SET-based PCS implementation as a
+fallback for firmware versions older than 1.0.80 which lack the
+XPCS API. mxl862xx_setup_pcs() selects between the XPCS ops and
+legacy SFP ops based on firmware version.
+
+This commit is for downstream use only and must not be submitted
+upstream.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h     |  22 +++
+ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h     |   1 +
+ drivers/net/dsa/mxl862xx/mxl862xx-phylink.c | 160 +++++++++++++++++++-
+ 3 files changed, 178 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+@@ -2400,6 +2400,28 @@ struct mxl862xx_sys_fw_image_version {
+ } __packed;
+ /**
++ * struct mxl862xx_sys_sfp_cfg - legacy SFP/SerDes port configuration
++ * @port_id: port id (0 or 1)
++ * @option: config options (0 - SFP mode/speed/link-status, 1 - flow control)
++ * @mode: SFP mode (0 - auto, 1 - fix, 2 - disable)
++ * @speed: select speed when mode is 1
++ * @link: get link state
++ * @fc_en: flow control (0 - disable, 1 - enable)
++ */
++struct mxl862xx_sys_sfp_cfg {
++      u8 port_id:4;
++      u8 option:4;
++      union {
++              struct {
++                      u8 mode;
++                      u8 speed;
++                      u8 link;
++              };
++              u8 fc_en;
++      };
++} __packed;
++
++/**
+  * enum mxl862xx_rmon_port_type - RMON counter table type
+  * @MXL862XX_RMON_CTP_PORT_RX: CTP RX counters
+  * @MXL862XX_RMON_CTP_PORT_TX: CTP TX counters
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+@@ -85,6 +85,7 @@
+ #define SYS_MISC_FW_UPDATE            (SYS_MISC_MAGIC + 0x1)
+ #define SYS_MISC_FW_VERSION           (SYS_MISC_MAGIC + 0x2)
++#define SYS_MISC_SFP_SET              (SYS_MISC_MAGIC + 0xe)
+ #define MXL862XX_XPCS_MAGIC           0x1a00
+ #define MXL862XX_XPCS_PCS_CONFIG      (MXL862XX_XPCS_MAGIC + 0x1)
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
+@@ -52,6 +52,155 @@ static struct mxl862xx_pcs *pcs_to_mxl86
+       return container_of(pcs, struct mxl862xx_pcs, pcs);
+ }
++/* Legacy SFP-based PCS implementation for firmware < 1.0.80 */
++static int mxl862xx_legacy_pcs_config(struct phylink_pcs *pcs,
++                                    unsigned int neg_mode,
++                                    phy_interface_t interface,
++                                    const unsigned long *advertising,
++                                    bool permit_pause_to_mac)
++{
++      struct mxl862xx_priv *priv = pcs_to_mxl862xx_pcs(pcs)->priv;
++      int port = pcs_to_mxl862xx_pcs(pcs)->port;
++      struct mxl862xx_sys_sfp_cfg ser_intf = {
++              .option = 0,
++              .mode = 1,
++      };
++
++      if (port != 9 && port != 13)
++              return 0;
++
++      if (port == 9)
++              ser_intf.port_id = 0;
++      else
++              ser_intf.port_id = 1;
++
++      switch (interface) {
++      case PHY_INTERFACE_MODE_SGMII:
++              ser_intf.speed = 8;
++              break;
++      case PHY_INTERFACE_MODE_1000BASEX:
++              ser_intf.speed = (neg_mode & PHYLINK_PCS_NEG_INBAND) ? 1 : 7;
++              break;
++      case PHY_INTERFACE_MODE_2500BASEX:
++              ser_intf.speed = 4;
++              break;
++      case PHY_INTERFACE_MODE_10GBASER:
++              ser_intf.speed = 2;
++              break;
++      case PHY_INTERFACE_MODE_USXGMII:
++              ser_intf.speed = 3;
++              break;
++      default:
++              dev_err(priv->ds->dev, "unsupported interface: %s\n",
++                      phy_modes(interface));
++              return -EINVAL;
++      }
++
++      return MXL862XX_API_WRITE(priv, SYS_MISC_SFP_SET, ser_intf);
++}
++
++static void mxl862xx_legacy_pcs_get_state(struct phylink_pcs *pcs,
++                                        struct phylink_link_state *state)
++{
++      struct mxl862xx_priv *priv = pcs_to_mxl862xx_pcs(pcs)->priv;
++      int port = pcs_to_mxl862xx_pcs(pcs)->port;
++      struct mxl862xx_port_link_cfg port_link_cfg = {
++              .port_id = port,
++      };
++      struct mxl862xx_port_cfg port_cfg = {
++              .port_id = port,
++      };
++      int ret;
++
++      ret = MXL862XX_API_READ(priv, MXL862XX_COMMON_PORTLINKCFGGET,
++                              port_link_cfg);
++      if (ret)
++              return;
++
++      ret = MXL862XX_API_READ(priv, MXL862XX_COMMON_PORTCFGGET, port_cfg);
++      if (ret)
++              return;
++
++      state->link = (port_link_cfg.link == MXL862XX_PORT_LINK_UP);
++      state->an_complete = state->link;
++
++      switch (port_link_cfg.speed) {
++      case MXL862XX_PORT_SPEED_10:
++              state->speed = SPEED_10;
++              break;
++      case MXL862XX_PORT_SPEED_100:
++              state->speed = SPEED_100;
++              break;
++      case MXL862XX_PORT_SPEED_1000:
++              state->speed = SPEED_1000;
++              break;
++      case MXL862XX_PORT_SPEED_2500:
++              state->speed = SPEED_2500;
++              break;
++      case MXL862XX_PORT_SPEED_5000:
++              state->speed = SPEED_5000;
++              break;
++      case MXL862XX_PORT_SPEED_10000:
++              state->speed = SPEED_10000;
++              break;
++      default:
++              state->speed = SPEED_UNKNOWN;
++              break;
++      }
++
++      switch (port_link_cfg.duplex) {
++      case MXL862XX_DUPLEX_HALF:
++              state->duplex = DUPLEX_HALF;
++              break;
++      case MXL862XX_DUPLEX_FULL:
++              state->duplex = DUPLEX_FULL;
++              break;
++      default:
++              state->duplex = DUPLEX_UNKNOWN;
++              break;
++      }
++
++      state->pause &= ~(MLO_PAUSE_RX | MLO_PAUSE_TX);
++      switch (port_cfg.flow_ctrl) {
++      case MXL862XX_FLOW_RXTX:
++              state->pause |= MLO_PAUSE_TXRX_MASK;
++              break;
++      case MXL862XX_FLOW_TX:
++              state->pause |= MLO_PAUSE_TX;
++              break;
++      case MXL862XX_FLOW_RX:
++              state->pause |= MLO_PAUSE_RX;
++              break;
++      case MXL862XX_FLOW_OFF:
++      default:
++              break;
++      }
++}
++
++static unsigned int
++mxl862xx_legacy_pcs_inband_caps(struct phylink_pcs *pcs,
++                              phy_interface_t interface)
++{
++      switch (interface) {
++      case PHY_INTERFACE_MODE_SGMII:
++      case PHY_INTERFACE_MODE_USXGMII:
++              return LINK_INBAND_ENABLE;
++      case PHY_INTERFACE_MODE_1000BASEX:
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++      case PHY_INTERFACE_MODE_10GBASER:
++      case PHY_INTERFACE_MODE_2500BASEX:
++              return LINK_INBAND_DISABLE;
++      default:
++              return 0;
++      }
++}
++
++static const struct phylink_pcs_ops mxl862xx_legacy_pcs_ops = {
++      .pcs_get_state = mxl862xx_legacy_pcs_get_state,
++      .pcs_config = mxl862xx_legacy_pcs_config,
++      .pcs_inband_caps = mxl862xx_legacy_pcs_inband_caps,
++};
++
+ static int mxl862xx_xpcs_port_id(int port)
+ {
+       return port >= 13;
+@@ -389,7 +538,10 @@ void mxl862xx_setup_pcs(struct mxl862xx_
+       pcs->priv = priv;
+       pcs->port = port;
+-      pcs->pcs.ops = &mxl862xx_pcs_ops;
++      if (MXL862XX_FW_VER_MIN(priv, 1, 0, 80))
++              pcs->pcs.ops = &mxl862xx_pcs_ops;
++      else
++              pcs->pcs.ops = &mxl862xx_legacy_pcs_ops;
+       pcs->pcs.poll = true;
+ }
+@@ -401,9 +553,6 @@ mxl862xx_phylink_mac_select_pcs(struct p
+       struct mxl862xx_priv *priv = dp->ds->priv;
+       int port = dp->index;
+-      if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 80))
+-              return NULL;
+-
+       switch (port) {
+       case 9 ... 16:
+               return &priv->serdes_ports[port - 9].pcs;
+@@ -534,7 +683,7 @@ void mxl862xx_serdes_get_stats(struct ds
+ }
+ void mxl862xx_serdes_self_test(struct dsa_switch *ds, int port,
+-                      struct ethtool_test *etest, u64 *data)
++                            struct ethtool_test *etest, u64 *data)
+ {
+       struct mxl862xx_xpcs_prbs_cfg prbs = {};
+       struct mxl862xx_xpcs_bert_cfg bert = {};
diff --git a/target/linux/generic/pending-6.12/780-ARM-kirkwood-add-missing-linux-if_ether.h-for-ETH_AL.patch b/target/linux/generic/pending-6.12/780-ARM-kirkwood-add-missing-linux-if_ether.h-for-ETH_AL.patch
new file mode 100644 (file)
index 0000000..39ba716
--- /dev/null
@@ -0,0 +1,61 @@
+From patchwork Thu Aug  5 22:23:30 2021
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+X-Patchwork-Submitter: Daniel Golle <daniel@makrotopia.org>
+X-Patchwork-Id: 12422209
+Date: Thu, 5 Aug 2021 23:23:30 +0100
+From: Daniel Golle <daniel@makrotopia.org>
+To: linux-arm-kernel@lists.infradead.org, netdev@vger.kernel.org,
+ linux-kernel@vger.kernel.org
+Cc: "David S. Miller" <davem@davemloft.net>, Andrew Lunn <andrew@lunn.ch>,
+ Michael Walle <michael@walle.cc>
+Subject: [PATCH] ARM: kirkwood: add missing <linux/if_ether.h> for ETH_ALEN
+Message-ID: <YQxk4jrbm31NM1US@makrotopia.org>
+MIME-Version: 1.0
+Content-Disposition: inline
+X-BeenThere: linux-arm-kernel@lists.infradead.org
+X-Mailman-Version: 2.1.34
+Precedence: list
+List-Id: <linux-arm-kernel.lists.infradead.org>
+List-Archive: <http://lists.infradead.org/pipermail/linux-arm-kernel/>
+Sender: "linux-arm-kernel" <linux-arm-kernel-bounces@lists.infradead.org>
+
+After commit 83216e3988cd1 ("of: net: pass the dst buffer to
+of_get_mac_address()") build fails for kirkwood as ETH_ALEN is not
+defined.
+
+arch/arm/mach-mvebu/kirkwood.c: In function 'kirkwood_dt_eth_fixup':
+arch/arm/mach-mvebu/kirkwood.c:87:13: error: 'ETH_ALEN' undeclared (first use in this function); did you mean 'ESTALE'?
+   u8 tmpmac[ETH_ALEN];
+             ^~~~~~~~
+             ESTALE
+arch/arm/mach-mvebu/kirkwood.c:87:13: note: each undeclared identifier is reported only once for each function it appears in
+arch/arm/mach-mvebu/kirkwood.c:87:6: warning: unused variable 'tmpmac' [-Wunused-variable]
+   u8 tmpmac[ETH_ALEN];
+      ^~~~~~
+make[5]: *** [scripts/Makefile.build:262: arch/arm/mach-mvebu/kirkwood.o] Error 1
+make[5]: *** Waiting for unfinished jobs....
+
+Add missing #include <linux/if_ether.h> to fix this.
+
+Cc: David S. Miller <davem@davemloft.net>
+Cc: Andrew Lunn <andrew@lunn.ch>
+Cc: Michael Walle <michael@walle.cc>
+Reported-by: https://buildbot.openwrt.org/master/images/#/builders/56/builds/220/steps/44/logs/stdio
+Fixes: 83216e3988cd1 ("of: net: pass the dst buffer to of_get_mac_address()")
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ arch/arm/mach-mvebu/kirkwood.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/arch/arm/mach-mvebu/kirkwood.c
++++ b/arch/arm/mach-mvebu/kirkwood.c
+@@ -11,6 +11,7 @@
+ #include <linux/kernel.h>
+ #include <linux/init.h>
+ #include <linux/mbus.h>
++#include <linux/if_ether.h>
+ #include <linux/of.h>
+ #include <linux/of_address.h>
+ #include <linux/of_net.h>
diff --git a/target/linux/generic/pending-6.12/790-bus-mhi-core-add-SBL-state-callback.patch b/target/linux/generic/pending-6.12/790-bus-mhi-core-add-SBL-state-callback.patch
new file mode 100644 (file)
index 0000000..80d1306
--- /dev/null
@@ -0,0 +1,48 @@
+From 5f7c5e1c0d7a79be144e5efc1f24728ddd7fc25c Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Sat, 5 Nov 2022 20:02:56 +0100
+Subject: [PATCH 1/2] bus: mhi: core: add SBL state callback
+
+Add support for SBL state callback in MHI core.
+
+It is required for ath11k MHI devices in order to be able to set QRTR
+instance ID in the SBL state so that QRTR instance ID-s dont conflict in
+case of multiple PCI/MHI cards or AHB + PCI/MHI card.
+Setting QRTR instance ID is only possible in SBL state and there is
+currently no way to ensure that we are in that state, so provide a
+callback that the controller can trigger off.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ drivers/bus/mhi/host/main.c | 1 +
+ include/linux/mhi.h         | 2 ++
+ 2 files changed, 3 insertions(+)
+
+--- a/drivers/bus/mhi/host/main.c
++++ b/drivers/bus/mhi/host/main.c
+@@ -916,6 +916,7 @@ int mhi_process_ctrl_ev_ring(struct mhi_
+                       switch (event) {
+                       case MHI_EE_SBL:
+                               st = DEV_ST_TRANSITION_SBL;
++                              mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_SBL_MODE);
+                               break;
+                       case MHI_EE_WFW:
+                       case MHI_EE_AMSS:
+--- a/include/linux/mhi.h
++++ b/include/linux/mhi.h
+@@ -34,6 +34,7 @@ struct mhi_buf_info;
+  * @MHI_CB_SYS_ERROR: MHI device entered error state (may recover)
+  * @MHI_CB_FATAL_ERROR: MHI device entered fatal error state
+  * @MHI_CB_BW_REQ: Received a bandwidth switch request from device
++ * @MHI_CB_EE_SBL_MODE: MHI device entered SBL mode
+  */
+ enum mhi_callback {
+       MHI_CB_IDLE,
+@@ -45,6 +46,7 @@ enum mhi_callback {
+       MHI_CB_SYS_ERROR,
+       MHI_CB_FATAL_ERROR,
+       MHI_CB_BW_REQ,
++      MHI_CB_EE_SBL_MODE,
+ };
+ /**
diff --git a/target/linux/generic/pending-6.12/791-tg3-Fix-DMA-allocations-on-57766-devices.patch b/target/linux/generic/pending-6.12/791-tg3-Fix-DMA-allocations-on-57766-devices.patch
new file mode 100644 (file)
index 0000000..5181041
--- /dev/null
@@ -0,0 +1,31 @@
+From f992b15965177e2f280fb6f41f292214f9a6f8d5 Mon Sep 17 00:00:00 2001
+From: Pavan Chebbi <pavan.chebbi@broadcom.com>
+Date: Tue, 10 Dec 2024 03:28:31 -0800
+Subject: [PATCH] tg3: Fix DMA allocations on 57766 devices
+
+The coherent DMA mask of 31b may not be accepted if
+the DMA mask is configured to use higher memories of
+64b. Set the DMA mask also to lower 32b for 57766
+devices.
+
+Fixes: 614f4d166eee ("tg3: Set coherent DMA mask bits to 31 for BCM57766 chipsets")
+Reported-By: Rui Salvaterra <rsalvaterra@gmail.com>
+Signed-off-by: Pavan Chebbi <pavan.chebbi@broadcom.com>
+---
+ drivers/net/ethernet/broadcom/tg3.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/broadcom/tg3.c
++++ b/drivers/net/ethernet/broadcom/tg3.c
+@@ -17799,8 +17799,10 @@ static int tg3_init_one(struct pci_dev *
+       } else
+               persist_dma_mask = dma_mask = DMA_BIT_MASK(64);
+-      if (tg3_asic_rev(tp) == ASIC_REV_57766)
++      if (tg3_asic_rev(tp) == ASIC_REV_57766) {
++              dma_mask = DMA_BIT_MASK(32);
+               persist_dma_mask = DMA_BIT_MASK(31);
++      }
+       /* Configure DMA attributes. */
+       if (dma_mask > DMA_BIT_MASK(32)) {
diff --git a/target/linux/generic/pending-6.12/800-bcma-get-SoC-device-struct-copy-its-DMA-params-to-th.patch b/target/linux/generic/pending-6.12/800-bcma-get-SoC-device-struct-copy-its-DMA-params-to-th.patch
new file mode 100644 (file)
index 0000000..4a9c188
--- /dev/null
@@ -0,0 +1,73 @@
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Subject: [PATCH] bcma: get SoC device struct & copy its DMA params to the
+ subdevices
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+For bus devices to be fully usable it's required to set their DMA
+parameters.
+
+For years it has been missing and remained unnoticed because of
+mips_dma_alloc_coherent() silently handling the empty coherent_dma_mask.
+Kernel 4.19 came with a lot of DMA changes and caused a regression on
+the bcm47xx. Starting with the commit f8c55dc6e828 ("MIPS: use generic
+dma noncoherent ops for simple noncoherent platforms") DMA coherent
+allocations just fail. Example:
+[    1.114914] bgmac_bcma bcma0:2: Allocation of TX ring 0x200 failed
+[    1.121215] bgmac_bcma bcma0:2: Unable to alloc memory for DMA
+[    1.127626] bgmac_bcma: probe of bcma0:2 failed with error -12
+[    1.133838] bgmac_bcma: Broadcom 47xx GBit MAC driver loaded
+
+This change fixes above regression in addition to the MIPS bcm47xx
+commit 321c46b91550 ("MIPS: BCM47XX: Setup struct device for the SoC").
+
+It also fixes another *old* GPIO regression caused by a parent pointing
+to the NULL:
+[    0.157054] missing gpiochip .dev parent pointer
+[    0.157287] bcma: bus0: Error registering GPIO driver: -22
+introduced by the commit 74f4e0cc6108 ("bcma: switch GPIO portions to
+use GPIOLIB_IRQCHIP").
+
+Fixes: f8c55dc6e828 ("MIPS: use generic dma noncoherent ops for simple noncoherent platforms")
+Fixes: 74f4e0cc6108 ("bcma: switch GPIO portions to use GPIOLIB_IRQCHIP")
+Cc: linux-mips@linux-mips.org
+Cc: Christoph Hellwig <hch@lst.de>
+Cc: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+---
+
+--- a/drivers/bcma/host_soc.c
++++ b/drivers/bcma/host_soc.c
+@@ -191,6 +191,8 @@ int __init bcma_host_soc_init(struct bcm
+       struct bcma_bus *bus = &soc->bus;
+       int err;
++      bus->dev = soc->dev;
++
+       /* Scan bus and initialize it */
+       err = bcma_bus_early_register(bus);
+       if (err)
+--- a/drivers/bcma/main.c
++++ b/drivers/bcma/main.c
+@@ -237,13 +237,17 @@ EXPORT_SYMBOL(bcma_core_irq);
+ void bcma_prepare_core(struct bcma_bus *bus, struct bcma_device *core)
+ {
+-      device_initialize(&core->dev);
++      struct device *dev = &core->dev;
++
++      device_initialize(dev);
+       core->dev.release = bcma_release_core_dev;
+       core->dev.bus = &bcma_bus_type;
+-      dev_set_name(&core->dev, "bcma%d:%d", bus->num, core->core_index);
++      dev_set_name(dev, "bcma%d:%d", bus->num, core->core_index);
+       core->dev.parent = bus->dev;
+-      if (bus->dev)
++      if (bus->dev) {
+               bcma_of_fill_device(bus->dev, core);
++              dma_coerce_mask_and_coherent(dev, bus->dev->coherent_dma_mask);
++      }
+       switch (bus->hosttype) {
+       case BCMA_HOSTTYPE_PCI:
diff --git a/target/linux/generic/pending-6.12/802-OPP-Provide-old-opp-to-config_clks-on-_set_opp.patch b/target/linux/generic/pending-6.12/802-OPP-Provide-old-opp-to-config_clks-on-_set_opp.patch
new file mode 100644 (file)
index 0000000..042afdc
--- /dev/null
@@ -0,0 +1,134 @@
+From fd59b838dd90452f61a17dc9e5ff175205003068 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 15 Sep 2022 18:49:43 +0200
+Subject: [PATCH] OPP: Provide old opp to config_clks on _set_opp
+
+With the target opp, also pass the old opp to config_clks function.
+This can be useful when a driver needs to take decision on what fequency
+to set based on what is the current frequency without using a
+clk_get_freq call.
+Update the only user of custom config_clks (tegra30 devfreq driver) to
+this new implementation.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/devfreq/tegra30-devfreq.c |  5 +++--
+ drivers/opp/core.c                | 11 ++++++-----
+ include/linux/pm_opp.h            | 11 ++++++-----
+ 3 files changed, 15 insertions(+), 12 deletions(-)
+
+--- a/drivers/devfreq/tegra30-devfreq.c
++++ b/drivers/devfreq/tegra30-devfreq.c
+@@ -823,8 +823,9 @@ static int devm_tegra_devfreq_init_hw(st
+ static int tegra_devfreq_config_clks_nop(struct device *dev,
+                                        struct opp_table *opp_table,
+-                                       struct dev_pm_opp *opp, void *data,
+-                                       bool scaling_down)
++                                       struct dev_pm_opp *old_opp,
++                                       struct dev_pm_opp *opp,
++                                       void *data, bool scaling_down)
+ {
+       /* We want to skip clk configuration via dev_pm_opp_set_opp() */
+       return 0;
+--- a/drivers/opp/core.c
++++ b/drivers/opp/core.c
+@@ -965,7 +965,8 @@ static int _set_opp_voltage(struct devic
+ static int
+ _opp_config_clk_single(struct device *dev, struct opp_table *opp_table,
+-                     struct dev_pm_opp *opp, void *data, bool scaling_down)
++                     struct dev_pm_opp *old_opp, struct dev_pm_opp *opp,
++                     void *data, bool scaling_down)
+ {
+       unsigned long *target = data;
+       unsigned long freq;
+@@ -997,8 +998,8 @@ _opp_config_clk_single(struct device *de
+  * the order in which they are present in the array while scaling up.
+  */
+ int dev_pm_opp_config_clks_simple(struct device *dev,
+-              struct opp_table *opp_table, struct dev_pm_opp *opp, void *data,
+-              bool scaling_down)
++              struct opp_table *opp_table, struct dev_pm_opp *old_opp,
++              struct dev_pm_opp *opp, void *data, bool scaling_down)
+ {
+       int ret, i;
+@@ -1265,7 +1266,7 @@ static int _set_opp(struct device *dev,
+       }
+       if (opp_table->config_clks) {
+-              ret = opp_table->config_clks(dev, opp_table, opp, clk_data, scaling_down);
++              ret = opp_table->config_clks(dev, opp_table, old_opp, opp, clk_data, scaling_down);
+               if (ret)
+                       return ret;
+       }
+@@ -1344,7 +1345,7 @@ int dev_pm_opp_set_rate(struct device *d
+                * equivalent to a clk_set_rate()
+                */
+               if (!_get_opp_count(opp_table)) {
+-                      ret = opp_table->config_clks(dev, opp_table, NULL,
++                      ret = opp_table->config_clks(dev, opp_table, NULL, NULL,
+                                                    &target_freq, false);
+                       goto put_opp_table;
+               }
+--- a/drivers/ufs/core/ufshcd.c
++++ b/drivers/ufs/core/ufshcd.c
+@@ -1122,8 +1122,8 @@ out:
+ }
+ int ufshcd_opp_config_clks(struct device *dev, struct opp_table *opp_table,
+-                         struct dev_pm_opp *opp, void *data,
+-                         bool scaling_down)
++                         struct dev_pm_opp *old_opp, struct dev_pm_opp *opp,
++                         void *data, bool scaling_down)
+ {
+       struct ufs_hba *hba = dev_get_drvdata(dev);
+       struct list_head *head = &hba->clk_list_head;
+--- a/include/linux/pm_opp.h
++++ b/include/linux/pm_opp.h
+@@ -50,7 +50,8 @@ typedef int (*config_regulators_t)(struc
+                       struct dev_pm_opp *old_opp, struct dev_pm_opp *new_opp,
+                       struct regulator **regulators, unsigned int count);
+-typedef int (*config_clks_t)(struct device *dev, struct opp_table *opp_table,
++typedef int (*config_clks_t)(struct device *dev,
++                      struct opp_table *opp_table, struct dev_pm_opp *old_opp,
+                       struct dev_pm_opp *opp, void *data, bool scaling_down);
+ /**
+@@ -184,8 +185,8 @@ int dev_pm_opp_set_config(struct device
+ int devm_pm_opp_set_config(struct device *dev, struct dev_pm_opp_config *config);
+ void dev_pm_opp_clear_config(int token);
+ int dev_pm_opp_config_clks_simple(struct device *dev,
+-              struct opp_table *opp_table, struct dev_pm_opp *opp, void *data,
+-              bool scaling_down);
++              struct opp_table *opp_table, struct dev_pm_opp *old_opp,
++              struct dev_pm_opp *opp, void *data, bool scaling_down);
+ struct dev_pm_opp *dev_pm_opp_xlate_required_opp(struct opp_table *src_table, struct opp_table *dst_table, struct dev_pm_opp *src_opp);
+ int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate);
+@@ -395,8 +396,8 @@ static inline int devm_pm_opp_set_config
+ static inline void dev_pm_opp_clear_config(int token) {}
+ static inline int dev_pm_opp_config_clks_simple(struct device *dev,
+-              struct opp_table *opp_table, struct dev_pm_opp *opp, void *data,
+-              bool scaling_down)
++              struct opp_table *opp_table, struct dev_pm_opp *old_opp,
++              struct dev_pm_opp *opp, void *data, bool scaling_down)
+ {
+       return -EOPNOTSUPP;
+ }
+--- a/include/ufs/ufshcd.h
++++ b/include/ufs/ufshcd.h
+@@ -1333,8 +1333,8 @@ void ufshcd_mcq_enable(struct ufs_hba *h
+ void ufshcd_mcq_config_esi(struct ufs_hba *hba, struct msi_msg *msg);
+ int ufshcd_opp_config_clks(struct device *dev, struct opp_table *opp_table,
+-                         struct dev_pm_opp *opp, void *data,
+-                         bool scaling_down);
++                         struct dev_pm_opp *old_opp, struct dev_pm_opp *opp,
++                         void *data, bool scaling_down);
+ /**
+  * ufshcd_set_variant - set variant specific data to the hba
+  * @hba: per adapter instance
diff --git a/target/linux/generic/pending-6.12/804-nvmem-core-support-mac-base-fixed-layout-cells.patch b/target/linux/generic/pending-6.12/804-nvmem-core-support-mac-base-fixed-layout-cells.patch
new file mode 100644 (file)
index 0000000..8f2706e
--- /dev/null
@@ -0,0 +1,124 @@
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Thu, 13 Jul 2023 18:29:19 +0200
+Subject: [PATCH] nvmem: core: support "mac-base" fixed layout cells
+
+Fixed layout binding allows specifying "mac-base" NVMEM cells. It's used
+for base MAC address (that can be used for calculating relative
+addresses). It can be stored in a raw binary format or as an ASCII
+string.
+---
+
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -2,6 +2,7 @@
+ menuconfig NVMEM
+       bool "NVMEM Support"
+       imply NVMEM_LAYOUTS
++      select GENERIC_NET_UTILS
+       help
+         Support for NVMEM(Non Volatile Memory) devices like EEPROM, EFUSES...
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -7,9 +7,12 @@
+  */
+ #include <linux/device.h>
++#include <linux/ctype.h>
++#include <linux/etherdevice.h>
+ #include <linux/export.h>
+ #include <linux/fs.h>
+ #include <linux/idr.h>
++#include <linux/if_ether.h>
+ #include <linux/init.h>
+ #include <linux/kref.h>
+ #include <linux/module.h>
+@@ -811,6 +814,62 @@ static int nvmem_validate_keepouts(struc
+       return 0;
+ }
++static int nvmem_mac_base_raw_read(void *context, const char *id, int index, unsigned int offset,
++                                 void *buf, size_t bytes)
++{
++      if (WARN_ON(bytes != ETH_ALEN))
++              return -EINVAL;
++
++      if (index)
++              eth_addr_add(buf, index);
++
++      return 0;
++}
++
++static int nvmem_mac_base_ascii_read(void *context, const char *id, int index, unsigned int offset,
++                                   void *buf, size_t bytes)
++{
++      u8 mac[ETH_ALEN];
++
++      if (WARN_ON(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 nvmem_mac_base_hex_read(void *context, const char *id, int index, unsigned int offset,
++                                 void *buf, size_t bytes)
++{
++      u8 mac[ETH_ALEN], *hexstr;
++      int i;
++
++      if (WARN_ON(bytes != 2 * ETH_ALEN))
++              return -EINVAL;
++
++      hexstr = (u8 *)buf;
++      for (i = 0; i < ETH_ALEN; i++) {
++              if (!isxdigit(hexstr[i * 2]) || !isxdigit(hexstr[i * 2 + 1]))
++                      return -EINVAL;
++
++              mac[i] = (hex_to_bin(hexstr[i * 2]) << 4) | hex_to_bin(hexstr[i * 2 + 1]);
++      }
++
++      if (index)
++              eth_addr_add(mac, index);
++
++      ether_addr_copy(buf, mac);
++
++      return 0;
++}
++
+ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np)
+ {
+       struct device *dev = &nvmem->dev;
+@@ -852,6 +911,25 @@ static int nvmem_add_cells_from_dt(struc
+               if (nvmem->fixup_dt_cell_info)
+                       nvmem->fixup_dt_cell_info(nvmem, &info);
++              if (of_device_is_compatible(np, "fixed-layout")) {
++                      if (of_device_is_compatible(child, "mac-base")) {
++                              if (info.bytes == ETH_ALEN) {
++                                      info.raw_len = info.bytes;
++                                      info.bytes = ETH_ALEN;
++                                      info.read_post_process = nvmem_mac_base_raw_read;
++                              } else if (info.bytes == 2 * ETH_ALEN) {
++                                      info.raw_len = info.bytes;
++                                      info.bytes = ETH_ALEN;
++                                      info.read_post_process = nvmem_mac_base_hex_read;
++                              } else if (info.bytes == 3 * ETH_ALEN - 1) {
++                                      info.raw_len = info.bytes;
++                                      info.bytes = ETH_ALEN;
++                                      info.read_post_process = nvmem_mac_base_ascii_read;
++                              }
++
++                      }
++              }
++
+               ret = nvmem_add_one_cell(nvmem, &info);
+               kfree(info.name);
+               if (ret) {
diff --git a/target/linux/generic/pending-6.12/809-01-nvmem-core-generalize-mac-base-cells-handling.patch b/target/linux/generic/pending-6.12/809-01-nvmem-core-generalize-mac-base-cells-handling.patch
new file mode 100644 (file)
index 0000000..94d43e0
--- /dev/null
@@ -0,0 +1,248 @@
+From fd0e523037439520813db7c57df5bd37cdf40f7e Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 3 Feb 2025 00:10:18 +0100
+Subject: [PATCH 1/2] nvmem: core: generalize "mac-base" cells handling
+
+Generalize support of "mac-base" nvmem cells and provide a GPL symbol to
+permit also other NVMEM layout driver to parse mac-base cells.
+
+It's VERY COMMON for some specially formatted NVMEM to expose a mac
+address in ASCII format or HEX format hence prevent code duplication by
+exposing a common helper.
+
+Such helper will change the nvmem_info_cell and apply the correct post
+process function to correctly parse the mac address.
+
+Since the API requires OF and is only related to layout, move the
+function in layouts.c to correctly handle non-OF NVMEM.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/nvmem/core.c           | 79 +--------------------------------
+ drivers/nvmem/layouts.c        | 80 ++++++++++++++++++++++++++++++++++
+ include/linux/nvmem-provider.h |  4 ++
+ 3 files changed, 86 insertions(+), 77 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -7,12 +7,9 @@
+  */
+ #include <linux/device.h>
+-#include <linux/ctype.h>
+-#include <linux/etherdevice.h>
+ #include <linux/export.h>
+ #include <linux/fs.h>
+ #include <linux/idr.h>
+-#include <linux/if_ether.h>
+ #include <linux/init.h>
+ #include <linux/kref.h>
+ #include <linux/module.h>
+@@ -814,62 +811,6 @@ static int nvmem_validate_keepouts(struc
+       return 0;
+ }
+-static int nvmem_mac_base_raw_read(void *context, const char *id, int index, unsigned int offset,
+-                                 void *buf, size_t bytes)
+-{
+-      if (WARN_ON(bytes != ETH_ALEN))
+-              return -EINVAL;
+-
+-      if (index)
+-              eth_addr_add(buf, index);
+-
+-      return 0;
+-}
+-
+-static int nvmem_mac_base_ascii_read(void *context, const char *id, int index, unsigned int offset,
+-                                   void *buf, size_t bytes)
+-{
+-      u8 mac[ETH_ALEN];
+-
+-      if (WARN_ON(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 nvmem_mac_base_hex_read(void *context, const char *id, int index, unsigned int offset,
+-                                 void *buf, size_t bytes)
+-{
+-      u8 mac[ETH_ALEN], *hexstr;
+-      int i;
+-
+-      if (WARN_ON(bytes != 2 * ETH_ALEN))
+-              return -EINVAL;
+-
+-      hexstr = (u8 *)buf;
+-      for (i = 0; i < ETH_ALEN; i++) {
+-              if (!isxdigit(hexstr[i * 2]) || !isxdigit(hexstr[i * 2 + 1]))
+-                      return -EINVAL;
+-
+-              mac[i] = (hex_to_bin(hexstr[i * 2]) << 4) | hex_to_bin(hexstr[i * 2 + 1]);
+-      }
+-
+-      if (index)
+-              eth_addr_add(mac, index);
+-
+-      ether_addr_copy(buf, mac);
+-
+-      return 0;
+-}
+-
+ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np)
+ {
+       struct device *dev = &nvmem->dev;
+@@ -911,24 +852,8 @@ static int nvmem_add_cells_from_dt(struc
+               if (nvmem->fixup_dt_cell_info)
+                       nvmem->fixup_dt_cell_info(nvmem, &info);
+-              if (of_device_is_compatible(np, "fixed-layout")) {
+-                      if (of_device_is_compatible(child, "mac-base")) {
+-                              if (info.bytes == ETH_ALEN) {
+-                                      info.raw_len = info.bytes;
+-                                      info.bytes = ETH_ALEN;
+-                                      info.read_post_process = nvmem_mac_base_raw_read;
+-                              } else if (info.bytes == 2 * ETH_ALEN) {
+-                                      info.raw_len = info.bytes;
+-                                      info.bytes = ETH_ALEN;
+-                                      info.read_post_process = nvmem_mac_base_hex_read;
+-                              } else if (info.bytes == 3 * ETH_ALEN - 1) {
+-                                      info.raw_len = info.bytes;
+-                                      info.bytes = ETH_ALEN;
+-                                      info.read_post_process = nvmem_mac_base_ascii_read;
+-                              }
+-
+-                      }
+-              }
++              if (of_device_is_compatible(np, "fixed-layout"))
++                      nvmem_layout_parse_mac_base(&info);
+               ret = nvmem_add_one_cell(nvmem, &info);
+               kfree(info.name);
+--- a/drivers/nvmem/layouts.c
++++ b/drivers/nvmem/layouts.c
+@@ -6,8 +6,11 @@
+  * Author: Miquel Raynal <miquel.raynal@bootlin.com
+  */
++#include <linux/ctype.h>
+ #include <linux/device.h>
+ #include <linux/dma-mapping.h>
++#include <linux/etherdevice.h>
++#include <linux/if_ether.h>
+ #include <linux/nvmem-consumer.h>
+ #include <linux/nvmem-provider.h>
+ #include <linux/of.h>
+@@ -21,6 +24,83 @@
+ #define to_nvmem_layout_device(_dev) \
+       container_of((_dev), struct nvmem_layout, dev)
++static int nvmem_mac_base_raw_read(void *context, const char *id, int index, unsigned int offset,
++                                 void *buf, size_t bytes)
++{
++      if (WARN_ON(bytes != ETH_ALEN))
++              return -EINVAL;
++
++      if (index)
++              eth_addr_add(buf, index);
++
++      return 0;
++}
++
++static int nvmem_mac_base_ascii_read(void *context, const char *id, int index, unsigned int offset,
++                                   void *buf, size_t bytes)
++{
++      u8 mac[ETH_ALEN];
++
++      if (WARN_ON(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 nvmem_mac_base_hex_read(void *context, const char *id, int index, unsigned int offset,
++                                 void *buf, size_t bytes)
++{
++      u8 mac[ETH_ALEN], *hexstr;
++      int i;
++
++      if (WARN_ON(bytes != 2 * ETH_ALEN))
++              return -EINVAL;
++
++      hexstr = (u8 *)buf;
++      for (i = 0; i < ETH_ALEN; i++) {
++              if (!isxdigit(hexstr[i * 2]) || !isxdigit(hexstr[i * 2 + 1]))
++                      return -EINVAL;
++
++              mac[i] = (hex_to_bin(hexstr[i * 2]) << 4) | hex_to_bin(hexstr[i * 2 + 1]);
++      }
++
++      if (index)
++              eth_addr_add(mac, index);
++
++      ether_addr_copy(buf, mac);
++
++      return 0;
++}
++
++void nvmem_layout_parse_mac_base(struct nvmem_cell_info *info)
++{
++      if (!of_device_is_compatible(info->np, "mac-base"))
++              return;
++
++      if (info->bytes == ETH_ALEN) {
++              info->raw_len = info->bytes;
++              info->bytes = ETH_ALEN;
++              info->read_post_process = nvmem_mac_base_raw_read;
++      } else if (info->bytes == 2 * ETH_ALEN) {
++              info->raw_len = info->bytes;
++              info->bytes = ETH_ALEN;
++              info->read_post_process = nvmem_mac_base_hex_read;
++      } else if (info->bytes == 3 * ETH_ALEN - 1) {
++              info->raw_len = info->bytes;
++              info->bytes = ETH_ALEN;
++              info->read_post_process = nvmem_mac_base_ascii_read;
++      }
++}
++EXPORT_SYMBOL_GPL(nvmem_layout_parse_mac_base);
++
+ static int nvmem_layout_bus_match(struct device *dev, const struct device_driver *drv)
+ {
+       return of_driver_match_device(dev, drv);
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -242,6 +242,8 @@ static inline void nvmem_layout_unregist
+ #if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OF)
++void nvmem_layout_parse_mac_base(struct nvmem_cell_info *info);
++
+ /**
+  * of_nvmem_layout_get_container() - Get OF node of layout container
+  *
+@@ -254,6 +256,8 @@ struct device_node *of_nvmem_layout_get_
+ #else  /* CONFIG_NVMEM && CONFIG_OF */
++static inline void nvmem_layout_parse_mac_base(struct nvmem_cell_info *info) {}
++
+ static inline struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
+ {
+       return NULL;
diff --git a/target/linux/generic/pending-6.12/809-02-nvmem-layouts-add-support-for-ascii-env-driver.patch b/target/linux/generic/pending-6.12/809-02-nvmem-layouts-add-support-for-ascii-env-driver.patch
new file mode 100644 (file)
index 0000000..0906e0f
--- /dev/null
@@ -0,0 +1,204 @@
+From 38287e8ec5c0281377fc70f11f20bcd9986a05f5 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 3 Feb 2025 00:36:12 +0100
+Subject: [PATCH 2/2] nvmem: layouts: add support for ascii-env driver
+
+Add support for simple ASCII envirorment driver for NVMEM layouts.
+
+It's very common for devices to store simple text file format in
+partition for environment varibles. The most common pattern is variable
+name, a delimiter and variable value all separated by a new line
+character (\n).
+
+This driver adds support for exporting such data and expose NVMEM cells
+so they can be referenced by other drivers. This driver also supports
+parsing mac-base NVMEM cells to parse ASCII or HEX mac address.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/nvmem/layouts/Kconfig     |  13 +++
+ drivers/nvmem/layouts/Makefile    |   1 +
+ drivers/nvmem/layouts/ascii-env.c | 140 ++++++++++++++++++++++++++++++
+ 3 files changed, 154 insertions(+)
+ create mode 100644 drivers/nvmem/layouts/ascii-env.c
+
+--- a/drivers/nvmem/layouts/Kconfig
++++ b/drivers/nvmem/layouts/Kconfig
+@@ -37,6 +37,19 @@ config NVMEM_LAYOUT_U_BOOT_ENV
+         If unsure, say N.
++config NVMEM_LAYOUT_ASCII_ENV
++      tristate "ASCII environment variables layout"
++      help
++        It's very common for devices to store simple text file format in
++        partition for environment varibles. The most common pattern is variable
++        name, a delimiter and variable value all separated by a new line
++        character (\n).
++        This driver adds support for exporting such data and expose NVMEM cells
++        so they can be referenced by other drivers. This driver also supports
++        parsing mac-base NVMEM cells to parse ASCII or HEX mac address.
++
++        If unsure, say N.
++
+ endmenu
+ endif
+--- a/drivers/nvmem/layouts/Makefile
++++ b/drivers/nvmem/layouts/Makefile
+@@ -6,3 +6,4 @@
+ obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o
+ obj-$(CONFIG_NVMEM_LAYOUT_ONIE_TLV) += onie-tlv.o
+ obj-$(CONFIG_NVMEM_LAYOUT_U_BOOT_ENV) += u-boot-env.o
++obj-$(CONFIG_NVMEM_LAYOUT_ASCII_ENV) += ascii-env.o
+--- /dev/null
++++ b/drivers/nvmem/layouts/ascii-env.c
+@@ -0,0 +1,148 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2024 Christian Marangi <ansuelsmth@gmail.com>
++ *
++ * This borrow some parse logic from u-boot-env.
++ */
++#include <linux/nvmem-consumer.h>
++#include <linux/nvmem-provider.h>
++#include <linux/of.h>
++#include <linux/slab.h>
++
++struct ascii_env_match_data {
++      const char delim;
++};
++
++/*
++ * Parse a buffer as an ASCII text with name delimiter value and each pattern separated
++ * with a new line char '\n'
++ * Example: (delimiter '=')
++ *   name=value\nname2=value2\n
++ *   2 Cell:
++ *   - name: value
++ *   - name2: value2
++ */
++static int ascii_env_parse_cells(struct device *dev, struct nvmem_device *nvmem, uint8_t *buf,
++                               size_t data_len, const char delim)
++{
++      char *var, *value, *eq, *lf;
++      char *data = buf;
++
++      /*
++       * Warning the inner loop take care of replacing '\n'
++       * with '\0', hence we can use strlen on value.
++       */
++      for (var = data; var < data + data_len && *var;
++           var = value + strlen(value) + 1) {
++              struct nvmem_cell_info info = {};
++              struct device_node *child;
++              const char *label;
++
++              eq = strchr(var, delim);
++              if (!eq)
++                      break;
++              *eq = '\0';
++              value = eq + 1;
++
++              /* Replace '\n' with '\0' to use strlen for value */
++              lf = strchr(value, '\n');
++              if (!lf)
++                      break;
++              *lf = '\0';
++
++              info.name = devm_kstrdup(dev, var, GFP_KERNEL);
++              if (!info.name)
++                      return -ENOMEM;
++              info.offset = value - data;
++              info.bytes = strlen(value);
++              info.np = of_get_child_by_name(dev->of_node, info.name);
++              for_each_child_of_node(dev->of_node, child) {
++                      if (!of_property_read_string(child, "label", &label) &&
++                          !strncmp(info.name, label, info.bytes))
++                              info.np = child;
++                      else if (of_node_name_eq(child, info.name))
++                              info.np = child;
++              }
++
++              nvmem_layout_parse_mac_base(&info);
++
++              nvmem_add_one_cell(nvmem, &info);
++      }
++
++      return 0;
++}
++
++static int ascii_env_add_cells(struct nvmem_layout *layout)
++{
++      struct nvmem_device *nvmem = layout->nvmem;
++      const struct ascii_env_match_data *data;
++      struct device *dev = &layout->dev;
++      size_t dev_size;
++      uint8_t *buf;
++      int bytes;
++      int ret;
++
++      /* Get the delimiter for name value pattern */
++      data = device_get_match_data(dev);
++
++      dev_size = nvmem_dev_size(nvmem);
++
++      buf = kzalloc(dev_size, GFP_KERNEL);
++      if (!buf) {
++              ret = -ENOMEM;
++              goto err_out;
++      }
++
++      bytes = nvmem_device_read(nvmem, 0, dev_size, buf);
++      if (bytes < 0) {
++              ret = bytes;
++              goto err_kfree;
++      } else if (bytes != dev_size) {
++              ret = -EIO;
++              goto err_kfree;
++      }
++
++      buf[dev_size - 1] = '\0';
++      ret = ascii_env_parse_cells(dev, nvmem, buf, dev_size, data->delim);
++
++err_kfree:
++      kfree(buf);
++err_out:
++      return ret;
++}
++
++static int ascii_env_probe(struct nvmem_layout *layout)
++{
++      layout->add_cells = ascii_env_add_cells;
++
++      return nvmem_layout_register(layout);
++}
++
++static void ascii_env_remove(struct nvmem_layout *layout)
++{
++      nvmem_layout_unregister(layout);
++}
++
++static const struct ascii_env_match_data ascii_env_eq = {
++      .delim = '=',
++};
++
++static const struct of_device_id ascii_env_of_match_table[] = {
++      { .compatible = "ascii-eq-delim-env", .data = &ascii_env_eq, },
++      {},
++};
++
++static struct nvmem_layout_driver ascii_env_layout = {
++      .driver = {
++              .name = "ascii-env-layout",
++              .of_match_table = ascii_env_of_match_table,
++      },
++      .probe = ascii_env_probe,
++      .remove = ascii_env_remove,
++};
++module_nvmem_layout_driver(ascii_env_layout);
++
++MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
++MODULE_LICENSE("GPL");
++MODULE_DEVICE_TABLE(of, ascii_env_of_match_table);
++MODULE_DESCRIPTION("NVMEM layout driver for ASCII environment variables");
diff --git a/target/linux/generic/pending-6.12/809-03-nvmem-layouts-ascii-env-handle-CRLF-while-parsing.patch b/target/linux/generic/pending-6.12/809-03-nvmem-layouts-ascii-env-handle-CRLF-while-parsing.patch
new file mode 100644 (file)
index 0000000..ab5fc58
--- /dev/null
@@ -0,0 +1,58 @@
+From: George Moussalem <george.moussalem@outlook.com>
+Date: Thu, 06 Feb 2025 21:55:28 +0400
+Subject: [PATCH] nvmem: layouts: ascii-env handle CRLF while parsing
+
+The current driver supports LF line endings only.
+
+For CRLF-based line endings in the ASCII env, the length of the value of
+the variable passed to nvmem_layout_parse_mac_base is 18 bytes instead
+of an expected length of 17 causing the parsing to fail.
+So, let's add the ability to handle CRLF line endings by adding a
+condition to check if the value ends with a '\r' character and replace it
+with '\0' to properly parse the mac address.
+
+Tested on Linksys MX2000, MX5500, and SPNMX56.
+
+Signed-off-by: George Moussalem <george.moussalem@outlook.com>
+---
+--- a/drivers/nvmem/layouts/ascii-env.c
++++ b/drivers/nvmem/layouts/ascii-env.c
+@@ -25,18 +25,20 @@ struct ascii_env_match_data {
+ static int ascii_env_parse_cells(struct device *dev, struct nvmem_device *nvmem, uint8_t *buf,
+                                size_t data_len, const char delim)
+ {
+-      char *var, *value, *eq, *lf;
++      char *var, *value, *eq, *lf, *cr;
+       char *data = buf;
++      uint incr = 0;
+       /*
+        * Warning the inner loop take care of replacing '\n'
+        * with '\0', hence we can use strlen on value.
+        */
+       for (var = data; var < data + data_len && *var;
+-           var = value + strlen(value) + 1) {
++           var = value + strlen(value) + incr) {
+               struct nvmem_cell_info info = {};
+               struct device_node *child;
+               const char *label;
++              incr = 0;
+               eq = strchr(var, delim);
+               if (!eq)
+@@ -49,6 +51,15 @@ static int ascii_env_parse_cells(struct
+               if (!lf)
+                       break;
+               *lf = '\0';
++              incr++;
++
++              /* For CRLF based env, replace '\r' with '\0' too to use strlen
++               * for value, and increment var by one in loop for next variable */
++              cr = strchr(value, '\r');
++              if (cr) {
++                      *cr = '\0';
++                      incr++;
++              }
+               info.name = devm_kstrdup(dev, var, GFP_KERNEL);
+               if (!info.name)
diff --git a/target/linux/generic/pending-6.12/810-pci_disable_common_quirks.patch b/target/linux/generic/pending-6.12/810-pci_disable_common_quirks.patch
new file mode 100644 (file)
index 0000000..89dcbc2
--- /dev/null
@@ -0,0 +1,62 @@
+From: Gabor Juhos <juhosg@openwrt.org>
+Subject: debloat: add kernel config option to disabling common PCI quirks
+
+Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
+---
+ drivers/pci/Kconfig  | 6 ++++++
+ drivers/pci/quirks.c | 6 ++++++
+ 2 files changed, 12 insertions(+)
+
+--- a/drivers/pci/Kconfig
++++ b/drivers/pci/Kconfig
+@@ -118,6 +118,13 @@ config XEN_PCIDEV_FRONTEND
+         The PCI device frontend driver allows the kernel to import arbitrary
+         PCI devices from a PCI backend to support PCI driver domains.
++config PCI_DISABLE_COMMON_QUIRKS
++      bool "PCI disable common quirks"
++      depends on PCI
++      help
++        If you don't know what to do here, say N.
++
++
+ config PCI_ATS
+       bool
+--- a/drivers/pci/quirks.c
++++ b/drivers/pci/quirks.c
+@@ -313,6 +313,7 @@ static void quirk_mmio_always_on(struct
+ DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_ANY_ID, PCI_ANY_ID,
+                               PCI_CLASS_BRIDGE_HOST, 8, quirk_mmio_always_on);
++#ifndef CONFIG_PCI_DISABLE_COMMON_QUIRKS
+ /*
+  * The Mellanox Tavor device gives false positive parity errors.  Disable
+  * parity error reporting.
+@@ -3509,6 +3510,8 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_I
+ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x65f9, quirk_intel_mc_errata);
+ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x65fa, quirk_intel_mc_errata);
++#endif /* !CONFIG_PCI_DISABLE_COMMON_QUIRKS */
++
+ /*
+  * Ivytown NTB BAR sizes are misreported by the hardware due to an erratum.
+  * To work around this, query the size it should be configured to by the
+@@ -3534,6 +3537,8 @@ static void quirk_intel_ntb(struct pci_d
+ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x0e08, quirk_intel_ntb);
+ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x0e0d, quirk_intel_ntb);
++#ifndef CONFIG_PCI_DISABLE_COMMON_QUIRKS
++
+ /*
+  * Some BIOS implementations leave the Intel GPU interrupts enabled, even
+  * though no one is handling them (e.g., if the i915 driver is never
+@@ -3572,6 +3577,8 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_IN
+ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x010a, disable_igfx_irq);
+ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x0152, disable_igfx_irq);
++#endif /* !CONFIG_PCI_DISABLE_COMMON_QUIRKS */
++
+ /*
+  * PCI devices which are on Intel chips can skip the 10ms delay
+  * before entering D3 mode.
diff --git a/target/linux/generic/pending-6.12/811-pci_disable_usb_common_quirks.patch b/target/linux/generic/pending-6.12/811-pci_disable_usb_common_quirks.patch
new file mode 100644 (file)
index 0000000..4e0360d
--- /dev/null
@@ -0,0 +1,63 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sat, 26 Apr 2025 21:10:40 +0200
+Subject: [PATCH] debloat: disable common USB quirks
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ drivers/usb/host/pci-quirks.c | 7 +++++++
+ drivers/usb/host/pci-quirks.h | 4 ++++
+ 2 files changed, 11 insertions(+)
+
+--- a/drivers/usb/host/pci-quirks.c
++++ b/drivers/usb/host/pci-quirks.c
+@@ -590,6 +590,7 @@ bool usb_amd_pt_check_port(struct device
+ EXPORT_SYMBOL_GPL(usb_amd_pt_check_port);
+ #endif /* CONFIG_USB_PCI_AMD */
++#ifndef CONFIG_PCI_DISABLE_COMMON_QUIRKS
+ static int usb_asmedia_wait_write(struct pci_dev *pdev)
+ {
+       unsigned long retry_count;
+@@ -642,6 +643,7 @@ static inline int io_type_enabled(struct
+ }
+ #define mmio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_MEMORY)
++#endif /* !CONFIG_PCI_DISABLE_COMMON_QUIRKS */
+ #if defined(CONFIG_HAS_IOPORT) && IS_ENABLED(CONFIG_USB_UHCI_HCD)
+ /*
+@@ -724,6 +726,10 @@ reset_needed:
+       return 1;
+ }
+ EXPORT_SYMBOL_GPL(uhci_check_and_reset_hc);
++#endif
++
++#ifndef CONFIG_PCI_DISABLE_COMMON_QUIRKS
++#if defined(CONFIG_HAS_IOPORT) && IS_ENABLED(CONFIG_USB_UHCI_HCD)
+ #define pio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_IO)
+@@ -1304,3 +1310,4 @@ static void quirk_usb_early_handoff(stru
+ }
+ DECLARE_PCI_FIXUP_CLASS_FINAL(PCI_ANY_ID, PCI_ANY_ID,
+                       PCI_CLASS_SERIAL_USB, 8, quirk_usb_early_handoff);
++#endif /* !CONFIG_PCI_DISABLE_COMMON_QUIRKS */
+--- a/drivers/usb/host/pci-quirks.h
++++ b/drivers/usb/host/pci-quirks.h
+@@ -38,12 +38,16 @@ static inline bool usb_amd_pt_check_port
+ #ifdef CONFIG_USB_PCI
+ void uhci_reset_hc(struct pci_dev *pdev, unsigned long base);
+ int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base);
++#endif
++
++#if defined(CONFIG_USB_PCI) && !defined(CONFIG_PCI_DISABLE_COMMON_QUIRKS)
+ void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev);
+ void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev);
+ void usb_disable_xhci_ports(struct pci_dev *xhci_pdev);
+ #else
+ struct pci_dev;
+ static inline void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev) {}
++static inline void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev) {}
+ static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {}
+ #endif  /* CONFIG_USB_PCI */
diff --git a/target/linux/generic/pending-6.12/812-PCI-sysfs-enforce-single-creation-of-sysfs-entry-for.patch b/target/linux/generic/pending-6.12/812-PCI-sysfs-enforce-single-creation-of-sysfs-entry-for.patch
new file mode 100644 (file)
index 0000000..e5f54d7
--- /dev/null
@@ -0,0 +1,142 @@
+From d33941523a8379e30070374b133b28a2077dcef8 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 13 Oct 2025 20:45:25 +0200
+Subject: [PATCH] PCI/sysfs: enforce single creation of sysfs entry for pdev
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+In some specific scenario it's possible that the
+pci_create_resource_files() gets called multiple times and the created
+entry actually gets wrongly deleted with extreme case of having a NULL
+pointer dereference when the PCI is removed.
+
+This mainly happen due to bad timing where the PCI bus is adding PCI
+devices and at the same time the sysfs code is adding the entry causing
+double execution of the pci_create_resource_files function and kernel
+WARNING.
+
+To be more precise there is a race between the late_initcall of
+pci-sysfs with pci_sysfs_init and PCI bus.c pci_bus_add_device that also
+call pci_create_sysfs_dev_files.
+
+With correct amount of ""luck"" (or better say bad luck)
+pci_create_sysfs_dev_files in bus.c might be called with pci_sysfs_init
+is executing the loop.
+
+This has been reported multiple times and on multiple system, like imx6
+system, ipq806x systems...
+
+To address this, imlement multiple improvement to the implementation:
+1. Add a bool to pci_dev to flag when sysfs entry are created
+   (sysfs_init)
+2. Implement a simple completion to wait pci_sysfs_init execution.
+3. Permit additional call of pci_create_sysfs_dev_files only after
+   pci_sysfs_init has finished.
+
+With such logic in place, we address al kind of timing problem with
+minimal change to any driver.
+
+A notice worth to mention is that the remove function are not affected
+by this as the pci_remove_resource_files have enough check in place to
+always work and it's always called by pci_stop_dev.
+
+Cc: stable@vger.kernel.org
+Reported-by: Krzysztof Hałasa <khalasa@piap.pl>
+Closes: https://bugzilla.kernel.org/show_bug.cgi?id=215515
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/pci/pci-sysfs.c | 34 +++++++++++++++++++++++++++++-----
+ include/linux/pci.h     |  1 +
+ 2 files changed, 30 insertions(+), 5 deletions(-)
+
+--- a/drivers/pci/pci-sysfs.c
++++ b/drivers/pci/pci-sysfs.c
+@@ -13,6 +13,7 @@
+  */
+ #include <linux/bitfield.h>
++#include <linux/completion.h>
+ #include <linux/kernel.h>
+ #include <linux/sched.h>
+ #include <linux/pci.h>
+@@ -36,6 +37,7 @@
+ #endif
+ static int sysfs_initialized; /* = 0 */
++static DECLARE_COMPLETION(sysfs_init_completion);
+ /* show configuration fields */
+ #define pci_config_attr(field, format_string)                         \
+@@ -1551,12 +1553,32 @@ static const struct attribute_group pci_
+       .is_visible = resource_resize_is_visible,
+ };
++static int __pci_create_sysfs_dev_files(struct pci_dev *pdev)
++{
++      int ret;
++
++      ret = pci_create_resource_files(pdev);
++      if (ret)
++              return ret;
++
++      /* on success set sysfs correctly created */
++      pdev->sysfs_init = true;
++      return 0;
++}
++
+ int __must_check pci_create_sysfs_dev_files(struct pci_dev *pdev)
+ {
+       if (!sysfs_initialized)
+               return -EACCES;
+-      return pci_create_resource_files(pdev);
++      /* sysfs entry already created */
++      if (pdev->sysfs_init)
++              return 0;
++
++      /* wait for pci_sysfs_init */
++      wait_for_completion(&sysfs_init_completion);
++
++      return __pci_create_sysfs_dev_files(pdev);
+ }
+ /**
+@@ -1577,21 +1599,23 @@ static int __init pci_sysfs_init(void)
+ {
+       struct pci_dev *pdev = NULL;
+       struct pci_bus *pbus = NULL;
+-      int retval;
++      int retval = 0;
+       sysfs_initialized = 1;
+       for_each_pci_dev(pdev) {
+-              retval = pci_create_sysfs_dev_files(pdev);
++              retval = __pci_create_sysfs_dev_files(pdev);
+               if (retval) {
+                       pci_dev_put(pdev);
+-                      return retval;
++                      goto exit;
+               }
+       }
+       while ((pbus = pci_find_next_bus(pbus)))
+               pci_create_legacy_files(pbus);
+-      return 0;
++exit:
++      complete_all(&sysfs_init_completion);
++      return retval;
+ }
+ late_initcall(pci_sysfs_init);
+--- a/include/linux/pci.h
++++ b/include/linux/pci.h
+@@ -484,6 +484,7 @@ struct pci_dev {
+       unsigned int    rom_attr_enabled:1;     /* Display of ROM attribute enabled? */
+       pci_dev_flags_t dev_flags;
+       atomic_t        enable_cnt;     /* pci_enable_device has been called */
++      bool            sysfs_init;     /* sysfs entry has been created */
+       spinlock_t      pcie_cap_lock;          /* Protects RMW ops in capability accessors */
+       u32             saved_config_space[16]; /* Config space saved at suspend time */
diff --git a/target/linux/generic/pending-6.12/834-ledtrig-libata.patch b/target/linux/generic/pending-6.12/834-ledtrig-libata.patch
new file mode 100644 (file)
index 0000000..4b497d9
--- /dev/null
@@ -0,0 +1,145 @@
+From: Daniel Golle <daniel@makrotopia.org>
+Subject: libata: add ledtrig support
+
+This adds a LED trigger for each ATA port indicating disk activity.
+
+As this is needed only on specific platforms (NAS SoCs and such),
+these platforms should define ARCH_WANTS_LIBATA_LEDS if there
+are boards with LED(s) intended to indicate ATA disk activity and
+need the OS to take care of that.
+In that way, if not selected, LED trigger support not will be
+included in libata-core and both, codepaths and structures remain
+untouched.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/ata/Kconfig       | 16 ++++++++++++++++
+ drivers/ata/libata-core.c | 41 +++++++++++++++++++++++++++++++++++++++++
+ include/linux/libata.h    |  9 +++++++++
+ 3 files changed, 66 insertions(+)
+
+--- a/drivers/ata/Kconfig
++++ b/drivers/ata/Kconfig
+@@ -67,6 +67,22 @@ config ATA_FORCE
+         If unsure, say Y.
++config ARCH_WANT_LIBATA_LEDS
++      bool
++
++config ATA_LEDS
++      bool "support ATA port LED triggers"
++      depends on ARCH_WANT_LIBATA_LEDS
++      select NEW_LEDS
++      select LEDS_CLASS
++      select LEDS_TRIGGERS
++      default y
++      help
++        This option adds a LED trigger for each registered ATA port.
++        It is used to drive disk activity leds connected via GPIO.
++
++        If unsure, say N.
++
+ config ATA_ACPI
+       bool "ATA ACPI Support"
+       depends on ACPI
+--- a/drivers/ata/libata-core.c
++++ b/drivers/ata/libata-core.c
+@@ -703,6 +703,17 @@ static inline void ata_set_tf_cdl(struct
+       qc->flags |= ATA_QCFLAG_HAS_CDL | ATA_QCFLAG_RESULT_TF;
+ }
++#ifdef CONFIG_ATA_LEDS
++#define LIBATA_BLINK_DELAY 20 /* ms */
++static inline void ata_led_act(struct ata_port *ap)
++{
++      if (unlikely(!ap->ledtrig))
++              return;
++
++      led_trigger_blink_oneshot(ap->ledtrig, LIBATA_BLINK_DELAY, LIBATA_BLINK_DELAY, 0);
++}
++#endif
++
+ /**
+  *    ata_build_rw_tf - Build ATA taskfile for given read/write request
+  *    @qc: Metadata associated with the taskfile to build
+@@ -4807,6 +4818,9 @@ void __ata_qc_complete(struct ata_queued
+               link->active_tag = ATA_TAG_POISON;
+               ap->nr_active_links--;
+       }
++#ifdef CONFIG_ATA_LEDS
++      ata_led_act(ap);
++#endif
+       /* clear exclusive status */
+       if (unlikely(qc->flags & ATA_QCFLAG_CLEAR_EXCL &&
+@@ -5536,6 +5550,9 @@ struct ata_port *ata_port_alloc(struct a
+       ap->stats.unhandled_irq = 1;
+       ap->stats.idle_irq = 1;
+ #endif
++#ifdef CONFIG_ATA_LEDS
++      ap->ledtrig = kzalloc(sizeof(struct led_trigger), GFP_KERNEL);
++#endif
+       ata_sff_port_init(ap);
+       ata_force_pflags(ap);
+@@ -5552,6 +5569,12 @@ void ata_port_free(struct ata_port *ap)
+       kfree(ap->pmp_link);
+       kfree(ap->slave_link);
+       ida_free(&ata_ida, ap->print_id);
++#ifdef CONFIG_ATA_LEDS
++      if (ap->ledtrig) {
++              led_trigger_unregister(ap->ledtrig);
++              kfree(ap->ledtrig);
++      };
++#endif
+       kfree(ap);
+ }
+ EXPORT_SYMBOL_GPL(ata_port_free);
+@@ -5956,7 +5979,23 @@ int ata_host_register(struct ata_host *h
+               WARN_ON(1);
+               return -EINVAL;
+       }
++#ifdef CONFIG_ATA_LEDS
++      for (i = 0; i < host->n_ports; i++) {
++              if (unlikely(!host->ports[i]->ledtrig))
++                      continue;
++              snprintf(host->ports[i]->ledtrig_name,
++                      sizeof(host->ports[i]->ledtrig_name), "ata%u",
++                      host->ports[i]->print_id);
++
++              host->ports[i]->ledtrig->name = host->ports[i]->ledtrig_name;
++
++              if (led_trigger_register(host->ports[i]->ledtrig)) {
++                      kfree(host->ports[i]->ledtrig);
++                      host->ports[i]->ledtrig = NULL;
++              }
++      }
++#endif
+       /* Create associated sysfs transport objects  */
+       for (i = 0; i < host->n_ports; i++) {
+               rc = ata_tport_add(host->dev,host->ports[i]);
+--- a/include/linux/libata.h
++++ b/include/linux/libata.h
+@@ -23,6 +23,9 @@
+ #include <linux/cdrom.h>
+ #include <linux/sched.h>
+ #include <linux/async.h>
++#ifdef CONFIG_ATA_LEDS
++#include <linux/leds.h>
++#endif
+ /*
+  * Define if arch has non-standard setup.  This is a _PCI_ standard
+@@ -937,6 +940,10 @@ struct ata_port {
+ #ifdef CONFIG_ATA_ACPI
+       struct ata_acpi_gtm     __acpi_init_gtm; /* use ata_acpi_init_gtm() */
+ #endif
++#ifdef CONFIG_ATA_LEDS
++      struct led_trigger      *ledtrig;
++      char                    ledtrig_name[8];
++#endif
+ };
+ /* The following initializer overrides a method to NULL whether one of
diff --git a/target/linux/generic/pending-6.12/840-hwrng-bcm2835-set-quality-to-1000.patch b/target/linux/generic/pending-6.12/840-hwrng-bcm2835-set-quality-to-1000.patch
new file mode 100644 (file)
index 0000000..3172ad5
--- /dev/null
@@ -0,0 +1,26 @@
+From d6988cf1d16faac56899918bb2b1be8d85155e3f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= <noltari@gmail.com>
+Date: Sat, 20 Feb 2021 18:36:38 +0100
+Subject: [PATCH] hwrng: bcm2835: set quality to 1000
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This allows devices without a high precission timer to reduce boot from >100s
+to <30s.
+
+Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+---
+ drivers/char/hw_random/bcm2835-rng.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/char/hw_random/bcm2835-rng.c
++++ b/drivers/char/hw_random/bcm2835-rng.c
+@@ -169,6 +169,7 @@ static int bcm2835_rng_probe(struct plat
+       priv->rng.init = bcm2835_rng_init;
+       priv->rng.read = bcm2835_rng_read;
+       priv->rng.cleanup = bcm2835_rng_cleanup;
++      priv->rng.quality = 1000;
+       if (dev_of_node(dev)) {
+               rng_id = of_match_node(bcm2835_rng_of_match, dev->of_node);
diff --git a/target/linux/generic/pending-6.12/850-0023-PCI-aardvark-Make-main-irq_chip-structure-a-static-d.patch b/target/linux/generic/pending-6.12/850-0023-PCI-aardvark-Make-main-irq_chip-structure-a-static-d.patch
new file mode 100644 (file)
index 0000000..6a90959
--- /dev/null
@@ -0,0 +1,102 @@
+From 663b9f99bb35dbc0c7b685f71ee3668a60d31320 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Mon, 10 Jan 2022 02:02:00 +0100
+Subject: [PATCH] PCI: aardvark: Make main irq_chip structure a static driver
+ structure
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Marc Zyngier says [1] that we should use struct irq_chip as a global
+static struct in the driver. Even though the structure currently
+contains a dynamic member (parent_device), Marc says [2] that he plans
+to kill it and make the structure completely static.
+
+We have already converted others irq_chip structures in this driver in
+this way, but we omitted this one because the .name member is
+dynamically created from device's name, and the name is displayed in
+sysfs, so changing it would break sysfs ABI.
+
+The rationale for changing the name (to "advk-INT") in spite of sysfs
+ABI, and thus allowing to convert to a static structure, is that after
+the other changes we made in this series, the IRQ chip is basically
+something different: it no logner generates ERR and PME interrupts (they
+are generated by emulated bridge's rp_irq_chip).
+
+[1] https://lore.kernel.org/linux-pci/877dbcvngf.wl-maz@kernel.org/
+[2] https://lore.kernel.org/linux-pci/874k6gvkhz.wl-maz@kernel.org/
+
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/pci/controller/pci-aardvark.c | 25 +++++++------------------
+ 1 file changed, 7 insertions(+), 18 deletions(-)
+
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -276,7 +276,6 @@ struct advk_pcie {
+       u8 wins_count;
+       struct irq_domain *rp_irq_domain;
+       struct irq_domain *irq_domain;
+-      struct irq_chip irq_chip;
+       raw_spinlock_t irq_lock;
+       struct irq_domain *msi_domain;
+       struct irq_domain *msi_inner_domain;
+@@ -1418,14 +1417,19 @@ static void advk_pcie_irq_unmask(struct
+       raw_spin_unlock_irqrestore(&pcie->irq_lock, flags);
+ }
++static struct irq_chip advk_irq_chip = {
++      .name           = "advk-INT",
++      .irq_mask       = advk_pcie_irq_mask,
++      .irq_unmask     = advk_pcie_irq_unmask,
++};
++
+ static int advk_pcie_irq_map(struct irq_domain *h,
+                            unsigned int virq, irq_hw_number_t hwirq)
+ {
+       struct advk_pcie *pcie = h->host_data;
+       irq_set_status_flags(virq, IRQ_LEVEL);
+-      irq_set_chip_and_handler(virq, &pcie->irq_chip,
+-                               handle_level_irq);
++      irq_set_chip_and_handler(virq, &advk_irq_chip, handle_level_irq);
+       irq_set_chip_data(virq, pcie);
+       return 0;
+@@ -1485,7 +1489,6 @@ static int advk_pcie_init_irq_domain(str
+       struct device *dev = &pcie->pdev->dev;
+       struct device_node *node = dev->of_node;
+       struct device_node *pcie_intc_node;
+-      struct irq_chip *irq_chip;
+       int ret = 0;
+       raw_spin_lock_init(&pcie->irq_lock);
+@@ -1496,28 +1499,14 @@ static int advk_pcie_init_irq_domain(str
+               return -ENODEV;
+       }
+-      irq_chip = &pcie->irq_chip;
+-
+-      irq_chip->name = devm_kasprintf(dev, GFP_KERNEL, "%s-irq",
+-                                      dev_name(dev));
+-      if (!irq_chip->name) {
+-              ret = -ENOMEM;
+-              goto out_put_node;
+-      }
+-
+-      irq_chip->irq_mask = advk_pcie_irq_mask;
+-      irq_chip->irq_unmask = advk_pcie_irq_unmask;
+-
+       pcie->irq_domain =
+               irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX,
+                                     &advk_pcie_irq_domain_ops, pcie);
+       if (!pcie->irq_domain) {
+               dev_err(dev, "Failed to get a INTx IRQ domain\n");
+               ret = -ENOMEM;
+-              goto out_put_node;
+       }
+-out_put_node:
+       of_node_put(pcie_intc_node);
+       return ret;
+ }
diff --git a/target/linux/generic/pending-6.12/890-usb-serial-add-support-for-CH348.patch b/target/linux/generic/pending-6.12/890-usb-serial-add-support-for-CH348.patch
new file mode 100644 (file)
index 0000000..028ecf4
--- /dev/null
@@ -0,0 +1,783 @@
+From df1357358eec062241bddd2995e7ef0ce86cf45a Mon Sep 17 00:00:00 2001
+X-Patchwork-Submitter: Corentin Labbe <clabbe@baylibre.com>
+X-Patchwork-Id: 13656881
+Message-Id: <20240507131522.3546113-2-clabbe@baylibre.com>
+X-Mailer: git-send-email 2.25.1
+In-Reply-To: <20240507131522.3546113-1-clabbe@baylibre.com>
+References: <20240507131522.3546113-1-clabbe@baylibre.com>
+Precedence: bulk
+X-Mailing-List: linux-usb@vger.kernel.org
+List-Id: <linux-usb.vger.kernel.org>
+From: Corentin Labbe <clabbe@baylibre.com>
+Date: Tue, 7 May 2024 13:15:22 +0000
+Subject: [PATCH v7] usb: serial: add support for CH348
+
+The CH348 is an USB octo port serial adapter.
+The device multiplexes all 8 ports in the same pair of Bulk endpoints.
+Since there is no public datasheet, unfortunately it remains some magic values
+
+Signed-off-by: Corentin Labbe <clabbe@baylibre.com>
+Tested-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+---
+ drivers/usb/serial/Kconfig  |   9 +
+ drivers/usb/serial/Makefile |   1 +
+ drivers/usb/serial/ch348.c  | 725 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 735 insertions(+)
+ create mode 100644 drivers/usb/serial/ch348.c
+
+--- a/drivers/usb/serial/Kconfig
++++ b/drivers/usb/serial/Kconfig
+@@ -112,6 +112,15 @@ config USB_SERIAL_CH341
+         To compile this driver as a module, choose M here: the
+         module will be called ch341.
++config USB_SERIAL_CH348
++      tristate "USB Winchiphead CH348 Octo Port Serial Driver"
++      help
++        Say Y here if you want to use a Winchiphead CH348 octo port
++        USB to serial adapter.
++
++        To compile this driver as a module, choose M here: the
++        module will be called ch348.
++
+ config USB_SERIAL_WHITEHEAT
+       tristate "USB ConnectTech WhiteHEAT Serial Driver"
+       select USB_EZUSB_FX2
+--- a/drivers/usb/serial/Makefile
++++ b/drivers/usb/serial/Makefile
+@@ -15,6 +15,7 @@ obj-$(CONFIG_USB_SERIAL_AIRCABLE)            += ai
+ obj-$(CONFIG_USB_SERIAL_ARK3116)              += ark3116.o
+ obj-$(CONFIG_USB_SERIAL_BELKIN)                       += belkin_sa.o
+ obj-$(CONFIG_USB_SERIAL_CH341)                        += ch341.o
++obj-$(CONFIG_USB_SERIAL_CH348)                        += ch348.o
+ obj-$(CONFIG_USB_SERIAL_CP210X)                       += cp210x.o
+ obj-$(CONFIG_USB_SERIAL_CYBERJACK)            += cyberjack.o
+ obj-$(CONFIG_USB_SERIAL_CYPRESS_M8)           += cypress_m8.o
+--- /dev/null
++++ b/drivers/usb/serial/ch348.c
+@@ -0,0 +1,725 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * USB serial driver for USB to Octal UARTs chip ch348.
++ *
++ * Copyright (C) 2022 Corentin Labbe <clabbe@baylibre.com>
++ * With the help of Neil Armstrong <neil.armstrong@linaro.org>
++ * and the help of Martin Blumenstingl <martin.blumenstingl@googlemail.com>
++ */
++
++#include <linux/completion.h>
++#include <linux/errno.h>
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/mutex.h>
++#include <linux/overflow.h>
++#include <linux/serial.h>
++#include <linux/slab.h>
++#include <linux/tty.h>
++#include <linux/tty_driver.h>
++#include <linux/tty_flip.h>
++#include <linux/usb.h>
++#include <linux/usb/serial.h>
++
++#define CH348_CMD_TIMEOUT   2000
++
++#define CH348_CTO_D   0x01
++#define CH348_CTO_R   0x02
++
++#define CH348_CTI_C   0x10
++#define CH348_CTI_DSR 0x20
++#define CH348_CTI_R   0x40
++#define CH348_CTI_DCD 0x80
++
++#define CH348_LO      0x02
++#define CH348_LP      0x04
++#define CH348_LF      0x08
++#define CH348_LB      0x10
++
++#define CMD_W_R               0xC0
++#define CMD_W_BR      0x80
++
++#define CMD_WB_E      0x90
++#define CMD_RB_E      0xC0
++
++#define M_NOR         0x00
++#define M_HF          0x03
++
++#define R_MOD         0x97
++#define R_IO_D                0x98
++#define R_IO_O                0x99
++#define R_IO_I                0x9b
++#define R_TM_O                0x9c
++#define R_INIT                0xa1
++
++#define R_C1          0x01
++#define R_C2          0x02
++#define R_C4          0x04
++#define R_C5          0x06
++
++#define R_II_B1               0x06
++#define R_II_B2               0x02
++#define R_II_B3               0x00
++
++#define CMD_VER               0x96
++
++#define CH348_RX_PORT_CHUNK_LENGTH    32
++#define CH348_RX_PORT_MAX_LENGTH      30
++
++struct ch348_rxbuf {
++      u8 port;
++      u8 length;
++      u8 data[CH348_RX_PORT_MAX_LENGTH];
++} __packed;
++
++struct ch348_txbuf {
++      u8 port;
++      __le16 length;
++      u8 data[];
++} __packed;
++
++#define CH348_TX_HDRSIZE offsetof(struct ch348_txbuf, data)
++
++struct ch348_initbuf {
++      u8 cmd;
++      u8 reg;
++      u8 port;
++      __be32 baudrate;
++      u8 format;
++      u8 paritytype;
++      u8 databits;
++      u8 rate;
++      u8 unknown;
++} __packed;
++
++#define CH348_MAXPORT 8
++
++/*
++ * The CH348 multiplexes rx & tx into a pair of Bulk USB endpoints for
++ * the 8 serial ports, and another pair of Bulk USB endpoints to
++ * set port settings and receive port status events.
++ *
++ * The USB serial cores ties every Bulk endpoints pairs to each ports,
++ * but in our case it will set port 0 with the rx/tx endpoints
++ * and port 1 with the setup/status endpoints.
++ *
++ * To still take advantage of the generic code, we (re-)initialize
++ * the USB serial port structure with the correct USB endpoint
++ * for read and write, and write proper process_read_urb()
++ * and prepare_write_buffer() to correctly (de-)multiplex data.
++ * Also we use a custom write() implementation to wait until the buffer
++ * has been fully transmitted to prevent TX buffer overruns.
++ */
++
++/*
++ * struct ch348_port - per-port information
++ * @uartmode:         UART port current mode
++ * @write_completion: completion event when the TX buffer has been written out
++ */
++struct ch348_port {
++      u8 uartmode;
++      struct completion write_completion;
++};
++
++/*
++ * struct ch348 - main container for all this driver information
++ * @udev:             pointer to the CH348 USB device
++ * @ports:            List of per-port information
++ * @serial:           pointer to the serial structure
++ * @write_lock:               protect against concurrent writes so we don't lose data
++ * @cmd_ep:           endpoint number for configure operations
++ * @status_urb:               URB for status
++ * @status_buffer:    buffer used by status_urb
++ */
++struct ch348 {
++      struct usb_device *udev;
++      struct ch348_port ports[CH348_MAXPORT];
++      struct usb_serial *serial;
++
++      struct mutex write_lock;
++
++      int cmd_ep;
++
++      struct urb *status_urb;
++      u8 status_buffer[];
++};
++
++struct ch348_magic {
++      u8 action;
++      u8 reg;
++      u8 control;
++} __packed;
++
++struct ch348_status_entry {
++      u8 portnum:4;
++      u8 unused:4;
++      u8 reg_iir;
++      union {
++              u8 lsr_signal;
++              u8 modem_signal;
++              u8 init_data[10];
++      };
++} __packed;
++
++static void ch348_process_status_urb(struct urb *urb)
++{
++      struct ch348_status_entry *status_entry;
++      struct ch348 *ch348 = urb->context;
++      int ret, status = urb->status;
++      struct usb_serial_port *port;
++      unsigned int i, status_len;
++
++      switch (status) {
++      case 0:
++              /* success */
++              break;
++      case -ECONNRESET:
++      case -ENOENT:
++      case -ESHUTDOWN:
++              /* this urb is terminated, clean up */
++              dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n",
++                      __func__, status);
++              return;
++      default:
++              dev_err(&urb->dev->dev, "%s - nonzero urb status received: %d\n",
++                      __func__, status);
++              goto exit;
++      }
++
++      if (urb->actual_length < 3) {
++              dev_warn(&ch348->udev->dev,
++                       "Received too short status buffer with %u bytes\n",
++                       urb->actual_length);
++              goto exit;
++      }
++
++      for (i = 0; i < urb->actual_length;) {
++              status_entry = urb->transfer_buffer + i;
++
++              if (status_entry->portnum >= CH348_MAXPORT) {
++                      dev_warn(&ch348->udev->dev,
++                               "Invalid port %d in status entry\n",
++                               status_entry->portnum);
++                      break;
++              }
++
++              port = ch348->serial->port[status_entry->portnum];
++              status_len = 3;
++
++              if (!status_entry->reg_iir) {
++                      dev_dbg(&port->dev, "Ignoring status with zero reg_iir\n");
++              } else if (status_entry->reg_iir == R_INIT) {
++                      status_len = 12;
++              } else if ((status_entry->reg_iir & 0x0f) == R_II_B1) {
++                      if (status_entry->lsr_signal & CH348_LO)
++                              port->icount.overrun++;
++                      if (status_entry->lsr_signal & CH348_LP)
++                              port->icount.parity++;
++                      if (status_entry->lsr_signal & CH348_LF)
++                              port->icount.frame++;
++                      if (status_entry->lsr_signal & CH348_LF)
++                              port->icount.brk++;
++              } else if ((status_entry->reg_iir & 0x0f) == R_II_B2) {
++                      complete_all(&ch348->ports[status_entry->portnum].write_completion);
++              } else {
++                      dev_warn(&port->dev,
++                               "Unsupported status with reg_iir 0x%02x\n",
++                               status_entry->reg_iir);
++              }
++
++              usb_serial_debug_data(&port->dev, __func__, status_len,
++                                    urb->transfer_buffer + i);
++
++              i += status_len;
++      }
++
++exit:
++      ret = usb_submit_urb(urb, GFP_ATOMIC);
++      if (ret)
++              dev_err(&urb->dev->dev, "%s - usb_submit_urb failed; %d\n",
++                      __func__, ret);
++}
++
++/*
++ * Some values came from vendor tree, and we have no meaning for them, this
++ * function simply use them.
++ */
++static int ch348_do_magic(struct ch348 *ch348, int portnum, u8 action, u8 reg, u8 control)
++{
++      struct ch348_magic *buffer;
++      int ret, len;
++
++      buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
++      if (!buffer)
++              return -ENOMEM;
++
++      if (portnum < 4)
++              reg += 0x10 * portnum;
++      else
++              reg += 0x10 * (portnum - 4) + 0x08;
++
++      buffer->action = action;
++      buffer->reg = reg;
++      buffer->control = control;
++
++      ret = usb_bulk_msg(ch348->udev, ch348->cmd_ep, buffer, 3, &len,
++                         CH348_CMD_TIMEOUT);
++      if (ret)
++              dev_err(&ch348->udev->dev, "Failed to write magic err=%d\n", ret);
++
++      kfree(buffer);
++
++      return ret;
++}
++
++static int ch348_configure(struct ch348 *ch348, int portnum)
++{
++      int ret;
++
++      ret = ch348_do_magic(ch348, portnum, CMD_W_R, R_C2, 0x87);
++      if (ret)
++              return ret;
++
++      return ch348_do_magic(ch348, portnum, CMD_W_R, R_C4, 0x08);
++}
++
++static void ch348_process_read_urb(struct urb *urb)
++{
++      struct usb_serial_port *port = urb->context;
++      struct ch348 *ch348 = usb_get_serial_data(port->serial);
++      unsigned int portnum, usblen, i;
++      struct ch348_rxbuf *rxb;
++
++      if (urb->actual_length < 2) {
++              dev_dbg(&ch348->udev->dev, "Empty rx buffer\n");
++              return;
++      }
++
++      for (i = 0; i < urb->actual_length; i += CH348_RX_PORT_CHUNK_LENGTH) {
++              rxb = urb->transfer_buffer + i;
++              portnum = rxb->port;
++              if (portnum >= CH348_MAXPORT) {
++                      dev_dbg(&ch348->udev->dev, "Invalid port %d\n", portnum);
++                      break;
++              }
++
++              port = ch348->serial->port[portnum];
++
++              usblen = rxb->length;
++              if (usblen > CH348_RX_PORT_MAX_LENGTH) {
++                      dev_dbg(&port->dev, "Invalid length %d for port %d\n",
++                              usblen, portnum);
++                      break;
++              }
++
++              tty_insert_flip_string(&port->port, rxb->data, usblen);
++              tty_flip_buffer_push(&port->port);
++              port->icount.rx += usblen;
++              usb_serial_debug_data(&port->dev, __func__, usblen, rxb->data);
++      }
++}
++
++static int ch348_prepare_write_buffer(struct usb_serial_port *port, void *dest, size_t size)
++{
++      struct ch348_txbuf *rxt = dest;
++      int count;
++
++      count = kfifo_out_locked(&port->write_fifo, rxt->data,
++                               size - CH348_TX_HDRSIZE, &port->lock);
++
++      rxt->port = port->port_number;
++      rxt->length = cpu_to_le16(count);
++
++      return count + CH348_TX_HDRSIZE;
++}
++
++static int ch348_write(struct tty_struct *tty, struct usb_serial_port *port,
++                     const unsigned char *buf, int count)
++{
++      struct ch348 *ch348 = usb_get_serial_data(port->serial);
++      struct ch348_port *ch348_port = &ch348->ports[port->port_number];
++      int ret, max_tx_size;
++
++      if (tty_get_baud_rate(tty) < 9600 && count >= 128)
++              /*
++               * Writing larger buffers can take longer than the hardware
++               * allows before discarding the write buffer. Limit the
++               * transfer size in such cases.
++               * These values have been found by empirical testing.
++               */
++              max_tx_size = 128;
++      else
++              /*
++              * Only ingest as many bytes as we can transfer with one URB at
++              * a time. Once an URB has been written we need to wait for the
++              * R_II_B2 status event before we are allowed to send more data.
++              * If we ingest more data then usb_serial_generic_write() will
++              * internally try to process as much data as possible with any
++              * number of URBs without giving us the chance to wait in
++              * between transfers.
++              */
++              max_tx_size = port->bulk_out_size - CH348_TX_HDRSIZE;
++
++      reinit_completion(&ch348_port->write_completion);
++
++      mutex_lock(&ch348->write_lock);
++
++      /*
++       * For any (remaining) bytes that we did not transfer TTY core will
++       * call us again, with the buffer and count adjusted to the remaining
++       * data.
++       */
++      ret = usb_serial_generic_write(tty, port, buf, min(count, max_tx_size));
++
++      mutex_unlock(&ch348->write_lock);
++
++      if (ret <= 0)
++              return ret;
++
++      if (!wait_for_completion_interruptible_timeout(&ch348_port->write_completion,
++                                                     msecs_to_jiffies(CH348_CMD_TIMEOUT))) {
++              dev_err_console(port, "Failed to wait for TX buffer flush\n");
++              return -ETIMEDOUT;
++      }
++
++      return ret;
++}
++
++static int ch348_set_uartmode(struct ch348 *ch348, int portnum, u8 mode)
++{
++      int ret;
++
++      if (ch348->ports[portnum].uartmode == M_NOR && mode == M_HF) {
++              ret = ch348_do_magic(ch348, portnum, CMD_W_BR, R_C4, 0x51);
++              if (ret)
++                      return ret;
++              ch348->ports[portnum].uartmode = M_HF;
++      }
++
++      if (ch348->ports[portnum].uartmode == M_HF && mode == M_NOR) {
++              ret = ch348_do_magic(ch348, portnum, CMD_W_BR, R_C4, 0x50);
++              if (ret)
++                      return ret;
++              ch348->ports[portnum].uartmode = M_NOR;
++      }
++      return 0;
++}
++
++static void ch348_set_termios(struct tty_struct *tty, struct usb_serial_port *port,
++                            const struct ktermios *termios_old)
++{
++      struct ch348 *ch348 = usb_get_serial_data(port->serial);
++      struct ktermios *termios = &tty->termios;
++      int ret, portnum = port->port_number;
++      struct ch348_initbuf *buffer;
++      speed_t baudrate;
++      u8 format;
++
++      if (termios_old && !tty_termios_hw_change(&tty->termios, termios_old))
++              return;
++
++      buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
++      if (!buffer) {
++              if (termios_old)
++                      tty->termios = *termios_old;
++              return;
++      }
++
++      /*
++       * The datasheet states that only baud rates in range of 1200..6000000
++       * are supported. Tests however show that even baud rates as low as 50
++       * and as high as 12000000 are working in practice.
++       */
++      baudrate = clamp(tty_get_baud_rate(tty), 50, 12000000);
++
++      format = termios->c_cflag & CSTOPB ? 2 : 1;
++
++      buffer->paritytype = 0;
++      if (termios->c_cflag & PARENB) {
++              if (termios->c_cflag & PARODD)
++                      buffer->paritytype += 1;
++              else
++                      buffer->paritytype += 2;
++              if  (termios->c_cflag & CMSPAR)
++                      buffer->paritytype += 2;
++      }
++
++      switch (C_CSIZE(tty)) {
++      case CS5:
++              buffer->databits = 5;
++              break;
++      case CS6:
++              buffer->databits = 6;
++              break;
++      case CS7:
++              buffer->databits = 7;
++              break;
++      case CS8:
++      default:
++              buffer->databits = 8;
++              break;
++      }
++      buffer->cmd = CMD_WB_E | (portnum & 0x0F);
++      buffer->reg = R_INIT;
++      buffer->port = portnum;
++      buffer->baudrate = cpu_to_be32(baudrate);
++
++      if (format == 2)
++              buffer->format = 0x02;
++      else if (format == 1)
++              buffer->format = 0x00;
++
++      buffer->rate = max_t(speed_t, 5, (10000 * 15 / baudrate) + 1);
++
++      ret = usb_bulk_msg(ch348->udev, ch348->cmd_ep, buffer,
++                         sizeof(*buffer), NULL, CH348_CMD_TIMEOUT);
++      if (ret < 0) {
++              dev_err(&ch348->udev->dev, "Failed to change line settings: err=%d\n",
++                      ret);
++              goto out;
++      }
++
++      ret = ch348_do_magic(ch348, portnum, CMD_W_R, R_C1, 0x0F);
++      if (ret < 0)
++              goto out;
++
++      if (C_CRTSCTS(tty))
++              ret = ch348_set_uartmode(ch348, portnum, M_HF);
++      else
++              ret = ch348_set_uartmode(ch348, portnum, M_NOR);
++
++out:
++      kfree(buffer);
++}
++
++static int ch348_open(struct tty_struct *tty, struct usb_serial_port *port)
++{
++      struct ch348 *ch348 = usb_get_serial_data(port->serial);
++      int ret;
++
++      if (tty)
++              ch348_set_termios(tty, port, NULL);
++
++      ret = ch348_configure(ch348, port->port_number);
++      if (ret) {
++              dev_err(&ch348->udev->dev, "Fail to configure err=%d\n", ret);
++              return ret;
++      }
++
++      return usb_serial_generic_open(tty, port);
++}
++
++static int ch348_attach(struct usb_serial *serial)
++{
++      struct usb_endpoint_descriptor *epcmd, *epstatus;
++      struct usb_serial_port *port0 = serial->port[1];
++      struct usb_device *usb_dev = serial->dev;
++      int status_buffer_size, i, ret;
++      struct usb_interface *intf;
++      struct ch348 *ch348;
++
++      intf = usb_ifnum_to_if(usb_dev, 0);
++      epstatus = &intf->cur_altsetting->endpoint[2].desc;
++      epcmd = &intf->cur_altsetting->endpoint[3].desc;
++
++      status_buffer_size = usb_endpoint_maxp(epstatus);
++
++      ch348 = kzalloc(struct_size(ch348, status_buffer, status_buffer_size),
++                      GFP_KERNEL);
++      if (!ch348)
++              return -ENOMEM;
++
++      usb_set_serial_data(serial, ch348);
++
++      ch348->udev = serial->dev;
++      ch348->serial = serial;
++      mutex_init(&ch348->write_lock);
++
++      for (i = 0; i < CH348_MAXPORT; i++)
++              init_completion(&ch348->ports[i].write_completion);
++
++      ch348->status_urb = usb_alloc_urb(0, GFP_KERNEL);
++      if (!ch348->status_urb) {
++              ret = -ENOMEM;
++              goto err_free_ch348;
++      }
++
++      usb_fill_bulk_urb(ch348->status_urb, ch348->udev,
++                        usb_rcvbulkpipe(ch348->udev, epstatus->bEndpointAddress),
++                        ch348->status_buffer, status_buffer_size,
++                        ch348_process_status_urb, ch348);
++
++      ret = usb_submit_urb(ch348->status_urb, GFP_KERNEL);
++      if (ret) {
++              dev_err(&ch348->udev->dev,
++                      "%s - failed to submit status/interrupt urb %i\n",
++                      __func__, ret);
++              goto err_free_status_urb;
++      }
++
++      ret = usb_serial_generic_submit_read_urbs(port0, GFP_KERNEL);
++      if (ret)
++              goto err_kill_status_urb;
++
++      ch348->cmd_ep = usb_sndbulkpipe(usb_dev, epcmd->bEndpointAddress);
++
++      return 0;
++
++err_kill_status_urb:
++      usb_kill_urb(ch348->status_urb);
++err_free_status_urb:
++      usb_free_urb(ch348->status_urb);
++err_free_ch348:
++      kfree(ch348);
++      return ret;
++}
++
++static void ch348_release(struct usb_serial *serial)
++{
++      struct ch348 *ch348 = usb_get_serial_data(serial);
++
++      usb_kill_urb(ch348->status_urb);
++      usb_free_urb(ch348->status_urb);
++
++      kfree(ch348);
++}
++
++static void ch348_print_version(struct usb_serial *serial)
++{
++      u8 *version_buf;
++      int ret;
++
++      version_buf = kzalloc(4, GFP_KERNEL);
++      if (!version_buf)
++              return;
++
++      ret = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
++                            CMD_VER,
++                            USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
++                            0, 0, version_buf, 4, CH348_CMD_TIMEOUT);
++      if (ret < 0)
++              dev_dbg(&serial->dev->dev, "Failed to read CMD_VER: %d\n", ret);
++      else
++              dev_info(&serial->dev->dev, "Found WCH CH348%s\n",
++                       (version_buf[1] & 0x80) ? "Q" : "L");
++
++      kfree(version_buf);
++}
++
++static int ch348_probe(struct usb_serial *serial, const struct usb_device_id *id)
++{
++      struct usb_endpoint_descriptor *epread, *epwrite, *epstatus, *epcmd;
++      struct usb_device *usb_dev = serial->dev;
++      struct usb_interface *intf;
++      int ret;
++
++      intf = usb_ifnum_to_if(usb_dev, 0);
++
++      ret = usb_find_common_endpoints(intf->cur_altsetting, &epread, &epwrite,
++                                      NULL, NULL);
++      if (ret) {
++              dev_err(&serial->dev->dev, "Failed to find basic endpoints ret=%d\n", ret);
++              return ret;
++      }
++
++      epstatus = &intf->cur_altsetting->endpoint[2].desc;
++      if (!usb_endpoint_is_bulk_in(epstatus)) {
++              dev_err(&serial->dev->dev, "Missing second bulk in (STATUS/INT)\n");
++              return -ENODEV;
++      }
++
++      epcmd = &intf->cur_altsetting->endpoint[3].desc;
++      if (!usb_endpoint_is_bulk_out(epcmd)) {
++              dev_err(&serial->dev->dev, "Missing second bulk out (CMD)\n");
++              return -ENODEV;
++      }
++
++      ch348_print_version(serial);
++
++      return 0;
++}
++
++static int ch348_calc_num_ports(struct usb_serial *serial,
++                              struct usb_serial_endpoints *epds)
++{
++      int i;
++
++      for (i = 1; i < CH348_MAXPORT; ++i) {
++              epds->bulk_out[i] = epds->bulk_out[0];
++              epds->bulk_in[i] = epds->bulk_in[0];
++      }
++
++      epds->num_bulk_out = CH348_MAXPORT;
++      epds->num_bulk_in = CH348_MAXPORT;
++
++      return CH348_MAXPORT;
++}
++
++static int ch348_suspend(struct usb_serial *serial, pm_message_t message)
++{
++      struct ch348 *ch348 = usb_get_serial_data(serial);
++
++      usb_kill_urb(ch348->status_urb);
++
++      return 0;
++}
++
++static int ch348_resume(struct usb_serial *serial)
++{
++      struct ch348 *ch348 = usb_get_serial_data(serial);
++      int ret;
++
++      ret = usb_submit_urb(ch348->status_urb, GFP_KERNEL);
++      if (ret) {
++              dev_err(&ch348->udev->dev,
++                      "%s - failed to submit status/interrupt urb %i\n",
++                      __func__, ret);
++              return ret;
++      }
++
++      ret = usb_serial_generic_resume(serial);
++      if (ret)
++              usb_kill_urb(ch348->status_urb);
++
++      return ret;
++}
++
++static const struct usb_device_id ch348_ids[] = {
++      { USB_DEVICE(0x1a86, 0x55d9), },
++      { /* sentinel */ }
++};
++
++MODULE_DEVICE_TABLE(usb, ch348_ids);
++
++static struct usb_serial_driver ch348_device = {
++      .driver = {
++              .owner = THIS_MODULE,
++              .name = "ch348",
++      },
++      .id_table =             ch348_ids,
++      .num_ports =            CH348_MAXPORT,
++      .num_bulk_in =          1,
++      .num_bulk_out =         1,
++      .open =                 ch348_open,
++      .set_termios =          ch348_set_termios,
++      .process_read_urb =     ch348_process_read_urb,
++      .prepare_write_buffer = ch348_prepare_write_buffer,
++      .write =                ch348_write,
++      .probe =                ch348_probe,
++      .calc_num_ports =       ch348_calc_num_ports,
++      .attach =               ch348_attach,
++      .release =              ch348_release,
++      .suspend =              ch348_suspend,
++      .resume =               ch348_resume,
++};
++
++static struct usb_serial_driver * const serial_drivers[] = {
++      &ch348_device, NULL
++};
++
++module_usb_serial_driver(serial_drivers, ch348_ids);
++
++MODULE_AUTHOR("Corentin Labbe <clabbe@baylibre.com>");
++MODULE_DESCRIPTION("USB CH348 Octo port serial converter driver");
++MODULE_LICENSE("GPL");
diff --git a/target/linux/generic/pending-6.12/900-net-ag71xx-fix-qca9530-and-qca9550-mdio-probe.patch b/target/linux/generic/pending-6.12/900-net-ag71xx-fix-qca9530-and-qca9550-mdio-probe.patch
new file mode 100644 (file)
index 0000000..c0e318e
--- /dev/null
@@ -0,0 +1,25 @@
+From 440415703692af4548e836832ef0434e87fbc357 Mon Sep 17 00:00:00 2001
+From: Oskari Lemmela <oskari@lemmela.net>
+Date: Sat, 13 Jul 2024 18:56:59 +0300
+Subject: [PATCH] net: ag71xx: fix qca9530 and qca9550 mdio probe
+
+Newer QCA9530 and QCA9550 devices should use same div table as AR933X.
+
+Fixes: d51b6ce441d3 ("net: ethernet: add ag71xx driver")
+Signed-off-by: Oskari Lemmela <oskari@lemmela.net>
+---
+ drivers/net/ethernet/atheros/ag71xx.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/atheros/ag71xx.c
++++ b/drivers/net/ethernet/atheros/ag71xx.c
+@@ -641,7 +641,8 @@ static int ag71xx_mdio_get_divider(struc
+       if (!ref_clock)
+               return -EINVAL;
+-      if (ag71xx_is(ag, AR9330) || ag71xx_is(ag, AR9340)) {
++      if (ag71xx_is(ag, AR9330) || ag71xx_is(ag, AR9340) ||
++          ag71xx_is(ag, QCA9530) || ag71xx_is(ag, QCA9550)) {
+               table = ar933x_mdio_div_table;
+               ndivs = ARRAY_SIZE(ar933x_mdio_div_table);
+       } else if (ag71xx_is(ag, AR7240)) {
diff --git a/target/linux/generic/pending-6.12/910-crypto-testmgr-Add-test-vectors-for-authenc-hmac-md5.patch b/target/linux/generic/pending-6.12/910-crypto-testmgr-Add-test-vectors-for-authenc-hmac-md5.patch
new file mode 100644 (file)
index 0000000..2d33d2a
--- /dev/null
@@ -0,0 +1,297 @@
+From dd227f442774a570ffccdfbc9536fec3b4666305 Mon Sep 17 00:00:00 2001
+From: Aleksander Jan Bajkowski <olek2@wp.pl>
+Date: Sat, 31 Jan 2026 12:35:30 +0100
+Subject: [PATCH] crypto: testmgr - Add test vectors for
+ authenc(hmac(md5),cbc(aes))
+
+Test vectors were generated starting from existing CBC(AES) test vectors
+(RFC3602, NIST SP800-38A) and adding HMAC(MD5) computed with Python
+script. Then, the results were double-checked on Mediatek MT7981 (safexcel)
+and NXP P2020 (talitos). Both platforms pass self-tests.
+
+Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
+---
+ crypto/testmgr.c |   7 ++
+ crypto/testmgr.h | 255 +++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 262 insertions(+)
+
+--- a/crypto/testmgr.c
++++ b/crypto/testmgr.c
+@@ -4375,6 +4375,13 @@ static const struct alg_test_desc alg_te
+                       .cprng = __VECS(ansi_cprng_aes_tv_template)
+               }
+       }, {
++              .alg = "authenc(hmac(md5),cbc(aes))",
++              .generic_driver = "authenc(hmac-md5-lib,cbc(aes-generic))",
++              .test = alg_test_aead,
++              .suite = {
++                      .aead = __VECS(hmac_md5_aes_cbc_tv_temp)
++              }
++      }, {
+               .alg = "authenc(hmac(md5),cbc(des))",
+               .generic_driver = "authenc(hmac-md5-lib,cbc(des-generic))",
+               .test = alg_test_aead,
+--- a/crypto/testmgr.h
++++ b/crypto/testmgr.h
+@@ -17133,6 +17133,261 @@ static const struct cipher_testvec aes_c
+       },
+ };
++static const struct aead_testvec hmac_md5_aes_cbc_tv_temp[] = {
++      { /* RFC 3602 Case 1 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x00\x00\x00\x00\x00\x00\x00\x00"
++                        "\x06\xa9\x21\x40\x36\xb8\xa1\x5b"
++                        "\x51\x2e\x03\xd5\x34\x12\x00\x06",
++              .klen   = 8 + 16 + 16,
++              .iv     = "\x3d\xaf\xba\x42\x9d\x9e\xb4\x30"
++                        "\xb4\x22\xda\x80\x2c\x9f\xac\x41",
++              .assoc  = "\x3d\xaf\xba\x42\x9d\x9e\xb4\x30"
++                        "\xb4\x22\xda\x80\x2c\x9f\xac\x41",
++              .alen   = 16,
++              .ptext  = "Single block msg",
++              .plen   = 16,
++              .ctext  = "\xe3\x53\x77\x9c\x10\x79\xae\xb8"
++                        "\x27\x08\x94\x2d\xbe\x77\x18\x1a"
++                        "\x22\x10\xf2\x25\x7f\xe9\x0d\x92"
++                        "\xfc\x00\x55\xb1\xd0\xb5\x3a\x74",
++              .clen   = 16 + 16,
++      }, { /* RFC 3602 Case 2 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\xc2\x86\x69\x6d\x88\x7c\x9a\xa0"
++                        "\x61\x1b\xbb\x3e\x20\x25\xa4\x5a",
++              .klen   = 8 + 16 + 16,
++              .iv     = "\x56\x2e\x17\x99\x6d\x09\x3d\x28"
++                        "\xdd\xb3\xba\x69\x5a\x2e\x6f\x58",
++              .assoc  = "\x56\x2e\x17\x99\x6d\x09\x3d\x28"
++                        "\xdd\xb3\xba\x69\x5a\x2e\x6f\x58",
++              .alen   = 16,
++              .ptext  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
++              .plen   = 32,
++              .ctext  = "\xd2\x96\xcd\x94\xc2\xcc\xcf\x8a"
++                        "\x3a\x86\x30\x28\xb5\xe1\xdc\x0a"
++                        "\x75\x86\x60\x2d\x25\x3c\xff\xf9"
++                        "\x1b\x82\x66\xbe\xa6\xd6\x1a\xb1"
++                        "\x31\xef\xd1\x5e\x2d\x83\xde\x59"
++                        "\x5c\x63\x6c\xd6\x6e\x96\x8c\x5b",
++              .clen   = 32 + 16,
++      }, { /* RFC 3602 Case 3 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x6c\x3e\xa0\x47\x76\x30\xce\x21"
++                        "\xa2\xce\x33\x4a\xa7\x46\xc2\xcd",
++              .klen   = 8 + 16 + 16,
++              .iv     = "\xc7\x82\xdc\x4c\x09\x8c\x66\xcb"
++                        "\xd9\xcd\x27\xd8\x25\x68\x2c\x81",
++              .assoc  = "\xc7\x82\xdc\x4c\x09\x8c\x66\xcb"
++                        "\xd9\xcd\x27\xd8\x25\x68\x2c\x81",
++              .alen   = 16,
++              .ptext  = "This is a 48-byte message (exactly 3 AES blocks)",
++              .plen   = 48,
++              .ctext  = "\xd0\xa0\x2b\x38\x36\x45\x17\x53"
++                        "\xd4\x93\x66\x5d\x33\xf0\xe8\x86"
++                        "\x2d\xea\x54\xcd\xb2\x93\xab\xc7"
++                        "\x50\x69\x39\x27\x67\x72\xf8\xd5"
++                        "\x02\x1c\x19\x21\x6b\xad\x52\x5c"
++                        "\x85\x79\x69\x5d\x83\xba\x26\x84"
++                        "\xa1\x9e\xc5\x65\x43\xc5\x51\x70"
++                        "\xb5\xc8\x38\xce\xbb\x3b\xc6\x0f",
++              .clen   = 48 + 16,
++      }, { /* RFC 3602 Case 4 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x56\xe4\x7a\x38\xc5\x59\x89\x74"
++                        "\xbc\x46\x90\x3d\xba\x29\x03\x49",
++              .klen   = 8 + 16 + 16,
++              .iv     = "\x8c\xe8\x2e\xef\xbe\xa0\xda\x3c"
++                        "\x44\x69\x9e\xd7\xdb\x51\xb7\xd9",
++              .assoc  = "\x8c\xe8\x2e\xef\xbe\xa0\xda\x3c"
++                        "\x44\x69\x9e\xd7\xdb\x51\xb7\xd9",
++              .alen   = 16,
++              .ptext  = "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7"
++                        "\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
++                        "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7"
++                        "\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
++                        "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7"
++                        "\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
++                        "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7"
++                        "\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf",
++              .plen   = 64,
++              .ctext  = "\xc3\x0e\x32\xff\xed\xc0\x77\x4e"
++                        "\x6a\xff\x6a\xf0\x86\x9f\x71\xaa"
++                        "\x0f\x3a\xf0\x7a\x9a\x31\xa9\xc6"
++                        "\x84\xdb\x20\x7e\xb0\xef\x8e\x4e"
++                        "\x35\x90\x7a\xa6\x32\xc3\xff\xdf"
++                        "\x86\x8b\xb7\xb2\x9d\x3d\x46\xad"
++                        "\x83\xce\x9f\x9a\x10\x2e\xe9\x9d"
++                        "\x49\xa5\x3e\x87\xf4\xc3\xda\x55"
++                        "\x19\x90\xcc\x2c\x6d\x76\x0f\xd6"
++                        "\x6c\x54\x09\xb1\x3e\x98\x0c\x11",
++              .clen   = 64 + 16,
++      }, { /* RFC 3602 Case 5 */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x10"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x90\xd3\x82\xb4\x10\xee\xba\x7a"
++                        "\xd9\x38\xc4\x6c\xec\x1a\x82\xbf",
++              .klen   = 8 + 16 + 16,
++              .iv     = "\xe9\x6e\x8c\x08\xab\x46\x57\x63"
++                        "\xfd\x09\x8d\x45\xdd\x3f\xf8\x93",
++              .assoc  = "\x00\x00\x43\x21\x00\x00\x00\x01"
++                        "\xe9\x6e\x8c\x08\xab\x46\x57\x63"
++                        "\xfd\x09\x8d\x45\xdd\x3f\xf8\x93",
++              .alen   = 24,
++              .ptext  = "\x08\x00\x0e\xbd\xa7\x0a\x00\x00"
++                        "\x8e\x9c\x08\x3d\xb9\x5b\x07\x00"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
++                        "\x10\x11\x12\x13\x14\x15\x16\x17"
++                        "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
++                        "\x20\x21\x22\x23\x24\x25\x26\x27"
++                        "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
++                        "\x30\x31\x32\x33\x34\x35\x36\x37"
++                        "\x01\x02\x03\x04\x05\x06\x07\x08"
++                        "\x09\x0a\x0b\x0c\x0d\x0e\x0e\x01",
++              .plen   = 80,
++              .ctext  = "\xf6\x63\xc2\x5d\x32\x5c\x18\xc6"
++                        "\xa9\x45\x3e\x19\x4e\x12\x08\x49"
++                        "\xa4\x87\x0b\x66\xcc\x6b\x99\x65"
++                        "\x33\x00\x13\xb4\x89\x8d\xc8\x56"
++                        "\xa4\x69\x9e\x52\x3a\x55\xdb\x08"
++                        "\x0b\x59\xec\x3a\x8e\x4b\x7e\x52"
++                        "\x77\x5b\x07\xd1\xdb\x34\xed\x9c"
++                        "\x53\x8a\xb5\x0c\x55\x1b\x87\x4a"
++                        "\xa2\x69\xad\xd0\x47\xad\x2d\x59"
++                        "\x13\xac\x19\xb7\xcf\xba\xd4\xa6"
++                        "\x9f\x6f\xa4\x85\x28\xf1\xc9\xea"
++                        "\xe1\xd0\x7d\x30\x4a\xd0\x81\x12",
++              .clen   = 80 + 16,
++      }, { /* NIST SP800-38A F.2.3 CBC-AES192.Encrypt */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x18"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x8e\x73\xb0\xf7\xda\x0e\x64\x52"
++                        "\xc8\x10\xf3\x2b\x80\x90\x79\xe5"
++                        "\x62\xf8\xea\xd2\x52\x2c\x6b\x7b",
++              .klen   = 8 + 16 + 24,
++              .iv     = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
++              .assoc  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
++              .alen   = 16,
++              .ptext  = "\x6b\xc1\xbe\xe2\x2e\x40\x9f\x96"
++                        "\xe9\x3d\x7e\x11\x73\x93\x17\x2a"
++                        "\xae\x2d\x8a\x57\x1e\x03\xac\x9c"
++                        "\x9e\xb7\x6f\xac\x45\xaf\x8e\x51"
++                        "\x30\xc8\x1c\x46\xa3\x5c\xe4\x11"
++                        "\xe5\xfb\xc1\x19\x1a\x0a\x52\xef"
++                        "\xf6\x9f\x24\x45\xdf\x4f\x9b\x17"
++                        "\xad\x2b\x41\x7b\xe6\x6c\x37\x10",
++              .plen   = 64,
++              .ctext  = "\x4f\x02\x1d\xb2\x43\xbc\x63\x3d"
++                        "\x71\x78\x18\x3a\x9f\xa0\x71\xe8"
++                        "\xb4\xd9\xad\xa9\xad\x7d\xed\xf4"
++                        "\xe5\xe7\x38\x76\x3f\x69\x14\x5a"
++                        "\x57\x1b\x24\x20\x12\xfb\x7a\xe0"
++                        "\x7f\xa9\xba\xac\x3d\xf1\x02\xe0"
++                        "\x08\xb0\xe2\x79\x88\x59\x88\x81"
++                        "\xd9\x20\xa9\xe6\x4f\x56\x15\xcd"
++                        "\xc3\x46\xe5\x2c\x07\x27\x50\xca"
++                        "\x50\x4a\x83\x5f\x72\xd9\x76\x8d",
++              .clen   = 64 + 16,
++      }, { /* NIST SP800-38A F.2.5 CBC-AES256.Encrypt */
++#ifdef __LITTLE_ENDIAN
++              .key    = "\x08\x00"            /* rta length */
++                        "\x01\x00"            /* rta type */
++#else
++              .key    = "\x00\x08"            /* rta length */
++                        "\x00\x01"            /* rta type */
++#endif
++                        "\x00\x00\x00\x20"    /* enc key length */
++                        "\x11\x22\x33\x44\x55\x66\x77\x88"
++                        "\x99\xaa\xbb\xcc\xdd\xee\xff\x11"
++                        "\x60\x3d\xeb\x10\x15\xca\x71\xbe"
++                        "\x2b\x73\xae\xf0\x85\x7d\x77\x81"
++                        "\x1f\x35\x2c\x07\x3b\x61\x08\xd7"
++                        "\x2d\x98\x10\xa3\x09\x14\xdf\xf4",
++              .klen   = 8 + 16 + 32,
++              .iv     = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
++              .assoc  = "\x00\x01\x02\x03\x04\x05\x06\x07"
++                        "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
++              .alen   = 16,
++              .ptext  = "\x6b\xc1\xbe\xe2\x2e\x40\x9f\x96"
++                        "\xe9\x3d\x7e\x11\x73\x93\x17\x2a"
++                        "\xae\x2d\x8a\x57\x1e\x03\xac\x9c"
++                        "\x9e\xb7\x6f\xac\x45\xaf\x8e\x51"
++                        "\x30\xc8\x1c\x46\xa3\x5c\xe4\x11"
++                        "\xe5\xfb\xc1\x19\x1a\x0a\x52\xef"
++                        "\xf6\x9f\x24\x45\xdf\x4f\x9b\x17"
++                        "\xad\x2b\x41\x7b\xe6\x6c\x37\x10",
++              .plen   = 64,
++              .ctext  = "\xf5\x8c\x4c\x04\xd6\xe5\xf1\xba"
++                        "\x77\x9e\xab\xfb\x5f\x7b\xfb\xd6"
++                        "\x9c\xfc\x4e\x96\x7e\xdb\x80\x8d"
++                        "\x67\x9f\x77\x7b\xc6\x70\x2c\x7d"
++                        "\x39\xf2\x33\x69\xa9\xd9\xba\xcf"
++                        "\xa5\x30\xe2\x63\x04\x23\x14\x61"
++                        "\xb2\xeb\x05\xe2\xc3\x9b\xe9\xfc"
++                        "\xda\x6c\x19\x07\x8c\x6a\x9d\x1b"
++                        "\x59\x62\x06\x71\x57\xdf\x18\x15"
++                        "\x32\x02\xfa\xce\x2c\xd2\x1a\x8d",
++              .clen   = 64 + 16,
++      },
++};
++
+ static const struct aead_testvec hmac_md5_ecb_cipher_null_tv_template[] = {
+       { /* Input data from RFC 2410 Case 1 */
+ #ifdef __LITTLE_ENDIAN
diff --git a/target/linux/generic/pending-6.12/920-mangle_bootargs.patch b/target/linux/generic/pending-6.12/920-mangle_bootargs.patch
new file mode 100644 (file)
index 0000000..3bf2fce
--- /dev/null
@@ -0,0 +1,71 @@
+From: Imre Kaloz <kaloz@openwrt.org>
+Subject: init: add CONFIG_MANGLE_BOOTARGS and disable it by default
+
+Enabling this option renames the bootloader supplied root=
+and rootfstype= variables, which might have to be know but
+would break the automatisms OpenWrt uses.
+
+Signed-off-by: Imre Kaloz <kaloz@openwrt.org>
+---
+ init/Kconfig |  9 +++++++++
+ init/main.c  | 24 ++++++++++++++++++++++++
+ 2 files changed, 33 insertions(+)
+
+--- a/init/Kconfig
++++ b/init/Kconfig
+@@ -1888,6 +1888,15 @@ config ARCH_HAS_MEMBARRIER_CALLBACKS
+ config ARCH_HAS_MEMBARRIER_SYNC_CORE
+       bool
++config MANGLE_BOOTARGS
++      bool "Rename offending bootargs"
++      depends on EXPERT
++      help
++        Sometimes the bootloader passed bogus root= and rootfstype=
++        parameters to the kernel, and while you want to ignore them,
++        you need to know the values f.e. to support dual firmware
++        layouts on the flash.
++
+ config HAVE_PERF_EVENTS
+       bool
+       help
+--- a/init/main.c
++++ b/init/main.c
+@@ -633,6 +633,29 @@ static inline void setup_nr_cpu_ids(void
+ static inline void smp_prepare_cpus(unsigned int maxcpus) { }
+ #endif
++#ifdef CONFIG_MANGLE_BOOTARGS
++static void __init mangle_bootargs(char *command_line)
++{
++      char *rootdev;
++      char *rootfs;
++
++      rootdev = strstr(command_line, "root=/dev/mtdblock");
++
++      if (rootdev)
++              strncpy(rootdev, "mangled_rootblock=", 18);
++
++      rootfs = strstr(command_line, "rootfstype");
++
++      if (rootfs)
++              strncpy(rootfs, "mangled_fs", 10);
++
++}
++#else
++static void __init mangle_bootargs(char *command_line)
++{
++}
++#endif
++
+ /*
+  * We need to store the untouched command line for future reference.
+  * We also need to store the touched command line since the parameter
+@@ -939,6 +962,7 @@ void start_kernel(void)
+       jump_label_init();
+       static_call_init();
+       early_security_init();
++      mangle_bootargs(command_line);
+       setup_boot_config();
+       setup_command_line(command_line);
+       setup_nr_cpu_ids();