浏览代码

generic: copy backport, hack, pending patch and config from 6.1 to 6.6

Copy backport, hack, pending patch and config from 6.1 to 6.6.

Signed-off-by: Weijie Gao <[email protected]>
Weijie Gao 1 年之前
父节点
当前提交
8a9273d51e
共有 100 个文件被更改,包括 27045 次插入0 次删除
  1. 352 0
      target/linux/generic/backport-6.6/020-v6.3-01-UPSTREAM-mm-multi-gen-LRU-rename-lru_gen_struct-to-l.patch
  2. 192 0
      target/linux/generic/backport-6.6/020-v6.3-03-UPSTREAM-mm-multi-gen-LRU-remove-eviction-fairness-s.patch
  3. 294 0
      target/linux/generic/backport-6.6/020-v6.3-04-BACKPORT-mm-multi-gen-LRU-remove-aging-fairness-safe.patch
  4. 166 0
      target/linux/generic/backport-6.6/020-v6.3-05-UPSTREAM-mm-multi-gen-LRU-shuffle-should_run_aging.patch
  5. 876 0
      target/linux/generic/backport-6.6/020-v6.3-06-BACKPORT-mm-multi-gen-LRU-per-node-lru_gen_folio-lis.patch
  6. 202 0
      target/linux/generic/backport-6.6/020-v6.3-07-BACKPORT-mm-multi-gen-LRU-clarify-scan_control-flags.patch
  7. 38 0
      target/linux/generic/backport-6.6/020-v6.3-08-UPSTREAM-mm-multi-gen-LRU-simplify-arch_has_hw_pte_y.patch
  8. 92 0
      target/linux/generic/backport-6.6/020-v6.3-09-UPSTREAM-mm-multi-gen-LRU-avoid-futile-retries.patch
  9. 191 0
      target/linux/generic/backport-6.6/020-v6.3-10-UPSTREAM-mm-add-vma_has_recency.patch
  10. 129 0
      target/linux/generic/backport-6.6/020-v6.3-11-UPSTREAM-mm-support-POSIX_FADV_NOREUSE.patch
  11. 67 0
      target/linux/generic/backport-6.6/020-v6.3-12-UPSTREAM-mm-multi-gen-LRU-section-for-working-set-pr.patch
  12. 57 0
      target/linux/generic/backport-6.6/020-v6.3-13-UPSTREAM-mm-multi-gen-LRU-section-for-rmap-PT-walk-f.patch
  13. 243 0
      target/linux/generic/backport-6.6/020-v6.3-14-UPSTREAM-mm-multi-gen-LRU-section-for-Bloom-filters.patch
  14. 427 0
      target/linux/generic/backport-6.6/020-v6.3-15-UPSTREAM-mm-multi-gen-LRU-section-for-memcg-LRU.patch
  15. 40 0
      target/linux/generic/backport-6.6/020-v6.3-16-UPSTREAM-mm-multi-gen-LRU-improve-lru_gen_exit_memcg.patch
  16. 135 0
      target/linux/generic/backport-6.6/020-v6.3-17-UPSTREAM-mm-multi-gen-LRU-improve-walk_pmd_range.patch
  17. 148 0
      target/linux/generic/backport-6.6/020-v6.3-18-UPSTREAM-mm-multi-gen-LRU-simplify-lru_gen_look_arou.patch
  18. 273 0
      target/linux/generic/backport-6.6/020-v6.4-19-mm-Multi-gen-LRU-remove-wait_event_killable.patch
  19. 58 0
      target/linux/generic/backport-6.6/200-v6.3-bitfield-add-FIELD_PREP_CONST.patch
  20. 63 0
      target/linux/generic/backport-6.6/300-v6.2-powerpc-suppress-some-linker-warnings-in-recent-link.patch
  21. 65 0
      target/linux/generic/backport-6.6/406-v6.2-0001-mtd-core-simplify-a-bit-code-find-partition-matching.patch
  22. 84 0
      target/linux/generic/backport-6.6/406-v6.2-0002-mtd-core-try-to-find-OF-node-for-every-MTD-partition.patch
  23. 47 0
      target/linux/generic/backport-6.6/408-v6.2-mtd-core-set-ROOT_DEV-for-partitions-marked-as-rootf.patch
  24. 229 0
      target/linux/generic/backport-6.6/421-v6.2-mtd-parsers-add-TP-Link-SafeLoader-partitions-table-.patch
  25. 35 0
      target/linux/generic/backport-6.6/423-v6.3-mtd-spinand-macronix-use-scratch-buffer-for-DMA-oper.patch
  26. 33 0
      target/linux/generic/backport-6.6/424-v6.3-mtd-ubi-wire-up-parent-MTD-device.patch
  27. 49 0
      target/linux/generic/backport-6.6/425-v6.3-mtd-ubi-block-wire-up-device-parent.patch
  28. 47 0
      target/linux/generic/backport-6.6/426-v6.4-0004-mtd-core-prepare-mtd_otp_nvmem_add-to-handle-EPROBE_.patch
  29. 41 0
      target/linux/generic/backport-6.6/611-v6.3-net-add-helper-eth_addr_add.patch
  30. 32 0
      target/linux/generic/backport-6.6/701-net-next-net-sfp-add-quirk-for-Fiberstone-GPON-ONU-34-20BI.patch
  31. 2306 0
      target/linux/generic/backport-6.6/702-01-v6.7-net-phy-aquantia-move-to-separate-directory.patch
  32. 183 0
      target/linux/generic/backport-6.6/702-02-v6.7-net-phy-aquantia-move-MMD_VEND-define-to-header.patch
  33. 504 0
      target/linux/generic/backport-6.6/702-03-v6.7-net-phy-aquantia-add-firmware-load-support.patch
  34. 326 0
      target/linux/generic/backport-6.6/703-v6.3-net-mdio-Add-dedicated-C45-API-to-MDIO-bus-drivers.patch
  35. 105 0
      target/linux/generic/backport-6.6/704-v6.6-net-phy-Introduce-PSGMII-PHY-interface-mode.patch
  36. 32 0
      target/linux/generic/backport-6.6/705-v6.4-net-phy-at803x-Replace-of_gpio.h-with-what-indeed-is.patch
  37. 70 0
      target/linux/generic/backport-6.6/706-v6.6-01-net-phy-at803x-support-qca8081-genphy_c45_pma_read_a.patch
  38. 73 0
      target/linux/generic/backport-6.6/706-v6.6-02-net-phy-at803x-merge-qca8081-slave-seed-function.patch
  39. 76 0
      target/linux/generic/backport-6.6/706-v6.6-03-net-phy-at803x-enable-qca8081-slave-seed-conditional.patch
  40. 48 0
      target/linux/generic/backport-6.6/706-v6.6-04-net-phy-at803x-support-qca8081-1G-chip-type.patch
  41. 98 0
      target/linux/generic/backport-6.6/706-v6.6-05-net-phy-at803x-remove-qca8081-1G-fast-retrain-and-sl.patch
  42. 55 0
      target/linux/generic/backport-6.6/706-v6.6-06-net-phy-at803x-add-qca8081-fifo-reset-on-the-link-ch.patch
  43. 69 0
      target/linux/generic/backport-6.6/707-v6.8-02-net-phy-at803x-move-disable-WOL-to-specific-at8031-p.patch
  44. 129 0
      target/linux/generic/backport-6.6/707-v6.8-03-net-phy-at803x-raname-hw_stats-functions-to-qca83xx-.patch
  45. 155 0
      target/linux/generic/backport-6.6/707-v6.8-04-net-phy-at803x-move-qca83xx-specific-check-in-dedica.patch
  46. 94 0
      target/linux/generic/backport-6.6/707-v6.8-05-net-phy-at803x-move-specific-DT-option-for-at8031-to.patch
  47. 78 0
      target/linux/generic/backport-6.6/707-v6.8-06-net-phy-at803x-move-specific-at8031-probe-mode-check.patch
  48. 86 0
      target/linux/generic/backport-6.6/707-v6.8-07-net-phy-at803x-move-specific-at8031-config_init-to-d.patch
  49. 92 0
      target/linux/generic/backport-6.6/707-v6.8-08-net-phy-at803x-move-specific-at8031-WOL-bits-to-dedi.patch
  50. 78 0
      target/linux/generic/backport-6.6/707-v6.8-09-net-phy-at803x-move-specific-at8031-config_intr-to-d.patch
  51. 78 0
      target/linux/generic/backport-6.6/707-v6.8-10-net-phy-at803x-make-at8031-related-DT-functions-name.patch
  52. 297 0
      target/linux/generic/backport-6.6/707-v6.8-11-net-phy-at803x-move-at8031-functions-in-dedicated-se.patch
  53. 114 0
      target/linux/generic/backport-6.6/707-v6.8-12-net-phy-at803x-move-at8035-specific-DT-parse-to-dedi.patch
  54. 219 0
      target/linux/generic/backport-6.6/707-v6.8-13-net-phy-at803x-drop-specific-PHY-ID-check-from-cable.patch
  55. 116 0
      target/linux/generic/backport-6.6/708-v6.8-01-net-phy-at803x-move-specific-qca808x-config_aneg-to-.patch
  56. 97 0
      target/linux/generic/backport-6.6/708-v6.8-02-net-phy-at803x-make-read-specific-status-function-mo.patch
  57. 27 0
      target/linux/generic/backport-6.6/709-v6.8-01-net-phy-at803x-remove-extra-space-after-cast.patch
  58. 38 0
      target/linux/generic/backport-6.6/709-v6.8-02-net-phy-at803x-replace-msleep-1-with-usleep_range.patch
  59. 152 0
      target/linux/generic/backport-6.6/710-v6.8-net-phy-at803x-better-align-function-varibles-to-ope.patch
  60. 62 0
      target/linux/generic/backport-6.6/711-v6.8-01-net-phy-at803x-generalize-cdt-fault-length-function.patch
  61. 118 0
      target/linux/generic/backport-6.6/711-v6.8-02-net-phy-at803x-refactor-qca808x-cable-test-get-statu.patch
  62. 182 0
      target/linux/generic/backport-6.6/711-v6.8-03-net-phy-at803x-add-support-for-cdt-cross-short-test-.patch
  63. 62 0
      target/linux/generic/backport-6.6/711-v6.8-04-net-phy-at803x-make-read_status-more-generic.patch
  64. 408 0
      target/linux/generic/backport-6.6/712-v6.9-net-phy-at803x-add-LED-support-for-qca808x.patch
  65. 5598 0
      target/linux/generic/backport-6.6/713-v6.9-01-net-phy-move-at803x-PHY-driver-to-dedicated-director.patch
  66. 243 0
      target/linux/generic/backport-6.6/713-v6.9-02-net-phy-qcom-create-and-move-functions-to-shared-lib.patch
  67. 638 0
      target/linux/generic/backport-6.6/713-v6.9-03-net-phy-qcom-deatch-qca83xx-PHY-driver-from-at803x.patch
  68. 1014 0
      target/linux/generic/backport-6.6/713-v6.9-04-net-phy-qcom-move-additional-functions-to-shared-lib.patch
  69. 1936 0
      target/linux/generic/backport-6.6/713-v6.9-05-net-phy-qcom-detach-qca808x-PHY-driver-from-at803x.patch
  70. 394 0
      target/linux/generic/backport-6.6/714-net-pcs-add-driver-for-MediaTek-SGMII-PCS.patch
  71. 28 0
      target/linux/generic/backport-6.6/714-v6.8-01-net-phy-make-addr-type-u8-in-phy_package_shared-stru.patch
  72. 341 0
      target/linux/generic/backport-6.6/714-v6.8-02-net-phy-extend-PHY-package-API-to-support-multiple-g.patch
  73. 116 0
      target/linux/generic/backport-6.6/714-v6.8-03-net-phy-restructure-__phy_write-read_mmd-to-helper-a.patch
  74. 196 0
      target/linux/generic/backport-6.6/714-v6.8-04-net-phy-add-support-for-PHY-package-MMD-read-write.patch
  75. 103 0
      target/linux/generic/backport-6.6/715-01-v6.2-net-fman-memac-Add-serdes-support.patch
  76. 384 0
      target/linux/generic/backport-6.6/715-02-v6.2-net-fman-memac-Use-lynx-pcs-driver.patch
  77. 2451 0
      target/linux/generic/backport-6.6/715-03-v6.2-net-dpaa-Convert-to-phylink.patch
  78. 93 0
      target/linux/generic/backport-6.6/715-04-v6.2-net-phylink-provide-phylink_validate_mask_caps-helpe.patch
  79. 39 0
      target/linux/generic/backport-6.6/715-05-v6.2-phylink-require-valid-state-argument-to-phylink_vali.patch
  80. 48 0
      target/linux/generic/backport-6.6/715-06-v6.2-net-phylink-add-phylink_get_link_timer_ns-helper.patch
  81. 250 0
      target/linux/generic/backport-6.6/715-07-v6.2-net-remove-explicit-phylink_generic_validate-referen.patch
  82. 48 0
      target/linux/generic/backport-6.6/715-08-net-sfp-make-sfp_bus_find_fwnode-take-a-const-fwnode.patch
  83. 31 0
      target/linux/generic/backport-6.6/715-09-v6.4-net-pcs-lynx-don-t-print-an_enabled-in-pcs_get_state.patch
  84. 32 0
      target/linux/generic/backport-6.6/715-10-v6.4-net-dpaa2-mac-use-Autoneg-bit-rather-than-an_enabled.patch
  85. 64 0
      target/linux/generic/backport-6.6/715-11-v6.4-net-phylink-support-validated-pause-and-autoneg-in-f.patch
  86. 177 0
      target/linux/generic/backport-6.6/715-12-v6.4-net-phylink-remove-an_enabled.patch
  87. 88 0
      target/linux/generic/backport-6.6/715-13-v6.5-net-phylink-constify-fwnode-arguments.patch
  88. 38 0
      target/linux/generic/backport-6.6/715-14-v6.3-net-phy-constify-fwnode_get_phy_node-fwnode-argument.patch
  89. 44 0
      target/linux/generic/backport-6.6/715-15-v6.4-net-phylink-fix-ksettings_set-ethtool-call.patch
  90. 149 0
      target/linux/generic/backport-6.6/715-16-v6.5-net-sfp-add-support-for-setting-signalling-rate.patch
  91. 147 0
      target/linux/generic/backport-6.6/715-17-v6.5-net-phy-add-helpers-for-comparing-phy-IDs.patch
  92. 71 0
      target/linux/generic/backport-6.6/715-18-v6.5-net-phylink-require-supported_interfaces-to-be-fille.patch
  93. 64 0
      target/linux/generic/backport-6.6/715-19-v6.5-net-phylink-remove-duplicated-linkmode-pause-resolut.patch
  94. 76 0
      target/linux/generic/backport-6.6/715-20-v6.5-net-phylink-add-function-to-resolve-clause-73-negoti.patch
  95. 100 0
      target/linux/generic/backport-6.6/715-21-v6.5-net-phylink-provide-phylink_pcs_config-and-phylink_p.patch
  96. 55 0
      target/linux/generic/backport-6.6/715-23-v6.4-net-phylink-actually-fix-ksettings_set-ethtool-call.patch
  97. 324 0
      target/linux/generic/backport-6.6/715-24-v6.5-net-phylink-add-PCS-negotiation-mode.patch
  98. 45 0
      target/linux/generic/backport-6.6/715-25-v6.5-net-phylink-convert-phylink_mii_c22_pcs_config-to-ne.patch
  99. 187 0
      target/linux/generic/backport-6.6/715-26-v6.5-net-phylink-pass-neg_mode-into-phylink_mii_c22_pcs_c.patch
  100. 101 0
      target/linux/generic/backport-6.6/715-27-v6.5-net-pcs-lynxi-update-PCS-driver-to-use-neg_mode.patch

+ 352 - 0
target/linux/generic/backport-6.6/020-v6.3-01-UPSTREAM-mm-multi-gen-LRU-rename-lru_gen_struct-to-l.patch

@@ -0,0 +1,352 @@
+From 8c20e2eb5f2a0175b774134685e4d7bd93e85ff8 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <[email protected]>
+Date: Wed, 21 Dec 2022 21:18:59 -0700
+Subject: [PATCH 01/19] UPSTREAM: mm: multi-gen LRU: rename lru_gen_struct to
+ lru_gen_folio
+
+Patch series "mm: multi-gen LRU: memcg LRU", v3.
+
+Overview
+========
+
+An memcg LRU is a per-node LRU of memcgs.  It is also an LRU of LRUs,
+since each node and memcg combination has an LRU of folios (see
+mem_cgroup_lruvec()).
+
+Its goal is to improve the scalability of global reclaim, which is
+critical to system-wide memory overcommit in data centers.  Note that
+memcg reclaim is currently out of scope.
+
+Its memory bloat is a pointer to each lruvec and negligible to each
+pglist_data.  In terms of traversing memcgs during global reclaim, it
+improves the best-case complexity from O(n) to O(1) and does not affect
+the worst-case complexity O(n).  Therefore, on average, it has a sublinear
+complexity in contrast to the current linear complexity.
+
+The basic structure of an memcg LRU can be understood by an analogy to
+the active/inactive LRU (of folios):
+1. It has the young and the old (generations), i.e., the counterparts
+   to the active and the inactive;
+2. The increment of max_seq triggers promotion, i.e., the counterpart
+   to activation;
+3. Other events trigger similar operations, e.g., offlining an memcg
+   triggers demotion, i.e., the counterpart to deactivation.
+
+In terms of global reclaim, it has two distinct features:
+1. Sharding, which allows each thread to start at a random memcg (in
+   the old generation) and improves parallelism;
+2. Eventual fairness, which allows direct reclaim to bail out at will
+   and reduces latency without affecting fairness over some time.
+
+The commit message in patch 6 details the workflow:
+https://lore.kernel.org/r/[email protected]/
+
+The following is a simple test to quickly verify its effectiveness.
+
+  Test design:
+  1. Create multiple memcgs.
+  2. Each memcg contains a job (fio).
+  3. All jobs access the same amount of memory randomly.
+  4. The system does not experience global memory pressure.
+  5. Periodically write to the root memory.reclaim.
+
+  Desired outcome:
+  1. All memcgs have similar pgsteal counts, i.e., stddev(pgsteal)
+     over mean(pgsteal) is close to 0%.
+  2. The total pgsteal is close to the total requested through
+     memory.reclaim, i.e., sum(pgsteal) over sum(requested) is close
+     to 100%.
+
+  Actual outcome [1]:
+                                     MGLRU off    MGLRU on
+  stddev(pgsteal) / mean(pgsteal)    75%          20%
+  sum(pgsteal) / sum(requested)      425%         95%
+
+  ####################################################################
+  MEMCGS=128
+
+  for ((memcg = 0; memcg < $MEMCGS; memcg++)); do
+      mkdir /sys/fs/cgroup/memcg$memcg
+  done
+
+  start() {
+      echo $BASHPID > /sys/fs/cgroup/memcg$memcg/cgroup.procs
+
+      fio -name=memcg$memcg --numjobs=1 --ioengine=mmap \
+          --filename=/dev/zero --size=1920M --rw=randrw \
+          --rate=64m,64m --random_distribution=random \
+          --fadvise_hint=0 --time_based --runtime=10h \
+          --group_reporting --minimal
+  }
+
+  for ((memcg = 0; memcg < $MEMCGS; memcg++)); do
+      start &
+  done
+
+  sleep 600
+
+  for ((i = 0; i < 600; i++)); do
+      echo 256m >/sys/fs/cgroup/memory.reclaim
+      sleep 6
+  done
+
+  for ((memcg = 0; memcg < $MEMCGS; memcg++)); do
+      grep "pgsteal " /sys/fs/cgroup/memcg$memcg/memory.stat
+  done
+  ####################################################################
+
+[1]: This was obtained from running the above script (touches less
+     than 256GB memory) on an EPYC 7B13 with 512GB DRAM for over an
+     hour.
+
+This patch (of 8):
+
+The new name lru_gen_folio will be more distinct from the coming
+lru_gen_memcg.
+
+Link: https://lkml.kernel.org/r/[email protected]
+Link: https://lkml.kernel.org/r/[email protected]
+Signed-off-by: Yu Zhao <[email protected]>
+Cc: Johannes Weiner <[email protected]>
+Cc: Jonathan Corbet <[email protected]>
+Cc: Michael Larabel <[email protected]>
+Cc: Michal Hocko <[email protected]>
+Cc: Mike Rapoport <[email protected]>
+Cc: Roman Gushchin <[email protected]>
+Cc: Suren Baghdasaryan <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+Bug: 274865848
+(cherry picked from commit 391655fe08d1f942359a11148aa9aaf3f99d6d6f)
+Change-Id: I7df67e0e2435ba28f10eaa57d28d98b61a9210a6
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ include/linux/mm_inline.h |  4 ++--
+ include/linux/mmzone.h    |  6 +++---
+ mm/vmscan.c               | 34 +++++++++++++++++-----------------
+ mm/workingset.c           |  4 ++--
+ 4 files changed, 24 insertions(+), 24 deletions(-)
+
+--- a/include/linux/mm_inline.h
++++ b/include/linux/mm_inline.h
+@@ -178,7 +178,7 @@ static inline void lru_gen_update_size(s
+ 	int zone = folio_zonenum(folio);
+ 	int delta = folio_nr_pages(folio);
+ 	enum lru_list lru = type * LRU_INACTIVE_FILE;
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 
+ 	VM_WARN_ON_ONCE(old_gen != -1 && old_gen >= MAX_NR_GENS);
+ 	VM_WARN_ON_ONCE(new_gen != -1 && new_gen >= MAX_NR_GENS);
+@@ -224,7 +224,7 @@ static inline bool lru_gen_add_folio(str
+ 	int gen = folio_lru_gen(folio);
+ 	int type = folio_is_file_lru(folio);
+ 	int zone = folio_zonenum(folio);
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 
+ 	VM_WARN_ON_ONCE_FOLIO(gen != -1, folio);
+ 
+--- a/include/linux/mmzone.h
++++ b/include/linux/mmzone.h
+@@ -404,7 +404,7 @@ enum {
+  * The number of pages in each generation is eventually consistent and therefore
+  * can be transiently negative when reset_batch_size() is pending.
+  */
+-struct lru_gen_struct {
++struct lru_gen_folio {
+ 	/* the aging increments the youngest generation number */
+ 	unsigned long max_seq;
+ 	/* the eviction increments the oldest generation numbers */
+@@ -461,7 +461,7 @@ struct lru_gen_mm_state {
+ struct lru_gen_mm_walk {
+ 	/* the lruvec under reclaim */
+ 	struct lruvec *lruvec;
+-	/* unstable max_seq from lru_gen_struct */
++	/* unstable max_seq from lru_gen_folio */
+ 	unsigned long max_seq;
+ 	/* the next address within an mm to scan */
+ 	unsigned long next_addr;
+@@ -524,7 +524,7 @@ struct lruvec {
+ 	unsigned long			flags;
+ #ifdef CONFIG_LRU_GEN
+ 	/* evictable pages divided into generations */
+-	struct lru_gen_struct		lrugen;
++	struct lru_gen_folio		lrugen;
+ 	/* to concurrently iterate lru_gen_mm_list */
+ 	struct lru_gen_mm_state		mm_state;
+ #endif
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -3190,7 +3190,7 @@ static int get_nr_gens(struct lruvec *lr
+ 
+ static bool __maybe_unused seq_is_valid(struct lruvec *lruvec)
+ {
+-	/* see the comment on lru_gen_struct */
++	/* see the comment on lru_gen_folio */
+ 	return get_nr_gens(lruvec, LRU_GEN_FILE) >= MIN_NR_GENS &&
+ 	       get_nr_gens(lruvec, LRU_GEN_FILE) <= get_nr_gens(lruvec, LRU_GEN_ANON) &&
+ 	       get_nr_gens(lruvec, LRU_GEN_ANON) <= MAX_NR_GENS;
+@@ -3596,7 +3596,7 @@ struct ctrl_pos {
+ static void read_ctrl_pos(struct lruvec *lruvec, int type, int tier, int gain,
+ 			  struct ctrl_pos *pos)
+ {
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 	int hist = lru_hist_from_seq(lrugen->min_seq[type]);
+ 
+ 	pos->refaulted = lrugen->avg_refaulted[type][tier] +
+@@ -3611,7 +3611,7 @@ static void read_ctrl_pos(struct lruvec
+ static void reset_ctrl_pos(struct lruvec *lruvec, int type, bool carryover)
+ {
+ 	int hist, tier;
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 	bool clear = carryover ? NR_HIST_GENS == 1 : NR_HIST_GENS > 1;
+ 	unsigned long seq = carryover ? lrugen->min_seq[type] : lrugen->max_seq + 1;
+ 
+@@ -3688,7 +3688,7 @@ static int folio_update_gen(struct folio
+ static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio, bool reclaiming)
+ {
+ 	int type = folio_is_file_lru(folio);
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 	int new_gen, old_gen = lru_gen_from_seq(lrugen->min_seq[type]);
+ 	unsigned long new_flags, old_flags = READ_ONCE(folio->flags);
+ 
+@@ -3733,7 +3733,7 @@ static void update_batch_size(struct lru
+ static void reset_batch_size(struct lruvec *lruvec, struct lru_gen_mm_walk *walk)
+ {
+ 	int gen, type, zone;
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 
+ 	walk->batched = 0;
+ 
+@@ -4250,7 +4250,7 @@ static bool inc_min_seq(struct lruvec *l
+ {
+ 	int zone;
+ 	int remaining = MAX_LRU_BATCH;
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 	int new_gen, old_gen = lru_gen_from_seq(lrugen->min_seq[type]);
+ 
+ 	if (type == LRU_GEN_ANON && !can_swap)
+@@ -4286,7 +4286,7 @@ static bool try_to_inc_min_seq(struct lr
+ {
+ 	int gen, type, zone;
+ 	bool success = false;
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 	DEFINE_MIN_SEQ(lruvec);
+ 
+ 	VM_WARN_ON_ONCE(!seq_is_valid(lruvec));
+@@ -4307,7 +4307,7 @@ next:
+ 		;
+ 	}
+ 
+-	/* see the comment on lru_gen_struct */
++	/* see the comment on lru_gen_folio */
+ 	if (can_swap) {
+ 		min_seq[LRU_GEN_ANON] = min(min_seq[LRU_GEN_ANON], min_seq[LRU_GEN_FILE]);
+ 		min_seq[LRU_GEN_FILE] = max(min_seq[LRU_GEN_ANON], lrugen->min_seq[LRU_GEN_FILE]);
+@@ -4329,7 +4329,7 @@ static void inc_max_seq(struct lruvec *l
+ {
+ 	int prev, next;
+ 	int type, zone;
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 
+ restart:
+ 	spin_lock_irq(&lruvec->lru_lock);
+@@ -4389,7 +4389,7 @@ static bool try_to_inc_max_seq(struct lr
+ 	bool success;
+ 	struct lru_gen_mm_walk *walk;
+ 	struct mm_struct *mm = NULL;
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 
+ 	VM_WARN_ON_ONCE(max_seq > READ_ONCE(lrugen->max_seq));
+ 
+@@ -4454,7 +4454,7 @@ static bool should_run_aging(struct lruv
+ 	unsigned long old = 0;
+ 	unsigned long young = 0;
+ 	unsigned long total = 0;
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ 
+ 	for (type = !can_swap; type < ANON_AND_FILE; type++) {
+@@ -4740,7 +4740,7 @@ static bool sort_folio(struct lruvec *lr
+ 	int delta = folio_nr_pages(folio);
+ 	int refs = folio_lru_refs(folio);
+ 	int tier = lru_tier_from_refs(refs);
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 
+ 	VM_WARN_ON_ONCE_FOLIO(gen >= MAX_NR_GENS, folio);
+ 
+@@ -4848,7 +4848,7 @@ static int scan_folios(struct lruvec *lr
+ 	int scanned = 0;
+ 	int isolated = 0;
+ 	int remaining = MAX_LRU_BATCH;
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ 
+ 	VM_WARN_ON_ONCE(!list_empty(list));
+@@ -5249,7 +5249,7 @@ done:
+ 
+ static bool __maybe_unused state_is_valid(struct lruvec *lruvec)
+ {
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 
+ 	if (lrugen->enabled) {
+ 		enum lru_list lru;
+@@ -5531,7 +5531,7 @@ static void lru_gen_seq_show_full(struct
+ 	int i;
+ 	int type, tier;
+ 	int hist = lru_hist_from_seq(seq);
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 
+ 	for (tier = 0; tier < MAX_NR_TIERS; tier++) {
+ 		seq_printf(m, "            %10d", tier);
+@@ -5581,7 +5581,7 @@ static int lru_gen_seq_show(struct seq_f
+ 	unsigned long seq;
+ 	bool full = !debugfs_real_fops(m->file)->write;
+ 	struct lruvec *lruvec = v;
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 	int nid = lruvec_pgdat(lruvec)->node_id;
+ 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ 	DEFINE_MAX_SEQ(lruvec);
+@@ -5835,7 +5835,7 @@ void lru_gen_init_lruvec(struct lruvec *
+ {
+ 	int i;
+ 	int gen, type, zone;
+-	struct lru_gen_struct *lrugen = &lruvec->lrugen;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 
+ 	lrugen->max_seq = MIN_NR_GENS + 1;
+ 	lrugen->enabled = lru_gen_enabled();
+--- a/mm/workingset.c
++++ b/mm/workingset.c
+@@ -223,7 +223,7 @@ static void *lru_gen_eviction(struct fol
+ 	unsigned long token;
+ 	unsigned long min_seq;
+ 	struct lruvec *lruvec;
+-	struct lru_gen_struct *lrugen;
++	struct lru_gen_folio *lrugen;
+ 	int type = folio_is_file_lru(folio);
+ 	int delta = folio_nr_pages(folio);
+ 	int refs = folio_lru_refs(folio);
+@@ -252,7 +252,7 @@ static void lru_gen_refault(struct folio
+ 	unsigned long token;
+ 	unsigned long min_seq;
+ 	struct lruvec *lruvec;
+-	struct lru_gen_struct *lrugen;
++	struct lru_gen_folio *lrugen;
+ 	struct mem_cgroup *memcg;
+ 	struct pglist_data *pgdat;
+ 	int type = folio_is_file_lru(folio);

+ 192 - 0
target/linux/generic/backport-6.6/020-v6.3-03-UPSTREAM-mm-multi-gen-LRU-remove-eviction-fairness-s.patch

@@ -0,0 +1,192 @@
+From 14f9a7a15f3d1af351f30e0438fd747b7ac253b0 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <[email protected]>
+Date: Wed, 21 Dec 2022 21:19:01 -0700
+Subject: [PATCH 03/19] UPSTREAM: mm: multi-gen LRU: remove eviction fairness
+ safeguard
+
+Recall that the eviction consumes the oldest generation: first it
+bucket-sorts folios whose gen counters were updated by the aging and
+reclaims the rest; then it increments lrugen->min_seq.
+
+The current eviction fairness safeguard for global reclaim has a
+dilemma: when there are multiple eligible memcgs, should it continue
+or stop upon meeting the reclaim goal? If it continues, it overshoots
+and increases direct reclaim latency; if it stops, it loses fairness
+between memcgs it has taken memory away from and those it has yet to.
+
+With memcg LRU, the eviction, while ensuring eventual fairness, will
+stop upon meeting its goal. Therefore the current eviction fairness
+safeguard for global reclaim will not be needed.
+
+Note that memcg LRU only applies to global reclaim. For memcg reclaim,
+the eviction will continue, even if it is overshooting. This becomes
+unconditional due to code simplification.
+
+Link: https://lkml.kernel.org/r/[email protected]
+Signed-off-by: Yu Zhao <[email protected]>
+Cc: Johannes Weiner <[email protected]>
+Cc: Jonathan Corbet <[email protected]>
+Cc: Michael Larabel <[email protected]>
+Cc: Michal Hocko <[email protected]>
+Cc: Mike Rapoport <[email protected]>
+Cc: Roman Gushchin <[email protected]>
+Cc: Suren Baghdasaryan <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+Bug: 274865848
+(cherry picked from commit a579086c99ed70cc4bfc104348dbe3dd8f2787e6)
+Change-Id: I08ac1b3c90e29cafd0566785aaa4bcdb5db7d22c
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ mm/vmscan.c | 81 +++++++++++++++--------------------------------------
+ 1 file changed, 23 insertions(+), 58 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -448,6 +448,11 @@ static bool cgroup_reclaim(struct scan_c
+ 	return sc->target_mem_cgroup;
+ }
+ 
++static bool global_reclaim(struct scan_control *sc)
++{
++	return !sc->target_mem_cgroup || mem_cgroup_is_root(sc->target_mem_cgroup);
++}
++
+ /**
+  * writeback_throttling_sane - is the usual dirty throttling mechanism available?
+  * @sc: scan_control in question
+@@ -498,6 +503,11 @@ static bool cgroup_reclaim(struct scan_c
+ 	return false;
+ }
+ 
++static bool global_reclaim(struct scan_control *sc)
++{
++	return true;
++}
++
+ static bool writeback_throttling_sane(struct scan_control *sc)
+ {
+ 	return true;
+@@ -5005,8 +5015,7 @@ static int isolate_folios(struct lruvec
+ 	return scanned;
+ }
+ 
+-static int evict_folios(struct lruvec *lruvec, struct scan_control *sc, int swappiness,
+-			bool *need_swapping)
++static int evict_folios(struct lruvec *lruvec, struct scan_control *sc, int swappiness)
+ {
+ 	int type;
+ 	int scanned;
+@@ -5095,9 +5104,6 @@ retry:
+ 		goto retry;
+ 	}
+ 
+-	if (need_swapping && type == LRU_GEN_ANON)
+-		*need_swapping = true;
+-
+ 	return scanned;
+ }
+ 
+@@ -5136,67 +5142,26 @@ done:
+ 	return min_seq[!can_swap] + MIN_NR_GENS <= max_seq ? nr_to_scan : 0;
+ }
+ 
+-static bool should_abort_scan(struct lruvec *lruvec, unsigned long seq,
+-			      struct scan_control *sc, bool need_swapping)
++static unsigned long get_nr_to_reclaim(struct scan_control *sc)
+ {
+-	int i;
+-	DEFINE_MAX_SEQ(lruvec);
+-
+-	if (!current_is_kswapd()) {
+-		/* age each memcg at most once to ensure fairness */
+-		if (max_seq - seq > 1)
+-			return true;
+-
+-		/* over-swapping can increase allocation latency */
+-		if (sc->nr_reclaimed >= sc->nr_to_reclaim && need_swapping)
+-			return true;
+-
+-		/* give this thread a chance to exit and free its memory */
+-		if (fatal_signal_pending(current)) {
+-			sc->nr_reclaimed += MIN_LRU_BATCH;
+-			return true;
+-		}
+-
+-		if (cgroup_reclaim(sc))
+-			return false;
+-	} else if (sc->nr_reclaimed - sc->last_reclaimed < sc->nr_to_reclaim)
+-		return false;
+-
+-	/* keep scanning at low priorities to ensure fairness */
+-	if (sc->priority > DEF_PRIORITY - 2)
+-		return false;
+-
+-	/*
+-	 * A minimum amount of work was done under global memory pressure. For
+-	 * kswapd, it may be overshooting. For direct reclaim, the allocation
+-	 * may succeed if all suitable zones are somewhat safe. In either case,
+-	 * it's better to stop now, and restart later if necessary.
+-	 */
+-	for (i = 0; i <= sc->reclaim_idx; i++) {
+-		unsigned long wmark;
+-		struct zone *zone = lruvec_pgdat(lruvec)->node_zones + i;
+-
+-		if (!managed_zone(zone))
+-			continue;
+-
+-		wmark = current_is_kswapd() ? high_wmark_pages(zone) : low_wmark_pages(zone);
+-		if (wmark > zone_page_state(zone, NR_FREE_PAGES))
+-			return false;
+-	}
++	/* don't abort memcg reclaim to ensure fairness */
++	if (!global_reclaim(sc))
++		return -1;
+ 
+-	sc->nr_reclaimed += MIN_LRU_BATCH;
++	/* discount the previous progress for kswapd */
++	if (current_is_kswapd())
++		return sc->nr_to_reclaim + sc->last_reclaimed;
+ 
+-	return true;
++	return max(sc->nr_to_reclaim, compact_gap(sc->order));
+ }
+ 
+ static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
+ {
+ 	struct blk_plug plug;
+ 	bool need_aging = false;
+-	bool need_swapping = false;
+ 	unsigned long scanned = 0;
+ 	unsigned long reclaimed = sc->nr_reclaimed;
+-	DEFINE_MAX_SEQ(lruvec);
++	unsigned long nr_to_reclaim = get_nr_to_reclaim(sc);
+ 
+ 	lru_add_drain();
+ 
+@@ -5220,7 +5185,7 @@ static void lru_gen_shrink_lruvec(struct
+ 		if (!nr_to_scan)
+ 			goto done;
+ 
+-		delta = evict_folios(lruvec, sc, swappiness, &need_swapping);
++		delta = evict_folios(lruvec, sc, swappiness);
+ 		if (!delta)
+ 			goto done;
+ 
+@@ -5228,7 +5193,7 @@ static void lru_gen_shrink_lruvec(struct
+ 		if (scanned >= nr_to_scan)
+ 			break;
+ 
+-		if (should_abort_scan(lruvec, max_seq, sc, need_swapping))
++		if (sc->nr_reclaimed >= nr_to_reclaim)
+ 			break;
+ 
+ 		cond_resched();
+@@ -5678,7 +5643,7 @@ static int run_eviction(struct lruvec *l
+ 		if (sc->nr_reclaimed >= nr_to_reclaim)
+ 			return 0;
+ 
+-		if (!evict_folios(lruvec, sc, swappiness, NULL))
++		if (!evict_folios(lruvec, sc, swappiness))
+ 			return 0;
+ 
+ 		cond_resched();

+ 294 - 0
target/linux/generic/backport-6.6/020-v6.3-04-BACKPORT-mm-multi-gen-LRU-remove-aging-fairness-safe.patch

@@ -0,0 +1,294 @@
+From f3c93d2e37a3c56593d7ccf4f4bcf1b58426fdd8 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <[email protected]>
+Date: Wed, 21 Dec 2022 21:19:02 -0700
+Subject: [PATCH 04/19] BACKPORT: mm: multi-gen LRU: remove aging fairness
+ safeguard
+
+Recall that the aging produces the youngest generation: first it scans
+for accessed folios and updates their gen counters; then it increments
+lrugen->max_seq.
+
+The current aging fairness safeguard for kswapd uses two passes to
+ensure the fairness to multiple eligible memcgs. On the first pass,
+which is shared with the eviction, it checks whether all eligible
+memcgs are low on cold folios. If so, it requires a second pass, on
+which it ages all those memcgs at the same time.
+
+With memcg LRU, the aging, while ensuring eventual fairness, will run
+when necessary. Therefore the current aging fairness safeguard for
+kswapd will not be needed.
+
+Note that memcg LRU only applies to global reclaim. For memcg reclaim,
+the aging can be unfair to different memcgs, i.e., their
+lrugen->max_seq can be incremented at different paces.
+
+Link: https://lkml.kernel.org/r/[email protected]
+Signed-off-by: Yu Zhao <[email protected]>
+Cc: Johannes Weiner <[email protected]>
+Cc: Jonathan Corbet <[email protected]>
+Cc: Michael Larabel <[email protected]>
+Cc: Michal Hocko <[email protected]>
+Cc: Mike Rapoport <[email protected]>
+Cc: Roman Gushchin <[email protected]>
+Cc: Suren Baghdasaryan <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+Bug: 274865848
+(cherry picked from commit 7348cc91821b0cb24dfb00e578047f68299a50ab)
+[TJ: Resolved conflicts with older function signatures for
+min_cgroup_below_min / min_cgroup_below_low]
+Change-Id: I6e36ecfbaaefbc0a56d9a9d5d7cbe404ed7f57a5
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ mm/vmscan.c | 126 ++++++++++++++++++++++++----------------------------
+ 1 file changed, 59 insertions(+), 67 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -136,7 +136,6 @@ struct scan_control {
+ 
+ #ifdef CONFIG_LRU_GEN
+ 	/* help kswapd make better choices among multiple memcgs */
+-	unsigned int memcgs_need_aging:1;
+ 	unsigned long last_reclaimed;
+ #endif
+ 
+@@ -4457,7 +4456,7 @@ done:
+ 	return true;
+ }
+ 
+-static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq, unsigned long *min_seq,
++static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
+ 			     struct scan_control *sc, bool can_swap, unsigned long *nr_to_scan)
+ {
+ 	int gen, type, zone;
+@@ -4466,6 +4465,13 @@ static bool should_run_aging(struct lruv
+ 	unsigned long total = 0;
+ 	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
++	DEFINE_MIN_SEQ(lruvec);
++
++	/* whether this lruvec is completely out of cold folios */
++	if (min_seq[!can_swap] + MIN_NR_GENS > max_seq) {
++		*nr_to_scan = 0;
++		return true;
++	}
+ 
+ 	for (type = !can_swap; type < ANON_AND_FILE; type++) {
+ 		unsigned long seq;
+@@ -4494,8 +4500,6 @@ static bool should_run_aging(struct lruv
+ 	 * stalls when the number of generations reaches MIN_NR_GENS. Hence, the
+ 	 * ideal number of generations is MIN_NR_GENS+1.
+ 	 */
+-	if (min_seq[!can_swap] + MIN_NR_GENS > max_seq)
+-		return true;
+ 	if (min_seq[!can_swap] + MIN_NR_GENS < max_seq)
+ 		return false;
+ 
+@@ -4514,40 +4518,54 @@ static bool should_run_aging(struct lruv
+ 	return false;
+ }
+ 
+-static bool age_lruvec(struct lruvec *lruvec, struct scan_control *sc, unsigned long min_ttl)
++static bool lruvec_is_sizable(struct lruvec *lruvec, struct scan_control *sc)
+ {
+-	bool need_aging;
+-	unsigned long nr_to_scan;
+-	int swappiness = get_swappiness(lruvec, sc);
++	int gen, type, zone;
++	unsigned long total = 0;
++	bool can_swap = get_swappiness(lruvec, sc);
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ 	DEFINE_MAX_SEQ(lruvec);
+ 	DEFINE_MIN_SEQ(lruvec);
+ 
+-	VM_WARN_ON_ONCE(sc->memcg_low_reclaim);
++	for (type = !can_swap; type < ANON_AND_FILE; type++) {
++		unsigned long seq;
+ 
+-	mem_cgroup_calculate_protection(NULL, memcg);
++		for (seq = min_seq[type]; seq <= max_seq; seq++) {
++			gen = lru_gen_from_seq(seq);
+ 
+-	if (mem_cgroup_below_min(memcg))
+-		return false;
++			for (zone = 0; zone < MAX_NR_ZONES; zone++)
++				total += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
++		}
++	}
+ 
+-	need_aging = should_run_aging(lruvec, max_seq, min_seq, sc, swappiness, &nr_to_scan);
++	/* whether the size is big enough to be helpful */
++	return mem_cgroup_online(memcg) ? (total >> sc->priority) : total;
++}
+ 
+-	if (min_ttl) {
+-		int gen = lru_gen_from_seq(min_seq[LRU_GEN_FILE]);
+-		unsigned long birth = READ_ONCE(lruvec->lrugen.timestamps[gen]);
++static bool lruvec_is_reclaimable(struct lruvec *lruvec, struct scan_control *sc,
++				  unsigned long min_ttl)
++{
++	int gen;
++	unsigned long birth;
++	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
++	DEFINE_MIN_SEQ(lruvec);
+ 
+-		if (time_is_after_jiffies(birth + min_ttl))
+-			return false;
++	VM_WARN_ON_ONCE(sc->memcg_low_reclaim);
+ 
+-		/* the size is likely too small to be helpful */
+-		if (!nr_to_scan && sc->priority != DEF_PRIORITY)
+-			return false;
+-	}
++	/* see the comment on lru_gen_folio */
++	gen = lru_gen_from_seq(min_seq[LRU_GEN_FILE]);
++	birth = READ_ONCE(lruvec->lrugen.timestamps[gen]);
+ 
+-	if (need_aging)
+-		try_to_inc_max_seq(lruvec, max_seq, sc, swappiness, false);
++	if (time_is_after_jiffies(birth + min_ttl))
++		return false;
+ 
+-	return true;
++	if (!lruvec_is_sizable(lruvec, sc))
++		return false;
++
++	mem_cgroup_calculate_protection(NULL, memcg);
++
++	return !mem_cgroup_below_min(memcg);
+ }
+ 
+ /* to protect the working set of the last N jiffies */
+@@ -4556,46 +4574,32 @@ static unsigned long lru_gen_min_ttl __r
+ static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc)
+ {
+ 	struct mem_cgroup *memcg;
+-	bool success = false;
+ 	unsigned long min_ttl = READ_ONCE(lru_gen_min_ttl);
+ 
+ 	VM_WARN_ON_ONCE(!current_is_kswapd());
+ 
+ 	sc->last_reclaimed = sc->nr_reclaimed;
+ 
+-	/*
+-	 * To reduce the chance of going into the aging path, which can be
+-	 * costly, optimistically skip it if the flag below was cleared in the
+-	 * eviction path. This improves the overall performance when multiple
+-	 * memcgs are available.
+-	 */
+-	if (!sc->memcgs_need_aging) {
+-		sc->memcgs_need_aging = true;
++	/* check the order to exclude compaction-induced reclaim */
++	if (!min_ttl || sc->order || sc->priority == DEF_PRIORITY)
+ 		return;
+-	}
+-
+-	set_mm_walk(pgdat);
+ 
+ 	memcg = mem_cgroup_iter(NULL, NULL, NULL);
+ 	do {
+ 		struct lruvec *lruvec = mem_cgroup_lruvec(memcg, pgdat);
+ 
+-		if (age_lruvec(lruvec, sc, min_ttl))
+-			success = true;
++		if (lruvec_is_reclaimable(lruvec, sc, min_ttl)) {
++			mem_cgroup_iter_break(NULL, memcg);
++			return;
++		}
+ 
+ 		cond_resched();
+ 	} while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)));
+ 
+-	clear_mm_walk();
+-
+-	/* check the order to exclude compaction-induced reclaim */
+-	if (success || !min_ttl || sc->order)
+-		return;
+-
+ 	/*
+ 	 * The main goal is to OOM kill if every generation from all memcgs is
+ 	 * younger than min_ttl. However, another possibility is all memcgs are
+-	 * either below min or empty.
++	 * either too small or below min.
+ 	 */
+ 	if (mutex_trylock(&oom_lock)) {
+ 		struct oom_control oc = {
+@@ -5113,33 +5117,27 @@ retry:
+  *    reclaim.
+  */
+ static unsigned long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc,
+-				    bool can_swap, bool *need_aging)
++				    bool can_swap)
+ {
+ 	unsigned long nr_to_scan;
+ 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ 	DEFINE_MAX_SEQ(lruvec);
+-	DEFINE_MIN_SEQ(lruvec);
+ 
+ 	if (mem_cgroup_below_min(memcg) ||
+ 	    (mem_cgroup_below_low(memcg) && !sc->memcg_low_reclaim))
+ 		return 0;
+ 
+-	*need_aging = should_run_aging(lruvec, max_seq, min_seq, sc, can_swap, &nr_to_scan);
+-	if (!*need_aging)
++	if (!should_run_aging(lruvec, max_seq, sc, can_swap, &nr_to_scan))
+ 		return nr_to_scan;
+ 
+ 	/* skip the aging path at the default priority */
+ 	if (sc->priority == DEF_PRIORITY)
+-		goto done;
++		return nr_to_scan;
+ 
+-	/* leave the work to lru_gen_age_node() */
+-	if (current_is_kswapd())
+-		return 0;
++	try_to_inc_max_seq(lruvec, max_seq, sc, can_swap, false);
+ 
+-	if (try_to_inc_max_seq(lruvec, max_seq, sc, can_swap, false))
+-		return nr_to_scan;
+-done:
+-	return min_seq[!can_swap] + MIN_NR_GENS <= max_seq ? nr_to_scan : 0;
++	/* skip this lruvec as it's low on cold folios */
++	return 0;
+ }
+ 
+ static unsigned long get_nr_to_reclaim(struct scan_control *sc)
+@@ -5158,9 +5156,7 @@ static unsigned long get_nr_to_reclaim(s
+ static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
+ {
+ 	struct blk_plug plug;
+-	bool need_aging = false;
+ 	unsigned long scanned = 0;
+-	unsigned long reclaimed = sc->nr_reclaimed;
+ 	unsigned long nr_to_reclaim = get_nr_to_reclaim(sc);
+ 
+ 	lru_add_drain();
+@@ -5181,13 +5177,13 @@ static void lru_gen_shrink_lruvec(struct
+ 		else
+ 			swappiness = 0;
+ 
+-		nr_to_scan = get_nr_to_scan(lruvec, sc, swappiness, &need_aging);
++		nr_to_scan = get_nr_to_scan(lruvec, sc, swappiness);
+ 		if (!nr_to_scan)
+-			goto done;
++			break;
+ 
+ 		delta = evict_folios(lruvec, sc, swappiness);
+ 		if (!delta)
+-			goto done;
++			break;
+ 
+ 		scanned += delta;
+ 		if (scanned >= nr_to_scan)
+@@ -5199,10 +5195,6 @@ static void lru_gen_shrink_lruvec(struct
+ 		cond_resched();
+ 	}
+ 
+-	/* see the comment in lru_gen_age_node() */
+-	if (sc->nr_reclaimed - reclaimed >= MIN_LRU_BATCH && !need_aging)
+-		sc->memcgs_need_aging = false;
+-done:
+ 	clear_mm_walk();
+ 
+ 	blk_finish_plug(&plug);

+ 166 - 0
target/linux/generic/backport-6.6/020-v6.3-05-UPSTREAM-mm-multi-gen-LRU-shuffle-should_run_aging.patch

@@ -0,0 +1,166 @@
+From eca3858631e0cbad2ca6e40f788892749428e4cb Mon Sep 17 00:00:00 2001
+From: Yu Zhao <[email protected]>
+Date: Wed, 21 Dec 2022 21:19:03 -0700
+Subject: [PATCH 05/19] UPSTREAM: mm: multi-gen LRU: shuffle should_run_aging()
+
+Move should_run_aging() next to its only caller left.
+
+Link: https://lkml.kernel.org/r/[email protected]
+Cc: Johannes Weiner <[email protected]>
+Cc: Jonathan Corbet <[email protected]>
+Cc: Michael Larabel <[email protected]>
+Cc: Michal Hocko <[email protected]>
+Cc: Mike Rapoport <[email protected]>
+Cc: Roman Gushchin <[email protected]>
+Cc: Suren Baghdasaryan <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+Bug: 274865848
+(cherry picked from commit 77d4459a4a1a472b7309e475f962dda87d950abd)
+Signed-off-by: T.J. Mercier <[email protected]>
+Change-Id: I3b0383fe16b93a783b4d8c0b3a0b325160392576
+Signed-off-by: Yu Zhao <[email protected]>
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ mm/vmscan.c | 124 ++++++++++++++++++++++++++--------------------------
+ 1 file changed, 62 insertions(+), 62 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -4456,68 +4456,6 @@ done:
+ 	return true;
+ }
+ 
+-static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
+-			     struct scan_control *sc, bool can_swap, unsigned long *nr_to_scan)
+-{
+-	int gen, type, zone;
+-	unsigned long old = 0;
+-	unsigned long young = 0;
+-	unsigned long total = 0;
+-	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+-	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+-	DEFINE_MIN_SEQ(lruvec);
+-
+-	/* whether this lruvec is completely out of cold folios */
+-	if (min_seq[!can_swap] + MIN_NR_GENS > max_seq) {
+-		*nr_to_scan = 0;
+-		return true;
+-	}
+-
+-	for (type = !can_swap; type < ANON_AND_FILE; type++) {
+-		unsigned long seq;
+-
+-		for (seq = min_seq[type]; seq <= max_seq; seq++) {
+-			unsigned long size = 0;
+-
+-			gen = lru_gen_from_seq(seq);
+-
+-			for (zone = 0; zone < MAX_NR_ZONES; zone++)
+-				size += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
+-
+-			total += size;
+-			if (seq == max_seq)
+-				young += size;
+-			else if (seq + MIN_NR_GENS == max_seq)
+-				old += size;
+-		}
+-	}
+-
+-	/* try to scrape all its memory if this memcg was deleted */
+-	*nr_to_scan = mem_cgroup_online(memcg) ? (total >> sc->priority) : total;
+-
+-	/*
+-	 * The aging tries to be lazy to reduce the overhead, while the eviction
+-	 * stalls when the number of generations reaches MIN_NR_GENS. Hence, the
+-	 * ideal number of generations is MIN_NR_GENS+1.
+-	 */
+-	if (min_seq[!can_swap] + MIN_NR_GENS < max_seq)
+-		return false;
+-
+-	/*
+-	 * It's also ideal to spread pages out evenly, i.e., 1/(MIN_NR_GENS+1)
+-	 * of the total number of pages for each generation. A reasonable range
+-	 * for this average portion is [1/MIN_NR_GENS, 1/(MIN_NR_GENS+2)]. The
+-	 * aging cares about the upper bound of hot pages, while the eviction
+-	 * cares about the lower bound of cold pages.
+-	 */
+-	if (young * MIN_NR_GENS > total)
+-		return true;
+-	if (old * (MIN_NR_GENS + 2) < total)
+-		return true;
+-
+-	return false;
+-}
+-
+ static bool lruvec_is_sizable(struct lruvec *lruvec, struct scan_control *sc)
+ {
+ 	int gen, type, zone;
+@@ -5111,6 +5049,68 @@ retry:
+ 	return scanned;
+ }
+ 
++static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
++			     struct scan_control *sc, bool can_swap, unsigned long *nr_to_scan)
++{
++	int gen, type, zone;
++	unsigned long old = 0;
++	unsigned long young = 0;
++	unsigned long total = 0;
++	struct lru_gen_folio *lrugen = &lruvec->lrugen;
++	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
++	DEFINE_MIN_SEQ(lruvec);
++
++	/* whether this lruvec is completely out of cold folios */
++	if (min_seq[!can_swap] + MIN_NR_GENS > max_seq) {
++		*nr_to_scan = 0;
++		return true;
++	}
++
++	for (type = !can_swap; type < ANON_AND_FILE; type++) {
++		unsigned long seq;
++
++		for (seq = min_seq[type]; seq <= max_seq; seq++) {
++			unsigned long size = 0;
++
++			gen = lru_gen_from_seq(seq);
++
++			for (zone = 0; zone < MAX_NR_ZONES; zone++)
++				size += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
++
++			total += size;
++			if (seq == max_seq)
++				young += size;
++			else if (seq + MIN_NR_GENS == max_seq)
++				old += size;
++		}
++	}
++
++	/* try to scrape all its memory if this memcg was deleted */
++	*nr_to_scan = mem_cgroup_online(memcg) ? (total >> sc->priority) : total;
++
++	/*
++	 * The aging tries to be lazy to reduce the overhead, while the eviction
++	 * stalls when the number of generations reaches MIN_NR_GENS. Hence, the
++	 * ideal number of generations is MIN_NR_GENS+1.
++	 */
++	if (min_seq[!can_swap] + MIN_NR_GENS < max_seq)
++		return false;
++
++	/*
++	 * It's also ideal to spread pages out evenly, i.e., 1/(MIN_NR_GENS+1)
++	 * of the total number of pages for each generation. A reasonable range
++	 * for this average portion is [1/MIN_NR_GENS, 1/(MIN_NR_GENS+2)]. The
++	 * aging cares about the upper bound of hot pages, while the eviction
++	 * cares about the lower bound of cold pages.
++	 */
++	if (young * MIN_NR_GENS > total)
++		return true;
++	if (old * (MIN_NR_GENS + 2) < total)
++		return true;
++
++	return false;
++}
++
+ /*
+  * For future optimizations:
+  * 1. Defer try_to_inc_max_seq() to workqueues to reduce latency for memcg

+ 876 - 0
target/linux/generic/backport-6.6/020-v6.3-06-BACKPORT-mm-multi-gen-LRU-per-node-lru_gen_folio-lis.patch

@@ -0,0 +1,876 @@
+From 8ee8571e47aa75221e5fbd4c9c7802fc4244c346 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <[email protected]>
+Date: Wed, 21 Dec 2022 21:19:04 -0700
+Subject: [PATCH 06/19] BACKPORT: mm: multi-gen LRU: per-node lru_gen_folio
+ lists
+
+For each node, memcgs are divided into two generations: the old and
+the young. For each generation, memcgs are randomly sharded into
+multiple bins to improve scalability. For each bin, an RCU hlist_nulls
+is virtually divided into three segments: the head, the tail and the
+default.
+
+An onlining memcg is added to the tail of a random bin in the old
+generation. The eviction starts at the head of a random bin in the old
+generation. The per-node memcg generation counter, whose reminder (mod
+2) indexes the old generation, is incremented when all its bins become
+empty.
+
+There are four operations:
+1. MEMCG_LRU_HEAD, which moves an memcg to the head of a random bin in
+   its current generation (old or young) and updates its "seg" to
+   "head";
+2. MEMCG_LRU_TAIL, which moves an memcg to the tail of a random bin in
+   its current generation (old or young) and updates its "seg" to
+   "tail";
+3. MEMCG_LRU_OLD, which moves an memcg to the head of a random bin in
+   the old generation, updates its "gen" to "old" and resets its "seg"
+   to "default";
+4. MEMCG_LRU_YOUNG, which moves an memcg to the tail of a random bin
+   in the young generation, updates its "gen" to "young" and resets
+   its "seg" to "default".
+
+The events that trigger the above operations are:
+1. Exceeding the soft limit, which triggers MEMCG_LRU_HEAD;
+2. The first attempt to reclaim an memcg below low, which triggers
+   MEMCG_LRU_TAIL;
+3. The first attempt to reclaim an memcg below reclaimable size
+   threshold, which triggers MEMCG_LRU_TAIL;
+4. The second attempt to reclaim an memcg below reclaimable size
+   threshold, which triggers MEMCG_LRU_YOUNG;
+5. Attempting to reclaim an memcg below min, which triggers
+   MEMCG_LRU_YOUNG;
+6. Finishing the aging on the eviction path, which triggers
+   MEMCG_LRU_YOUNG;
+7. Offlining an memcg, which triggers MEMCG_LRU_OLD.
+
+Note that memcg LRU only applies to global reclaim, and the
+round-robin incrementing of their max_seq counters ensures the
+eventual fairness to all eligible memcgs. For memcg reclaim, it still
+relies on mem_cgroup_iter().
+
+Link: https://lkml.kernel.org/r/[email protected]
+Signed-off-by: Yu Zhao <[email protected]>
+Cc: Johannes Weiner <[email protected]>
+Cc: Jonathan Corbet <[email protected]>
+Cc: Michael Larabel <[email protected]>
+Cc: Michal Hocko <[email protected]>
+Cc: Mike Rapoport <[email protected]>
+Cc: Roman Gushchin <[email protected]>
+Cc: Suren Baghdasaryan <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+Bug: 274865848
+(cherry picked from commit e4dde56cd208674ce899b47589f263499e5b8cdc)
+[TJ: Resolved conflicts with older function signatures for
+min_cgroup_below_min / min_cgroup_below_low and includes]
+Change-Id: Idc8a0f635e035d72dd911f807d1224cb47cbd655
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ include/linux/memcontrol.h |  10 +
+ include/linux/mm_inline.h  |  17 ++
+ include/linux/mmzone.h     | 117 +++++++++++-
+ mm/memcontrol.c            |  16 ++
+ mm/page_alloc.c            |   1 +
+ mm/vmscan.c                | 374 +++++++++++++++++++++++++++++++++----
+ 6 files changed, 500 insertions(+), 35 deletions(-)
+
+--- a/include/linux/memcontrol.h
++++ b/include/linux/memcontrol.h
+@@ -795,6 +795,11 @@ static inline void obj_cgroup_put(struct
+ 	percpu_ref_put(&objcg->refcnt);
+ }
+ 
++static inline bool mem_cgroup_tryget(struct mem_cgroup *memcg)
++{
++	return !memcg || css_tryget(&memcg->css);
++}
++
+ static inline void mem_cgroup_put(struct mem_cgroup *memcg)
+ {
+ 	if (memcg)
+@@ -1295,6 +1300,11 @@ static inline void obj_cgroup_put(struct
+ {
+ }
+ 
++static inline bool mem_cgroup_tryget(struct mem_cgroup *memcg)
++{
++	return true;
++}
++
+ static inline void mem_cgroup_put(struct mem_cgroup *memcg)
+ {
+ }
+--- a/include/linux/mm_inline.h
++++ b/include/linux/mm_inline.h
+@@ -122,6 +122,18 @@ static inline bool lru_gen_in_fault(void
+ 	return current->in_lru_fault;
+ }
+ 
++#ifdef CONFIG_MEMCG
++static inline int lru_gen_memcg_seg(struct lruvec *lruvec)
++{
++	return READ_ONCE(lruvec->lrugen.seg);
++}
++#else
++static inline int lru_gen_memcg_seg(struct lruvec *lruvec)
++{
++	return 0;
++}
++#endif
++
+ static inline int lru_gen_from_seq(unsigned long seq)
+ {
+ 	return seq % MAX_NR_GENS;
+@@ -302,6 +314,11 @@ static inline bool lru_gen_in_fault(void
+ 	return false;
+ }
+ 
++static inline int lru_gen_memcg_seg(struct lruvec *lruvec)
++{
++	return 0;
++}
++
+ static inline bool lru_gen_add_folio(struct lruvec *lruvec, struct folio *folio, bool reclaiming)
+ {
+ 	return false;
+--- a/include/linux/mmzone.h
++++ b/include/linux/mmzone.h
+@@ -7,6 +7,7 @@
+ 
+ #include <linux/spinlock.h>
+ #include <linux/list.h>
++#include <linux/list_nulls.h>
+ #include <linux/wait.h>
+ #include <linux/bitops.h>
+ #include <linux/cache.h>
+@@ -367,6 +368,15 @@ struct page_vma_mapped_walk;
+ #define LRU_GEN_MASK		((BIT(LRU_GEN_WIDTH) - 1) << LRU_GEN_PGOFF)
+ #define LRU_REFS_MASK		((BIT(LRU_REFS_WIDTH) - 1) << LRU_REFS_PGOFF)
+ 
++/* see the comment on MEMCG_NR_GENS */
++enum {
++	MEMCG_LRU_NOP,
++	MEMCG_LRU_HEAD,
++	MEMCG_LRU_TAIL,
++	MEMCG_LRU_OLD,
++	MEMCG_LRU_YOUNG,
++};
++
+ #ifdef CONFIG_LRU_GEN
+ 
+ enum {
+@@ -426,6 +436,14 @@ struct lru_gen_folio {
+ 	atomic_long_t refaulted[NR_HIST_GENS][ANON_AND_FILE][MAX_NR_TIERS];
+ 	/* whether the multi-gen LRU is enabled */
+ 	bool enabled;
++#ifdef CONFIG_MEMCG
++	/* the memcg generation this lru_gen_folio belongs to */
++	u8 gen;
++	/* the list segment this lru_gen_folio belongs to */
++	u8 seg;
++	/* per-node lru_gen_folio list for global reclaim */
++	struct hlist_nulls_node list;
++#endif
+ };
+ 
+ enum {
+@@ -479,12 +497,87 @@ void lru_gen_init_lruvec(struct lruvec *
+ void lru_gen_look_around(struct page_vma_mapped_walk *pvmw);
+ 
+ #ifdef CONFIG_MEMCG
++
++/*
++ * For each node, memcgs are divided into two generations: the old and the
++ * young. For each generation, memcgs are randomly sharded into multiple bins
++ * to improve scalability. For each bin, the hlist_nulls is virtually divided
++ * into three segments: the head, the tail and the default.
++ *
++ * An onlining memcg is added to the tail of a random bin in the old generation.
++ * The eviction starts at the head of a random bin in the old generation. The
++ * per-node memcg generation counter, whose reminder (mod MEMCG_NR_GENS) indexes
++ * the old generation, is incremented when all its bins become empty.
++ *
++ * There are four operations:
++ * 1. MEMCG_LRU_HEAD, which moves an memcg to the head of a random bin in its
++ *    current generation (old or young) and updates its "seg" to "head";
++ * 2. MEMCG_LRU_TAIL, which moves an memcg to the tail of a random bin in its
++ *    current generation (old or young) and updates its "seg" to "tail";
++ * 3. MEMCG_LRU_OLD, which moves an memcg to the head of a random bin in the old
++ *    generation, updates its "gen" to "old" and resets its "seg" to "default";
++ * 4. MEMCG_LRU_YOUNG, which moves an memcg to the tail of a random bin in the
++ *    young generation, updates its "gen" to "young" and resets its "seg" to
++ *    "default".
++ *
++ * The events that trigger the above operations are:
++ * 1. Exceeding the soft limit, which triggers MEMCG_LRU_HEAD;
++ * 2. The first attempt to reclaim an memcg below low, which triggers
++ *    MEMCG_LRU_TAIL;
++ * 3. The first attempt to reclaim an memcg below reclaimable size threshold,
++ *    which triggers MEMCG_LRU_TAIL;
++ * 4. The second attempt to reclaim an memcg below reclaimable size threshold,
++ *    which triggers MEMCG_LRU_YOUNG;
++ * 5. Attempting to reclaim an memcg below min, which triggers MEMCG_LRU_YOUNG;
++ * 6. Finishing the aging on the eviction path, which triggers MEMCG_LRU_YOUNG;
++ * 7. Offlining an memcg, which triggers MEMCG_LRU_OLD.
++ *
++ * Note that memcg LRU only applies to global reclaim, and the round-robin
++ * incrementing of their max_seq counters ensures the eventual fairness to all
++ * eligible memcgs. For memcg reclaim, it still relies on mem_cgroup_iter().
++ */
++#define MEMCG_NR_GENS	2
++#define MEMCG_NR_BINS	8
++
++struct lru_gen_memcg {
++	/* the per-node memcg generation counter */
++	unsigned long seq;
++	/* each memcg has one lru_gen_folio per node */
++	unsigned long nr_memcgs[MEMCG_NR_GENS];
++	/* per-node lru_gen_folio list for global reclaim */
++	struct hlist_nulls_head	fifo[MEMCG_NR_GENS][MEMCG_NR_BINS];
++	/* protects the above */
++	spinlock_t lock;
++};
++
++void lru_gen_init_pgdat(struct pglist_data *pgdat);
++
+ void lru_gen_init_memcg(struct mem_cgroup *memcg);
+ void lru_gen_exit_memcg(struct mem_cgroup *memcg);
+-#endif
++void lru_gen_online_memcg(struct mem_cgroup *memcg);
++void lru_gen_offline_memcg(struct mem_cgroup *memcg);
++void lru_gen_release_memcg(struct mem_cgroup *memcg);
++void lru_gen_rotate_memcg(struct lruvec *lruvec, int op);
++
++#else /* !CONFIG_MEMCG */
++
++#define MEMCG_NR_GENS	1
++
++struct lru_gen_memcg {
++};
++
++static inline void lru_gen_init_pgdat(struct pglist_data *pgdat)
++{
++}
++
++#endif /* CONFIG_MEMCG */
+ 
+ #else /* !CONFIG_LRU_GEN */
+ 
++static inline void lru_gen_init_pgdat(struct pglist_data *pgdat)
++{
++}
++
+ static inline void lru_gen_init_lruvec(struct lruvec *lruvec)
+ {
+ }
+@@ -494,6 +587,7 @@ static inline void lru_gen_look_around(s
+ }
+ 
+ #ifdef CONFIG_MEMCG
++
+ static inline void lru_gen_init_memcg(struct mem_cgroup *memcg)
+ {
+ }
+@@ -501,7 +595,24 @@ static inline void lru_gen_init_memcg(st
+ static inline void lru_gen_exit_memcg(struct mem_cgroup *memcg)
+ {
+ }
+-#endif
++
++static inline void lru_gen_online_memcg(struct mem_cgroup *memcg)
++{
++}
++
++static inline void lru_gen_offline_memcg(struct mem_cgroup *memcg)
++{
++}
++
++static inline void lru_gen_release_memcg(struct mem_cgroup *memcg)
++{
++}
++
++static inline void lru_gen_rotate_memcg(struct lruvec *lruvec, int op)
++{
++}
++
++#endif /* CONFIG_MEMCG */
+ 
+ #endif /* CONFIG_LRU_GEN */
+ 
+@@ -1219,6 +1330,8 @@ typedef struct pglist_data {
+ #ifdef CONFIG_LRU_GEN
+ 	/* kswap mm walk data */
+ 	struct lru_gen_mm_walk	mm_walk;
++	/* lru_gen_folio list */
++	struct lru_gen_memcg memcg_lru;
+ #endif
+ 
+ 	CACHELINE_PADDING(_pad2_);
+--- a/mm/memcontrol.c
++++ b/mm/memcontrol.c
+@@ -477,6 +477,16 @@ static void mem_cgroup_update_tree(struc
+ 	struct mem_cgroup_per_node *mz;
+ 	struct mem_cgroup_tree_per_node *mctz;
+ 
++	if (lru_gen_enabled()) {
++		struct lruvec *lruvec = &memcg->nodeinfo[nid]->lruvec;
++
++		/* see the comment on MEMCG_NR_GENS */
++		if (soft_limit_excess(memcg) && lru_gen_memcg_seg(lruvec) != MEMCG_LRU_HEAD)
++			lru_gen_rotate_memcg(lruvec, MEMCG_LRU_HEAD);
++
++		return;
++	}
++
+ 	mctz = soft_limit_tree.rb_tree_per_node[nid];
+ 	if (!mctz)
+ 		return;
+@@ -3524,6 +3534,9 @@ unsigned long mem_cgroup_soft_limit_recl
+ 	struct mem_cgroup_tree_per_node *mctz;
+ 	unsigned long excess;
+ 
++	if (lru_gen_enabled())
++		return 0;
++
+ 	if (order > 0)
+ 		return 0;
+ 
+@@ -5387,6 +5400,7 @@ static int mem_cgroup_css_online(struct
+ 	if (unlikely(mem_cgroup_is_root(memcg)))
+ 		queue_delayed_work(system_unbound_wq, &stats_flush_dwork,
+ 				   2UL*HZ);
++	lru_gen_online_memcg(memcg);
+ 	return 0;
+ offline_kmem:
+ 	memcg_offline_kmem(memcg);
+@@ -5418,6 +5432,7 @@ static void mem_cgroup_css_offline(struc
+ 	memcg_offline_kmem(memcg);
+ 	reparent_shrinker_deferred(memcg);
+ 	wb_memcg_offline(memcg);
++	lru_gen_offline_memcg(memcg);
+ 
+ 	drain_all_stock(memcg);
+ 
+@@ -5429,6 +5444,7 @@ static void mem_cgroup_css_released(stru
+ 	struct mem_cgroup *memcg = mem_cgroup_from_css(css);
+ 
+ 	invalidate_reclaim_iterators(memcg);
++	lru_gen_release_memcg(memcg);
+ }
+ 
+ static void mem_cgroup_css_free(struct cgroup_subsys_state *css)
+--- a/mm/page_alloc.c
++++ b/mm/page_alloc.c
+@@ -7943,6 +7943,7 @@ static void __init free_area_init_node(i
+ 	pgdat_set_deferred_range(pgdat);
+ 
+ 	free_area_init_core(pgdat);
++	lru_gen_init_pgdat(pgdat);
+ }
+ 
+ static void __init free_area_init_memoryless_node(int nid)
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -54,6 +54,8 @@
+ #include <linux/shmem_fs.h>
+ #include <linux/ctype.h>
+ #include <linux/debugfs.h>
++#include <linux/rculist_nulls.h>
++#include <linux/random.h>
+ 
+ #include <asm/tlbflush.h>
+ #include <asm/div64.h>
+@@ -134,11 +136,6 @@ struct scan_control {
+ 	/* Always discard instead of demoting to lower tier memory */
+ 	unsigned int no_demotion:1;
+ 
+-#ifdef CONFIG_LRU_GEN
+-	/* help kswapd make better choices among multiple memcgs */
+-	unsigned long last_reclaimed;
+-#endif
+-
+ 	/* Allocation order */
+ 	s8 order;
+ 
+@@ -3160,6 +3157,9 @@ DEFINE_STATIC_KEY_ARRAY_FALSE(lru_gen_ca
+ 		for ((type) = 0; (type) < ANON_AND_FILE; (type)++)	\
+ 			for ((zone) = 0; (zone) < MAX_NR_ZONES; (zone)++)
+ 
++#define get_memcg_gen(seq)	((seq) % MEMCG_NR_GENS)
++#define get_memcg_bin(bin)	((bin) % MEMCG_NR_BINS)
++
+ static struct lruvec *get_lruvec(struct mem_cgroup *memcg, int nid)
+ {
+ 	struct pglist_data *pgdat = NODE_DATA(nid);
+@@ -4442,8 +4442,7 @@ done:
+ 		if (sc->priority <= DEF_PRIORITY - 2)
+ 			wait_event_killable(lruvec->mm_state.wait,
+ 					    max_seq < READ_ONCE(lrugen->max_seq));
+-
+-		return max_seq < READ_ONCE(lrugen->max_seq);
++		return false;
+ 	}
+ 
+ 	VM_WARN_ON_ONCE(max_seq != READ_ONCE(lrugen->max_seq));
+@@ -4516,8 +4515,6 @@ static void lru_gen_age_node(struct pgli
+ 
+ 	VM_WARN_ON_ONCE(!current_is_kswapd());
+ 
+-	sc->last_reclaimed = sc->nr_reclaimed;
+-
+ 	/* check the order to exclude compaction-induced reclaim */
+ 	if (!min_ttl || sc->order || sc->priority == DEF_PRIORITY)
+ 		return;
+@@ -5116,8 +5113,7 @@ static bool should_run_aging(struct lruv
+  * 1. Defer try_to_inc_max_seq() to workqueues to reduce latency for memcg
+  *    reclaim.
+  */
+-static unsigned long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc,
+-				    bool can_swap)
++static long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc, bool can_swap)
+ {
+ 	unsigned long nr_to_scan;
+ 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+@@ -5134,10 +5130,8 @@ static unsigned long get_nr_to_scan(stru
+ 	if (sc->priority == DEF_PRIORITY)
+ 		return nr_to_scan;
+ 
+-	try_to_inc_max_seq(lruvec, max_seq, sc, can_swap, false);
+-
+ 	/* skip this lruvec as it's low on cold folios */
+-	return 0;
++	return try_to_inc_max_seq(lruvec, max_seq, sc, can_swap, false) ? -1 : 0;
+ }
+ 
+ static unsigned long get_nr_to_reclaim(struct scan_control *sc)
+@@ -5146,29 +5140,18 @@ static unsigned long get_nr_to_reclaim(s
+ 	if (!global_reclaim(sc))
+ 		return -1;
+ 
+-	/* discount the previous progress for kswapd */
+-	if (current_is_kswapd())
+-		return sc->nr_to_reclaim + sc->last_reclaimed;
+-
+ 	return max(sc->nr_to_reclaim, compact_gap(sc->order));
+ }
+ 
+-static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
++static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
+ {
+-	struct blk_plug plug;
++	long nr_to_scan;
+ 	unsigned long scanned = 0;
+ 	unsigned long nr_to_reclaim = get_nr_to_reclaim(sc);
+ 
+-	lru_add_drain();
+-
+-	blk_start_plug(&plug);
+-
+-	set_mm_walk(lruvec_pgdat(lruvec));
+-
+ 	while (true) {
+ 		int delta;
+ 		int swappiness;
+-		unsigned long nr_to_scan;
+ 
+ 		if (sc->may_swap)
+ 			swappiness = get_swappiness(lruvec, sc);
+@@ -5178,7 +5161,7 @@ static void lru_gen_shrink_lruvec(struct
+ 			swappiness = 0;
+ 
+ 		nr_to_scan = get_nr_to_scan(lruvec, sc, swappiness);
+-		if (!nr_to_scan)
++		if (nr_to_scan <= 0)
+ 			break;
+ 
+ 		delta = evict_folios(lruvec, sc, swappiness);
+@@ -5195,10 +5178,251 @@ static void lru_gen_shrink_lruvec(struct
+ 		cond_resched();
+ 	}
+ 
++	/* whether try_to_inc_max_seq() was successful */
++	return nr_to_scan < 0;
++}
++
++static int shrink_one(struct lruvec *lruvec, struct scan_control *sc)
++{
++	bool success;
++	unsigned long scanned = sc->nr_scanned;
++	unsigned long reclaimed = sc->nr_reclaimed;
++	int seg = lru_gen_memcg_seg(lruvec);
++	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
++	struct pglist_data *pgdat = lruvec_pgdat(lruvec);
++
++	/* see the comment on MEMCG_NR_GENS */
++	if (!lruvec_is_sizable(lruvec, sc))
++		return seg != MEMCG_LRU_TAIL ? MEMCG_LRU_TAIL : MEMCG_LRU_YOUNG;
++
++	mem_cgroup_calculate_protection(NULL, memcg);
++
++	if (mem_cgroup_below_min(memcg))
++		return MEMCG_LRU_YOUNG;
++
++	if (mem_cgroup_below_low(memcg)) {
++		/* see the comment on MEMCG_NR_GENS */
++		if (seg != MEMCG_LRU_TAIL)
++			return MEMCG_LRU_TAIL;
++
++		memcg_memory_event(memcg, MEMCG_LOW);
++	}
++
++	success = try_to_shrink_lruvec(lruvec, sc);
++
++	shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, sc->priority);
++
++	if (!sc->proactive)
++		vmpressure(sc->gfp_mask, memcg, false, sc->nr_scanned - scanned,
++			   sc->nr_reclaimed - reclaimed);
++
++	sc->nr_reclaimed += current->reclaim_state->reclaimed_slab;
++	current->reclaim_state->reclaimed_slab = 0;
++
++	return success ? MEMCG_LRU_YOUNG : 0;
++}
++
++#ifdef CONFIG_MEMCG
++
++static void shrink_many(struct pglist_data *pgdat, struct scan_control *sc)
++{
++	int gen;
++	int bin;
++	int first_bin;
++	struct lruvec *lruvec;
++	struct lru_gen_folio *lrugen;
++	const struct hlist_nulls_node *pos;
++	int op = 0;
++	struct mem_cgroup *memcg = NULL;
++	unsigned long nr_to_reclaim = get_nr_to_reclaim(sc);
++
++	bin = first_bin = get_random_u32_below(MEMCG_NR_BINS);
++restart:
++	gen = get_memcg_gen(READ_ONCE(pgdat->memcg_lru.seq));
++
++	rcu_read_lock();
++
++	hlist_nulls_for_each_entry_rcu(lrugen, pos, &pgdat->memcg_lru.fifo[gen][bin], list) {
++		if (op)
++			lru_gen_rotate_memcg(lruvec, op);
++
++		mem_cgroup_put(memcg);
++
++		lruvec = container_of(lrugen, struct lruvec, lrugen);
++		memcg = lruvec_memcg(lruvec);
++
++		if (!mem_cgroup_tryget(memcg)) {
++			op = 0;
++			memcg = NULL;
++			continue;
++		}
++
++		rcu_read_unlock();
++
++		op = shrink_one(lruvec, sc);
++
++		if (sc->nr_reclaimed >= nr_to_reclaim)
++			goto success;
++
++		rcu_read_lock();
++	}
++
++	rcu_read_unlock();
++
++	/* restart if raced with lru_gen_rotate_memcg() */
++	if (gen != get_nulls_value(pos))
++		goto restart;
++
++	/* try the rest of the bins of the current generation */
++	bin = get_memcg_bin(bin + 1);
++	if (bin != first_bin)
++		goto restart;
++success:
++	if (op)
++		lru_gen_rotate_memcg(lruvec, op);
++
++	mem_cgroup_put(memcg);
++}
++
++static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
++{
++	struct blk_plug plug;
++
++	VM_WARN_ON_ONCE(global_reclaim(sc));
++
++	lru_add_drain();
++
++	blk_start_plug(&plug);
++
++	set_mm_walk(lruvec_pgdat(lruvec));
++
++	if (try_to_shrink_lruvec(lruvec, sc))
++		lru_gen_rotate_memcg(lruvec, MEMCG_LRU_YOUNG);
++
++	clear_mm_walk();
++
++	blk_finish_plug(&plug);
++}
++
++#else /* !CONFIG_MEMCG */
++
++static void shrink_many(struct pglist_data *pgdat, struct scan_control *sc)
++{
++	BUILD_BUG();
++}
++
++static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
++{
++	BUILD_BUG();
++}
++
++#endif
++
++static void set_initial_priority(struct pglist_data *pgdat, struct scan_control *sc)
++{
++	int priority;
++	unsigned long reclaimable;
++	struct lruvec *lruvec = mem_cgroup_lruvec(NULL, pgdat);
++
++	if (sc->priority != DEF_PRIORITY || sc->nr_to_reclaim < MIN_LRU_BATCH)
++		return;
++	/*
++	 * Determine the initial priority based on ((total / MEMCG_NR_GENS) >>
++	 * priority) * reclaimed_to_scanned_ratio = nr_to_reclaim, where the
++	 * estimated reclaimed_to_scanned_ratio = inactive / total.
++	 */
++	reclaimable = node_page_state(pgdat, NR_INACTIVE_FILE);
++	if (get_swappiness(lruvec, sc))
++		reclaimable += node_page_state(pgdat, NR_INACTIVE_ANON);
++
++	reclaimable /= MEMCG_NR_GENS;
++
++	/* round down reclaimable and round up sc->nr_to_reclaim */
++	priority = fls_long(reclaimable) - 1 - fls_long(sc->nr_to_reclaim - 1);
++
++	sc->priority = clamp(priority, 0, DEF_PRIORITY);
++}
++
++static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *sc)
++{
++	struct blk_plug plug;
++	unsigned long reclaimed = sc->nr_reclaimed;
++
++	VM_WARN_ON_ONCE(!global_reclaim(sc));
++
++	lru_add_drain();
++
++	blk_start_plug(&plug);
++
++	set_mm_walk(pgdat);
++
++	set_initial_priority(pgdat, sc);
++
++	if (current_is_kswapd())
++		sc->nr_reclaimed = 0;
++
++	if (mem_cgroup_disabled())
++		shrink_one(&pgdat->__lruvec, sc);
++	else
++		shrink_many(pgdat, sc);
++
++	if (current_is_kswapd())
++		sc->nr_reclaimed += reclaimed;
++
+ 	clear_mm_walk();
+ 
+ 	blk_finish_plug(&plug);
++
++	/* kswapd should never fail */
++	pgdat->kswapd_failures = 0;
++}
++
++#ifdef CONFIG_MEMCG
++void lru_gen_rotate_memcg(struct lruvec *lruvec, int op)
++{
++	int seg;
++	int old, new;
++	int bin = get_random_u32_below(MEMCG_NR_BINS);
++	struct pglist_data *pgdat = lruvec_pgdat(lruvec);
++
++	spin_lock(&pgdat->memcg_lru.lock);
++
++	VM_WARN_ON_ONCE(hlist_nulls_unhashed(&lruvec->lrugen.list));
++
++	seg = 0;
++	new = old = lruvec->lrugen.gen;
++
++	/* see the comment on MEMCG_NR_GENS */
++	if (op == MEMCG_LRU_HEAD)
++		seg = MEMCG_LRU_HEAD;
++	else if (op == MEMCG_LRU_TAIL)
++		seg = MEMCG_LRU_TAIL;
++	else if (op == MEMCG_LRU_OLD)
++		new = get_memcg_gen(pgdat->memcg_lru.seq);
++	else if (op == MEMCG_LRU_YOUNG)
++		new = get_memcg_gen(pgdat->memcg_lru.seq + 1);
++	else
++		VM_WARN_ON_ONCE(true);
++
++	hlist_nulls_del_rcu(&lruvec->lrugen.list);
++
++	if (op == MEMCG_LRU_HEAD || op == MEMCG_LRU_OLD)
++		hlist_nulls_add_head_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[new][bin]);
++	else
++		hlist_nulls_add_tail_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[new][bin]);
++
++	pgdat->memcg_lru.nr_memcgs[old]--;
++	pgdat->memcg_lru.nr_memcgs[new]++;
++
++	lruvec->lrugen.gen = new;
++	WRITE_ONCE(lruvec->lrugen.seg, seg);
++
++	if (!pgdat->memcg_lru.nr_memcgs[old] && old == get_memcg_gen(pgdat->memcg_lru.seq))
++		WRITE_ONCE(pgdat->memcg_lru.seq, pgdat->memcg_lru.seq + 1);
++
++	spin_unlock(&pgdat->memcg_lru.lock);
+ }
++#endif
+ 
+ /******************************************************************************
+  *                          state change
+@@ -5656,11 +5880,11 @@ static int run_cmd(char cmd, int memcg_i
+ 
+ 	if (!mem_cgroup_disabled()) {
+ 		rcu_read_lock();
++
+ 		memcg = mem_cgroup_from_id(memcg_id);
+-#ifdef CONFIG_MEMCG
+-		if (memcg && !css_tryget(&memcg->css))
++		if (!mem_cgroup_tryget(memcg))
+ 			memcg = NULL;
+-#endif
++
+ 		rcu_read_unlock();
+ 
+ 		if (!memcg)
+@@ -5808,6 +6032,19 @@ void lru_gen_init_lruvec(struct lruvec *
+ }
+ 
+ #ifdef CONFIG_MEMCG
++
++void lru_gen_init_pgdat(struct pglist_data *pgdat)
++{
++	int i, j;
++
++	spin_lock_init(&pgdat->memcg_lru.lock);
++
++	for (i = 0; i < MEMCG_NR_GENS; i++) {
++		for (j = 0; j < MEMCG_NR_BINS; j++)
++			INIT_HLIST_NULLS_HEAD(&pgdat->memcg_lru.fifo[i][j], i);
++	}
++}
++
+ void lru_gen_init_memcg(struct mem_cgroup *memcg)
+ {
+ 	INIT_LIST_HEAD(&memcg->mm_list.fifo);
+@@ -5831,7 +6068,69 @@ void lru_gen_exit_memcg(struct mem_cgrou
+ 		}
+ 	}
+ }
+-#endif
++
++void lru_gen_online_memcg(struct mem_cgroup *memcg)
++{
++	int gen;
++	int nid;
++	int bin = get_random_u32_below(MEMCG_NR_BINS);
++
++	for_each_node(nid) {
++		struct pglist_data *pgdat = NODE_DATA(nid);
++		struct lruvec *lruvec = get_lruvec(memcg, nid);
++
++		spin_lock(&pgdat->memcg_lru.lock);
++
++		VM_WARN_ON_ONCE(!hlist_nulls_unhashed(&lruvec->lrugen.list));
++
++		gen = get_memcg_gen(pgdat->memcg_lru.seq);
++
++		hlist_nulls_add_tail_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[gen][bin]);
++		pgdat->memcg_lru.nr_memcgs[gen]++;
++
++		lruvec->lrugen.gen = gen;
++
++		spin_unlock(&pgdat->memcg_lru.lock);
++	}
++}
++
++void lru_gen_offline_memcg(struct mem_cgroup *memcg)
++{
++	int nid;
++
++	for_each_node(nid) {
++		struct lruvec *lruvec = get_lruvec(memcg, nid);
++
++		lru_gen_rotate_memcg(lruvec, MEMCG_LRU_OLD);
++	}
++}
++
++void lru_gen_release_memcg(struct mem_cgroup *memcg)
++{
++	int gen;
++	int nid;
++
++	for_each_node(nid) {
++		struct pglist_data *pgdat = NODE_DATA(nid);
++		struct lruvec *lruvec = get_lruvec(memcg, nid);
++
++		spin_lock(&pgdat->memcg_lru.lock);
++
++		VM_WARN_ON_ONCE(hlist_nulls_unhashed(&lruvec->lrugen.list));
++
++		gen = lruvec->lrugen.gen;
++
++		hlist_nulls_del_rcu(&lruvec->lrugen.list);
++		pgdat->memcg_lru.nr_memcgs[gen]--;
++
++		if (!pgdat->memcg_lru.nr_memcgs[gen] && gen == get_memcg_gen(pgdat->memcg_lru.seq))
++			WRITE_ONCE(pgdat->memcg_lru.seq, pgdat->memcg_lru.seq + 1);
++
++		spin_unlock(&pgdat->memcg_lru.lock);
++	}
++}
++
++#endif /* CONFIG_MEMCG */
+ 
+ static int __init init_lru_gen(void)
+ {
+@@ -5858,6 +6157,10 @@ static void lru_gen_shrink_lruvec(struct
+ {
+ }
+ 
++static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *sc)
++{
++}
++
+ #endif /* CONFIG_LRU_GEN */
+ 
+ static void shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
+@@ -5871,7 +6174,7 @@ static void shrink_lruvec(struct lruvec
+ 	bool proportional_reclaim;
+ 	struct blk_plug plug;
+ 
+-	if (lru_gen_enabled()) {
++	if (lru_gen_enabled() && !global_reclaim(sc)) {
+ 		lru_gen_shrink_lruvec(lruvec, sc);
+ 		return;
+ 	}
+@@ -6114,6 +6417,11 @@ static void shrink_node(pg_data_t *pgdat
+ 	struct lruvec *target_lruvec;
+ 	bool reclaimable = false;
+ 
++	if (lru_gen_enabled() && global_reclaim(sc)) {
++		lru_gen_shrink_node(pgdat, sc);
++		return;
++	}
++
+ 	target_lruvec = mem_cgroup_lruvec(sc->target_mem_cgroup, pgdat);
+ 
+ again:

+ 202 - 0
target/linux/generic/backport-6.6/020-v6.3-07-BACKPORT-mm-multi-gen-LRU-clarify-scan_control-flags.patch

@@ -0,0 +1,202 @@
+From 11b14ee8cbbbebd8204609076a9327a1171cd253 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <[email protected]>
+Date: Wed, 21 Dec 2022 21:19:05 -0700
+Subject: [PATCH 07/19] BACKPORT: mm: multi-gen LRU: clarify scan_control flags
+
+Among the flags in scan_control:
+1. sc->may_swap, which indicates swap constraint due to memsw.max, is
+   supported as usual.
+2. sc->proactive, which indicates reclaim by memory.reclaim, may not
+   opportunistically skip the aging path, since it is considered less
+   latency sensitive.
+3. !(sc->gfp_mask & __GFP_IO), which indicates IO constraint, lowers
+   swappiness to prioritize file LRU, since clean file folios are more
+   likely to exist.
+4. sc->may_writepage and sc->may_unmap, which indicates opportunistic
+   reclaim, are rejected, since unmapped clean folios are already
+   prioritized. Scanning for more of them is likely futile and can
+   cause high reclaim latency when there is a large number of memcgs.
+
+The rest are handled by the existing code.
+
+Link: https://lkml.kernel.org/r/[email protected]
+Signed-off-by: Yu Zhao <[email protected]>
+Cc: Johannes Weiner <[email protected]>
+Cc: Jonathan Corbet <[email protected]>
+Cc: Michael Larabel <[email protected]>
+Cc: Michal Hocko <[email protected]>
+Cc: Mike Rapoport <[email protected]>
+Cc: Roman Gushchin <[email protected]>
+Cc: Suren Baghdasaryan <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+Bug: 274865848
+(cherry picked from commit e9d4e1ee788097484606c32122f146d802a9c5fb)
+[TJ: Resolved conflict with older function signature for min_cgroup_below_min, and over
+cdded861182142ac4488a4d64c571107aeb77f53 ("ANDROID: MGLRU: Don't skip anon reclaim if swap low")]
+Change-Id: Ic2e779eaf4e91a3921831b4e2fa10c740dc59d50
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ mm/vmscan.c | 55 +++++++++++++++++++++++++++--------------------------
+ 1 file changed, 28 insertions(+), 27 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -3185,6 +3185,9 @@ static int get_swappiness(struct lruvec
+ 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ 	struct pglist_data *pgdat = lruvec_pgdat(lruvec);
+ 
++	if (!sc->may_swap)
++		return 0;
++
+ 	if (!can_demote(pgdat->node_id, sc) &&
+ 	    mem_cgroup_get_nr_swap_pages(memcg) < MIN_LRU_BATCH)
+ 		return 0;
+@@ -4223,7 +4226,7 @@ static void walk_mm(struct lruvec *lruve
+ 	} while (err == -EAGAIN);
+ }
+ 
+-static struct lru_gen_mm_walk *set_mm_walk(struct pglist_data *pgdat)
++static struct lru_gen_mm_walk *set_mm_walk(struct pglist_data *pgdat, bool force_alloc)
+ {
+ 	struct lru_gen_mm_walk *walk = current->reclaim_state->mm_walk;
+ 
+@@ -4231,7 +4234,7 @@ static struct lru_gen_mm_walk *set_mm_wa
+ 		VM_WARN_ON_ONCE(walk);
+ 
+ 		walk = &pgdat->mm_walk;
+-	} else if (!pgdat && !walk) {
++	} else if (!walk && force_alloc) {
+ 		VM_WARN_ON_ONCE(current_is_kswapd());
+ 
+ 		walk = kzalloc(sizeof(*walk), __GFP_HIGH | __GFP_NOMEMALLOC | __GFP_NOWARN);
+@@ -4419,7 +4422,7 @@ static bool try_to_inc_max_seq(struct lr
+ 		goto done;
+ 	}
+ 
+-	walk = set_mm_walk(NULL);
++	walk = set_mm_walk(NULL, true);
+ 	if (!walk) {
+ 		success = iterate_mm_list_nowalk(lruvec, max_seq);
+ 		goto done;
+@@ -4488,8 +4491,6 @@ static bool lruvec_is_reclaimable(struct
+ 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ 	DEFINE_MIN_SEQ(lruvec);
+ 
+-	VM_WARN_ON_ONCE(sc->memcg_low_reclaim);
+-
+ 	/* see the comment on lru_gen_folio */
+ 	gen = lru_gen_from_seq(min_seq[LRU_GEN_FILE]);
+ 	birth = READ_ONCE(lruvec->lrugen.timestamps[gen]);
+@@ -4753,12 +4754,8 @@ static bool isolate_folio(struct lruvec
+ {
+ 	bool success;
+ 
+-	/* unmapping inhibited */
+-	if (!sc->may_unmap && folio_mapped(folio))
+-		return false;
+-
+ 	/* swapping inhibited */
+-	if (!(sc->may_writepage && (sc->gfp_mask & __GFP_IO)) &&
++	if (!(sc->gfp_mask & __GFP_IO) &&
+ 	    (folio_test_dirty(folio) ||
+ 	     (folio_test_anon(folio) && !folio_test_swapcache(folio))))
+ 		return false;
+@@ -4857,9 +4854,8 @@ static int scan_folios(struct lruvec *lr
+ 	__count_vm_events(PGSCAN_ANON + type, isolated);
+ 
+ 	/*
+-	 * There might not be eligible pages due to reclaim_idx, may_unmap and
+-	 * may_writepage. Check the remaining to prevent livelock if it's not
+-	 * making progress.
++	 * There might not be eligible folios due to reclaim_idx. Check the
++	 * remaining to prevent livelock if it's not making progress.
+ 	 */
+ 	return isolated || !remaining ? scanned : 0;
+ }
+@@ -5119,8 +5115,7 @@ static long get_nr_to_scan(struct lruvec
+ 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ 	DEFINE_MAX_SEQ(lruvec);
+ 
+-	if (mem_cgroup_below_min(memcg) ||
+-	    (mem_cgroup_below_low(memcg) && !sc->memcg_low_reclaim))
++	if (mem_cgroup_below_min(memcg))
+ 		return 0;
+ 
+ 	if (!should_run_aging(lruvec, max_seq, sc, can_swap, &nr_to_scan))
+@@ -5148,17 +5143,14 @@ static bool try_to_shrink_lruvec(struct
+ 	long nr_to_scan;
+ 	unsigned long scanned = 0;
+ 	unsigned long nr_to_reclaim = get_nr_to_reclaim(sc);
++	int swappiness = get_swappiness(lruvec, sc);
++
++	/* clean file folios are more likely to exist */
++	if (swappiness && !(sc->gfp_mask & __GFP_IO))
++		swappiness = 1;
+ 
+ 	while (true) {
+ 		int delta;
+-		int swappiness;
+-
+-		if (sc->may_swap)
+-			swappiness = get_swappiness(lruvec, sc);
+-		else if (!cgroup_reclaim(sc) && get_swappiness(lruvec, sc))
+-			swappiness = 1;
+-		else
+-			swappiness = 0;
+ 
+ 		nr_to_scan = get_nr_to_scan(lruvec, sc, swappiness);
+ 		if (nr_to_scan <= 0)
+@@ -5289,12 +5281,13 @@ static void lru_gen_shrink_lruvec(struct
+ 	struct blk_plug plug;
+ 
+ 	VM_WARN_ON_ONCE(global_reclaim(sc));
++	VM_WARN_ON_ONCE(!sc->may_writepage || !sc->may_unmap);
+ 
+ 	lru_add_drain();
+ 
+ 	blk_start_plug(&plug);
+ 
+-	set_mm_walk(lruvec_pgdat(lruvec));
++	set_mm_walk(NULL, sc->proactive);
+ 
+ 	if (try_to_shrink_lruvec(lruvec, sc))
+ 		lru_gen_rotate_memcg(lruvec, MEMCG_LRU_YOUNG);
+@@ -5350,11 +5343,19 @@ static void lru_gen_shrink_node(struct p
+ 
+ 	VM_WARN_ON_ONCE(!global_reclaim(sc));
+ 
++	/*
++	 * Unmapped clean folios are already prioritized. Scanning for more of
++	 * them is likely futile and can cause high reclaim latency when there
++	 * is a large number of memcgs.
++	 */
++	if (!sc->may_writepage || !sc->may_unmap)
++		goto done;
++
+ 	lru_add_drain();
+ 
+ 	blk_start_plug(&plug);
+ 
+-	set_mm_walk(pgdat);
++	set_mm_walk(pgdat, sc->proactive);
+ 
+ 	set_initial_priority(pgdat, sc);
+ 
+@@ -5372,7 +5373,7 @@ static void lru_gen_shrink_node(struct p
+ 	clear_mm_walk();
+ 
+ 	blk_finish_plug(&plug);
+-
++done:
+ 	/* kswapd should never fail */
+ 	pgdat->kswapd_failures = 0;
+ }
+@@ -5944,7 +5945,7 @@ static ssize_t lru_gen_seq_write(struct
+ 	set_task_reclaim_state(current, &sc.reclaim_state);
+ 	flags = memalloc_noreclaim_save();
+ 	blk_start_plug(&plug);
+-	if (!set_mm_walk(NULL)) {
++	if (!set_mm_walk(NULL, true)) {
+ 		err = -ENOMEM;
+ 		goto done;
+ 	}

+ 38 - 0
target/linux/generic/backport-6.6/020-v6.3-08-UPSTREAM-mm-multi-gen-LRU-simplify-arch_has_hw_pte_y.patch

@@ -0,0 +1,38 @@
+From 25887d48dff860751a06caa4188bfaf6bfb6e4b2 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <[email protected]>
+Date: Wed, 21 Dec 2022 21:19:06 -0700
+Subject: [PATCH 08/19] UPSTREAM: mm: multi-gen LRU: simplify
+ arch_has_hw_pte_young() check
+
+Scanning page tables when hardware does not set the accessed bit has
+no real use cases.
+
+Link: https://lkml.kernel.org/r/[email protected]
+Signed-off-by: Yu Zhao <[email protected]>
+Cc: Johannes Weiner <[email protected]>
+Cc: Jonathan Corbet <[email protected]>
+Cc: Michael Larabel <[email protected]>
+Cc: Michal Hocko <[email protected]>
+Cc: Mike Rapoport <[email protected]>
+Cc: Roman Gushchin <[email protected]>
+Cc: Suren Baghdasaryan <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+Bug: 274865848
+(cherry picked from commit f386e9314025ea99dae639ed2032560a92081430)
+Change-Id: I84d97ab665b4e3bb862a9bc7d72f50dea7191a6b
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ mm/vmscan.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -4417,7 +4417,7 @@ static bool try_to_inc_max_seq(struct lr
+ 	 * handful of PTEs. Spreading the work out over a period of time usually
+ 	 * is less efficient, but it avoids bursty page faults.
+ 	 */
+-	if (!force_scan && !(arch_has_hw_pte_young() && get_cap(LRU_GEN_MM_WALK))) {
++	if (!arch_has_hw_pte_young() || !get_cap(LRU_GEN_MM_WALK)) {
+ 		success = iterate_mm_list_nowalk(lruvec, max_seq);
+ 		goto done;
+ 	}

+ 92 - 0
target/linux/generic/backport-6.6/020-v6.3-09-UPSTREAM-mm-multi-gen-LRU-avoid-futile-retries.patch

@@ -0,0 +1,92 @@
+From 620b0ee94455e48d124414cd06d8a53f69fb6453 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <[email protected]>
+Date: Mon, 13 Feb 2023 00:53:22 -0700
+Subject: [PATCH 09/19] UPSTREAM: mm: multi-gen LRU: avoid futile retries
+
+Recall that the per-node memcg LRU has two generations and they alternate
+when the last memcg (of a given node) is moved from one to the other.
+Each generation is also sharded into multiple bins to improve scalability.
+A reclaimer starts with a random bin (in the old generation) and, if it
+fails, it will retry, i.e., to try the rest of the bins.
+
+If a reclaimer fails with the last memcg, it should move this memcg to the
+young generation first, which causes the generations to alternate, and
+then retry.  Otherwise, the retries will be futile because all other bins
+are empty.
+
+Link: https://lkml.kernel.org/r/[email protected]
+Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
+Signed-off-by: Yu Zhao <[email protected]>
+Reported-by: T.J. Mercier <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+Bug: 274865848
+(cherry picked from commit 9f550d78b40da21b4da515db4c37d8d7b12aa1a6)
+Change-Id: Ie92535676b005ec9e7987632b742fdde8d54436f
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ mm/vmscan.c | 25 +++++++++++++++----------
+ 1 file changed, 15 insertions(+), 10 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -5218,18 +5218,20 @@ static int shrink_one(struct lruvec *lru
+ 
+ static void shrink_many(struct pglist_data *pgdat, struct scan_control *sc)
+ {
++	int op;
+ 	int gen;
+ 	int bin;
+ 	int first_bin;
+ 	struct lruvec *lruvec;
+ 	struct lru_gen_folio *lrugen;
++	struct mem_cgroup *memcg;
+ 	const struct hlist_nulls_node *pos;
+-	int op = 0;
+-	struct mem_cgroup *memcg = NULL;
+ 	unsigned long nr_to_reclaim = get_nr_to_reclaim(sc);
+ 
+ 	bin = first_bin = get_random_u32_below(MEMCG_NR_BINS);
+ restart:
++	op = 0;
++	memcg = NULL;
+ 	gen = get_memcg_gen(READ_ONCE(pgdat->memcg_lru.seq));
+ 
+ 	rcu_read_lock();
+@@ -5253,14 +5255,22 @@ restart:
+ 
+ 		op = shrink_one(lruvec, sc);
+ 
+-		if (sc->nr_reclaimed >= nr_to_reclaim)
+-			goto success;
+-
+ 		rcu_read_lock();
++
++		if (sc->nr_reclaimed >= nr_to_reclaim)
++			break;
+ 	}
+ 
+ 	rcu_read_unlock();
+ 
++	if (op)
++		lru_gen_rotate_memcg(lruvec, op);
++
++	mem_cgroup_put(memcg);
++
++	if (sc->nr_reclaimed >= nr_to_reclaim)
++		return;
++
+ 	/* restart if raced with lru_gen_rotate_memcg() */
+ 	if (gen != get_nulls_value(pos))
+ 		goto restart;
+@@ -5269,11 +5279,6 @@ restart:
+ 	bin = get_memcg_bin(bin + 1);
+ 	if (bin != first_bin)
+ 		goto restart;
+-success:
+-	if (op)
+-		lru_gen_rotate_memcg(lruvec, op);
+-
+-	mem_cgroup_put(memcg);
+ }
+ 
+ static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)

+ 191 - 0
target/linux/generic/backport-6.6/020-v6.3-10-UPSTREAM-mm-add-vma_has_recency.patch

@@ -0,0 +1,191 @@
+From 70d216c71ff5c5b17dd1da6294f97b91fb6aba7a Mon Sep 17 00:00:00 2001
+From: Yu Zhao <[email protected]>
+Date: Fri, 30 Dec 2022 14:52:51 -0700
+Subject: [PATCH 10/19] UPSTREAM: mm: add vma_has_recency()
+
+Add vma_has_recency() to indicate whether a VMA may exhibit temporal
+locality that the LRU algorithm relies on.
+
+This function returns false for VMAs marked by VM_SEQ_READ or
+VM_RAND_READ.  While the former flag indicates linear access, i.e., a
+special case of spatial locality, both flags indicate a lack of temporal
+locality, i.e., the reuse of an area within a relatively small duration.
+
+"Recency" is chosen over "locality" to avoid confusion between temporal
+and spatial localities.
+
+Before this patch, the active/inactive LRU only ignored the accessed bit
+from VMAs marked by VM_SEQ_READ.  After this patch, the active/inactive
+LRU and MGLRU share the same logic: they both ignore the accessed bit if
+vma_has_recency() returns false.
+
+For the active/inactive LRU, the following fio test showed a [6, 8]%
+increase in IOPS when randomly accessing mapped files under memory
+pressure.
+
+  kb=$(awk '/MemTotal/ { print $2 }' /proc/meminfo)
+  kb=$((kb - 8*1024*1024))
+
+  modprobe brd rd_nr=1 rd_size=$kb
+  dd if=/dev/zero of=/dev/ram0 bs=1M
+
+  mkfs.ext4 /dev/ram0
+  mount /dev/ram0 /mnt/
+  swapoff -a
+
+  fio --name=test --directory=/mnt/ --ioengine=mmap --numjobs=8 \
+      --size=8G --rw=randrw --time_based --runtime=10m \
+      --group_reporting
+
+The discussion that led to this patch is here [1].  Additional test
+results are available in that thread.
+
+[1] https://lore.kernel.org/r/Y31s%[email protected]/
+
+Link: https://lkml.kernel.org/r/[email protected]
+Change-Id: I291dcb795197659e40e46539cd32b857677c34ad
+Signed-off-by: Yu Zhao <[email protected]>
+Cc: Alexander Viro <[email protected]>
+Cc: Andrea Righi <[email protected]>
+Cc: Johannes Weiner <[email protected]>
+Cc: Michael Larabel <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+(cherry picked from commit 8788f6781486769d9598dcaedc3fe0eb12fc3e59)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ include/linux/mm_inline.h |  8 ++++++++
+ mm/memory.c               |  7 +++----
+ mm/rmap.c                 | 42 +++++++++++++++++----------------------
+ mm/vmscan.c               |  5 ++++-
+ 4 files changed, 33 insertions(+), 29 deletions(-)
+
+--- a/include/linux/mm_inline.h
++++ b/include/linux/mm_inline.h
+@@ -600,4 +600,12 @@ pte_install_uffd_wp_if_needed(struct vm_
+ #endif
+ }
+ 
++static inline bool vma_has_recency(struct vm_area_struct *vma)
++{
++	if (vma->vm_flags & (VM_SEQ_READ | VM_RAND_READ))
++		return false;
++
++	return true;
++}
++
+ #endif
+--- a/mm/memory.c
++++ b/mm/memory.c
+@@ -1445,8 +1445,7 @@ again:
+ 					force_flush = 1;
+ 					set_page_dirty(page);
+ 				}
+-				if (pte_young(ptent) &&
+-				    likely(!(vma->vm_flags & VM_SEQ_READ)))
++				if (pte_young(ptent) && likely(vma_has_recency(vma)))
+ 					mark_page_accessed(page);
+ 			}
+ 			rss[mm_counter(page)]--;
+@@ -5219,8 +5218,8 @@ static inline void mm_account_fault(stru
+ #ifdef CONFIG_LRU_GEN
+ static void lru_gen_enter_fault(struct vm_area_struct *vma)
+ {
+-	/* the LRU algorithm doesn't apply to sequential or random reads */
+-	current->in_lru_fault = !(vma->vm_flags & (VM_SEQ_READ | VM_RAND_READ));
++	/* the LRU algorithm only applies to accesses with recency */
++	current->in_lru_fault = vma_has_recency(vma);
+ }
+ 
+ static void lru_gen_exit_fault(void)
+--- a/mm/rmap.c
++++ b/mm/rmap.c
+@@ -823,25 +823,14 @@ static bool folio_referenced_one(struct
+ 		}
+ 
+ 		if (pvmw.pte) {
+-			if (lru_gen_enabled() && pte_young(*pvmw.pte) &&
+-			    !(vma->vm_flags & (VM_SEQ_READ | VM_RAND_READ))) {
++			if (lru_gen_enabled() && pte_young(*pvmw.pte)) {
+ 				lru_gen_look_around(&pvmw);
+ 				referenced++;
+ 			}
+ 
+ 			if (ptep_clear_flush_young_notify(vma, address,
+-						pvmw.pte)) {
+-				/*
+-				 * Don't treat a reference through
+-				 * a sequentially read mapping as such.
+-				 * If the folio has been used in another mapping,
+-				 * we will catch it; if this other mapping is
+-				 * already gone, the unmap path will have set
+-				 * the referenced flag or activated the folio.
+-				 */
+-				if (likely(!(vma->vm_flags & VM_SEQ_READ)))
+-					referenced++;
+-			}
++						pvmw.pte))
++				referenced++;
+ 		} else if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE)) {
+ 			if (pmdp_clear_flush_young_notify(vma, address,
+ 						pvmw.pmd))
+@@ -875,7 +864,20 @@ static bool invalid_folio_referenced_vma
+ 	struct folio_referenced_arg *pra = arg;
+ 	struct mem_cgroup *memcg = pra->memcg;
+ 
+-	if (!mm_match_cgroup(vma->vm_mm, memcg))
++	/*
++	 * Ignore references from this mapping if it has no recency. If the
++	 * folio has been used in another mapping, we will catch it; if this
++	 * other mapping is already gone, the unmap path will have set the
++	 * referenced flag or activated the folio in zap_pte_range().
++	 */
++	if (!vma_has_recency(vma))
++		return true;
++
++	/*
++	 * If we are reclaiming on behalf of a cgroup, skip counting on behalf
++	 * of references from different cgroups.
++	 */
++	if (memcg && !mm_match_cgroup(vma->vm_mm, memcg))
+ 		return true;
+ 
+ 	return false;
+@@ -906,6 +908,7 @@ int folio_referenced(struct folio *folio
+ 		.arg = (void *)&pra,
+ 		.anon_lock = folio_lock_anon_vma_read,
+ 		.try_lock = true,
++		.invalid_vma = invalid_folio_referenced_vma,
+ 	};
+ 
+ 	*vm_flags = 0;
+@@ -921,15 +924,6 @@ int folio_referenced(struct folio *folio
+ 			return 1;
+ 	}
+ 
+-	/*
+-	 * If we are reclaiming on behalf of a cgroup, skip
+-	 * counting on behalf of references from different
+-	 * cgroups
+-	 */
+-	if (memcg) {
+-		rwc.invalid_vma = invalid_folio_referenced_vma;
+-	}
+-
+ 	rmap_walk(folio, &rwc);
+ 	*vm_flags = pra.vm_flags;
+ 
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -3778,7 +3778,10 @@ static int should_skip_vma(unsigned long
+ 	if (is_vm_hugetlb_page(vma))
+ 		return true;
+ 
+-	if (vma->vm_flags & (VM_LOCKED | VM_SPECIAL | VM_SEQ_READ | VM_RAND_READ))
++	if (!vma_has_recency(vma))
++		return true;
++
++	if (vma->vm_flags & (VM_LOCKED | VM_SPECIAL))
+ 		return true;
+ 
+ 	if (vma == get_gate_vma(vma->vm_mm))

+ 129 - 0
target/linux/generic/backport-6.6/020-v6.3-11-UPSTREAM-mm-support-POSIX_FADV_NOREUSE.patch

@@ -0,0 +1,129 @@
+From 9ca4e437a24dfc4ec6c362f319eb9850b9eca497 Mon Sep 17 00:00:00 2001
+From: Yu Zhao <[email protected]>
+Date: Fri, 30 Dec 2022 14:52:52 -0700
+Subject: [PATCH 11/19] UPSTREAM: mm: support POSIX_FADV_NOREUSE
+
+This patch adds POSIX_FADV_NOREUSE to vma_has_recency() so that the LRU
+algorithm can ignore access to mapped files marked by this flag.
+
+The advantages of POSIX_FADV_NOREUSE are:
+1. Unlike MADV_SEQUENTIAL and MADV_RANDOM, it does not alter the
+   default readahead behavior.
+2. Unlike MADV_SEQUENTIAL and MADV_RANDOM, it does not split VMAs and
+   therefore does not take mmap_lock.
+3. Unlike MADV_COLD, setting it has a negligible cost, regardless of
+   how many pages it affects.
+
+Its limitations are:
+1. Like POSIX_FADV_RANDOM and POSIX_FADV_SEQUENTIAL, it currently does
+   not support range. IOW, its scope is the entire file.
+2. It currently does not ignore access through file descriptors.
+   Specifically, for the active/inactive LRU, given a file page shared
+   by two users and one of them having set POSIX_FADV_NOREUSE on the
+   file, this page will be activated upon the second user accessing
+   it. This corner case can be covered by checking POSIX_FADV_NOREUSE
+   before calling folio_mark_accessed() on the read path. But it is
+   considered not worth the effort.
+
+There have been a few attempts to support POSIX_FADV_NOREUSE, e.g., [1].
+This time the goal is to fill a niche: a few desktop applications, e.g.,
+large file transferring and video encoding/decoding, want fast file
+streaming with mmap() rather than direct IO.  Among those applications, an
+SVT-AV1 regression was reported when running with MGLRU [2].  The
+following test can reproduce that regression.
+
+  kb=$(awk '/MemTotal/ { print $2 }' /proc/meminfo)
+  kb=$((kb - 8*1024*1024))
+
+  modprobe brd rd_nr=1 rd_size=$kb
+  dd if=/dev/zero of=/dev/ram0 bs=1M
+
+  mkfs.ext4 /dev/ram0
+  mount /dev/ram0 /mnt/
+  swapoff -a
+
+  fallocate -l 8G /mnt/swapfile
+  mkswap /mnt/swapfile
+  swapon /mnt/swapfile
+
+  wget http://ultravideo.cs.tut.fi/video/Bosphorus_3840x2160_120fps_420_8bit_YUV_Y4M.7z
+  7z e -o/mnt/ Bosphorus_3840x2160_120fps_420_8bit_YUV_Y4M.7z
+  SvtAv1EncApp --preset 12 -w 3840 -h 2160 \
+               -i /mnt/Bosphorus_3840x2160.y4m
+
+For MGLRU, the following change showed a [9-11]% increase in FPS,
+which makes it on par with the active/inactive LRU.
+
+  patch Source/App/EncApp/EbAppMain.c <<EOF
+  31a32
+  > #include <fcntl.h>
+  35d35
+  < #include <fcntl.h> /* _O_BINARY */
+  117a118
+  >             posix_fadvise(config->mmap.fd, 0, 0, POSIX_FADV_NOREUSE);
+  EOF
+
+[1] https://lore.kernel.org/r/[email protected]/
+[2] https://openbenchmarking.org/result/2209259-PTS-MGLRU8GB57
+
+Link: https://lkml.kernel.org/r/[email protected]
+Change-Id: I0b7f5f971d78014ea1ba44cee6a8ec902a4330d0
+Signed-off-by: Yu Zhao <[email protected]>
+Cc: Alexander Viro <[email protected]>
+Cc: Andrea Righi <[email protected]>
+Cc: Johannes Weiner <[email protected]>
+Cc: Michael Larabel <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+(cherry picked from commit 17e810229cb3068b692fa078bd9b3a6527e0866a)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ include/linux/fs.h        | 2 ++
+ include/linux/mm_inline.h | 3 +++
+ mm/fadvise.c              | 5 ++++-
+ 3 files changed, 9 insertions(+), 1 deletion(-)
+
+--- a/include/linux/fs.h
++++ b/include/linux/fs.h
+@@ -166,6 +166,8 @@ typedef int (dio_iodone_t)(struct kiocb
+ /* File supports DIRECT IO */
+ #define	FMODE_CAN_ODIRECT	((__force fmode_t)0x400000)
+ 
++#define	FMODE_NOREUSE		((__force fmode_t)0x800000)
++
+ /* File was opened by fanotify and shouldn't generate fanotify events */
+ #define FMODE_NONOTIFY		((__force fmode_t)0x4000000)
+ 
+--- a/include/linux/mm_inline.h
++++ b/include/linux/mm_inline.h
+@@ -605,6 +605,9 @@ static inline bool vma_has_recency(struc
+ 	if (vma->vm_flags & (VM_SEQ_READ | VM_RAND_READ))
+ 		return false;
+ 
++	if (vma->vm_file && (vma->vm_file->f_mode & FMODE_NOREUSE))
++		return false;
++
+ 	return true;
+ }
+ 
+--- a/mm/fadvise.c
++++ b/mm/fadvise.c
+@@ -80,7 +80,7 @@ int generic_fadvise(struct file *file, l
+ 	case POSIX_FADV_NORMAL:
+ 		file->f_ra.ra_pages = bdi->ra_pages;
+ 		spin_lock(&file->f_lock);
+-		file->f_mode &= ~FMODE_RANDOM;
++		file->f_mode &= ~(FMODE_RANDOM | FMODE_NOREUSE);
+ 		spin_unlock(&file->f_lock);
+ 		break;
+ 	case POSIX_FADV_RANDOM:
+@@ -107,6 +107,9 @@ int generic_fadvise(struct file *file, l
+ 		force_page_cache_readahead(mapping, file, start_index, nrpages);
+ 		break;
+ 	case POSIX_FADV_NOREUSE:
++		spin_lock(&file->f_lock);
++		file->f_mode |= FMODE_NOREUSE;
++		spin_unlock(&file->f_lock);
+ 		break;
+ 	case POSIX_FADV_DONTNEED:
+ 		__filemap_fdatawrite_range(mapping, offset, endbyte,

+ 67 - 0
target/linux/generic/backport-6.6/020-v6.3-12-UPSTREAM-mm-multi-gen-LRU-section-for-working-set-pr.patch

@@ -0,0 +1,67 @@
+From 1b5e4c317d80f4826eceb3781702d18d06b14394 Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <[email protected]>
+Date: Wed, 18 Jan 2023 00:18:21 +0000
+Subject: [PATCH 12/19] UPSTREAM: mm: multi-gen LRU: section for working set
+ protection
+
+Patch series "mm: multi-gen LRU: improve".
+
+This patch series improves a few MGLRU functions, collects related
+functions, and adds additional documentation.
+
+This patch (of 7):
+
+Add a section for working set protection in the code and the design doc.
+The admin doc already contains its usage.
+
+Link: https://lkml.kernel.org/r/[email protected]
+Link: https://lkml.kernel.org/r/[email protected]
+Change-Id: I65599075fd42951db7739a2ab7cee78516e157b3
+Signed-off-by: T.J. Alumbaugh <[email protected]>
+Cc: Yu Zhao <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+(cherry picked from commit 7b8144e63d84716f16a1b929e0c7e03ae5c4d5c1)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ Documentation/mm/multigen_lru.rst | 15 +++++++++++++++
+ mm/vmscan.c                       |  4 ++++
+ 2 files changed, 19 insertions(+)
+
+--- a/Documentation/mm/multigen_lru.rst
++++ b/Documentation/mm/multigen_lru.rst
+@@ -141,6 +141,21 @@ loop has detected outlying refaults from
+ this end, the feedback loop uses the first tier as the baseline, for
+ the reason stated earlier.
+ 
++Working set protection
++----------------------
++Each generation is timestamped at birth. If ``lru_gen_min_ttl`` is
++set, an ``lruvec`` is protected from the eviction when its oldest
++generation was born within ``lru_gen_min_ttl`` milliseconds. In other
++words, it prevents the working set of ``lru_gen_min_ttl`` milliseconds
++from getting evicted. The OOM killer is triggered if this working set
++cannot be kept in memory.
++
++This time-based approach has the following advantages:
++
++1. It is easier to configure because it is agnostic to applications
++   and memory sizes.
++2. It is more reliable because it is directly wired to the OOM killer.
++
+ Summary
+ -------
+ The multi-gen LRU can be disassembled into the following parts:
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -4461,6 +4461,10 @@ done:
+ 	return true;
+ }
+ 
++/******************************************************************************
++ *                          working set protection
++ ******************************************************************************/
++
+ static bool lruvec_is_sizable(struct lruvec *lruvec, struct scan_control *sc)
+ {
+ 	int gen, type, zone;

+ 57 - 0
target/linux/generic/backport-6.6/020-v6.3-13-UPSTREAM-mm-multi-gen-LRU-section-for-rmap-PT-walk-f.patch

@@ -0,0 +1,57 @@
+From 5ddf9d53d375e42af49b744bd7c2f8247c6bce15 Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <[email protected]>
+Date: Wed, 18 Jan 2023 00:18:22 +0000
+Subject: [PATCH 13/19] UPSTREAM: mm: multi-gen LRU: section for rmap/PT walk
+ feedback
+
+Add a section for lru_gen_look_around() in the code and the design doc.
+
+Link: https://lkml.kernel.org/r/[email protected]
+Change-Id: I5097af63f61b3b69ec2abee6cdbdc33c296df213
+Signed-off-by: T.J. Alumbaugh <[email protected]>
+Cc: Yu Zhao <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+(cherry picked from commit db19a43d9b3a8876552f00f656008206ef9a5efa)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ Documentation/mm/multigen_lru.rst | 14 ++++++++++++++
+ mm/vmscan.c                       |  4 ++++
+ 2 files changed, 18 insertions(+)
+
+--- a/Documentation/mm/multigen_lru.rst
++++ b/Documentation/mm/multigen_lru.rst
+@@ -156,6 +156,20 @@ This time-based approach has the followi
+    and memory sizes.
+ 2. It is more reliable because it is directly wired to the OOM killer.
+ 
++Rmap/PT walk feedback
++---------------------
++Searching the rmap for PTEs mapping each page on an LRU list (to test
++and clear the accessed bit) can be expensive because pages from
++different VMAs (PA space) are not cache friendly to the rmap (VA
++space). For workloads mostly using mapped pages, searching the rmap
++can incur the highest CPU cost in the reclaim path.
++
++``lru_gen_look_around()`` exploits spatial locality to reduce the
++trips into the rmap. It scans the adjacent PTEs of a young PTE and
++promotes hot pages. If the scan was done cacheline efficiently, it
++adds the PMD entry pointing to the PTE table to the Bloom filter. This
++forms a feedback loop between the eviction and the aging.
++
+ Summary
+ -------
+ The multi-gen LRU can be disassembled into the following parts:
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -4555,6 +4555,10 @@ static void lru_gen_age_node(struct pgli
+ 	}
+ }
+ 
++/******************************************************************************
++ *                          rmap/PT walk feedback
++ ******************************************************************************/
++
+ /*
+  * This function exploits spatial locality when shrink_folio_list() walks the
+  * rmap. It scans the adjacent PTEs of a young PTE and promotes hot pages. If

+ 243 - 0
target/linux/generic/backport-6.6/020-v6.3-14-UPSTREAM-mm-multi-gen-LRU-section-for-Bloom-filters.patch

@@ -0,0 +1,243 @@
+From 397624e12244ec038f51cb1f178ccb7a2ec562e5 Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <[email protected]>
+Date: Wed, 18 Jan 2023 00:18:23 +0000
+Subject: [PATCH 14/19] UPSTREAM: mm: multi-gen LRU: section for Bloom filters
+
+Move Bloom filters code into a dedicated section.  Improve the design doc
+to explain Bloom filter usage and connection between aging and eviction in
+their use.
+
+Link: https://lkml.kernel.org/r/[email protected]
+Change-Id: I73e866f687c1ed9f5c8538086aa39408b79897db
+Signed-off-by: T.J. Alumbaugh <[email protected]>
+Cc: Yu Zhao <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+(cherry picked from commit ccbbbb85945d8f0255aa9dbc1b617017e2294f2c)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ Documentation/mm/multigen_lru.rst |  16 +++
+ mm/vmscan.c                       | 180 +++++++++++++++---------------
+ 2 files changed, 108 insertions(+), 88 deletions(-)
+
+--- a/Documentation/mm/multigen_lru.rst
++++ b/Documentation/mm/multigen_lru.rst
+@@ -170,6 +170,22 @@ promotes hot pages. If the scan was done
+ adds the PMD entry pointing to the PTE table to the Bloom filter. This
+ forms a feedback loop between the eviction and the aging.
+ 
++Bloom Filters
++-------------
++Bloom filters are a space and memory efficient data structure for set
++membership test, i.e., test if an element is not in the set or may be
++in the set.
++
++In the eviction path, specifically, in ``lru_gen_look_around()``, if a
++PMD has a sufficient number of hot pages, its address is placed in the
++filter. In the aging path, set membership means that the PTE range
++will be scanned for young pages.
++
++Note that Bloom filters are probabilistic on set membership. If a test
++is false positive, the cost is an additional scan of a range of PTEs,
++which may yield hot pages anyway. Parameters of the filter itself can
++control the false positive rate in the limit.
++
+ Summary
+ -------
+ The multi-gen LRU can be disassembled into the following parts:
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -3209,6 +3209,98 @@ static bool __maybe_unused seq_is_valid(
+ }
+ 
+ /******************************************************************************
++ *                          Bloom filters
++ ******************************************************************************/
++
++/*
++ * Bloom filters with m=1<<15, k=2 and the false positive rates of ~1/5 when
++ * n=10,000 and ~1/2 when n=20,000, where, conventionally, m is the number of
++ * bits in a bitmap, k is the number of hash functions and n is the number of
++ * inserted items.
++ *
++ * Page table walkers use one of the two filters to reduce their search space.
++ * To get rid of non-leaf entries that no longer have enough leaf entries, the
++ * aging uses the double-buffering technique to flip to the other filter each
++ * time it produces a new generation. For non-leaf entries that have enough
++ * leaf entries, the aging carries them over to the next generation in
++ * walk_pmd_range(); the eviction also report them when walking the rmap
++ * in lru_gen_look_around().
++ *
++ * For future optimizations:
++ * 1. It's not necessary to keep both filters all the time. The spare one can be
++ *    freed after the RCU grace period and reallocated if needed again.
++ * 2. And when reallocating, it's worth scaling its size according to the number
++ *    of inserted entries in the other filter, to reduce the memory overhead on
++ *    small systems and false positives on large systems.
++ * 3. Jenkins' hash function is an alternative to Knuth's.
++ */
++#define BLOOM_FILTER_SHIFT	15
++
++static inline int filter_gen_from_seq(unsigned long seq)
++{
++	return seq % NR_BLOOM_FILTERS;
++}
++
++static void get_item_key(void *item, int *key)
++{
++	u32 hash = hash_ptr(item, BLOOM_FILTER_SHIFT * 2);
++
++	BUILD_BUG_ON(BLOOM_FILTER_SHIFT * 2 > BITS_PER_TYPE(u32));
++
++	key[0] = hash & (BIT(BLOOM_FILTER_SHIFT) - 1);
++	key[1] = hash >> BLOOM_FILTER_SHIFT;
++}
++
++static bool test_bloom_filter(struct lruvec *lruvec, unsigned long seq, void *item)
++{
++	int key[2];
++	unsigned long *filter;
++	int gen = filter_gen_from_seq(seq);
++
++	filter = READ_ONCE(lruvec->mm_state.filters[gen]);
++	if (!filter)
++		return true;
++
++	get_item_key(item, key);
++
++	return test_bit(key[0], filter) && test_bit(key[1], filter);
++}
++
++static void update_bloom_filter(struct lruvec *lruvec, unsigned long seq, void *item)
++{
++	int key[2];
++	unsigned long *filter;
++	int gen = filter_gen_from_seq(seq);
++
++	filter = READ_ONCE(lruvec->mm_state.filters[gen]);
++	if (!filter)
++		return;
++
++	get_item_key(item, key);
++
++	if (!test_bit(key[0], filter))
++		set_bit(key[0], filter);
++	if (!test_bit(key[1], filter))
++		set_bit(key[1], filter);
++}
++
++static void reset_bloom_filter(struct lruvec *lruvec, unsigned long seq)
++{
++	unsigned long *filter;
++	int gen = filter_gen_from_seq(seq);
++
++	filter = lruvec->mm_state.filters[gen];
++	if (filter) {
++		bitmap_clear(filter, 0, BIT(BLOOM_FILTER_SHIFT));
++		return;
++	}
++
++	filter = bitmap_zalloc(BIT(BLOOM_FILTER_SHIFT),
++			       __GFP_HIGH | __GFP_NOMEMALLOC | __GFP_NOWARN);
++	WRITE_ONCE(lruvec->mm_state.filters[gen], filter);
++}
++
++/******************************************************************************
+  *                          mm_struct list
+  ******************************************************************************/
+ 
+@@ -3333,94 +3425,6 @@ void lru_gen_migrate_mm(struct mm_struct
+ }
+ #endif
+ 
+-/*
+- * Bloom filters with m=1<<15, k=2 and the false positive rates of ~1/5 when
+- * n=10,000 and ~1/2 when n=20,000, where, conventionally, m is the number of
+- * bits in a bitmap, k is the number of hash functions and n is the number of
+- * inserted items.
+- *
+- * Page table walkers use one of the two filters to reduce their search space.
+- * To get rid of non-leaf entries that no longer have enough leaf entries, the
+- * aging uses the double-buffering technique to flip to the other filter each
+- * time it produces a new generation. For non-leaf entries that have enough
+- * leaf entries, the aging carries them over to the next generation in
+- * walk_pmd_range(); the eviction also report them when walking the rmap
+- * in lru_gen_look_around().
+- *
+- * For future optimizations:
+- * 1. It's not necessary to keep both filters all the time. The spare one can be
+- *    freed after the RCU grace period and reallocated if needed again.
+- * 2. And when reallocating, it's worth scaling its size according to the number
+- *    of inserted entries in the other filter, to reduce the memory overhead on
+- *    small systems and false positives on large systems.
+- * 3. Jenkins' hash function is an alternative to Knuth's.
+- */
+-#define BLOOM_FILTER_SHIFT	15
+-
+-static inline int filter_gen_from_seq(unsigned long seq)
+-{
+-	return seq % NR_BLOOM_FILTERS;
+-}
+-
+-static void get_item_key(void *item, int *key)
+-{
+-	u32 hash = hash_ptr(item, BLOOM_FILTER_SHIFT * 2);
+-
+-	BUILD_BUG_ON(BLOOM_FILTER_SHIFT * 2 > BITS_PER_TYPE(u32));
+-
+-	key[0] = hash & (BIT(BLOOM_FILTER_SHIFT) - 1);
+-	key[1] = hash >> BLOOM_FILTER_SHIFT;
+-}
+-
+-static void reset_bloom_filter(struct lruvec *lruvec, unsigned long seq)
+-{
+-	unsigned long *filter;
+-	int gen = filter_gen_from_seq(seq);
+-
+-	filter = lruvec->mm_state.filters[gen];
+-	if (filter) {
+-		bitmap_clear(filter, 0, BIT(BLOOM_FILTER_SHIFT));
+-		return;
+-	}
+-
+-	filter = bitmap_zalloc(BIT(BLOOM_FILTER_SHIFT),
+-			       __GFP_HIGH | __GFP_NOMEMALLOC | __GFP_NOWARN);
+-	WRITE_ONCE(lruvec->mm_state.filters[gen], filter);
+-}
+-
+-static void update_bloom_filter(struct lruvec *lruvec, unsigned long seq, void *item)
+-{
+-	int key[2];
+-	unsigned long *filter;
+-	int gen = filter_gen_from_seq(seq);
+-
+-	filter = READ_ONCE(lruvec->mm_state.filters[gen]);
+-	if (!filter)
+-		return;
+-
+-	get_item_key(item, key);
+-
+-	if (!test_bit(key[0], filter))
+-		set_bit(key[0], filter);
+-	if (!test_bit(key[1], filter))
+-		set_bit(key[1], filter);
+-}
+-
+-static bool test_bloom_filter(struct lruvec *lruvec, unsigned long seq, void *item)
+-{
+-	int key[2];
+-	unsigned long *filter;
+-	int gen = filter_gen_from_seq(seq);
+-
+-	filter = READ_ONCE(lruvec->mm_state.filters[gen]);
+-	if (!filter)
+-		return true;
+-
+-	get_item_key(item, key);
+-
+-	return test_bit(key[0], filter) && test_bit(key[1], filter);
+-}
+-
+ static void reset_mm_stats(struct lruvec *lruvec, struct lru_gen_mm_walk *walk, bool last)
+ {
+ 	int i;

+ 427 - 0
target/linux/generic/backport-6.6/020-v6.3-15-UPSTREAM-mm-multi-gen-LRU-section-for-memcg-LRU.patch

@@ -0,0 +1,427 @@
+From 48c916b812652f9453be5bd45a703728926d41ca Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <[email protected]>
+Date: Wed, 18 Jan 2023 00:18:24 +0000
+Subject: [PATCH 15/19] UPSTREAM: mm: multi-gen LRU: section for memcg LRU
+
+Move memcg LRU code into a dedicated section.  Improve the design doc to
+outline its architecture.
+
+Link: https://lkml.kernel.org/r/[email protected]
+Change-Id: Id252e420cff7a858acb098cf2b3642da5c40f602
+Signed-off-by: T.J. Alumbaugh <[email protected]>
+Cc: Yu Zhao <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+(cherry picked from commit 36c7b4db7c942ae9e1b111f0c6b468c8b2e33842)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ Documentation/mm/multigen_lru.rst |  33 +++-
+ include/linux/mm_inline.h         |  17 --
+ include/linux/mmzone.h            |  13 +-
+ mm/memcontrol.c                   |   8 +-
+ mm/vmscan.c                       | 250 +++++++++++++++++-------------
+ 5 files changed, 178 insertions(+), 143 deletions(-)
+
+--- a/Documentation/mm/multigen_lru.rst
++++ b/Documentation/mm/multigen_lru.rst
+@@ -186,9 +186,40 @@ is false positive, the cost is an additi
+ which may yield hot pages anyway. Parameters of the filter itself can
+ control the false positive rate in the limit.
+ 
++Memcg LRU
++---------
++An memcg LRU is a per-node LRU of memcgs. It is also an LRU of LRUs,
++since each node and memcg combination has an LRU of folios (see
++``mem_cgroup_lruvec()``). Its goal is to improve the scalability of
++global reclaim, which is critical to system-wide memory overcommit in
++data centers. Note that memcg LRU only applies to global reclaim.
++
++The basic structure of an memcg LRU can be understood by an analogy to
++the active/inactive LRU (of folios):
++
++1. It has the young and the old (generations), i.e., the counterparts
++   to the active and the inactive;
++2. The increment of ``max_seq`` triggers promotion, i.e., the
++   counterpart to activation;
++3. Other events trigger similar operations, e.g., offlining an memcg
++   triggers demotion, i.e., the counterpart to deactivation.
++
++In terms of global reclaim, it has two distinct features:
++
++1. Sharding, which allows each thread to start at a random memcg (in
++   the old generation) and improves parallelism;
++2. Eventual fairness, which allows direct reclaim to bail out at will
++   and reduces latency without affecting fairness over some time.
++
++In terms of traversing memcgs during global reclaim, it improves the
++best-case complexity from O(n) to O(1) and does not affect the
++worst-case complexity O(n). Therefore, on average, it has a sublinear
++complexity.
++
+ Summary
+ -------
+-The multi-gen LRU can be disassembled into the following parts:
++The multi-gen LRU (of folios) can be disassembled into the following
++parts:
+ 
+ * Generations
+ * Rmap walks
+--- a/include/linux/mm_inline.h
++++ b/include/linux/mm_inline.h
+@@ -122,18 +122,6 @@ static inline bool lru_gen_in_fault(void
+ 	return current->in_lru_fault;
+ }
+ 
+-#ifdef CONFIG_MEMCG
+-static inline int lru_gen_memcg_seg(struct lruvec *lruvec)
+-{
+-	return READ_ONCE(lruvec->lrugen.seg);
+-}
+-#else
+-static inline int lru_gen_memcg_seg(struct lruvec *lruvec)
+-{
+-	return 0;
+-}
+-#endif
+-
+ static inline int lru_gen_from_seq(unsigned long seq)
+ {
+ 	return seq % MAX_NR_GENS;
+@@ -314,11 +302,6 @@ static inline bool lru_gen_in_fault(void
+ 	return false;
+ }
+ 
+-static inline int lru_gen_memcg_seg(struct lruvec *lruvec)
+-{
+-	return 0;
+-}
+-
+ static inline bool lru_gen_add_folio(struct lruvec *lruvec, struct folio *folio, bool reclaiming)
+ {
+ 	return false;
+--- a/include/linux/mmzone.h
++++ b/include/linux/mmzone.h
+@@ -368,15 +368,6 @@ struct page_vma_mapped_walk;
+ #define LRU_GEN_MASK		((BIT(LRU_GEN_WIDTH) - 1) << LRU_GEN_PGOFF)
+ #define LRU_REFS_MASK		((BIT(LRU_REFS_WIDTH) - 1) << LRU_REFS_PGOFF)
+ 
+-/* see the comment on MEMCG_NR_GENS */
+-enum {
+-	MEMCG_LRU_NOP,
+-	MEMCG_LRU_HEAD,
+-	MEMCG_LRU_TAIL,
+-	MEMCG_LRU_OLD,
+-	MEMCG_LRU_YOUNG,
+-};
+-
+ #ifdef CONFIG_LRU_GEN
+ 
+ enum {
+@@ -557,7 +548,7 @@ void lru_gen_exit_memcg(struct mem_cgrou
+ void lru_gen_online_memcg(struct mem_cgroup *memcg);
+ void lru_gen_offline_memcg(struct mem_cgroup *memcg);
+ void lru_gen_release_memcg(struct mem_cgroup *memcg);
+-void lru_gen_rotate_memcg(struct lruvec *lruvec, int op);
++void lru_gen_soft_reclaim(struct lruvec *lruvec);
+ 
+ #else /* !CONFIG_MEMCG */
+ 
+@@ -608,7 +599,7 @@ static inline void lru_gen_release_memcg
+ {
+ }
+ 
+-static inline void lru_gen_rotate_memcg(struct lruvec *lruvec, int op)
++static inline void lru_gen_soft_reclaim(struct lruvec *lruvec)
+ {
+ }
+ 
+--- a/mm/memcontrol.c
++++ b/mm/memcontrol.c
+@@ -478,12 +478,8 @@ static void mem_cgroup_update_tree(struc
+ 	struct mem_cgroup_tree_per_node *mctz;
+ 
+ 	if (lru_gen_enabled()) {
+-		struct lruvec *lruvec = &memcg->nodeinfo[nid]->lruvec;
+-
+-		/* see the comment on MEMCG_NR_GENS */
+-		if (soft_limit_excess(memcg) && lru_gen_memcg_seg(lruvec) != MEMCG_LRU_HEAD)
+-			lru_gen_rotate_memcg(lruvec, MEMCG_LRU_HEAD);
+-
++		if (soft_limit_excess(memcg))
++			lru_gen_soft_reclaim(&memcg->nodeinfo[nid]->lruvec);
+ 		return;
+ 	}
+ 
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -4692,6 +4692,148 @@ void lru_gen_look_around(struct page_vma
+ }
+ 
+ /******************************************************************************
++ *                          memcg LRU
++ ******************************************************************************/
++
++/* see the comment on MEMCG_NR_GENS */
++enum {
++	MEMCG_LRU_NOP,
++	MEMCG_LRU_HEAD,
++	MEMCG_LRU_TAIL,
++	MEMCG_LRU_OLD,
++	MEMCG_LRU_YOUNG,
++};
++
++#ifdef CONFIG_MEMCG
++
++static int lru_gen_memcg_seg(struct lruvec *lruvec)
++{
++	return READ_ONCE(lruvec->lrugen.seg);
++}
++
++static void lru_gen_rotate_memcg(struct lruvec *lruvec, int op)
++{
++	int seg;
++	int old, new;
++	int bin = get_random_u32_below(MEMCG_NR_BINS);
++	struct pglist_data *pgdat = lruvec_pgdat(lruvec);
++
++	spin_lock(&pgdat->memcg_lru.lock);
++
++	VM_WARN_ON_ONCE(hlist_nulls_unhashed(&lruvec->lrugen.list));
++
++	seg = 0;
++	new = old = lruvec->lrugen.gen;
++
++	/* see the comment on MEMCG_NR_GENS */
++	if (op == MEMCG_LRU_HEAD)
++		seg = MEMCG_LRU_HEAD;
++	else if (op == MEMCG_LRU_TAIL)
++		seg = MEMCG_LRU_TAIL;
++	else if (op == MEMCG_LRU_OLD)
++		new = get_memcg_gen(pgdat->memcg_lru.seq);
++	else if (op == MEMCG_LRU_YOUNG)
++		new = get_memcg_gen(pgdat->memcg_lru.seq + 1);
++	else
++		VM_WARN_ON_ONCE(true);
++
++	hlist_nulls_del_rcu(&lruvec->lrugen.list);
++
++	if (op == MEMCG_LRU_HEAD || op == MEMCG_LRU_OLD)
++		hlist_nulls_add_head_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[new][bin]);
++	else
++		hlist_nulls_add_tail_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[new][bin]);
++
++	pgdat->memcg_lru.nr_memcgs[old]--;
++	pgdat->memcg_lru.nr_memcgs[new]++;
++
++	lruvec->lrugen.gen = new;
++	WRITE_ONCE(lruvec->lrugen.seg, seg);
++
++	if (!pgdat->memcg_lru.nr_memcgs[old] && old == get_memcg_gen(pgdat->memcg_lru.seq))
++		WRITE_ONCE(pgdat->memcg_lru.seq, pgdat->memcg_lru.seq + 1);
++
++	spin_unlock(&pgdat->memcg_lru.lock);
++}
++
++void lru_gen_online_memcg(struct mem_cgroup *memcg)
++{
++	int gen;
++	int nid;
++	int bin = get_random_u32_below(MEMCG_NR_BINS);
++
++	for_each_node(nid) {
++		struct pglist_data *pgdat = NODE_DATA(nid);
++		struct lruvec *lruvec = get_lruvec(memcg, nid);
++
++		spin_lock(&pgdat->memcg_lru.lock);
++
++		VM_WARN_ON_ONCE(!hlist_nulls_unhashed(&lruvec->lrugen.list));
++
++		gen = get_memcg_gen(pgdat->memcg_lru.seq);
++
++		hlist_nulls_add_tail_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[gen][bin]);
++		pgdat->memcg_lru.nr_memcgs[gen]++;
++
++		lruvec->lrugen.gen = gen;
++
++		spin_unlock(&pgdat->memcg_lru.lock);
++	}
++}
++
++void lru_gen_offline_memcg(struct mem_cgroup *memcg)
++{
++	int nid;
++
++	for_each_node(nid) {
++		struct lruvec *lruvec = get_lruvec(memcg, nid);
++
++		lru_gen_rotate_memcg(lruvec, MEMCG_LRU_OLD);
++	}
++}
++
++void lru_gen_release_memcg(struct mem_cgroup *memcg)
++{
++	int gen;
++	int nid;
++
++	for_each_node(nid) {
++		struct pglist_data *pgdat = NODE_DATA(nid);
++		struct lruvec *lruvec = get_lruvec(memcg, nid);
++
++		spin_lock(&pgdat->memcg_lru.lock);
++
++		VM_WARN_ON_ONCE(hlist_nulls_unhashed(&lruvec->lrugen.list));
++
++		gen = lruvec->lrugen.gen;
++
++		hlist_nulls_del_rcu(&lruvec->lrugen.list);
++		pgdat->memcg_lru.nr_memcgs[gen]--;
++
++		if (!pgdat->memcg_lru.nr_memcgs[gen] && gen == get_memcg_gen(pgdat->memcg_lru.seq))
++			WRITE_ONCE(pgdat->memcg_lru.seq, pgdat->memcg_lru.seq + 1);
++
++		spin_unlock(&pgdat->memcg_lru.lock);
++	}
++}
++
++void lru_gen_soft_reclaim(struct lruvec *lruvec)
++{
++	/* see the comment on MEMCG_NR_GENS */
++	if (lru_gen_memcg_seg(lruvec) != MEMCG_LRU_HEAD)
++		lru_gen_rotate_memcg(lruvec, MEMCG_LRU_HEAD);
++}
++
++#else /* !CONFIG_MEMCG */
++
++static int lru_gen_memcg_seg(struct lruvec *lruvec)
++{
++	return 0;
++}
++
++#endif
++
++/******************************************************************************
+  *                          the eviction
+  ******************************************************************************/
+ 
+@@ -5398,53 +5540,6 @@ done:
+ 	pgdat->kswapd_failures = 0;
+ }
+ 
+-#ifdef CONFIG_MEMCG
+-void lru_gen_rotate_memcg(struct lruvec *lruvec, int op)
+-{
+-	int seg;
+-	int old, new;
+-	int bin = get_random_u32_below(MEMCG_NR_BINS);
+-	struct pglist_data *pgdat = lruvec_pgdat(lruvec);
+-
+-	spin_lock(&pgdat->memcg_lru.lock);
+-
+-	VM_WARN_ON_ONCE(hlist_nulls_unhashed(&lruvec->lrugen.list));
+-
+-	seg = 0;
+-	new = old = lruvec->lrugen.gen;
+-
+-	/* see the comment on MEMCG_NR_GENS */
+-	if (op == MEMCG_LRU_HEAD)
+-		seg = MEMCG_LRU_HEAD;
+-	else if (op == MEMCG_LRU_TAIL)
+-		seg = MEMCG_LRU_TAIL;
+-	else if (op == MEMCG_LRU_OLD)
+-		new = get_memcg_gen(pgdat->memcg_lru.seq);
+-	else if (op == MEMCG_LRU_YOUNG)
+-		new = get_memcg_gen(pgdat->memcg_lru.seq + 1);
+-	else
+-		VM_WARN_ON_ONCE(true);
+-
+-	hlist_nulls_del_rcu(&lruvec->lrugen.list);
+-
+-	if (op == MEMCG_LRU_HEAD || op == MEMCG_LRU_OLD)
+-		hlist_nulls_add_head_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[new][bin]);
+-	else
+-		hlist_nulls_add_tail_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[new][bin]);
+-
+-	pgdat->memcg_lru.nr_memcgs[old]--;
+-	pgdat->memcg_lru.nr_memcgs[new]++;
+-
+-	lruvec->lrugen.gen = new;
+-	WRITE_ONCE(lruvec->lrugen.seg, seg);
+-
+-	if (!pgdat->memcg_lru.nr_memcgs[old] && old == get_memcg_gen(pgdat->memcg_lru.seq))
+-		WRITE_ONCE(pgdat->memcg_lru.seq, pgdat->memcg_lru.seq + 1);
+-
+-	spin_unlock(&pgdat->memcg_lru.lock);
+-}
+-#endif
+-
+ /******************************************************************************
+  *                          state change
+  ******************************************************************************/
+@@ -6090,67 +6185,6 @@ void lru_gen_exit_memcg(struct mem_cgrou
+ 	}
+ }
+ 
+-void lru_gen_online_memcg(struct mem_cgroup *memcg)
+-{
+-	int gen;
+-	int nid;
+-	int bin = get_random_u32_below(MEMCG_NR_BINS);
+-
+-	for_each_node(nid) {
+-		struct pglist_data *pgdat = NODE_DATA(nid);
+-		struct lruvec *lruvec = get_lruvec(memcg, nid);
+-
+-		spin_lock(&pgdat->memcg_lru.lock);
+-
+-		VM_WARN_ON_ONCE(!hlist_nulls_unhashed(&lruvec->lrugen.list));
+-
+-		gen = get_memcg_gen(pgdat->memcg_lru.seq);
+-
+-		hlist_nulls_add_tail_rcu(&lruvec->lrugen.list, &pgdat->memcg_lru.fifo[gen][bin]);
+-		pgdat->memcg_lru.nr_memcgs[gen]++;
+-
+-		lruvec->lrugen.gen = gen;
+-
+-		spin_unlock(&pgdat->memcg_lru.lock);
+-	}
+-}
+-
+-void lru_gen_offline_memcg(struct mem_cgroup *memcg)
+-{
+-	int nid;
+-
+-	for_each_node(nid) {
+-		struct lruvec *lruvec = get_lruvec(memcg, nid);
+-
+-		lru_gen_rotate_memcg(lruvec, MEMCG_LRU_OLD);
+-	}
+-}
+-
+-void lru_gen_release_memcg(struct mem_cgroup *memcg)
+-{
+-	int gen;
+-	int nid;
+-
+-	for_each_node(nid) {
+-		struct pglist_data *pgdat = NODE_DATA(nid);
+-		struct lruvec *lruvec = get_lruvec(memcg, nid);
+-
+-		spin_lock(&pgdat->memcg_lru.lock);
+-
+-		VM_WARN_ON_ONCE(hlist_nulls_unhashed(&lruvec->lrugen.list));
+-
+-		gen = lruvec->lrugen.gen;
+-
+-		hlist_nulls_del_rcu(&lruvec->lrugen.list);
+-		pgdat->memcg_lru.nr_memcgs[gen]--;
+-
+-		if (!pgdat->memcg_lru.nr_memcgs[gen] && gen == get_memcg_gen(pgdat->memcg_lru.seq))
+-			WRITE_ONCE(pgdat->memcg_lru.seq, pgdat->memcg_lru.seq + 1);
+-
+-		spin_unlock(&pgdat->memcg_lru.lock);
+-	}
+-}
+-
+ #endif /* CONFIG_MEMCG */
+ 
+ static int __init init_lru_gen(void)

+ 40 - 0
target/linux/generic/backport-6.6/020-v6.3-16-UPSTREAM-mm-multi-gen-LRU-improve-lru_gen_exit_memcg.patch

@@ -0,0 +1,40 @@
+From bec433f29537652ed054148edfd7e2183ddcf7c3 Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <[email protected]>
+Date: Wed, 18 Jan 2023 00:18:25 +0000
+Subject: [PATCH 16/19] UPSTREAM: mm: multi-gen LRU: improve
+ lru_gen_exit_memcg()
+
+Add warnings and poison ->next.
+
+Link: https://lkml.kernel.org/r/[email protected]
+Change-Id: I53de9e04c1ae941e122b33cd45d2bbb5f34aae0c
+Signed-off-by: T.J. Alumbaugh <[email protected]>
+Cc: Yu Zhao <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+(cherry picked from commit 37cc99979d04cca677c0ad5c0acd1149ec165d1b)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ mm/vmscan.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -6172,12 +6172,17 @@ void lru_gen_exit_memcg(struct mem_cgrou
+ 	int i;
+ 	int nid;
+ 
++	VM_WARN_ON_ONCE(!list_empty(&memcg->mm_list.fifo));
++
+ 	for_each_node(nid) {
+ 		struct lruvec *lruvec = get_lruvec(memcg, nid);
+ 
++		VM_WARN_ON_ONCE(lruvec->mm_state.nr_walkers);
+ 		VM_WARN_ON_ONCE(memchr_inv(lruvec->lrugen.nr_pages, 0,
+ 					   sizeof(lruvec->lrugen.nr_pages)));
+ 
++		lruvec->lrugen.list.next = LIST_POISON1;
++
+ 		for (i = 0; i < NR_BLOOM_FILTERS; i++) {
+ 			bitmap_free(lruvec->mm_state.filters[i]);
+ 			lruvec->mm_state.filters[i] = NULL;

+ 135 - 0
target/linux/generic/backport-6.6/020-v6.3-17-UPSTREAM-mm-multi-gen-LRU-improve-walk_pmd_range.patch

@@ -0,0 +1,135 @@
+From fc0e3b06e0f19917b7ecad7967a72f61d4743644 Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <[email protected]>
+Date: Wed, 18 Jan 2023 00:18:26 +0000
+Subject: [PATCH 17/19] UPSTREAM: mm: multi-gen LRU: improve walk_pmd_range()
+
+Improve readability of walk_pmd_range() and walk_pmd_range_locked().
+
+Link: https://lkml.kernel.org/r/[email protected]
+Change-Id: Ia084fbf53fe989673b7804ca8ca520af12d7d52a
+Signed-off-by: T.J. Alumbaugh <[email protected]>
+Cc: Yu Zhao <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+(cherry picked from commit b5ff4133617d0eced35b685da0bd0929dd9fabb7)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ mm/vmscan.c | 40 ++++++++++++++++++++--------------------
+ 1 file changed, 20 insertions(+), 20 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -3980,8 +3980,8 @@ restart:
+ }
+ 
+ #if defined(CONFIG_TRANSPARENT_HUGEPAGE) || defined(CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG)
+-static void walk_pmd_range_locked(pud_t *pud, unsigned long next, struct vm_area_struct *vma,
+-				  struct mm_walk *args, unsigned long *bitmap, unsigned long *start)
++static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area_struct *vma,
++				  struct mm_walk *args, unsigned long *bitmap, unsigned long *first)
+ {
+ 	int i;
+ 	pmd_t *pmd;
+@@ -3994,18 +3994,19 @@ static void walk_pmd_range_locked(pud_t
+ 	VM_WARN_ON_ONCE(pud_leaf(*pud));
+ 
+ 	/* try to batch at most 1+MIN_LRU_BATCH+1 entries */
+-	if (*start == -1) {
+-		*start = next;
++	if (*first == -1) {
++		*first = addr;
++		bitmap_zero(bitmap, MIN_LRU_BATCH);
+ 		return;
+ 	}
+ 
+-	i = next == -1 ? 0 : pmd_index(next) - pmd_index(*start);
++	i = addr == -1 ? 0 : pmd_index(addr) - pmd_index(*first);
+ 	if (i && i <= MIN_LRU_BATCH) {
+ 		__set_bit(i - 1, bitmap);
+ 		return;
+ 	}
+ 
+-	pmd = pmd_offset(pud, *start);
++	pmd = pmd_offset(pud, *first);
+ 
+ 	ptl = pmd_lockptr(args->mm, pmd);
+ 	if (!spin_trylock(ptl))
+@@ -4016,15 +4017,16 @@ static void walk_pmd_range_locked(pud_t
+ 	do {
+ 		unsigned long pfn;
+ 		struct folio *folio;
+-		unsigned long addr = i ? (*start & PMD_MASK) + i * PMD_SIZE : *start;
++
++		/* don't round down the first address */
++		addr = i ? (*first & PMD_MASK) + i * PMD_SIZE : *first;
+ 
+ 		pfn = get_pmd_pfn(pmd[i], vma, addr);
+ 		if (pfn == -1)
+ 			goto next;
+ 
+ 		if (!pmd_trans_huge(pmd[i])) {
+-			if (arch_has_hw_nonleaf_pmd_young() &&
+-			    get_cap(LRU_GEN_NONLEAF_YOUNG))
++			if (arch_has_hw_nonleaf_pmd_young() && get_cap(LRU_GEN_NONLEAF_YOUNG))
+ 				pmdp_test_and_clear_young(vma, addr, pmd + i);
+ 			goto next;
+ 		}
+@@ -4053,12 +4055,11 @@ next:
+ 	arch_leave_lazy_mmu_mode();
+ 	spin_unlock(ptl);
+ done:
+-	*start = -1;
+-	bitmap_zero(bitmap, MIN_LRU_BATCH);
++	*first = -1;
+ }
+ #else
+-static void walk_pmd_range_locked(pud_t *pud, unsigned long next, struct vm_area_struct *vma,
+-				  struct mm_walk *args, unsigned long *bitmap, unsigned long *start)
++static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area_struct *vma,
++				  struct mm_walk *args, unsigned long *bitmap, unsigned long *first)
+ {
+ }
+ #endif
+@@ -4071,9 +4072,9 @@ static void walk_pmd_range(pud_t *pud, u
+ 	unsigned long next;
+ 	unsigned long addr;
+ 	struct vm_area_struct *vma;
+-	unsigned long pos = -1;
++	unsigned long bitmap[BITS_TO_LONGS(MIN_LRU_BATCH)];
++	unsigned long first = -1;
+ 	struct lru_gen_mm_walk *walk = args->private;
+-	unsigned long bitmap[BITS_TO_LONGS(MIN_LRU_BATCH)] = {};
+ 
+ 	VM_WARN_ON_ONCE(pud_leaf(*pud));
+ 
+@@ -4115,18 +4116,17 @@ restart:
+ 			if (pfn < pgdat->node_start_pfn || pfn >= pgdat_end_pfn(pgdat))
+ 				continue;
+ 
+-			walk_pmd_range_locked(pud, addr, vma, args, bitmap, &pos);
++			walk_pmd_range_locked(pud, addr, vma, args, bitmap, &first);
+ 			continue;
+ 		}
+ #endif
+ 		walk->mm_stats[MM_NONLEAF_TOTAL]++;
+ 
+-		if (arch_has_hw_nonleaf_pmd_young() &&
+-		    get_cap(LRU_GEN_NONLEAF_YOUNG)) {
++		if (arch_has_hw_nonleaf_pmd_young() && get_cap(LRU_GEN_NONLEAF_YOUNG)) {
+ 			if (!pmd_young(val))
+ 				continue;
+ 
+-			walk_pmd_range_locked(pud, addr, vma, args, bitmap, &pos);
++			walk_pmd_range_locked(pud, addr, vma, args, bitmap, &first);
+ 		}
+ 
+ 		if (!walk->force_scan && !test_bloom_filter(walk->lruvec, walk->max_seq, pmd + i))
+@@ -4143,7 +4143,7 @@ restart:
+ 		update_bloom_filter(walk->lruvec, walk->max_seq + 1, pmd + i);
+ 	}
+ 
+-	walk_pmd_range_locked(pud, -1, vma, args, bitmap, &pos);
++	walk_pmd_range_locked(pud, -1, vma, args, bitmap, &first);
+ 
+ 	if (i < PTRS_PER_PMD && get_next_vma(PUD_MASK, PMD_SIZE, args, &start, &end))
+ 		goto restart;

+ 148 - 0
target/linux/generic/backport-6.6/020-v6.3-18-UPSTREAM-mm-multi-gen-LRU-simplify-lru_gen_look_arou.patch

@@ -0,0 +1,148 @@
+From e604c3ccb4dfbdde2467fccef9bb36170a392695 Mon Sep 17 00:00:00 2001
+From: "T.J. Alumbaugh" <[email protected]>
+Date: Wed, 18 Jan 2023 00:18:27 +0000
+Subject: [PATCH 18/19] UPSTREAM: mm: multi-gen LRU: simplify
+ lru_gen_look_around()
+
+Update the folio generation in place with or without
+current->reclaim_state->mm_walk.  The LRU lock is held for longer, if
+mm_walk is NULL and the number of folios to update is more than
+PAGEVEC_SIZE.
+
+This causes a measurable regression from the LRU lock contention during a
+microbencmark.  But a tiny regression is not worth the complexity.
+
+Link: https://lkml.kernel.org/r/[email protected]
+Change-Id: I9ce18b4f4062e6c1c13c98ece9422478eb8e1846
+Signed-off-by: T.J. Alumbaugh <[email protected]>
+Cc: Yu Zhao <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+(cherry picked from commit abf086721a2f1e6897c57796f7268df1b194c750)
+Bug: 274865848
+Signed-off-by: T.J. Mercier <[email protected]>
+---
+ mm/vmscan.c | 73 +++++++++++++++++------------------------------------
+ 1 file changed, 23 insertions(+), 50 deletions(-)
+
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -4573,13 +4573,12 @@ static void lru_gen_age_node(struct pgli
+ void lru_gen_look_around(struct page_vma_mapped_walk *pvmw)
+ {
+ 	int i;
+-	pte_t *pte;
+ 	unsigned long start;
+ 	unsigned long end;
+-	unsigned long addr;
+ 	struct lru_gen_mm_walk *walk;
+ 	int young = 0;
+-	unsigned long bitmap[BITS_TO_LONGS(MIN_LRU_BATCH)] = {};
++	pte_t *pte = pvmw->pte;
++	unsigned long addr = pvmw->address;
+ 	struct folio *folio = pfn_folio(pvmw->pfn);
+ 	struct mem_cgroup *memcg = folio_memcg(folio);
+ 	struct pglist_data *pgdat = folio_pgdat(folio);
+@@ -4596,25 +4595,28 @@ void lru_gen_look_around(struct page_vma
+ 	/* avoid taking the LRU lock under the PTL when possible */
+ 	walk = current->reclaim_state ? current->reclaim_state->mm_walk : NULL;
+ 
+-	start = max(pvmw->address & PMD_MASK, pvmw->vma->vm_start);
+-	end = min(pvmw->address | ~PMD_MASK, pvmw->vma->vm_end - 1) + 1;
++	start = max(addr & PMD_MASK, pvmw->vma->vm_start);
++	end = min(addr | ~PMD_MASK, pvmw->vma->vm_end - 1) + 1;
+ 
+ 	if (end - start > MIN_LRU_BATCH * PAGE_SIZE) {
+-		if (pvmw->address - start < MIN_LRU_BATCH * PAGE_SIZE / 2)
++		if (addr - start < MIN_LRU_BATCH * PAGE_SIZE / 2)
+ 			end = start + MIN_LRU_BATCH * PAGE_SIZE;
+-		else if (end - pvmw->address < MIN_LRU_BATCH * PAGE_SIZE / 2)
++		else if (end - addr < MIN_LRU_BATCH * PAGE_SIZE / 2)
+ 			start = end - MIN_LRU_BATCH * PAGE_SIZE;
+ 		else {
+-			start = pvmw->address - MIN_LRU_BATCH * PAGE_SIZE / 2;
+-			end = pvmw->address + MIN_LRU_BATCH * PAGE_SIZE / 2;
++			start = addr - MIN_LRU_BATCH * PAGE_SIZE / 2;
++			end = addr + MIN_LRU_BATCH * PAGE_SIZE / 2;
+ 		}
+ 	}
+ 
+-	pte = pvmw->pte - (pvmw->address - start) / PAGE_SIZE;
++	/* folio_update_gen() requires stable folio_memcg() */
++	if (!mem_cgroup_trylock_pages(memcg))
++		return;
+ 
+-	rcu_read_lock();
+ 	arch_enter_lazy_mmu_mode();
+ 
++	pte -= (addr - start) / PAGE_SIZE;
++
+ 	for (i = 0, addr = start; addr != end; i++, addr += PAGE_SIZE) {
+ 		unsigned long pfn;
+ 
+@@ -4639,56 +4641,27 @@ void lru_gen_look_around(struct page_vma
+ 		      !folio_test_swapcache(folio)))
+ 			folio_mark_dirty(folio);
+ 
++		if (walk) {
++			old_gen = folio_update_gen(folio, new_gen);
++			if (old_gen >= 0 && old_gen != new_gen)
++				update_batch_size(walk, folio, old_gen, new_gen);
++
++			continue;
++		}
++
+ 		old_gen = folio_lru_gen(folio);
+ 		if (old_gen < 0)
+ 			folio_set_referenced(folio);
+ 		else if (old_gen != new_gen)
+-			__set_bit(i, bitmap);
++			folio_activate(folio);
+ 	}
+ 
+ 	arch_leave_lazy_mmu_mode();
+-	rcu_read_unlock();
++	mem_cgroup_unlock_pages();
+ 
+ 	/* feedback from rmap walkers to page table walkers */
+ 	if (suitable_to_scan(i, young))
+ 		update_bloom_filter(lruvec, max_seq, pvmw->pmd);
+-
+-	if (!walk && bitmap_weight(bitmap, MIN_LRU_BATCH) < PAGEVEC_SIZE) {
+-		for_each_set_bit(i, bitmap, MIN_LRU_BATCH) {
+-			folio = pfn_folio(pte_pfn(pte[i]));
+-			folio_activate(folio);
+-		}
+-		return;
+-	}
+-
+-	/* folio_update_gen() requires stable folio_memcg() */
+-	if (!mem_cgroup_trylock_pages(memcg))
+-		return;
+-
+-	if (!walk) {
+-		spin_lock_irq(&lruvec->lru_lock);
+-		new_gen = lru_gen_from_seq(lruvec->lrugen.max_seq);
+-	}
+-
+-	for_each_set_bit(i, bitmap, MIN_LRU_BATCH) {
+-		folio = pfn_folio(pte_pfn(pte[i]));
+-		if (folio_memcg_rcu(folio) != memcg)
+-			continue;
+-
+-		old_gen = folio_update_gen(folio, new_gen);
+-		if (old_gen < 0 || old_gen == new_gen)
+-			continue;
+-
+-		if (walk)
+-			update_batch_size(walk, folio, old_gen, new_gen);
+-		else
+-			lru_gen_update_size(lruvec, folio, old_gen, new_gen);
+-	}
+-
+-	if (!walk)
+-		spin_unlock_irq(&lruvec->lru_lock);
+-
+-	mem_cgroup_unlock_pages();
+ }
+ 
+ /******************************************************************************

+ 273 - 0
target/linux/generic/backport-6.6/020-v6.4-19-mm-Multi-gen-LRU-remove-wait_event_killable.patch

@@ -0,0 +1,273 @@
+From 418038c22452df38cde519cc8c662bb15139764a Mon Sep 17 00:00:00 2001
+From: Kalesh Singh <[email protected]>
+Date: Thu, 13 Apr 2023 14:43:26 -0700
+Subject: [PATCH 19/19] mm: Multi-gen LRU: remove wait_event_killable()
+
+Android 14 and later default to MGLRU [1] and field telemetry showed
+occasional long tail latency (>100ms) in the reclaim path.
+
+Tracing revealed priority inversion in the reclaim path.  In
+try_to_inc_max_seq(), when high priority tasks were blocked on
+wait_event_killable(), the preemption of the low priority task to call
+wake_up_all() caused those high priority tasks to wait longer than
+necessary.  In general, this problem is not different from others of its
+kind, e.g., one caused by mutex_lock().  However, it is specific to MGLRU
+because it introduced the new wait queue lruvec->mm_state.wait.
+
+The purpose of this new wait queue is to avoid the thundering herd
+problem.  If many direct reclaimers rush into try_to_inc_max_seq(), only
+one can succeed, i.e., the one to wake up the rest, and the rest who
+failed might cause premature OOM kills if they do not wait.  So far there
+is no evidence supporting this scenario, based on how often the wait has
+been hit.  And this begs the question how useful the wait queue is in
+practice.
+
+Based on Minchan's recommendation, which is in line with his commit
+6d4675e60135 ("mm: don't be stuck to rmap lock on reclaim path") and the
+rest of the MGLRU code which also uses trylock when possible, remove the
+wait queue.
+
+[1] https://android-review.googlesource.com/q/I7ed7fbfd6ef9ce10053347528125dd98c39e50bf
+
+Link: https://lkml.kernel.org/r/[email protected]
+Fixes: bd74fdaea146 ("mm: multi-gen LRU: support page table walks")
+Signed-off-by: Kalesh Singh <[email protected]>
+Suggested-by: Minchan Kim <[email protected]>
+Reported-by: Wei Wang <[email protected]>
+Acked-by: Yu Zhao <[email protected]>
+Cc: Minchan Kim <[email protected]>
+Cc: Jan Alexander Steffens (heftig) <[email protected]>
+Cc: Oleksandr Natalenko <[email protected]>
+Cc: Suleiman Souhlal <[email protected]>
+Cc: Suren Baghdasaryan <[email protected]>
+Signed-off-by: Andrew Morton <[email protected]>
+---
+ include/linux/mmzone.h |   8 +--
+ mm/vmscan.c            | 112 +++++++++++++++--------------------------
+ 2 files changed, 42 insertions(+), 78 deletions(-)
+
+--- a/include/linux/mmzone.h
++++ b/include/linux/mmzone.h
+@@ -453,18 +453,14 @@ enum {
+ struct lru_gen_mm_state {
+ 	/* set to max_seq after each iteration */
+ 	unsigned long seq;
+-	/* where the current iteration continues (inclusive) */
++	/* where the current iteration continues after */
+ 	struct list_head *head;
+-	/* where the last iteration ended (exclusive) */
++	/* where the last iteration ended before */
+ 	struct list_head *tail;
+-	/* to wait for the last page table walker to finish */
+-	struct wait_queue_head wait;
+ 	/* Bloom filters flip after each iteration */
+ 	unsigned long *filters[NR_BLOOM_FILTERS];
+ 	/* the mm stats for debugging */
+ 	unsigned long stats[NR_HIST_GENS][NR_MM_STATS];
+-	/* the number of concurrent page table walkers */
+-	int nr_walkers;
+ };
+ 
+ struct lru_gen_mm_walk {
+--- a/mm/vmscan.c
++++ b/mm/vmscan.c
+@@ -3371,18 +3371,13 @@ void lru_gen_del_mm(struct mm_struct *mm
+ 		if (!lruvec)
+ 			continue;
+ 
+-		/* where the last iteration ended (exclusive) */
++		/* where the current iteration continues after */
++		if (lruvec->mm_state.head == &mm->lru_gen.list)
++			lruvec->mm_state.head = lruvec->mm_state.head->prev;
++
++		/* where the last iteration ended before */
+ 		if (lruvec->mm_state.tail == &mm->lru_gen.list)
+ 			lruvec->mm_state.tail = lruvec->mm_state.tail->next;
+-
+-		/* where the current iteration continues (inclusive) */
+-		if (lruvec->mm_state.head != &mm->lru_gen.list)
+-			continue;
+-
+-		lruvec->mm_state.head = lruvec->mm_state.head->next;
+-		/* the deletion ends the current iteration */
+-		if (lruvec->mm_state.head == &mm_list->fifo)
+-			WRITE_ONCE(lruvec->mm_state.seq, lruvec->mm_state.seq + 1);
+ 	}
+ 
+ 	list_del_init(&mm->lru_gen.list);
+@@ -3478,68 +3473,54 @@ static bool iterate_mm_list(struct lruve
+ 			    struct mm_struct **iter)
+ {
+ 	bool first = false;
+-	bool last = true;
++	bool last = false;
+ 	struct mm_struct *mm = NULL;
+ 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
+ 	struct lru_gen_mm_list *mm_list = get_mm_list(memcg);
+ 	struct lru_gen_mm_state *mm_state = &lruvec->mm_state;
+ 
+ 	/*
+-	 * There are four interesting cases for this page table walker:
+-	 * 1. It tries to start a new iteration of mm_list with a stale max_seq;
+-	 *    there is nothing left to do.
+-	 * 2. It's the first of the current generation, and it needs to reset
+-	 *    the Bloom filter for the next generation.
+-	 * 3. It reaches the end of mm_list, and it needs to increment
+-	 *    mm_state->seq; the iteration is done.
+-	 * 4. It's the last of the current generation, and it needs to reset the
+-	 *    mm stats counters for the next generation.
++	 * mm_state->seq is incremented after each iteration of mm_list. There
++	 * are three interesting cases for this page table walker:
++	 * 1. It tries to start a new iteration with a stale max_seq: there is
++	 *    nothing left to do.
++	 * 2. It started the next iteration: it needs to reset the Bloom filter
++	 *    so that a fresh set of PTE tables can be recorded.
++	 * 3. It ended the current iteration: it needs to reset the mm stats
++	 *    counters and tell its caller to increment max_seq.
+ 	 */
+ 	spin_lock(&mm_list->lock);
+ 
+ 	VM_WARN_ON_ONCE(mm_state->seq + 1 < walk->max_seq);
+-	VM_WARN_ON_ONCE(*iter && mm_state->seq > walk->max_seq);
+-	VM_WARN_ON_ONCE(*iter && !mm_state->nr_walkers);
+ 
+-	if (walk->max_seq <= mm_state->seq) {
+-		if (!*iter)
+-			last = false;
++	if (walk->max_seq <= mm_state->seq)
+ 		goto done;
+-	}
+ 
+-	if (!mm_state->nr_walkers) {
+-		VM_WARN_ON_ONCE(mm_state->head && mm_state->head != &mm_list->fifo);
++	if (!mm_state->head)
++		mm_state->head = &mm_list->fifo;
+ 
+-		mm_state->head = mm_list->fifo.next;
++	if (mm_state->head == &mm_list->fifo)
+ 		first = true;
+-	}
+-
+-	while (!mm && mm_state->head != &mm_list->fifo) {
+-		mm = list_entry(mm_state->head, struct mm_struct, lru_gen.list);
+ 
++	do {
+ 		mm_state->head = mm_state->head->next;
++		if (mm_state->head == &mm_list->fifo) {
++			WRITE_ONCE(mm_state->seq, mm_state->seq + 1);
++			last = true;
++			break;
++		}
+ 
+ 		/* force scan for those added after the last iteration */
+-		if (!mm_state->tail || mm_state->tail == &mm->lru_gen.list) {
+-			mm_state->tail = mm_state->head;
++		if (!mm_state->tail || mm_state->tail == mm_state->head) {
++			mm_state->tail = mm_state->head->next;
+ 			walk->force_scan = true;
+ 		}
+ 
++		mm = list_entry(mm_state->head, struct mm_struct, lru_gen.list);
+ 		if (should_skip_mm(mm, walk))
+ 			mm = NULL;
+-	}
+-
+-	if (mm_state->head == &mm_list->fifo)
+-		WRITE_ONCE(mm_state->seq, mm_state->seq + 1);
++	} while (!mm);
+ done:
+-	if (*iter && !mm)
+-		mm_state->nr_walkers--;
+-	if (!*iter && mm)
+-		mm_state->nr_walkers++;
+-
+-	if (mm_state->nr_walkers)
+-		last = false;
+-
+ 	if (*iter || last)
+ 		reset_mm_stats(lruvec, walk, last);
+ 
+@@ -3567,9 +3548,9 @@ static bool iterate_mm_list_nowalk(struc
+ 
+ 	VM_WARN_ON_ONCE(mm_state->seq + 1 < max_seq);
+ 
+-	if (max_seq > mm_state->seq && !mm_state->nr_walkers) {
+-		VM_WARN_ON_ONCE(mm_state->head && mm_state->head != &mm_list->fifo);
+-
++	if (max_seq > mm_state->seq) {
++		mm_state->head = NULL;
++		mm_state->tail = NULL;
+ 		WRITE_ONCE(mm_state->seq, mm_state->seq + 1);
+ 		reset_mm_stats(lruvec, NULL, true);
+ 		success = true;
+@@ -4172,10 +4153,6 @@ restart:
+ 
+ 		walk_pmd_range(&val, addr, next, args);
+ 
+-		/* a racy check to curtail the waiting time */
+-		if (wq_has_sleeper(&walk->lruvec->mm_state.wait))
+-			return 1;
+-
+ 		if (need_resched() || walk->batched >= MAX_LRU_BATCH) {
+ 			end = (addr | ~PUD_MASK) + 1;
+ 			goto done;
+@@ -4208,8 +4185,14 @@ static void walk_mm(struct lruvec *lruve
+ 	walk->next_addr = FIRST_USER_ADDRESS;
+ 
+ 	do {
++		DEFINE_MAX_SEQ(lruvec);
++
+ 		err = -EBUSY;
+ 
++		/* another thread might have called inc_max_seq() */
++		if (walk->max_seq != max_seq)
++			break;
++
+ 		/* folio_update_gen() requires stable folio_memcg() */
+ 		if (!mem_cgroup_trylock_pages(memcg))
+ 			break;
+@@ -4444,25 +4427,12 @@ static bool try_to_inc_max_seq(struct lr
+ 		success = iterate_mm_list(lruvec, walk, &mm);
+ 		if (mm)
+ 			walk_mm(lruvec, mm, walk);
+-
+-		cond_resched();
+ 	} while (mm);
+ done:
+-	if (!success) {
+-		if (sc->priority <= DEF_PRIORITY - 2)
+-			wait_event_killable(lruvec->mm_state.wait,
+-					    max_seq < READ_ONCE(lrugen->max_seq));
+-		return false;
+-	}
++	if (success)
++		inc_max_seq(lruvec, can_swap, force_scan);
+ 
+-	VM_WARN_ON_ONCE(max_seq != READ_ONCE(lrugen->max_seq));
+-
+-	inc_max_seq(lruvec, can_swap, force_scan);
+-	/* either this sees any waiters or they will see updated max_seq */
+-	if (wq_has_sleeper(&lruvec->mm_state.wait))
+-		wake_up_all(&lruvec->mm_state.wait);
+-
+-	return true;
++	return success;
+ }
+ 
+ /******************************************************************************
+@@ -6117,7 +6087,6 @@ void lru_gen_init_lruvec(struct lruvec *
+ 		INIT_LIST_HEAD(&lrugen->folios[gen][type][zone]);
+ 
+ 	lruvec->mm_state.seq = MIN_NR_GENS;
+-	init_waitqueue_head(&lruvec->mm_state.wait);
+ }
+ 
+ #ifdef CONFIG_MEMCG
+@@ -6150,7 +6119,6 @@ void lru_gen_exit_memcg(struct mem_cgrou
+ 	for_each_node(nid) {
+ 		struct lruvec *lruvec = get_lruvec(memcg, nid);
+ 
+-		VM_WARN_ON_ONCE(lruvec->mm_state.nr_walkers);
+ 		VM_WARN_ON_ONCE(memchr_inv(lruvec->lrugen.nr_pages, 0,
+ 					   sizeof(lruvec->lrugen.nr_pages)));
+ 

+ 58 - 0
target/linux/generic/backport-6.6/200-v6.3-bitfield-add-FIELD_PREP_CONST.patch

@@ -0,0 +1,58 @@
+From e2192de59e457aef8d1f055a452131f0b3e5d097 Mon Sep 17 00:00:00 2001
+From: Johannes Berg <[email protected]>
+Date: Wed, 18 Jan 2023 14:26:53 +0100
+Subject: [PATCH] bitfield: add FIELD_PREP_CONST()
+
+Neither FIELD_PREP() nor *_encode_bits() can be used
+in constant contexts (such as initializers), but we
+don't want to define shift constants for all masks
+just for use in initializers, and having checks that
+the values fit is also useful.
+
+Therefore, add FIELD_PREP_CONST() which is a smaller
+version of FIELD_PREP() that can only take constant
+arguments and has less friendly (but not less strict)
+error checks, and expands to a constant value.
+
+Signed-off-by: Johannes Berg <[email protected]>
+Link: https://lore.kernel.org/r/20230118142652.53f20593504b.Iaeea0aee77a6493d70e573b4aa55c91c00e01e4b@changeid
+Signed-off-by: Johannes Berg <[email protected]>
+---
+ include/linux/bitfield.h | 26 ++++++++++++++++++++++++++
+ 1 file changed, 26 insertions(+)
+
+--- a/include/linux/bitfield.h
++++ b/include/linux/bitfield.h
+@@ -115,6 +115,32 @@
+ 		((typeof(_mask))(_val) << __bf_shf(_mask)) & (_mask);	\
+ 	})
+ 
++#define __BF_CHECK_POW2(n)	BUILD_BUG_ON_ZERO(((n) & ((n) - 1)) != 0)
++
++/**
++ * FIELD_PREP_CONST() - prepare a constant bitfield element
++ * @_mask: shifted mask defining the field's length and position
++ * @_val:  value to put in the field
++ *
++ * FIELD_PREP_CONST() masks and shifts up the value.  The result should
++ * be combined with other fields of the bitfield using logical OR.
++ *
++ * Unlike FIELD_PREP() this is a constant expression and can therefore
++ * be used in initializers. Error checking is less comfortable for this
++ * version, and non-constant masks cannot be used.
++ */
++#define FIELD_PREP_CONST(_mask, _val)					\
++	(								\
++		/* mask must be non-zero */				\
++		BUILD_BUG_ON_ZERO((_mask) == 0) +			\
++		/* check if value fits */				\
++		BUILD_BUG_ON_ZERO(~((_mask) >> __bf_shf(_mask)) & (_val)) + \
++		/* check if mask is contiguous */			\
++		__BF_CHECK_POW2((_mask) + (1ULL << __bf_shf(_mask))) +	\
++		/* and create the value */				\
++		(((typeof(_mask))(_val) << __bf_shf(_mask)) & (_mask))	\
++	)
++
+ /**
+  * FIELD_GET() - extract a bitfield element
+  * @_mask: shifted mask defining the field's length and position

+ 63 - 0
target/linux/generic/backport-6.6/300-v6.2-powerpc-suppress-some-linker-warnings-in-recent-link.patch

@@ -0,0 +1,63 @@
+From 579aee9fc594af94c242068c011b0233563d4bbf Mon Sep 17 00:00:00 2001
+From: Stephen Rothwell <[email protected]>
+Date: Mon, 10 Oct 2022 16:57:21 +1100
+Subject: [PATCH] powerpc: suppress some linker warnings in recent linker
+ versions
+
+This is a follow on from commit
+
+  0d362be5b142 ("Makefile: link with -z noexecstack --no-warn-rwx-segments")
+
+for arch/powerpc/boot to address wanrings like:
+
+  ld: warning: opal-calls.o: missing .note.GNU-stack section implies executable stack
+  ld: NOTE: This behaviour is deprecated and will be removed in a future version of the linker
+  ld: warning: arch/powerpc/boot/zImage.epapr has a LOAD segment with RWX permissions
+
+This fixes issue https://github.com/linuxppc/issues/issues/417
+
+Signed-off-by: Stephen Rothwell <[email protected]>
+Signed-off-by: Michael Ellerman <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+---
+ arch/powerpc/boot/wrapper | 15 ++++++++++++++-
+ 1 file changed, 14 insertions(+), 1 deletion(-)
+
+--- a/arch/powerpc/boot/wrapper
++++ b/arch/powerpc/boot/wrapper
+@@ -215,6 +215,11 @@ ld_version()
+     }'
+ }
+ 
++ld_is_lld()
++{
++	${CROSS}ld -V 2>&1 | grep -q LLD
++}
++
+ # Do not include PT_INTERP segment when linking pie. Non-pie linking
+ # just ignores this option.
+ LD_VERSION=$(${CROSS}ld --version | ld_version)
+@@ -223,6 +228,14 @@ if [ "$LD_VERSION" -ge "$LD_NO_DL_MIN_VE
+ 	nodl="--no-dynamic-linker"
+ fi
+ 
++# suppress some warnings in recent ld versions
++nowarn="-z noexecstack"
++if ! ld_is_lld; then
++	if [ "$LD_VERSION" -ge "$(echo 2.39 | ld_version)" ]; then
++		nowarn="$nowarn --no-warn-rwx-segments"
++	fi
++fi
++
+ platformo=$object/"$platform".o
+ lds=$object/zImage.lds
+ ext=strip
+@@ -504,7 +517,7 @@ if [ "$platform" != "miboot" ]; then
+         text_start="-Ttext $link_address"
+     fi
+ #link everything
+-    ${CROSS}ld -m $format -T $lds $text_start $pie $nodl $rodynamic $notext -o "$ofile" $map \
++    ${CROSS}ld -m $format -T $lds $text_start $pie $nodl $nowarn $rodynamic $notext -o "$ofile" $map \
+ 	$platformo $tmp $object/wrapper.a
+     rm $tmp
+ fi

+ 65 - 0
target/linux/generic/backport-6.6/406-v6.2-0001-mtd-core-simplify-a-bit-code-find-partition-matching.patch

@@ -0,0 +1,65 @@
+From 63db0cb35e1cb3b3c134906d1062f65513fdda2d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <[email protected]>
+Date: Tue, 4 Oct 2022 10:37:09 +0200
+Subject: [PATCH] mtd: core: simplify (a bit) code find partition-matching
+ dynamic OF node
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+1. Don't hardcode "partition-" string twice
+2. Use simpler logic & use ->name to avoid of_property_read_string()
+3. Use mtd_get_of_node() helper
+
+Cc: Christian Marangi <[email protected]>
+Signed-off-by: Rafał Miłecki <[email protected]>
+Signed-off-by: Miquel Raynal <[email protected]>
+Link: https://lore.kernel.org/linux-mtd/[email protected]
+---
+ drivers/mtd/mtdcore.c | 16 +++++++---------
+ 1 file changed, 7 insertions(+), 9 deletions(-)
+
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -551,18 +551,16 @@ static void mtd_check_of_node(struct mtd
+ 	struct device_node *partitions, *parent_dn, *mtd_dn = NULL;
+ 	const char *pname, *prefix = "partition-";
+ 	int plen, mtd_name_len, offset, prefix_len;
+-	struct mtd_info *parent;
+ 	bool found = false;
+ 
+ 	/* Check if MTD already has a device node */
+-	if (dev_of_node(&mtd->dev))
++	if (mtd_get_of_node(mtd))
+ 		return;
+ 
+ 	/* Check if a partitions node exist */
+ 	if (!mtd_is_partition(mtd))
+ 		return;
+-	parent = mtd->parent;
+-	parent_dn = of_node_get(dev_of_node(&parent->dev));
++	parent_dn = of_node_get(mtd_get_of_node(mtd->parent));
+ 	if (!parent_dn)
+ 		return;
+ 
+@@ -575,15 +573,15 @@ static void mtd_check_of_node(struct mtd
+ 
+ 	/* Search if a partition is defined with the same name */
+ 	for_each_child_of_node(partitions, mtd_dn) {
+-		offset = 0;
+-
+ 		/* Skip partition with no/wrong prefix */
+-		if (!of_node_name_prefix(mtd_dn, "partition-"))
++		if (!of_node_name_prefix(mtd_dn, prefix))
+ 			continue;
+ 
+ 		/* Label have priority. Check that first */
+-		if (of_property_read_string(mtd_dn, "label", &pname)) {
+-			of_property_read_string(mtd_dn, "name", &pname);
++		if (!of_property_read_string(mtd_dn, "label", &pname)) {
++			offset = 0;
++		} else {
++			pname = mtd_dn->name;
+ 			offset = prefix_len;
+ 		}
+ 

+ 84 - 0
target/linux/generic/backport-6.6/406-v6.2-0002-mtd-core-try-to-find-OF-node-for-every-MTD-partition.patch

@@ -0,0 +1,84 @@
+From ddb8cefb7af288950447ca6eeeafb09977dab56f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <[email protected]>
+Date: Tue, 4 Oct 2022 10:37:10 +0200
+Subject: [PATCH] mtd: core: try to find OF node for every MTD partition
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+So far this feature was limited to the top-level "nvmem-cells" node.
+There are multiple parsers creating partitions and subpartitions
+dynamically. Extend that code to handle them too.
+
+This allows finding partition-* node for every MTD (sub)partition.
+
+Random example:
+
+partitions {
+	compatible = "brcm,bcm947xx-cfe-partitions";
+
+	partition-firmware {
+		compatible = "brcm,trx";
+
+		partition-loader {
+		};
+	};
+};
+
+Cc: Christian Marangi <[email protected]>
+Signed-off-by: Rafał Miłecki <[email protected]>
+Signed-off-by: Miquel Raynal <[email protected]>
+Link: https://lore.kernel.org/linux-mtd/[email protected]
+---
+ drivers/mtd/mtdcore.c | 18 ++++++------------
+ 1 file changed, 6 insertions(+), 12 deletions(-)
+
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -551,20 +551,22 @@ static void mtd_check_of_node(struct mtd
+ 	struct device_node *partitions, *parent_dn, *mtd_dn = NULL;
+ 	const char *pname, *prefix = "partition-";
+ 	int plen, mtd_name_len, offset, prefix_len;
+-	bool found = false;
+ 
+ 	/* Check if MTD already has a device node */
+ 	if (mtd_get_of_node(mtd))
+ 		return;
+ 
+-	/* Check if a partitions node exist */
+ 	if (!mtd_is_partition(mtd))
+ 		return;
++
+ 	parent_dn = of_node_get(mtd_get_of_node(mtd->parent));
+ 	if (!parent_dn)
+ 		return;
+ 
+-	partitions = of_get_child_by_name(parent_dn, "partitions");
++	if (mtd_is_partition(mtd->parent))
++		partitions = of_node_get(parent_dn);
++	else
++		partitions = of_get_child_by_name(parent_dn, "partitions");
+ 	if (!partitions)
+ 		goto exit_parent;
+ 
+@@ -588,19 +590,11 @@ static void mtd_check_of_node(struct mtd
+ 		plen = strlen(pname) - offset;
+ 		if (plen == mtd_name_len &&
+ 		    !strncmp(mtd->name, pname + offset, plen)) {
+-			found = true;
++			mtd_set_of_node(mtd, mtd_dn);
+ 			break;
+ 		}
+ 	}
+ 
+-	if (!found)
+-		goto exit_partitions;
+-
+-	/* Set of_node only for nvmem */
+-	if (of_device_is_compatible(mtd_dn, "nvmem-cells"))
+-		mtd_set_of_node(mtd, mtd_dn);
+-
+-exit_partitions:
+ 	of_node_put(partitions);
+ exit_parent:
+ 	of_node_put(parent_dn);

+ 47 - 0
target/linux/generic/backport-6.6/408-v6.2-mtd-core-set-ROOT_DEV-for-partitions-marked-as-rootf.patch

@@ -0,0 +1,47 @@
+From 26422ac78e9d8767bd4aabfbae616b15edbf6a1b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <[email protected]>
+Date: Sat, 22 Oct 2022 23:13:18 +0200
+Subject: [PATCH] mtd: core: set ROOT_DEV for partitions marked as rootfs in DT
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This adds support for "linux,rootfs" binding that is used to mark flash
+partition containing rootfs. It's useful for devices using device tree
+that don't have bootloader passing root info in cmdline.
+
+Signed-off-by: Rafał Miłecki <[email protected]>
+Signed-off-by: Miquel Raynal <[email protected]>
+Link: https://lore.kernel.org/linux-mtd/[email protected]
+---
+ drivers/mtd/mtdcore.c | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -28,6 +28,7 @@
+ #include <linux/leds.h>
+ #include <linux/debugfs.h>
+ #include <linux/nvmem-provider.h>
++#include <linux/root_dev.h>
+ 
+ #include <linux/mtd/mtd.h>
+ #include <linux/mtd/partitions.h>
+@@ -737,6 +738,17 @@ int add_mtd_device(struct mtd_info *mtd)
+ 		not->add(mtd);
+ 
+ 	mutex_unlock(&mtd_table_mutex);
++
++	if (of_find_property(mtd_get_of_node(mtd), "linux,rootfs", NULL)) {
++		if (IS_BUILTIN(CONFIG_MTD)) {
++			pr_info("mtd: setting mtd%d (%s) as root device\n", mtd->index, mtd->name);
++			ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, mtd->index);
++		} else {
++			pr_warn("mtd: can't set mtd%d (%s) as root device - mtd must be builtin\n",
++				mtd->index, mtd->name);
++		}
++	}
++
+ 	/* We _know_ we aren't being removed, because
+ 	   our caller is still holding us here. So none
+ 	   of this try_ nonsense, and no bitching about it

+ 229 - 0
target/linux/generic/backport-6.6/421-v6.2-mtd-parsers-add-TP-Link-SafeLoader-partitions-table-.patch

@@ -0,0 +1,229 @@
+From aec4d5f5ffd0f0092bd9dc21ea90e0bc237d4b74 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <[email protected]>
+Date: Sat, 15 Oct 2022 11:29:50 +0200
+Subject: [PATCH] mtd: parsers: add TP-Link SafeLoader partitions table parser
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This parser deals with most TP-Link home routers. It reads info about
+partitions and registers them in the MTD subsystem.
+
+Example from TP-Link Archer C5 V2:
+
+spi-nor spi0.0: s25fl128s1 (16384 Kbytes)
+15 tplink-safeloader partitions found on MTD device spi0.0
+Creating 15 MTD partitions on "spi0.0":
+0x000000000000-0x000000040000 : "fs-uboot"
+0x000000040000-0x000000440000 : "os-image"
+0x000000440000-0x000000e40000 : "rootfs"
+0x000000e40000-0x000000e40200 : "default-mac"
+0x000000e40200-0x000000e40400 : "pin"
+0x000000e40400-0x000000e40600 : "product-info"
+0x000000e50000-0x000000e60000 : "partition-table"
+0x000000e60000-0x000000e60200 : "soft-version"
+0x000000e61000-0x000000e70000 : "support-list"
+0x000000e70000-0x000000e80000 : "profile"
+0x000000e80000-0x000000e90000 : "default-config"
+0x000000e90000-0x000000ee0000 : "user-config"
+0x000000ee0000-0x000000fe0000 : "log"
+0x000000fe0000-0x000000ff0000 : "radio_bk"
+0x000000ff0000-0x000001000000 : "radio"
+
+Signed-off-by: Rafał Miłecki <[email protected]>
+Signed-off-by: Miquel Raynal <[email protected]>
+Link: https://lore.kernel.org/linux-mtd/[email protected]
+---
+ drivers/mtd/parsers/Kconfig             |  15 +++
+ drivers/mtd/parsers/Makefile            |   1 +
+ drivers/mtd/parsers/tplink_safeloader.c | 150 ++++++++++++++++++++++++
+ 3 files changed, 166 insertions(+)
+ create mode 100644 drivers/mtd/parsers/tplink_safeloader.c
+
+--- a/drivers/mtd/parsers/Kconfig
++++ b/drivers/mtd/parsers/Kconfig
+@@ -123,6 +123,21 @@ config MTD_AFS_PARTS
+ 	  for your particular device. It won't happen automatically. The
+ 	  'physmap' map driver (CONFIG_MTD_PHYSMAP) does this, for example.
+ 
++config MTD_PARSER_TPLINK_SAFELOADER
++	tristate "TP-Link Safeloader partitions parser"
++	depends on MTD && (ARCH_BCM_5301X || ATH79 || SOC_MT7620 || SOC_MT7621 || COMPILE_TEST)
++	help
++	  TP-Link home routers use flash partitions to store various data. Info
++	  about flash space layout is stored in a partitions table using a
++	  custom ASCII-based format.
++
++	  That format was first found in devices with SafeLoader bootloader and
++	  was named after it. Later it was adapted to CFE and U-Boot
++	  bootloaders.
++
++	  This driver reads partitions table, parses it and creates MTD
++	  partitions.
++
+ config MTD_PARSER_TRX
+ 	tristate "Parser for TRX format partitions"
+ 	depends on MTD && (BCM47XX || ARCH_BCM_5301X || ARCH_MEDIATEK || RALINK || COMPILE_TEST)
+--- a/drivers/mtd/parsers/Makefile
++++ b/drivers/mtd/parsers/Makefile
+@@ -10,6 +10,7 @@ ofpart-$(CONFIG_MTD_OF_PARTS_BCM4908)	+=
+ ofpart-$(CONFIG_MTD_OF_PARTS_LINKSYS_NS)+= ofpart_linksys_ns.o
+ obj-$(CONFIG_MTD_PARSER_IMAGETAG)	+= parser_imagetag.o
+ obj-$(CONFIG_MTD_AFS_PARTS)		+= afs.o
++obj-$(CONFIG_MTD_PARSER_TPLINK_SAFELOADER)	+= tplink_safeloader.o
+ obj-$(CONFIG_MTD_PARSER_TRX)		+= parser_trx.o
+ obj-$(CONFIG_MTD_SERCOMM_PARTS)		+= scpart.o
+ obj-$(CONFIG_MTD_SHARPSL_PARTS)		+= sharpslpart.o
+--- /dev/null
++++ b/drivers/mtd/parsers/tplink_safeloader.c
+@@ -0,0 +1,150 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright © 2022 Rafał Miłecki <[email protected]>
++ */
++
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/mtd/mtd.h>
++#include <linux/mtd/partitions.h>
++#include <linux/of.h>
++#include <linux/slab.h>
++
++#define TPLINK_SAFELOADER_DATA_OFFSET		4
++#define TPLINK_SAFELOADER_MAX_PARTS		32
++
++struct safeloader_cmn_header {
++	__be32 size;
++	uint32_t unused;
++} __packed;
++
++static void *mtd_parser_tplink_safeloader_read_table(struct mtd_info *mtd)
++{
++	struct safeloader_cmn_header hdr;
++	struct device_node *np;
++	size_t bytes_read;
++	size_t offset;
++	size_t size;
++	char *buf;
++	int err;
++
++	np = mtd_get_of_node(mtd);
++	if (mtd_is_partition(mtd))
++		of_node_get(np);
++	else
++		np = of_get_child_by_name(np, "partitions");
++
++	if (of_property_read_u32(np, "partitions-table-offset", (u32 *)&offset)) {
++		pr_err("Failed to get partitions table offset\n");
++		goto err_put;
++	}
++
++	err = mtd_read(mtd, offset, sizeof(hdr), &bytes_read, (uint8_t *)&hdr);
++	if (err && !mtd_is_bitflip(err)) {
++		pr_err("Failed to read from %s at 0x%zx\n", mtd->name, offset);
++		goto err_put;
++	}
++
++	size = be32_to_cpu(hdr.size);
++
++	buf = kmalloc(size + 1, GFP_KERNEL);
++	if (!buf)
++		goto err_put;
++
++	err = mtd_read(mtd, offset + sizeof(hdr), size, &bytes_read, buf);
++	if (err && !mtd_is_bitflip(err)) {
++		pr_err("Failed to read from %s at 0x%zx\n", mtd->name, offset + sizeof(hdr));
++		goto err_kfree;
++	}
++
++	buf[size] = '\0';
++
++	of_node_put(np);
++
++	return buf;
++
++err_kfree:
++	kfree(buf);
++err_put:
++	of_node_put(np);
++	return NULL;
++}
++
++static int mtd_parser_tplink_safeloader_parse(struct mtd_info *mtd,
++					      const struct mtd_partition **pparts,
++					      struct mtd_part_parser_data *data)
++{
++	struct mtd_partition *parts;
++	char name[65];
++	size_t offset;
++	size_t bytes;
++	char *buf;
++	int idx;
++	int err;
++
++	parts = kcalloc(TPLINK_SAFELOADER_MAX_PARTS, sizeof(*parts), GFP_KERNEL);
++	if (!parts) {
++		err = -ENOMEM;
++		goto err_out;
++	}
++
++	buf = mtd_parser_tplink_safeloader_read_table(mtd);
++	if (!buf) {
++		err = -ENOENT;
++		goto err_out;
++	}
++
++	for (idx = 0, offset = TPLINK_SAFELOADER_DATA_OFFSET;
++	     idx < TPLINK_SAFELOADER_MAX_PARTS &&
++	     sscanf(buf + offset, "partition %64s base 0x%llx size 0x%llx%zn\n",
++		    name, &parts[idx].offset, &parts[idx].size, &bytes) == 3;
++	     idx++, offset += bytes + 1) {
++		parts[idx].name = kstrdup(name, GFP_KERNEL);
++		if (!parts[idx].name) {
++			err = -ENOMEM;
++			goto err_free;
++		}
++	}
++
++	if (idx == TPLINK_SAFELOADER_MAX_PARTS)
++		pr_warn("Reached maximum number of partitions!\n");
++
++	kfree(buf);
++
++	*pparts = parts;
++
++	return idx;
++
++err_free:
++	for (idx -= 1; idx >= 0; idx--)
++		kfree(parts[idx].name);
++err_out:
++	return err;
++};
++
++static void mtd_parser_tplink_safeloader_cleanup(const struct mtd_partition *pparts,
++						 int nr_parts)
++{
++	int i;
++
++	for (i = 0; i < nr_parts; i++)
++		kfree(pparts[i].name);
++
++	kfree(pparts);
++}
++
++static const struct of_device_id mtd_parser_tplink_safeloader_of_match_table[] = {
++	{ .compatible = "tplink,safeloader-partitions" },
++	{},
++};
++MODULE_DEVICE_TABLE(of, mtd_parser_tplink_safeloader_of_match_table);
++
++static struct mtd_part_parser mtd_parser_tplink_safeloader = {
++	.parse_fn = mtd_parser_tplink_safeloader_parse,
++	.cleanup = mtd_parser_tplink_safeloader_cleanup,
++	.name = "tplink-safeloader",
++	.of_match_table = mtd_parser_tplink_safeloader_of_match_table,
++};
++module_mtd_part_parser(mtd_parser_tplink_safeloader);
++
++MODULE_LICENSE("GPL");

+ 35 - 0
target/linux/generic/backport-6.6/423-v6.3-mtd-spinand-macronix-use-scratch-buffer-for-DMA-oper.patch

@@ -0,0 +1,35 @@
+From ebed787a0becb9354f0a23620a5130cccd6c730c Mon Sep 17 00:00:00 2001
+From: Daniel Golle <[email protected]>
+Date: Thu, 19 Jan 2023 03:45:43 +0000
+Subject: [PATCH] mtd: spinand: macronix: use scratch buffer for DMA operation
+
+The mx35lf1ge4ab_get_eccsr() function uses an SPI DMA operation to
+read the eccsr, hence the buffer should not be on stack. Since commit
+380583227c0c7f ("spi: spi-mem: Add extra sanity checks on the op param")
+the kernel emmits a warning and blocks such operations.
+
+Use the scratch buffer to get eccsr instead of trying to directly read
+into a stack-allocated variable.
+
+Signed-off-by: Daniel Golle <[email protected]>
+Reviewed-by: Dhruva Gole <[email protected]>
+Signed-off-by: Miquel Raynal <[email protected]>
+Link: https://lore.kernel.org/linux-mtd/[email protected]
+---
+ drivers/mtd/nand/spi/macronix.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/mtd/nand/spi/macronix.c
++++ b/drivers/mtd/nand/spi/macronix.c
+@@ -83,9 +83,10 @@ static int mx35lf1ge4ab_ecc_get_status(s
+ 		 * in order to avoid forcing the wear-leveling layer to move
+ 		 * data around if it's not necessary.
+ 		 */
+-		if (mx35lf1ge4ab_get_eccsr(spinand, &eccsr))
++		if (mx35lf1ge4ab_get_eccsr(spinand, spinand->scratchbuf))
+ 			return nanddev_get_ecc_conf(nand)->strength;
+ 
++		eccsr = *spinand->scratchbuf;
+ 		if (WARN_ON(eccsr > nanddev_get_ecc_conf(nand)->strength ||
+ 			    !eccsr))
+ 			return nanddev_get_ecc_conf(nand)->strength;

+ 33 - 0
target/linux/generic/backport-6.6/424-v6.3-mtd-ubi-wire-up-parent-MTD-device.patch

@@ -0,0 +1,33 @@
+From 1ecf9e390452e73a362ea7fbde8f3f0db83de856 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <[email protected]>
+Date: Thu, 22 Dec 2022 19:33:04 +0000
+Subject: [PATCH] mtd: ubi: wire-up parent MTD device
+
+Wire up the device parent pointer of UBI devices to their lower MTD
+device, typically an MTD partition or whole-chip device.
+
+The most noticeable change is that in sysfs, previously ubi devices
+would be could in /sys/devices/virtual/ubi while after this change they
+would be correctly attached to their parent MTD device, e.g.
+
+/sys/devices/platform/1100d000.spi/spi_master/spi1/spi1.0/mtd/mtd2/ubi0.
+
+Locating UBI devices using /sys/class/ubi/ of course still works as
+well.
+
+Signed-off-by: Daniel Golle <[email protected]>
+Signed-off-by: Richard Weinberger <[email protected]>
+---
+ drivers/mtd/ubi/build.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/mtd/ubi/build.c
++++ b/drivers/mtd/ubi/build.c
+@@ -929,6 +929,7 @@ int ubi_attach_mtd_dev(struct mtd_info *
+ 	ubi->dev.release = dev_release;
+ 	ubi->dev.class = &ubi_class;
+ 	ubi->dev.groups = ubi_dev_groups;
++	ubi->dev.parent = &mtd->dev;
+ 
+ 	ubi->mtd = mtd;
+ 	ubi->ubi_num = ubi_num;

+ 49 - 0
target/linux/generic/backport-6.6/425-v6.3-mtd-ubi-block-wire-up-device-parent.patch

@@ -0,0 +1,49 @@
+From 05b8773ca33253ea562be145cf3145b05ef19f86 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <[email protected]>
+Date: Thu, 22 Dec 2022 19:33:31 +0000
+Subject: [PATCH] mtd: ubi: block: wire-up device parent
+
+ubiblock devices were previously only identifyable by their name, but
+not connected to their parent UBI volume device e.g. in sysfs.
+Properly parent ubiblock device as descendant of a UBI volume device
+to reflect device model hierachy.
+
+Signed-off-by: Daniel Golle <[email protected]>
+Signed-off-by: Richard Weinberger <[email protected]>
+---
+ drivers/mtd/ubi/block.c | 2 +-
+ drivers/mtd/ubi/kapi.c  | 1 +
+ include/linux/mtd/ubi.h | 1 +
+ 3 files changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/mtd/ubi/block.c
++++ b/drivers/mtd/ubi/block.c
+@@ -452,7 +452,7 @@ int ubiblock_create(struct ubi_volume_in
+ 	list_add_tail(&dev->list, &ubiblock_devices);
+ 
+ 	/* Must be the last step: anyone can call file ops from now on */
+-	ret = add_disk(dev->gd);
++	ret = device_add_disk(vi->dev, dev->gd, NULL);
+ 	if (ret)
+ 		goto out_destroy_wq;
+ 
+--- a/drivers/mtd/ubi/kapi.c
++++ b/drivers/mtd/ubi/kapi.c
+@@ -79,6 +79,7 @@ void ubi_do_get_volume_info(struct ubi_d
+ 	vi->name_len = vol->name_len;
+ 	vi->name = vol->name;
+ 	vi->cdev = vol->cdev.dev;
++	vi->dev = &vol->dev;
+ }
+ 
+ /**
+--- a/include/linux/mtd/ubi.h
++++ b/include/linux/mtd/ubi.h
+@@ -110,6 +110,7 @@ struct ubi_volume_info {
+ 	int name_len;
+ 	const char *name;
+ 	dev_t cdev;
++	struct device *dev;
+ };
+ 
+ /**

+ 47 - 0
target/linux/generic/backport-6.6/426-v6.4-0004-mtd-core-prepare-mtd_otp_nvmem_add-to-handle-EPROBE_.patch

@@ -0,0 +1,47 @@
+From 281f7a6c1a33fffcde32001bacbb4f672140fbf9 Mon Sep 17 00:00:00 2001
+From: Michael Walle <[email protected]>
+Date: Wed, 8 Mar 2023 09:20:21 +0100
+Subject: [PATCH] mtd: core: prepare mtd_otp_nvmem_add() to handle
+ -EPROBE_DEFER
+
+NVMEM soon will get the ability for nvmem layouts and these might
+not be ready when nvmem_register() is called and thus it might
+return -EPROBE_DEFER. Don't print the error message in this case.
+
+Signed-off-by: Michael Walle <[email protected]>
+Signed-off-by: Miquel Raynal <[email protected]>
+Link: https://lore.kernel.org/linux-mtd/[email protected]
+---
+ drivers/mtd/mtdcore.c | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -953,8 +953,8 @@ static int mtd_otp_nvmem_add(struct mtd_
+ 			nvmem = mtd_otp_nvmem_register(mtd, "user-otp", size,
+ 						       mtd_nvmem_user_otp_reg_read);
+ 			if (IS_ERR(nvmem)) {
+-				dev_err(dev, "Failed to register OTP NVMEM device\n");
+-				return PTR_ERR(nvmem);
++				err = PTR_ERR(nvmem);
++				goto err;
+ 			}
+ 			mtd->otp_user_nvmem = nvmem;
+ 		}
+@@ -971,7 +971,6 @@ static int mtd_otp_nvmem_add(struct mtd_
+ 			nvmem = mtd_otp_nvmem_register(mtd, "factory-otp", size,
+ 						       mtd_nvmem_fact_otp_reg_read);
+ 			if (IS_ERR(nvmem)) {
+-				dev_err(dev, "Failed to register OTP NVMEM device\n");
+ 				err = PTR_ERR(nvmem);
+ 				goto err;
+ 			}
+@@ -983,7 +982,7 @@ static int mtd_otp_nvmem_add(struct mtd_
+ 
+ err:
+ 	nvmem_unregister(mtd->otp_user_nvmem);
+-	return err;
++	return dev_err_probe(dev, err, "Failed to register OTP NVMEM device\n");
+ }
+ 
+ /**

+ 41 - 0
target/linux/generic/backport-6.6/611-v6.3-net-add-helper-eth_addr_add.patch

@@ -0,0 +1,41 @@
+From 7390609b0121a1b982c5ecdfcd72dc328e5784ee Mon Sep 17 00:00:00 2001
+From: Michael Walle <[email protected]>
+Date: Mon, 6 Feb 2023 13:43:42 +0000
+Subject: [PATCH] net: add helper eth_addr_add()
+
+Add a helper to add an offset to a ethernet address. This comes in handy
+if you have a base ethernet address for multiple interfaces.
+
+Signed-off-by: Michael Walle <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Acked-by: Jakub Kicinski <[email protected]>
+Signed-off-by: Srinivas Kandagatla <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Greg Kroah-Hartman <[email protected]>
+---
+ include/linux/etherdevice.h | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/include/linux/etherdevice.h
++++ b/include/linux/etherdevice.h
+@@ -508,6 +508,20 @@ static inline void eth_addr_inc(u8 *addr
+ }
+ 
+ /**
++ * eth_addr_add() - Add (or subtract) an offset to/from the given MAC address.
++ *
++ * @offset: Offset to add.
++ * @addr: Pointer to a six-byte array containing Ethernet address to increment.
++ */
++static inline void eth_addr_add(u8 *addr, long offset)
++{
++	u64 u = ether_addr_to_u64(addr);
++
++	u += offset;
++	u64_to_ether_addr(u, addr);
++}
++
++/**
+  * is_etherdev_addr - Tell if given Ethernet address belongs to the device.
+  * @dev: Pointer to a device structure
+  * @addr: Pointer to a six-byte array containing the Ethernet address

+ 32 - 0
target/linux/generic/backport-6.6/701-net-next-net-sfp-add-quirk-for-Fiberstone-GPON-ONU-34-20BI.patch

@@ -0,0 +1,32 @@
+From d387e34fec407f881fdf165b5d7ec128ebff362f Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Tue, 19 Sep 2023 14:47:20 +0200
+Subject: [PATCH] net: sfp: add quirk for Fiberstone GPON-ONU-34-20BI
+
+Fiberstone GPON-ONU-34-20B can operate at 2500base-X, but report 1.2GBd
+NRZ in their EEPROM.
+
+The module also require the ignore tx fault fixup similar to Huawei MA5671A
+as it gets disabled on error messages with serial redirection enabled.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/phy/sfp.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -393,6 +393,11 @@ static const struct sfp_quirk sfp_quirks
+ 	SFP_QUIRK("ALCATELLUCENT", "3FE46541AA", sfp_quirk_2500basex,
+ 		  sfp_fixup_long_startup),
+ 
++	// Fiberstore GPON-ONU-34-20BI can operate at 2500base-X, but report 1.2GBd
++	// NRZ in their EEPROM
++	SFP_QUIRK("FS", "GPON-ONU-34-20BI", sfp_quirk_2500basex,
++		  sfp_fixup_ignore_tx_fault),
++
+ 	SFP_QUIRK_F("HALNy", "HL-GSFP", sfp_fixup_halny_gsfp),
+ 
+ 	// HG MXPD-483II-F 2.5G supports 2500Base-X, but incorrectly reports

+ 2306 - 0
target/linux/generic/backport-6.6/702-01-v6.7-net-phy-aquantia-move-to-separate-directory.patch

@@ -0,0 +1,2306 @@
+From d2213db3f49bce8e7a87c8de05b9a091f78f654e Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Tue, 14 Nov 2023 15:08:41 +0100
+Subject: [PATCH 1/3] net: phy: aquantia: move to separate directory
+
+Move aquantia PHY driver to separate driectory in preparation for
+firmware loading support to keep things tidy.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/Kconfig                         | 5 +----
+ drivers/net/phy/Makefile                        | 6 +-----
+ drivers/net/phy/aquantia/Kconfig                | 5 +++++
+ drivers/net/phy/aquantia/Makefile               | 6 ++++++
+ drivers/net/phy/{ => aquantia}/aquantia.h       | 0
+ drivers/net/phy/{ => aquantia}/aquantia_hwmon.c | 0
+ drivers/net/phy/{ => aquantia}/aquantia_main.c  | 0
+ 7 files changed, 13 insertions(+), 9 deletions(-)
+ create mode 100644 drivers/net/phy/aquantia/Kconfig
+ create mode 100644 drivers/net/phy/aquantia/Makefile
+ rename drivers/net/phy/{ => aquantia}/aquantia.h (100%)
+ rename drivers/net/phy/{ => aquantia}/aquantia_hwmon.c (100%)
+ rename drivers/net/phy/{ => aquantia}/aquantia_main.c (100%)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -90,10 +90,7 @@ config ADIN1100_PHY
+ 	  Currently supports the:
+ 	  - ADIN1100 - Robust,Industrial, Low Power 10BASE-T1L Ethernet PHY
+ 
+-config AQUANTIA_PHY
+-	tristate "Aquantia PHYs"
+-	help
+-	  Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405
++source "drivers/net/phy/aquantia/Kconfig"
+ 
+ config AX88796B_PHY
+ 	tristate "Asix PHYs"
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -33,11 +33,7 @@ obj-y				+= $(sfp-obj-y) $(sfp-obj-m)
+ obj-$(CONFIG_ADIN_PHY)		+= adin.o
+ obj-$(CONFIG_ADIN1100_PHY)	+= adin1100.o
+ obj-$(CONFIG_AMD_PHY)		+= amd.o
+-aquantia-objs			+= aquantia_main.o
+-ifdef CONFIG_HWMON
+-aquantia-objs			+= aquantia_hwmon.o
+-endif
+-obj-$(CONFIG_AQUANTIA_PHY)	+= aquantia.o
++obj-$(CONFIG_AQUANTIA_PHY)	+= aquantia/
+ obj-$(CONFIG_AT803X_PHY)	+= at803x.o
+ obj-$(CONFIG_AX88796B_PHY)	+= ax88796b.o
+ obj-$(CONFIG_BCM54140_PHY)	+= bcm54140.o
+--- /dev/null
++++ b/drivers/net/phy/aquantia/Kconfig
+@@ -0,0 +1,5 @@
++# SPDX-License-Identifier: GPL-2.0-only
++config AQUANTIA_PHY
++	tristate "Aquantia PHYs"
++	help
++	  Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405
+--- /dev/null
++++ b/drivers/net/phy/aquantia/Makefile
+@@ -0,0 +1,6 @@
++# SPDX-License-Identifier: GPL-2.0
++aquantia-objs			+= aquantia_main.o
++ifdef CONFIG_HWMON
++aquantia-objs			+= aquantia_hwmon.o
++endif
++obj-$(CONFIG_AQUANTIA_PHY)	+= aquantia.o
+--- a/drivers/net/phy/aquantia.h
++++ /dev/null
+@@ -1,16 +0,0 @@
+-/* SPDX-License-Identifier: GPL-2.0 */
+-/* HWMON driver for Aquantia PHY
+- *
+- * Author: Nikita Yushchenko <[email protected]>
+- * Author: Andrew Lunn <[email protected]>
+- * Author: Heiner Kallweit <[email protected]>
+- */
+-
+-#include <linux/device.h>
+-#include <linux/phy.h>
+-
+-#if IS_REACHABLE(CONFIG_HWMON)
+-int aqr_hwmon_probe(struct phy_device *phydev);
+-#else
+-static inline int aqr_hwmon_probe(struct phy_device *phydev) { return 0; }
+-#endif
+--- /dev/null
++++ b/drivers/net/phy/aquantia/aquantia.h
+@@ -0,0 +1,16 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++/* HWMON driver for Aquantia PHY
++ *
++ * Author: Nikita Yushchenko <[email protected]>
++ * Author: Andrew Lunn <[email protected]>
++ * Author: Heiner Kallweit <[email protected]>
++ */
++
++#include <linux/device.h>
++#include <linux/phy.h>
++
++#if IS_REACHABLE(CONFIG_HWMON)
++int aqr_hwmon_probe(struct phy_device *phydev);
++#else
++static inline int aqr_hwmon_probe(struct phy_device *phydev) { return 0; }
++#endif
+--- /dev/null
++++ b/drivers/net/phy/aquantia/aquantia_hwmon.c
+@@ -0,0 +1,250 @@
++// SPDX-License-Identifier: GPL-2.0
++/* HWMON driver for Aquantia PHY
++ *
++ * Author: Nikita Yushchenko <[email protected]>
++ * Author: Andrew Lunn <[email protected]>
++ * Author: Heiner Kallweit <[email protected]>
++ */
++
++#include <linux/phy.h>
++#include <linux/device.h>
++#include <linux/ctype.h>
++#include <linux/hwmon.h>
++
++#include "aquantia.h"
++
++/* Vendor specific 1, MDIO_MMD_VEND2 */
++#define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL	0xc421
++#define VEND1_THERMAL_PROV_LOW_TEMP_FAIL	0xc422
++#define VEND1_THERMAL_PROV_HIGH_TEMP_WARN	0xc423
++#define VEND1_THERMAL_PROV_LOW_TEMP_WARN	0xc424
++#define VEND1_THERMAL_STAT1			0xc820
++#define VEND1_THERMAL_STAT2			0xc821
++#define VEND1_THERMAL_STAT2_VALID		BIT(0)
++#define VEND1_GENERAL_STAT1			0xc830
++#define VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL	BIT(14)
++#define VEND1_GENERAL_STAT1_LOW_TEMP_FAIL	BIT(13)
++#define VEND1_GENERAL_STAT1_HIGH_TEMP_WARN	BIT(12)
++#define VEND1_GENERAL_STAT1_LOW_TEMP_WARN	BIT(11)
++
++#if IS_REACHABLE(CONFIG_HWMON)
++
++static umode_t aqr_hwmon_is_visible(const void *data,
++				    enum hwmon_sensor_types type,
++				    u32 attr, int channel)
++{
++	if (type != hwmon_temp)
++		return 0;
++
++	switch (attr) {
++	case hwmon_temp_input:
++	case hwmon_temp_min_alarm:
++	case hwmon_temp_max_alarm:
++	case hwmon_temp_lcrit_alarm:
++	case hwmon_temp_crit_alarm:
++		return 0444;
++	case hwmon_temp_min:
++	case hwmon_temp_max:
++	case hwmon_temp_lcrit:
++	case hwmon_temp_crit:
++		return 0644;
++	default:
++		return 0;
++	}
++}
++
++static int aqr_hwmon_get(struct phy_device *phydev, int reg, long *value)
++{
++	int temp = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
++
++	if (temp < 0)
++		return temp;
++
++	/* 16 bit value is 2's complement with LSB = 1/256th degree Celsius */
++	*value = (s16)temp * 1000 / 256;
++
++	return 0;
++}
++
++static int aqr_hwmon_set(struct phy_device *phydev, int reg, long value)
++{
++	int temp;
++
++	if (value >= 128000 || value < -128000)
++		return -ERANGE;
++
++	temp = value * 256 / 1000;
++
++	/* temp is in s16 range and we're interested in lower 16 bits only */
++	return phy_write_mmd(phydev, MDIO_MMD_VEND1, reg, (u16)temp);
++}
++
++static int aqr_hwmon_test_bit(struct phy_device *phydev, int reg, int bit)
++{
++	int val = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
++
++	if (val < 0)
++		return val;
++
++	return !!(val & bit);
++}
++
++static int aqr_hwmon_status1(struct phy_device *phydev, int bit, long *value)
++{
++	int val = aqr_hwmon_test_bit(phydev, VEND1_GENERAL_STAT1, bit);
++
++	if (val < 0)
++		return val;
++
++	*value = val;
++
++	return 0;
++}
++
++static int aqr_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
++			  u32 attr, int channel, long *value)
++{
++	struct phy_device *phydev = dev_get_drvdata(dev);
++	int reg;
++
++	if (type != hwmon_temp)
++		return -EOPNOTSUPP;
++
++	switch (attr) {
++	case hwmon_temp_input:
++		reg = aqr_hwmon_test_bit(phydev, VEND1_THERMAL_STAT2,
++					 VEND1_THERMAL_STAT2_VALID);
++		if (reg < 0)
++			return reg;
++		if (!reg)
++			return -EBUSY;
++
++		return aqr_hwmon_get(phydev, VEND1_THERMAL_STAT1, value);
++
++	case hwmon_temp_lcrit:
++		return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
++				     value);
++	case hwmon_temp_min:
++		return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
++				     value);
++	case hwmon_temp_max:
++		return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
++				     value);
++	case hwmon_temp_crit:
++		return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
++				     value);
++	case hwmon_temp_lcrit_alarm:
++		return aqr_hwmon_status1(phydev,
++					 VEND1_GENERAL_STAT1_LOW_TEMP_FAIL,
++					 value);
++	case hwmon_temp_min_alarm:
++		return aqr_hwmon_status1(phydev,
++					 VEND1_GENERAL_STAT1_LOW_TEMP_WARN,
++					 value);
++	case hwmon_temp_max_alarm:
++		return aqr_hwmon_status1(phydev,
++					 VEND1_GENERAL_STAT1_HIGH_TEMP_WARN,
++					 value);
++	case hwmon_temp_crit_alarm:
++		return aqr_hwmon_status1(phydev,
++					 VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL,
++					 value);
++	default:
++		return -EOPNOTSUPP;
++	}
++}
++
++static int aqr_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
++			   u32 attr, int channel, long value)
++{
++	struct phy_device *phydev = dev_get_drvdata(dev);
++
++	if (type != hwmon_temp)
++		return -EOPNOTSUPP;
++
++	switch (attr) {
++	case hwmon_temp_lcrit:
++		return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
++				     value);
++	case hwmon_temp_min:
++		return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
++				     value);
++	case hwmon_temp_max:
++		return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
++				     value);
++	case hwmon_temp_crit:
++		return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
++				     value);
++	default:
++		return -EOPNOTSUPP;
++	}
++}
++
++static const struct hwmon_ops aqr_hwmon_ops = {
++	.is_visible = aqr_hwmon_is_visible,
++	.read = aqr_hwmon_read,
++	.write = aqr_hwmon_write,
++};
++
++static u32 aqr_hwmon_chip_config[] = {
++	HWMON_C_REGISTER_TZ,
++	0,
++};
++
++static const struct hwmon_channel_info aqr_hwmon_chip = {
++	.type = hwmon_chip,
++	.config = aqr_hwmon_chip_config,
++};
++
++static u32 aqr_hwmon_temp_config[] = {
++	HWMON_T_INPUT |
++	HWMON_T_MAX | HWMON_T_MIN |
++	HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM |
++	HWMON_T_CRIT | HWMON_T_LCRIT |
++	HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM,
++	0,
++};
++
++static const struct hwmon_channel_info aqr_hwmon_temp = {
++	.type = hwmon_temp,
++	.config = aqr_hwmon_temp_config,
++};
++
++static const struct hwmon_channel_info *aqr_hwmon_info[] = {
++	&aqr_hwmon_chip,
++	&aqr_hwmon_temp,
++	NULL,
++};
++
++static const struct hwmon_chip_info aqr_hwmon_chip_info = {
++	.ops = &aqr_hwmon_ops,
++	.info = aqr_hwmon_info,
++};
++
++int aqr_hwmon_probe(struct phy_device *phydev)
++{
++	struct device *dev = &phydev->mdio.dev;
++	struct device *hwmon_dev;
++	char *hwmon_name;
++	int i, j;
++
++	hwmon_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL);
++	if (!hwmon_name)
++		return -ENOMEM;
++
++	for (i = j = 0; hwmon_name[i]; i++) {
++		if (isalnum(hwmon_name[i])) {
++			if (i != j)
++				hwmon_name[j] = hwmon_name[i];
++			j++;
++		}
++	}
++	hwmon_name[j] = '\0';
++
++	hwmon_dev = devm_hwmon_device_register_with_info(dev, hwmon_name,
++					phydev, &aqr_hwmon_chip_info, NULL);
++
++	return PTR_ERR_OR_ZERO(hwmon_dev);
++}
++
++#endif
+--- /dev/null
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -0,0 +1,842 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Driver for Aquantia PHY
++ *
++ * Author: Shaohui Xie <[email protected]>
++ *
++ * Copyright 2015 Freescale Semiconductor, Inc.
++ */
++
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/delay.h>
++#include <linux/bitfield.h>
++#include <linux/phy.h>
++
++#include "aquantia.h"
++
++#define PHY_ID_AQ1202	0x03a1b445
++#define PHY_ID_AQ2104	0x03a1b460
++#define PHY_ID_AQR105	0x03a1b4a2
++#define PHY_ID_AQR106	0x03a1b4d0
++#define PHY_ID_AQR107	0x03a1b4e0
++#define PHY_ID_AQCS109	0x03a1b5c2
++#define PHY_ID_AQR405	0x03a1b4b0
++#define PHY_ID_AQR113C	0x31c31c12
++
++#define MDIO_PHYXS_VEND_IF_STATUS		0xe812
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK	GENMASK(7, 3)
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR	0
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX	1
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI	2
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII	3
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI	4
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII	6
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI	7
++#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII	10
++
++#define MDIO_AN_VEND_PROV			0xc400
++#define MDIO_AN_VEND_PROV_1000BASET_FULL	BIT(15)
++#define MDIO_AN_VEND_PROV_1000BASET_HALF	BIT(14)
++#define MDIO_AN_VEND_PROV_5000BASET_FULL	BIT(11)
++#define MDIO_AN_VEND_PROV_2500BASET_FULL	BIT(10)
++#define MDIO_AN_VEND_PROV_DOWNSHIFT_EN		BIT(4)
++#define MDIO_AN_VEND_PROV_DOWNSHIFT_MASK	GENMASK(3, 0)
++#define MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT	4
++
++#define MDIO_AN_TX_VEND_STATUS1			0xc800
++#define MDIO_AN_TX_VEND_STATUS1_RATE_MASK	GENMASK(3, 1)
++#define MDIO_AN_TX_VEND_STATUS1_10BASET		0
++#define MDIO_AN_TX_VEND_STATUS1_100BASETX	1
++#define MDIO_AN_TX_VEND_STATUS1_1000BASET	2
++#define MDIO_AN_TX_VEND_STATUS1_10GBASET	3
++#define MDIO_AN_TX_VEND_STATUS1_2500BASET	4
++#define MDIO_AN_TX_VEND_STATUS1_5000BASET	5
++#define MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX	BIT(0)
++
++#define MDIO_AN_TX_VEND_INT_STATUS1		0xcc00
++#define MDIO_AN_TX_VEND_INT_STATUS1_DOWNSHIFT	BIT(1)
++
++#define MDIO_AN_TX_VEND_INT_STATUS2		0xcc01
++#define MDIO_AN_TX_VEND_INT_STATUS2_MASK	BIT(0)
++
++#define MDIO_AN_TX_VEND_INT_MASK2		0xd401
++#define MDIO_AN_TX_VEND_INT_MASK2_LINK		BIT(0)
++
++#define MDIO_AN_RX_LP_STAT1			0xe820
++#define MDIO_AN_RX_LP_STAT1_1000BASET_FULL	BIT(15)
++#define MDIO_AN_RX_LP_STAT1_1000BASET_HALF	BIT(14)
++#define MDIO_AN_RX_LP_STAT1_SHORT_REACH		BIT(13)
++#define MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT	BIT(12)
++#define MDIO_AN_RX_LP_STAT1_AQ_PHY		BIT(2)
++
++#define MDIO_AN_RX_LP_STAT4			0xe823
++#define MDIO_AN_RX_LP_STAT4_FW_MAJOR		GENMASK(15, 8)
++#define MDIO_AN_RX_LP_STAT4_FW_MINOR		GENMASK(7, 0)
++
++#define MDIO_AN_RX_VEND_STAT3			0xe832
++#define MDIO_AN_RX_VEND_STAT3_AFR		BIT(0)
++
++/* MDIO_MMD_C22EXT */
++#define MDIO_C22EXT_STAT_SGMII_RX_GOOD_FRAMES		0xd292
++#define MDIO_C22EXT_STAT_SGMII_RX_BAD_FRAMES		0xd294
++#define MDIO_C22EXT_STAT_SGMII_RX_FALSE_CARRIER		0xd297
++#define MDIO_C22EXT_STAT_SGMII_TX_GOOD_FRAMES		0xd313
++#define MDIO_C22EXT_STAT_SGMII_TX_BAD_FRAMES		0xd315
++#define MDIO_C22EXT_STAT_SGMII_TX_FALSE_CARRIER		0xd317
++#define MDIO_C22EXT_STAT_SGMII_TX_COLLISIONS		0xd318
++#define MDIO_C22EXT_STAT_SGMII_TX_LINE_COLLISIONS	0xd319
++#define MDIO_C22EXT_STAT_SGMII_TX_FRAME_ALIGN_ERR	0xd31a
++#define MDIO_C22EXT_STAT_SGMII_TX_RUNT_FRAMES		0xd31b
++
++/* Vendor specific 1, MDIO_MMD_VEND1 */
++#define VEND1_GLOBAL_FW_ID			0x0020
++#define VEND1_GLOBAL_FW_ID_MAJOR		GENMASK(15, 8)
++#define VEND1_GLOBAL_FW_ID_MINOR		GENMASK(7, 0)
++
++#define VEND1_GLOBAL_GEN_STAT2			0xc831
++#define VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG	BIT(15)
++
++/* The following registers all have similar layouts; first the registers... */
++#define VEND1_GLOBAL_CFG_10M			0x0310
++#define VEND1_GLOBAL_CFG_100M			0x031b
++#define VEND1_GLOBAL_CFG_1G			0x031c
++#define VEND1_GLOBAL_CFG_2_5G			0x031d
++#define VEND1_GLOBAL_CFG_5G			0x031e
++#define VEND1_GLOBAL_CFG_10G			0x031f
++/* ...and now the fields */
++#define VEND1_GLOBAL_CFG_RATE_ADAPT		GENMASK(8, 7)
++#define VEND1_GLOBAL_CFG_RATE_ADAPT_NONE	0
++#define VEND1_GLOBAL_CFG_RATE_ADAPT_USX		1
++#define VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE	2
++
++#define VEND1_GLOBAL_RSVD_STAT1			0xc885
++#define VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID	GENMASK(7, 4)
++#define VEND1_GLOBAL_RSVD_STAT1_PROV_ID		GENMASK(3, 0)
++
++#define VEND1_GLOBAL_RSVD_STAT9			0xc88d
++#define VEND1_GLOBAL_RSVD_STAT9_MODE		GENMASK(7, 0)
++#define VEND1_GLOBAL_RSVD_STAT9_1000BT2		0x23
++
++#define VEND1_GLOBAL_INT_STD_STATUS		0xfc00
++#define VEND1_GLOBAL_INT_VEND_STATUS		0xfc01
++
++#define VEND1_GLOBAL_INT_STD_MASK		0xff00
++#define VEND1_GLOBAL_INT_STD_MASK_PMA1		BIT(15)
++#define VEND1_GLOBAL_INT_STD_MASK_PMA2		BIT(14)
++#define VEND1_GLOBAL_INT_STD_MASK_PCS1		BIT(13)
++#define VEND1_GLOBAL_INT_STD_MASK_PCS2		BIT(12)
++#define VEND1_GLOBAL_INT_STD_MASK_PCS3		BIT(11)
++#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS1	BIT(10)
++#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS2	BIT(9)
++#define VEND1_GLOBAL_INT_STD_MASK_AN1		BIT(8)
++#define VEND1_GLOBAL_INT_STD_MASK_AN2		BIT(7)
++#define VEND1_GLOBAL_INT_STD_MASK_GBE		BIT(6)
++#define VEND1_GLOBAL_INT_STD_MASK_ALL		BIT(0)
++
++#define VEND1_GLOBAL_INT_VEND_MASK		0xff01
++#define VEND1_GLOBAL_INT_VEND_MASK_PMA		BIT(15)
++#define VEND1_GLOBAL_INT_VEND_MASK_PCS		BIT(14)
++#define VEND1_GLOBAL_INT_VEND_MASK_PHY_XS	BIT(13)
++#define VEND1_GLOBAL_INT_VEND_MASK_AN		BIT(12)
++#define VEND1_GLOBAL_INT_VEND_MASK_GBE		BIT(11)
++#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL1	BIT(2)
++#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL2	BIT(1)
++#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3	BIT(0)
++
++/* Sleep and timeout for checking if the Processor-Intensive
++ * MDIO operation is finished
++ */
++#define AQR107_OP_IN_PROG_SLEEP		1000
++#define AQR107_OP_IN_PROG_TIMEOUT	100000
++
++struct aqr107_hw_stat {
++	const char *name;
++	int reg;
++	int size;
++};
++
++#define SGMII_STAT(n, r, s) { n, MDIO_C22EXT_STAT_SGMII_ ## r, s }
++static const struct aqr107_hw_stat aqr107_hw_stats[] = {
++	SGMII_STAT("sgmii_rx_good_frames",	    RX_GOOD_FRAMES,	26),
++	SGMII_STAT("sgmii_rx_bad_frames",	    RX_BAD_FRAMES,	26),
++	SGMII_STAT("sgmii_rx_false_carrier_events", RX_FALSE_CARRIER,	 8),
++	SGMII_STAT("sgmii_tx_good_frames",	    TX_GOOD_FRAMES,	26),
++	SGMII_STAT("sgmii_tx_bad_frames",	    TX_BAD_FRAMES,	26),
++	SGMII_STAT("sgmii_tx_false_carrier_events", TX_FALSE_CARRIER,	 8),
++	SGMII_STAT("sgmii_tx_collisions",	    TX_COLLISIONS,	 8),
++	SGMII_STAT("sgmii_tx_line_collisions",	    TX_LINE_COLLISIONS,	 8),
++	SGMII_STAT("sgmii_tx_frame_alignment_err",  TX_FRAME_ALIGN_ERR,	16),
++	SGMII_STAT("sgmii_tx_runt_frames",	    TX_RUNT_FRAMES,	22),
++};
++#define AQR107_SGMII_STAT_SZ ARRAY_SIZE(aqr107_hw_stats)
++
++struct aqr107_priv {
++	u64 sgmii_stats[AQR107_SGMII_STAT_SZ];
++};
++
++static int aqr107_get_sset_count(struct phy_device *phydev)
++{
++	return AQR107_SGMII_STAT_SZ;
++}
++
++static void aqr107_get_strings(struct phy_device *phydev, u8 *data)
++{
++	int i;
++
++	for (i = 0; i < AQR107_SGMII_STAT_SZ; i++)
++		strscpy(data + i * ETH_GSTRING_LEN, aqr107_hw_stats[i].name,
++			ETH_GSTRING_LEN);
++}
++
++static u64 aqr107_get_stat(struct phy_device *phydev, int index)
++{
++	const struct aqr107_hw_stat *stat = aqr107_hw_stats + index;
++	int len_l = min(stat->size, 16);
++	int len_h = stat->size - len_l;
++	u64 ret;
++	int val;
++
++	val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg);
++	if (val < 0)
++		return U64_MAX;
++
++	ret = val & GENMASK(len_l - 1, 0);
++	if (len_h) {
++		val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg + 1);
++		if (val < 0)
++			return U64_MAX;
++
++		ret += (val & GENMASK(len_h - 1, 0)) << 16;
++	}
++
++	return ret;
++}
++
++static void aqr107_get_stats(struct phy_device *phydev,
++			     struct ethtool_stats *stats, u64 *data)
++{
++	struct aqr107_priv *priv = phydev->priv;
++	u64 val;
++	int i;
++
++	for (i = 0; i < AQR107_SGMII_STAT_SZ; i++) {
++		val = aqr107_get_stat(phydev, i);
++		if (val == U64_MAX)
++			phydev_err(phydev, "Reading HW Statistics failed for %s\n",
++				   aqr107_hw_stats[i].name);
++		else
++			priv->sgmii_stats[i] += val;
++
++		data[i] = priv->sgmii_stats[i];
++	}
++}
++
++static int aqr_config_aneg(struct phy_device *phydev)
++{
++	bool changed = false;
++	u16 reg;
++	int ret;
++
++	if (phydev->autoneg == AUTONEG_DISABLE)
++		return genphy_c45_pma_setup_forced(phydev);
++
++	ret = genphy_c45_an_config_aneg(phydev);
++	if (ret < 0)
++		return ret;
++	if (ret > 0)
++		changed = true;
++
++	/* Clause 45 has no standardized support for 1000BaseT, therefore
++	 * use vendor registers for this mode.
++	 */
++	reg = 0;
++	if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
++			      phydev->advertising))
++		reg |= MDIO_AN_VEND_PROV_1000BASET_FULL;
++
++	if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
++			      phydev->advertising))
++		reg |= MDIO_AN_VEND_PROV_1000BASET_HALF;
++
++	/* Handle the case when the 2.5G and 5G speeds are not advertised */
++	if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
++			      phydev->advertising))
++		reg |= MDIO_AN_VEND_PROV_2500BASET_FULL;
++
++	if (linkmode_test_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
++			      phydev->advertising))
++		reg |= MDIO_AN_VEND_PROV_5000BASET_FULL;
++
++	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
++				     MDIO_AN_VEND_PROV_1000BASET_HALF |
++				     MDIO_AN_VEND_PROV_1000BASET_FULL |
++				     MDIO_AN_VEND_PROV_2500BASET_FULL |
++				     MDIO_AN_VEND_PROV_5000BASET_FULL, reg);
++	if (ret < 0)
++		return ret;
++	if (ret > 0)
++		changed = true;
++
++	return genphy_c45_check_and_restart_aneg(phydev, changed);
++}
++
++static int aqr_config_intr(struct phy_device *phydev)
++{
++	bool en = phydev->interrupts == PHY_INTERRUPT_ENABLED;
++	int err;
++
++	if (en) {
++		/* Clear any pending interrupts before enabling them */
++		err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
++		if (err < 0)
++			return err;
++	}
++
++	err = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_MASK2,
++			    en ? MDIO_AN_TX_VEND_INT_MASK2_LINK : 0);
++	if (err < 0)
++		return err;
++
++	err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_STD_MASK,
++			    en ? VEND1_GLOBAL_INT_STD_MASK_ALL : 0);
++	if (err < 0)
++		return err;
++
++	err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_VEND_MASK,
++			    en ? VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 |
++			    VEND1_GLOBAL_INT_VEND_MASK_AN : 0);
++	if (err < 0)
++		return err;
++
++	if (!en) {
++		/* Clear any pending interrupts after we have disabled them */
++		err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
++		if (err < 0)
++			return err;
++	}
++
++	return 0;
++}
++
++static irqreturn_t aqr_handle_interrupt(struct phy_device *phydev)
++{
++	int irq_status;
++
++	irq_status = phy_read_mmd(phydev, MDIO_MMD_AN,
++				  MDIO_AN_TX_VEND_INT_STATUS2);
++	if (irq_status < 0) {
++		phy_error(phydev);
++		return IRQ_NONE;
++	}
++
++	if (!(irq_status & MDIO_AN_TX_VEND_INT_STATUS2_MASK))
++		return IRQ_NONE;
++
++	phy_trigger_machine(phydev);
++
++	return IRQ_HANDLED;
++}
++
++static int aqr_read_status(struct phy_device *phydev)
++{
++	int val;
++
++	if (phydev->autoneg == AUTONEG_ENABLE) {
++		val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
++		if (val < 0)
++			return val;
++
++		linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
++				 phydev->lp_advertising,
++				 val & MDIO_AN_RX_LP_STAT1_1000BASET_FULL);
++		linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
++				 phydev->lp_advertising,
++				 val & MDIO_AN_RX_LP_STAT1_1000BASET_HALF);
++	}
++
++	return genphy_c45_read_status(phydev);
++}
++
++static int aqr107_read_rate(struct phy_device *phydev)
++{
++	u32 config_reg;
++	int val;
++
++	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_STATUS1);
++	if (val < 0)
++		return val;
++
++	if (val & MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX)
++		phydev->duplex = DUPLEX_FULL;
++	else
++		phydev->duplex = DUPLEX_HALF;
++
++	switch (FIELD_GET(MDIO_AN_TX_VEND_STATUS1_RATE_MASK, val)) {
++	case MDIO_AN_TX_VEND_STATUS1_10BASET:
++		phydev->speed = SPEED_10;
++		config_reg = VEND1_GLOBAL_CFG_10M;
++		break;
++	case MDIO_AN_TX_VEND_STATUS1_100BASETX:
++		phydev->speed = SPEED_100;
++		config_reg = VEND1_GLOBAL_CFG_100M;
++		break;
++	case MDIO_AN_TX_VEND_STATUS1_1000BASET:
++		phydev->speed = SPEED_1000;
++		config_reg = VEND1_GLOBAL_CFG_1G;
++		break;
++	case MDIO_AN_TX_VEND_STATUS1_2500BASET:
++		phydev->speed = SPEED_2500;
++		config_reg = VEND1_GLOBAL_CFG_2_5G;
++		break;
++	case MDIO_AN_TX_VEND_STATUS1_5000BASET:
++		phydev->speed = SPEED_5000;
++		config_reg = VEND1_GLOBAL_CFG_5G;
++		break;
++	case MDIO_AN_TX_VEND_STATUS1_10GBASET:
++		phydev->speed = SPEED_10000;
++		config_reg = VEND1_GLOBAL_CFG_10G;
++		break;
++	default:
++		phydev->speed = SPEED_UNKNOWN;
++		return 0;
++	}
++
++	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, config_reg);
++	if (val < 0)
++		return val;
++
++	if (FIELD_GET(VEND1_GLOBAL_CFG_RATE_ADAPT, val) ==
++	    VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE)
++		phydev->rate_matching = RATE_MATCH_PAUSE;
++	else
++		phydev->rate_matching = RATE_MATCH_NONE;
++
++	return 0;
++}
++
++static int aqr107_read_status(struct phy_device *phydev)
++{
++	int val, ret;
++
++	ret = aqr_read_status(phydev);
++	if (ret)
++		return ret;
++
++	if (!phydev->link || phydev->autoneg == AUTONEG_DISABLE)
++		return 0;
++
++	val = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MDIO_PHYXS_VEND_IF_STATUS);
++	if (val < 0)
++		return val;
++
++	switch (FIELD_GET(MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK, val)) {
++	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR:
++		phydev->interface = PHY_INTERFACE_MODE_10GKR;
++		break;
++	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX:
++		phydev->interface = PHY_INTERFACE_MODE_1000BASEKX;
++		break;
++	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI:
++		phydev->interface = PHY_INTERFACE_MODE_10GBASER;
++		break;
++	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII:
++		phydev->interface = PHY_INTERFACE_MODE_USXGMII;
++		break;
++	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI:
++		phydev->interface = PHY_INTERFACE_MODE_XAUI;
++		break;
++	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII:
++		phydev->interface = PHY_INTERFACE_MODE_SGMII;
++		break;
++	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI:
++		phydev->interface = PHY_INTERFACE_MODE_RXAUI;
++		break;
++	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII:
++		phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
++		break;
++	default:
++		phydev->interface = PHY_INTERFACE_MODE_NA;
++		break;
++	}
++
++	/* Read possibly downshifted rate from vendor register */
++	return aqr107_read_rate(phydev);
++}
++
++static int aqr107_get_downshift(struct phy_device *phydev, u8 *data)
++{
++	int val, cnt, enable;
++
++	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV);
++	if (val < 0)
++		return val;
++
++	enable = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_EN, val);
++	cnt = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
++
++	*data = enable && cnt ? cnt : DOWNSHIFT_DEV_DISABLE;
++
++	return 0;
++}
++
++static int aqr107_set_downshift(struct phy_device *phydev, u8 cnt)
++{
++	int val = 0;
++
++	if (!FIELD_FIT(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt))
++		return -E2BIG;
++
++	if (cnt != DOWNSHIFT_DEV_DISABLE) {
++		val = MDIO_AN_VEND_PROV_DOWNSHIFT_EN;
++		val |= FIELD_PREP(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt);
++	}
++
++	return phy_modify_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
++			      MDIO_AN_VEND_PROV_DOWNSHIFT_EN |
++			      MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
++}
++
++static int aqr107_get_tunable(struct phy_device *phydev,
++			      struct ethtool_tunable *tuna, void *data)
++{
++	switch (tuna->id) {
++	case ETHTOOL_PHY_DOWNSHIFT:
++		return aqr107_get_downshift(phydev, data);
++	default:
++		return -EOPNOTSUPP;
++	}
++}
++
++static int aqr107_set_tunable(struct phy_device *phydev,
++			      struct ethtool_tunable *tuna, const void *data)
++{
++	switch (tuna->id) {
++	case ETHTOOL_PHY_DOWNSHIFT:
++		return aqr107_set_downshift(phydev, *(const u8 *)data);
++	default:
++		return -EOPNOTSUPP;
++	}
++}
++
++/* If we configure settings whilst firmware is still initializing the chip,
++ * then these settings may be overwritten. Therefore make sure chip
++ * initialization has completed. Use presence of the firmware ID as
++ * indicator for initialization having completed.
++ * The chip also provides a "reset completed" bit, but it's cleared after
++ * read. Therefore function would time out if called again.
++ */
++static int aqr107_wait_reset_complete(struct phy_device *phydev)
++{
++	int val;
++
++	return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
++					 VEND1_GLOBAL_FW_ID, val, val != 0,
++					 20000, 2000000, false);
++}
++
++static void aqr107_chip_info(struct phy_device *phydev)
++{
++	u8 fw_major, fw_minor, build_id, prov_id;
++	int val;
++
++	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_FW_ID);
++	if (val < 0)
++		return;
++
++	fw_major = FIELD_GET(VEND1_GLOBAL_FW_ID_MAJOR, val);
++	fw_minor = FIELD_GET(VEND1_GLOBAL_FW_ID_MINOR, val);
++
++	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT1);
++	if (val < 0)
++		return;
++
++	build_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID, val);
++	prov_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_PROV_ID, val);
++
++	phydev_dbg(phydev, "FW %u.%u, Build %u, Provisioning %u\n",
++		   fw_major, fw_minor, build_id, prov_id);
++}
++
++static int aqr107_config_init(struct phy_device *phydev)
++{
++	int ret;
++
++	/* Check that the PHY interface type is compatible */
++	if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
++	    phydev->interface != PHY_INTERFACE_MODE_1000BASEKX &&
++	    phydev->interface != PHY_INTERFACE_MODE_2500BASEX &&
++	    phydev->interface != PHY_INTERFACE_MODE_XGMII &&
++	    phydev->interface != PHY_INTERFACE_MODE_USXGMII &&
++	    phydev->interface != PHY_INTERFACE_MODE_10GKR &&
++	    phydev->interface != PHY_INTERFACE_MODE_10GBASER &&
++	    phydev->interface != PHY_INTERFACE_MODE_XAUI &&
++	    phydev->interface != PHY_INTERFACE_MODE_RXAUI)
++		return -ENODEV;
++
++	WARN(phydev->interface == PHY_INTERFACE_MODE_XGMII,
++	     "Your devicetree is out of date, please update it. The AQR107 family doesn't support XGMII, maybe you mean USXGMII.\n");
++
++	ret = aqr107_wait_reset_complete(phydev);
++	if (!ret)
++		aqr107_chip_info(phydev);
++
++	return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
++}
++
++static int aqcs109_config_init(struct phy_device *phydev)
++{
++	int ret;
++
++	/* Check that the PHY interface type is compatible */
++	if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
++	    phydev->interface != PHY_INTERFACE_MODE_2500BASEX)
++		return -ENODEV;
++
++	ret = aqr107_wait_reset_complete(phydev);
++	if (!ret)
++		aqr107_chip_info(phydev);
++
++	/* AQCS109 belongs to a chip family partially supporting 10G and 5G.
++	 * PMA speed ability bits are the same for all members of the family,
++	 * AQCS109 however supports speeds up to 2.5G only.
++	 */
++	phy_set_max_speed(phydev, SPEED_2500);
++
++	return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
++}
++
++static void aqr107_link_change_notify(struct phy_device *phydev)
++{
++	u8 fw_major, fw_minor;
++	bool downshift, short_reach, afr;
++	int mode, val;
++
++	if (phydev->state != PHY_RUNNING || phydev->autoneg == AUTONEG_DISABLE)
++		return;
++
++	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
++	/* call failed or link partner is no Aquantia PHY */
++	if (val < 0 || !(val & MDIO_AN_RX_LP_STAT1_AQ_PHY))
++		return;
++
++	short_reach = val & MDIO_AN_RX_LP_STAT1_SHORT_REACH;
++	downshift = val & MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT;
++
++	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT4);
++	if (val < 0)
++		return;
++
++	fw_major = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MAJOR, val);
++	fw_minor = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MINOR, val);
++
++	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_VEND_STAT3);
++	if (val < 0)
++		return;
++
++	afr = val & MDIO_AN_RX_VEND_STAT3_AFR;
++
++	phydev_dbg(phydev, "Link partner is Aquantia PHY, FW %u.%u%s%s%s\n",
++		   fw_major, fw_minor,
++		   short_reach ? ", short reach mode" : "",
++		   downshift ? ", fast-retrain downshift advertised" : "",
++		   afr ? ", fast reframe advertised" : "");
++
++	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT9);
++	if (val < 0)
++		return;
++
++	mode = FIELD_GET(VEND1_GLOBAL_RSVD_STAT9_MODE, val);
++	if (mode == VEND1_GLOBAL_RSVD_STAT9_1000BT2)
++		phydev_info(phydev, "Aquantia 1000Base-T2 mode active\n");
++}
++
++static int aqr107_wait_processor_intensive_op(struct phy_device *phydev)
++{
++	int val, err;
++
++	/* The datasheet notes to wait at least 1ms after issuing a
++	 * processor intensive operation before checking.
++	 * We cannot use the 'sleep_before_read' parameter of read_poll_timeout
++	 * because that just determines the maximum time slept, not the minimum.
++	 */
++	usleep_range(1000, 5000);
++
++	err = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
++					VEND1_GLOBAL_GEN_STAT2, val,
++					!(val & VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG),
++					AQR107_OP_IN_PROG_SLEEP,
++					AQR107_OP_IN_PROG_TIMEOUT, false);
++	if (err) {
++		phydev_err(phydev, "timeout: processor-intensive MDIO operation\n");
++		return err;
++	}
++
++	return 0;
++}
++
++static int aqr107_get_rate_matching(struct phy_device *phydev,
++				    phy_interface_t iface)
++{
++	if (iface == PHY_INTERFACE_MODE_10GBASER ||
++	    iface == PHY_INTERFACE_MODE_2500BASEX ||
++	    iface == PHY_INTERFACE_MODE_NA)
++		return RATE_MATCH_PAUSE;
++	return RATE_MATCH_NONE;
++}
++
++static int aqr107_suspend(struct phy_device *phydev)
++{
++	int err;
++
++	err = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
++			       MDIO_CTRL1_LPOWER);
++	if (err)
++		return err;
++
++	return aqr107_wait_processor_intensive_op(phydev);
++}
++
++static int aqr107_resume(struct phy_device *phydev)
++{
++	int err;
++
++	err = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
++				 MDIO_CTRL1_LPOWER);
++	if (err)
++		return err;
++
++	return aqr107_wait_processor_intensive_op(phydev);
++}
++
++static int aqr107_probe(struct phy_device *phydev)
++{
++	phydev->priv = devm_kzalloc(&phydev->mdio.dev,
++				    sizeof(struct aqr107_priv), GFP_KERNEL);
++	if (!phydev->priv)
++		return -ENOMEM;
++
++	return aqr_hwmon_probe(phydev);
++}
++
++static struct phy_driver aqr_driver[] = {
++{
++	PHY_ID_MATCH_MODEL(PHY_ID_AQ1202),
++	.name		= "Aquantia AQ1202",
++	.config_aneg    = aqr_config_aneg,
++	.config_intr	= aqr_config_intr,
++	.handle_interrupt = aqr_handle_interrupt,
++	.read_status	= aqr_read_status,
++},
++{
++	PHY_ID_MATCH_MODEL(PHY_ID_AQ2104),
++	.name		= "Aquantia AQ2104",
++	.config_aneg    = aqr_config_aneg,
++	.config_intr	= aqr_config_intr,
++	.handle_interrupt = aqr_handle_interrupt,
++	.read_status	= aqr_read_status,
++},
++{
++	PHY_ID_MATCH_MODEL(PHY_ID_AQR105),
++	.name		= "Aquantia AQR105",
++	.config_aneg    = aqr_config_aneg,
++	.config_intr	= aqr_config_intr,
++	.handle_interrupt = aqr_handle_interrupt,
++	.read_status	= aqr_read_status,
++	.suspend	= aqr107_suspend,
++	.resume		= aqr107_resume,
++},
++{
++	PHY_ID_MATCH_MODEL(PHY_ID_AQR106),
++	.name		= "Aquantia AQR106",
++	.config_aneg    = aqr_config_aneg,
++	.config_intr	= aqr_config_intr,
++	.handle_interrupt = aqr_handle_interrupt,
++	.read_status	= aqr_read_status,
++},
++{
++	PHY_ID_MATCH_MODEL(PHY_ID_AQR107),
++	.name		= "Aquantia AQR107",
++	.probe		= aqr107_probe,
++	.get_rate_matching = aqr107_get_rate_matching,
++	.config_init	= aqr107_config_init,
++	.config_aneg    = aqr_config_aneg,
++	.config_intr	= aqr_config_intr,
++	.handle_interrupt = aqr_handle_interrupt,
++	.read_status	= aqr107_read_status,
++	.get_tunable    = aqr107_get_tunable,
++	.set_tunable    = aqr107_set_tunable,
++	.suspend	= aqr107_suspend,
++	.resume		= aqr107_resume,
++	.get_sset_count	= aqr107_get_sset_count,
++	.get_strings	= aqr107_get_strings,
++	.get_stats	= aqr107_get_stats,
++	.link_change_notify = aqr107_link_change_notify,
++},
++{
++	PHY_ID_MATCH_MODEL(PHY_ID_AQCS109),
++	.name		= "Aquantia AQCS109",
++	.probe		= aqr107_probe,
++	.get_rate_matching = aqr107_get_rate_matching,
++	.config_init	= aqcs109_config_init,
++	.config_aneg    = aqr_config_aneg,
++	.config_intr	= aqr_config_intr,
++	.handle_interrupt = aqr_handle_interrupt,
++	.read_status	= aqr107_read_status,
++	.get_tunable    = aqr107_get_tunable,
++	.set_tunable    = aqr107_set_tunable,
++	.suspend	= aqr107_suspend,
++	.resume		= aqr107_resume,
++	.get_sset_count	= aqr107_get_sset_count,
++	.get_strings	= aqr107_get_strings,
++	.get_stats	= aqr107_get_stats,
++	.link_change_notify = aqr107_link_change_notify,
++},
++{
++	PHY_ID_MATCH_MODEL(PHY_ID_AQR405),
++	.name		= "Aquantia AQR405",
++	.config_aneg    = aqr_config_aneg,
++	.config_intr	= aqr_config_intr,
++	.handle_interrupt = aqr_handle_interrupt,
++	.read_status	= aqr_read_status,
++},
++{
++	PHY_ID_MATCH_MODEL(PHY_ID_AQR113C),
++	.name           = "Aquantia AQR113C",
++	.probe          = aqr107_probe,
++	.get_rate_matching = aqr107_get_rate_matching,
++	.config_init    = aqr107_config_init,
++	.config_aneg    = aqr_config_aneg,
++	.config_intr    = aqr_config_intr,
++	.handle_interrupt       = aqr_handle_interrupt,
++	.read_status    = aqr107_read_status,
++	.get_tunable    = aqr107_get_tunable,
++	.set_tunable    = aqr107_set_tunable,
++	.suspend        = aqr107_suspend,
++	.resume         = aqr107_resume,
++	.get_sset_count = aqr107_get_sset_count,
++	.get_strings    = aqr107_get_strings,
++	.get_stats      = aqr107_get_stats,
++	.link_change_notify = aqr107_link_change_notify,
++},
++};
++
++module_phy_driver(aqr_driver);
++
++static struct mdio_device_id __maybe_unused aqr_tbl[] = {
++	{ PHY_ID_MATCH_MODEL(PHY_ID_AQ1202) },
++	{ PHY_ID_MATCH_MODEL(PHY_ID_AQ2104) },
++	{ PHY_ID_MATCH_MODEL(PHY_ID_AQR105) },
++	{ PHY_ID_MATCH_MODEL(PHY_ID_AQR106) },
++	{ PHY_ID_MATCH_MODEL(PHY_ID_AQR107) },
++	{ PHY_ID_MATCH_MODEL(PHY_ID_AQCS109) },
++	{ PHY_ID_MATCH_MODEL(PHY_ID_AQR405) },
++	{ PHY_ID_MATCH_MODEL(PHY_ID_AQR113C) },
++	{ }
++};
++
++MODULE_DEVICE_TABLE(mdio, aqr_tbl);
++
++MODULE_DESCRIPTION("Aquantia PHY driver");
++MODULE_AUTHOR("Shaohui Xie <[email protected]>");
++MODULE_LICENSE("GPL v2");
+--- a/drivers/net/phy/aquantia_hwmon.c
++++ /dev/null
+@@ -1,250 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-/* HWMON driver for Aquantia PHY
+- *
+- * Author: Nikita Yushchenko <[email protected]>
+- * Author: Andrew Lunn <[email protected]>
+- * Author: Heiner Kallweit <[email protected]>
+- */
+-
+-#include <linux/phy.h>
+-#include <linux/device.h>
+-#include <linux/ctype.h>
+-#include <linux/hwmon.h>
+-
+-#include "aquantia.h"
+-
+-/* Vendor specific 1, MDIO_MMD_VEND2 */
+-#define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL	0xc421
+-#define VEND1_THERMAL_PROV_LOW_TEMP_FAIL	0xc422
+-#define VEND1_THERMAL_PROV_HIGH_TEMP_WARN	0xc423
+-#define VEND1_THERMAL_PROV_LOW_TEMP_WARN	0xc424
+-#define VEND1_THERMAL_STAT1			0xc820
+-#define VEND1_THERMAL_STAT2			0xc821
+-#define VEND1_THERMAL_STAT2_VALID		BIT(0)
+-#define VEND1_GENERAL_STAT1			0xc830
+-#define VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL	BIT(14)
+-#define VEND1_GENERAL_STAT1_LOW_TEMP_FAIL	BIT(13)
+-#define VEND1_GENERAL_STAT1_HIGH_TEMP_WARN	BIT(12)
+-#define VEND1_GENERAL_STAT1_LOW_TEMP_WARN	BIT(11)
+-
+-#if IS_REACHABLE(CONFIG_HWMON)
+-
+-static umode_t aqr_hwmon_is_visible(const void *data,
+-				    enum hwmon_sensor_types type,
+-				    u32 attr, int channel)
+-{
+-	if (type != hwmon_temp)
+-		return 0;
+-
+-	switch (attr) {
+-	case hwmon_temp_input:
+-	case hwmon_temp_min_alarm:
+-	case hwmon_temp_max_alarm:
+-	case hwmon_temp_lcrit_alarm:
+-	case hwmon_temp_crit_alarm:
+-		return 0444;
+-	case hwmon_temp_min:
+-	case hwmon_temp_max:
+-	case hwmon_temp_lcrit:
+-	case hwmon_temp_crit:
+-		return 0644;
+-	default:
+-		return 0;
+-	}
+-}
+-
+-static int aqr_hwmon_get(struct phy_device *phydev, int reg, long *value)
+-{
+-	int temp = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
+-
+-	if (temp < 0)
+-		return temp;
+-
+-	/* 16 bit value is 2's complement with LSB = 1/256th degree Celsius */
+-	*value = (s16)temp * 1000 / 256;
+-
+-	return 0;
+-}
+-
+-static int aqr_hwmon_set(struct phy_device *phydev, int reg, long value)
+-{
+-	int temp;
+-
+-	if (value >= 128000 || value < -128000)
+-		return -ERANGE;
+-
+-	temp = value * 256 / 1000;
+-
+-	/* temp is in s16 range and we're interested in lower 16 bits only */
+-	return phy_write_mmd(phydev, MDIO_MMD_VEND1, reg, (u16)temp);
+-}
+-
+-static int aqr_hwmon_test_bit(struct phy_device *phydev, int reg, int bit)
+-{
+-	int val = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
+-
+-	if (val < 0)
+-		return val;
+-
+-	return !!(val & bit);
+-}
+-
+-static int aqr_hwmon_status1(struct phy_device *phydev, int bit, long *value)
+-{
+-	int val = aqr_hwmon_test_bit(phydev, VEND1_GENERAL_STAT1, bit);
+-
+-	if (val < 0)
+-		return val;
+-
+-	*value = val;
+-
+-	return 0;
+-}
+-
+-static int aqr_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+-			  u32 attr, int channel, long *value)
+-{
+-	struct phy_device *phydev = dev_get_drvdata(dev);
+-	int reg;
+-
+-	if (type != hwmon_temp)
+-		return -EOPNOTSUPP;
+-
+-	switch (attr) {
+-	case hwmon_temp_input:
+-		reg = aqr_hwmon_test_bit(phydev, VEND1_THERMAL_STAT2,
+-					 VEND1_THERMAL_STAT2_VALID);
+-		if (reg < 0)
+-			return reg;
+-		if (!reg)
+-			return -EBUSY;
+-
+-		return aqr_hwmon_get(phydev, VEND1_THERMAL_STAT1, value);
+-
+-	case hwmon_temp_lcrit:
+-		return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
+-				     value);
+-	case hwmon_temp_min:
+-		return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
+-				     value);
+-	case hwmon_temp_max:
+-		return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
+-				     value);
+-	case hwmon_temp_crit:
+-		return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
+-				     value);
+-	case hwmon_temp_lcrit_alarm:
+-		return aqr_hwmon_status1(phydev,
+-					 VEND1_GENERAL_STAT1_LOW_TEMP_FAIL,
+-					 value);
+-	case hwmon_temp_min_alarm:
+-		return aqr_hwmon_status1(phydev,
+-					 VEND1_GENERAL_STAT1_LOW_TEMP_WARN,
+-					 value);
+-	case hwmon_temp_max_alarm:
+-		return aqr_hwmon_status1(phydev,
+-					 VEND1_GENERAL_STAT1_HIGH_TEMP_WARN,
+-					 value);
+-	case hwmon_temp_crit_alarm:
+-		return aqr_hwmon_status1(phydev,
+-					 VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL,
+-					 value);
+-	default:
+-		return -EOPNOTSUPP;
+-	}
+-}
+-
+-static int aqr_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+-			   u32 attr, int channel, long value)
+-{
+-	struct phy_device *phydev = dev_get_drvdata(dev);
+-
+-	if (type != hwmon_temp)
+-		return -EOPNOTSUPP;
+-
+-	switch (attr) {
+-	case hwmon_temp_lcrit:
+-		return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
+-				     value);
+-	case hwmon_temp_min:
+-		return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
+-				     value);
+-	case hwmon_temp_max:
+-		return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
+-				     value);
+-	case hwmon_temp_crit:
+-		return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
+-				     value);
+-	default:
+-		return -EOPNOTSUPP;
+-	}
+-}
+-
+-static const struct hwmon_ops aqr_hwmon_ops = {
+-	.is_visible = aqr_hwmon_is_visible,
+-	.read = aqr_hwmon_read,
+-	.write = aqr_hwmon_write,
+-};
+-
+-static u32 aqr_hwmon_chip_config[] = {
+-	HWMON_C_REGISTER_TZ,
+-	0,
+-};
+-
+-static const struct hwmon_channel_info aqr_hwmon_chip = {
+-	.type = hwmon_chip,
+-	.config = aqr_hwmon_chip_config,
+-};
+-
+-static u32 aqr_hwmon_temp_config[] = {
+-	HWMON_T_INPUT |
+-	HWMON_T_MAX | HWMON_T_MIN |
+-	HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM |
+-	HWMON_T_CRIT | HWMON_T_LCRIT |
+-	HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM,
+-	0,
+-};
+-
+-static const struct hwmon_channel_info aqr_hwmon_temp = {
+-	.type = hwmon_temp,
+-	.config = aqr_hwmon_temp_config,
+-};
+-
+-static const struct hwmon_channel_info *aqr_hwmon_info[] = {
+-	&aqr_hwmon_chip,
+-	&aqr_hwmon_temp,
+-	NULL,
+-};
+-
+-static const struct hwmon_chip_info aqr_hwmon_chip_info = {
+-	.ops = &aqr_hwmon_ops,
+-	.info = aqr_hwmon_info,
+-};
+-
+-int aqr_hwmon_probe(struct phy_device *phydev)
+-{
+-	struct device *dev = &phydev->mdio.dev;
+-	struct device *hwmon_dev;
+-	char *hwmon_name;
+-	int i, j;
+-
+-	hwmon_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL);
+-	if (!hwmon_name)
+-		return -ENOMEM;
+-
+-	for (i = j = 0; hwmon_name[i]; i++) {
+-		if (isalnum(hwmon_name[i])) {
+-			if (i != j)
+-				hwmon_name[j] = hwmon_name[i];
+-			j++;
+-		}
+-	}
+-	hwmon_name[j] = '\0';
+-
+-	hwmon_dev = devm_hwmon_device_register_with_info(dev, hwmon_name,
+-					phydev, &aqr_hwmon_chip_info, NULL);
+-
+-	return PTR_ERR_OR_ZERO(hwmon_dev);
+-}
+-
+-#endif
+--- a/drivers/net/phy/aquantia_main.c
++++ /dev/null
+@@ -1,842 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-/*
+- * Driver for Aquantia PHY
+- *
+- * Author: Shaohui Xie <[email protected]>
+- *
+- * Copyright 2015 Freescale Semiconductor, Inc.
+- */
+-
+-#include <linux/kernel.h>
+-#include <linux/module.h>
+-#include <linux/delay.h>
+-#include <linux/bitfield.h>
+-#include <linux/phy.h>
+-
+-#include "aquantia.h"
+-
+-#define PHY_ID_AQ1202	0x03a1b445
+-#define PHY_ID_AQ2104	0x03a1b460
+-#define PHY_ID_AQR105	0x03a1b4a2
+-#define PHY_ID_AQR106	0x03a1b4d0
+-#define PHY_ID_AQR107	0x03a1b4e0
+-#define PHY_ID_AQCS109	0x03a1b5c2
+-#define PHY_ID_AQR405	0x03a1b4b0
+-#define PHY_ID_AQR113C	0x31c31c12
+-
+-#define MDIO_PHYXS_VEND_IF_STATUS		0xe812
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK	GENMASK(7, 3)
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR	0
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX	1
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI	2
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII	3
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI	4
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII	6
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI	7
+-#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII	10
+-
+-#define MDIO_AN_VEND_PROV			0xc400
+-#define MDIO_AN_VEND_PROV_1000BASET_FULL	BIT(15)
+-#define MDIO_AN_VEND_PROV_1000BASET_HALF	BIT(14)
+-#define MDIO_AN_VEND_PROV_5000BASET_FULL	BIT(11)
+-#define MDIO_AN_VEND_PROV_2500BASET_FULL	BIT(10)
+-#define MDIO_AN_VEND_PROV_DOWNSHIFT_EN		BIT(4)
+-#define MDIO_AN_VEND_PROV_DOWNSHIFT_MASK	GENMASK(3, 0)
+-#define MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT	4
+-
+-#define MDIO_AN_TX_VEND_STATUS1			0xc800
+-#define MDIO_AN_TX_VEND_STATUS1_RATE_MASK	GENMASK(3, 1)
+-#define MDIO_AN_TX_VEND_STATUS1_10BASET		0
+-#define MDIO_AN_TX_VEND_STATUS1_100BASETX	1
+-#define MDIO_AN_TX_VEND_STATUS1_1000BASET	2
+-#define MDIO_AN_TX_VEND_STATUS1_10GBASET	3
+-#define MDIO_AN_TX_VEND_STATUS1_2500BASET	4
+-#define MDIO_AN_TX_VEND_STATUS1_5000BASET	5
+-#define MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX	BIT(0)
+-
+-#define MDIO_AN_TX_VEND_INT_STATUS1		0xcc00
+-#define MDIO_AN_TX_VEND_INT_STATUS1_DOWNSHIFT	BIT(1)
+-
+-#define MDIO_AN_TX_VEND_INT_STATUS2		0xcc01
+-#define MDIO_AN_TX_VEND_INT_STATUS2_MASK	BIT(0)
+-
+-#define MDIO_AN_TX_VEND_INT_MASK2		0xd401
+-#define MDIO_AN_TX_VEND_INT_MASK2_LINK		BIT(0)
+-
+-#define MDIO_AN_RX_LP_STAT1			0xe820
+-#define MDIO_AN_RX_LP_STAT1_1000BASET_FULL	BIT(15)
+-#define MDIO_AN_RX_LP_STAT1_1000BASET_HALF	BIT(14)
+-#define MDIO_AN_RX_LP_STAT1_SHORT_REACH		BIT(13)
+-#define MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT	BIT(12)
+-#define MDIO_AN_RX_LP_STAT1_AQ_PHY		BIT(2)
+-
+-#define MDIO_AN_RX_LP_STAT4			0xe823
+-#define MDIO_AN_RX_LP_STAT4_FW_MAJOR		GENMASK(15, 8)
+-#define MDIO_AN_RX_LP_STAT4_FW_MINOR		GENMASK(7, 0)
+-
+-#define MDIO_AN_RX_VEND_STAT3			0xe832
+-#define MDIO_AN_RX_VEND_STAT3_AFR		BIT(0)
+-
+-/* MDIO_MMD_C22EXT */
+-#define MDIO_C22EXT_STAT_SGMII_RX_GOOD_FRAMES		0xd292
+-#define MDIO_C22EXT_STAT_SGMII_RX_BAD_FRAMES		0xd294
+-#define MDIO_C22EXT_STAT_SGMII_RX_FALSE_CARRIER		0xd297
+-#define MDIO_C22EXT_STAT_SGMII_TX_GOOD_FRAMES		0xd313
+-#define MDIO_C22EXT_STAT_SGMII_TX_BAD_FRAMES		0xd315
+-#define MDIO_C22EXT_STAT_SGMII_TX_FALSE_CARRIER		0xd317
+-#define MDIO_C22EXT_STAT_SGMII_TX_COLLISIONS		0xd318
+-#define MDIO_C22EXT_STAT_SGMII_TX_LINE_COLLISIONS	0xd319
+-#define MDIO_C22EXT_STAT_SGMII_TX_FRAME_ALIGN_ERR	0xd31a
+-#define MDIO_C22EXT_STAT_SGMII_TX_RUNT_FRAMES		0xd31b
+-
+-/* Vendor specific 1, MDIO_MMD_VEND1 */
+-#define VEND1_GLOBAL_FW_ID			0x0020
+-#define VEND1_GLOBAL_FW_ID_MAJOR		GENMASK(15, 8)
+-#define VEND1_GLOBAL_FW_ID_MINOR		GENMASK(7, 0)
+-
+-#define VEND1_GLOBAL_GEN_STAT2			0xc831
+-#define VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG	BIT(15)
+-
+-/* The following registers all have similar layouts; first the registers... */
+-#define VEND1_GLOBAL_CFG_10M			0x0310
+-#define VEND1_GLOBAL_CFG_100M			0x031b
+-#define VEND1_GLOBAL_CFG_1G			0x031c
+-#define VEND1_GLOBAL_CFG_2_5G			0x031d
+-#define VEND1_GLOBAL_CFG_5G			0x031e
+-#define VEND1_GLOBAL_CFG_10G			0x031f
+-/* ...and now the fields */
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT		GENMASK(8, 7)
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT_NONE	0
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT_USX		1
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE	2
+-
+-#define VEND1_GLOBAL_RSVD_STAT1			0xc885
+-#define VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID	GENMASK(7, 4)
+-#define VEND1_GLOBAL_RSVD_STAT1_PROV_ID		GENMASK(3, 0)
+-
+-#define VEND1_GLOBAL_RSVD_STAT9			0xc88d
+-#define VEND1_GLOBAL_RSVD_STAT9_MODE		GENMASK(7, 0)
+-#define VEND1_GLOBAL_RSVD_STAT9_1000BT2		0x23
+-
+-#define VEND1_GLOBAL_INT_STD_STATUS		0xfc00
+-#define VEND1_GLOBAL_INT_VEND_STATUS		0xfc01
+-
+-#define VEND1_GLOBAL_INT_STD_MASK		0xff00
+-#define VEND1_GLOBAL_INT_STD_MASK_PMA1		BIT(15)
+-#define VEND1_GLOBAL_INT_STD_MASK_PMA2		BIT(14)
+-#define VEND1_GLOBAL_INT_STD_MASK_PCS1		BIT(13)
+-#define VEND1_GLOBAL_INT_STD_MASK_PCS2		BIT(12)
+-#define VEND1_GLOBAL_INT_STD_MASK_PCS3		BIT(11)
+-#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS1	BIT(10)
+-#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS2	BIT(9)
+-#define VEND1_GLOBAL_INT_STD_MASK_AN1		BIT(8)
+-#define VEND1_GLOBAL_INT_STD_MASK_AN2		BIT(7)
+-#define VEND1_GLOBAL_INT_STD_MASK_GBE		BIT(6)
+-#define VEND1_GLOBAL_INT_STD_MASK_ALL		BIT(0)
+-
+-#define VEND1_GLOBAL_INT_VEND_MASK		0xff01
+-#define VEND1_GLOBAL_INT_VEND_MASK_PMA		BIT(15)
+-#define VEND1_GLOBAL_INT_VEND_MASK_PCS		BIT(14)
+-#define VEND1_GLOBAL_INT_VEND_MASK_PHY_XS	BIT(13)
+-#define VEND1_GLOBAL_INT_VEND_MASK_AN		BIT(12)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GBE		BIT(11)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL1	BIT(2)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL2	BIT(1)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3	BIT(0)
+-
+-/* Sleep and timeout for checking if the Processor-Intensive
+- * MDIO operation is finished
+- */
+-#define AQR107_OP_IN_PROG_SLEEP		1000
+-#define AQR107_OP_IN_PROG_TIMEOUT	100000
+-
+-struct aqr107_hw_stat {
+-	const char *name;
+-	int reg;
+-	int size;
+-};
+-
+-#define SGMII_STAT(n, r, s) { n, MDIO_C22EXT_STAT_SGMII_ ## r, s }
+-static const struct aqr107_hw_stat aqr107_hw_stats[] = {
+-	SGMII_STAT("sgmii_rx_good_frames",	    RX_GOOD_FRAMES,	26),
+-	SGMII_STAT("sgmii_rx_bad_frames",	    RX_BAD_FRAMES,	26),
+-	SGMII_STAT("sgmii_rx_false_carrier_events", RX_FALSE_CARRIER,	 8),
+-	SGMII_STAT("sgmii_tx_good_frames",	    TX_GOOD_FRAMES,	26),
+-	SGMII_STAT("sgmii_tx_bad_frames",	    TX_BAD_FRAMES,	26),
+-	SGMII_STAT("sgmii_tx_false_carrier_events", TX_FALSE_CARRIER,	 8),
+-	SGMII_STAT("sgmii_tx_collisions",	    TX_COLLISIONS,	 8),
+-	SGMII_STAT("sgmii_tx_line_collisions",	    TX_LINE_COLLISIONS,	 8),
+-	SGMII_STAT("sgmii_tx_frame_alignment_err",  TX_FRAME_ALIGN_ERR,	16),
+-	SGMII_STAT("sgmii_tx_runt_frames",	    TX_RUNT_FRAMES,	22),
+-};
+-#define AQR107_SGMII_STAT_SZ ARRAY_SIZE(aqr107_hw_stats)
+-
+-struct aqr107_priv {
+-	u64 sgmii_stats[AQR107_SGMII_STAT_SZ];
+-};
+-
+-static int aqr107_get_sset_count(struct phy_device *phydev)
+-{
+-	return AQR107_SGMII_STAT_SZ;
+-}
+-
+-static void aqr107_get_strings(struct phy_device *phydev, u8 *data)
+-{
+-	int i;
+-
+-	for (i = 0; i < AQR107_SGMII_STAT_SZ; i++)
+-		strscpy(data + i * ETH_GSTRING_LEN, aqr107_hw_stats[i].name,
+-			ETH_GSTRING_LEN);
+-}
+-
+-static u64 aqr107_get_stat(struct phy_device *phydev, int index)
+-{
+-	const struct aqr107_hw_stat *stat = aqr107_hw_stats + index;
+-	int len_l = min(stat->size, 16);
+-	int len_h = stat->size - len_l;
+-	u64 ret;
+-	int val;
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg);
+-	if (val < 0)
+-		return U64_MAX;
+-
+-	ret = val & GENMASK(len_l - 1, 0);
+-	if (len_h) {
+-		val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg + 1);
+-		if (val < 0)
+-			return U64_MAX;
+-
+-		ret += (val & GENMASK(len_h - 1, 0)) << 16;
+-	}
+-
+-	return ret;
+-}
+-
+-static void aqr107_get_stats(struct phy_device *phydev,
+-			     struct ethtool_stats *stats, u64 *data)
+-{
+-	struct aqr107_priv *priv = phydev->priv;
+-	u64 val;
+-	int i;
+-
+-	for (i = 0; i < AQR107_SGMII_STAT_SZ; i++) {
+-		val = aqr107_get_stat(phydev, i);
+-		if (val == U64_MAX)
+-			phydev_err(phydev, "Reading HW Statistics failed for %s\n",
+-				   aqr107_hw_stats[i].name);
+-		else
+-			priv->sgmii_stats[i] += val;
+-
+-		data[i] = priv->sgmii_stats[i];
+-	}
+-}
+-
+-static int aqr_config_aneg(struct phy_device *phydev)
+-{
+-	bool changed = false;
+-	u16 reg;
+-	int ret;
+-
+-	if (phydev->autoneg == AUTONEG_DISABLE)
+-		return genphy_c45_pma_setup_forced(phydev);
+-
+-	ret = genphy_c45_an_config_aneg(phydev);
+-	if (ret < 0)
+-		return ret;
+-	if (ret > 0)
+-		changed = true;
+-
+-	/* Clause 45 has no standardized support for 1000BaseT, therefore
+-	 * use vendor registers for this mode.
+-	 */
+-	reg = 0;
+-	if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+-			      phydev->advertising))
+-		reg |= MDIO_AN_VEND_PROV_1000BASET_FULL;
+-
+-	if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+-			      phydev->advertising))
+-		reg |= MDIO_AN_VEND_PROV_1000BASET_HALF;
+-
+-	/* Handle the case when the 2.5G and 5G speeds are not advertised */
+-	if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+-			      phydev->advertising))
+-		reg |= MDIO_AN_VEND_PROV_2500BASET_FULL;
+-
+-	if (linkmode_test_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+-			      phydev->advertising))
+-		reg |= MDIO_AN_VEND_PROV_5000BASET_FULL;
+-
+-	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
+-				     MDIO_AN_VEND_PROV_1000BASET_HALF |
+-				     MDIO_AN_VEND_PROV_1000BASET_FULL |
+-				     MDIO_AN_VEND_PROV_2500BASET_FULL |
+-				     MDIO_AN_VEND_PROV_5000BASET_FULL, reg);
+-	if (ret < 0)
+-		return ret;
+-	if (ret > 0)
+-		changed = true;
+-
+-	return genphy_c45_check_and_restart_aneg(phydev, changed);
+-}
+-
+-static int aqr_config_intr(struct phy_device *phydev)
+-{
+-	bool en = phydev->interrupts == PHY_INTERRUPT_ENABLED;
+-	int err;
+-
+-	if (en) {
+-		/* Clear any pending interrupts before enabling them */
+-		err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
+-		if (err < 0)
+-			return err;
+-	}
+-
+-	err = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_MASK2,
+-			    en ? MDIO_AN_TX_VEND_INT_MASK2_LINK : 0);
+-	if (err < 0)
+-		return err;
+-
+-	err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_STD_MASK,
+-			    en ? VEND1_GLOBAL_INT_STD_MASK_ALL : 0);
+-	if (err < 0)
+-		return err;
+-
+-	err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_VEND_MASK,
+-			    en ? VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 |
+-			    VEND1_GLOBAL_INT_VEND_MASK_AN : 0);
+-	if (err < 0)
+-		return err;
+-
+-	if (!en) {
+-		/* Clear any pending interrupts after we have disabled them */
+-		err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
+-		if (err < 0)
+-			return err;
+-	}
+-
+-	return 0;
+-}
+-
+-static irqreturn_t aqr_handle_interrupt(struct phy_device *phydev)
+-{
+-	int irq_status;
+-
+-	irq_status = phy_read_mmd(phydev, MDIO_MMD_AN,
+-				  MDIO_AN_TX_VEND_INT_STATUS2);
+-	if (irq_status < 0) {
+-		phy_error(phydev);
+-		return IRQ_NONE;
+-	}
+-
+-	if (!(irq_status & MDIO_AN_TX_VEND_INT_STATUS2_MASK))
+-		return IRQ_NONE;
+-
+-	phy_trigger_machine(phydev);
+-
+-	return IRQ_HANDLED;
+-}
+-
+-static int aqr_read_status(struct phy_device *phydev)
+-{
+-	int val;
+-
+-	if (phydev->autoneg == AUTONEG_ENABLE) {
+-		val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
+-		if (val < 0)
+-			return val;
+-
+-		linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+-				 phydev->lp_advertising,
+-				 val & MDIO_AN_RX_LP_STAT1_1000BASET_FULL);
+-		linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+-				 phydev->lp_advertising,
+-				 val & MDIO_AN_RX_LP_STAT1_1000BASET_HALF);
+-	}
+-
+-	return genphy_c45_read_status(phydev);
+-}
+-
+-static int aqr107_read_rate(struct phy_device *phydev)
+-{
+-	u32 config_reg;
+-	int val;
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_STATUS1);
+-	if (val < 0)
+-		return val;
+-
+-	if (val & MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX)
+-		phydev->duplex = DUPLEX_FULL;
+-	else
+-		phydev->duplex = DUPLEX_HALF;
+-
+-	switch (FIELD_GET(MDIO_AN_TX_VEND_STATUS1_RATE_MASK, val)) {
+-	case MDIO_AN_TX_VEND_STATUS1_10BASET:
+-		phydev->speed = SPEED_10;
+-		config_reg = VEND1_GLOBAL_CFG_10M;
+-		break;
+-	case MDIO_AN_TX_VEND_STATUS1_100BASETX:
+-		phydev->speed = SPEED_100;
+-		config_reg = VEND1_GLOBAL_CFG_100M;
+-		break;
+-	case MDIO_AN_TX_VEND_STATUS1_1000BASET:
+-		phydev->speed = SPEED_1000;
+-		config_reg = VEND1_GLOBAL_CFG_1G;
+-		break;
+-	case MDIO_AN_TX_VEND_STATUS1_2500BASET:
+-		phydev->speed = SPEED_2500;
+-		config_reg = VEND1_GLOBAL_CFG_2_5G;
+-		break;
+-	case MDIO_AN_TX_VEND_STATUS1_5000BASET:
+-		phydev->speed = SPEED_5000;
+-		config_reg = VEND1_GLOBAL_CFG_5G;
+-		break;
+-	case MDIO_AN_TX_VEND_STATUS1_10GBASET:
+-		phydev->speed = SPEED_10000;
+-		config_reg = VEND1_GLOBAL_CFG_10G;
+-		break;
+-	default:
+-		phydev->speed = SPEED_UNKNOWN;
+-		return 0;
+-	}
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, config_reg);
+-	if (val < 0)
+-		return val;
+-
+-	if (FIELD_GET(VEND1_GLOBAL_CFG_RATE_ADAPT, val) ==
+-	    VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE)
+-		phydev->rate_matching = RATE_MATCH_PAUSE;
+-	else
+-		phydev->rate_matching = RATE_MATCH_NONE;
+-
+-	return 0;
+-}
+-
+-static int aqr107_read_status(struct phy_device *phydev)
+-{
+-	int val, ret;
+-
+-	ret = aqr_read_status(phydev);
+-	if (ret)
+-		return ret;
+-
+-	if (!phydev->link || phydev->autoneg == AUTONEG_DISABLE)
+-		return 0;
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MDIO_PHYXS_VEND_IF_STATUS);
+-	if (val < 0)
+-		return val;
+-
+-	switch (FIELD_GET(MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK, val)) {
+-	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR:
+-		phydev->interface = PHY_INTERFACE_MODE_10GKR;
+-		break;
+-	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX:
+-		phydev->interface = PHY_INTERFACE_MODE_1000BASEKX;
+-		break;
+-	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI:
+-		phydev->interface = PHY_INTERFACE_MODE_10GBASER;
+-		break;
+-	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII:
+-		phydev->interface = PHY_INTERFACE_MODE_USXGMII;
+-		break;
+-	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI:
+-		phydev->interface = PHY_INTERFACE_MODE_XAUI;
+-		break;
+-	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII:
+-		phydev->interface = PHY_INTERFACE_MODE_SGMII;
+-		break;
+-	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI:
+-		phydev->interface = PHY_INTERFACE_MODE_RXAUI;
+-		break;
+-	case MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII:
+-		phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+-		break;
+-	default:
+-		phydev->interface = PHY_INTERFACE_MODE_NA;
+-		break;
+-	}
+-
+-	/* Read possibly downshifted rate from vendor register */
+-	return aqr107_read_rate(phydev);
+-}
+-
+-static int aqr107_get_downshift(struct phy_device *phydev, u8 *data)
+-{
+-	int val, cnt, enable;
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV);
+-	if (val < 0)
+-		return val;
+-
+-	enable = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_EN, val);
+-	cnt = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
+-
+-	*data = enable && cnt ? cnt : DOWNSHIFT_DEV_DISABLE;
+-
+-	return 0;
+-}
+-
+-static int aqr107_set_downshift(struct phy_device *phydev, u8 cnt)
+-{
+-	int val = 0;
+-
+-	if (!FIELD_FIT(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt))
+-		return -E2BIG;
+-
+-	if (cnt != DOWNSHIFT_DEV_DISABLE) {
+-		val = MDIO_AN_VEND_PROV_DOWNSHIFT_EN;
+-		val |= FIELD_PREP(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt);
+-	}
+-
+-	return phy_modify_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
+-			      MDIO_AN_VEND_PROV_DOWNSHIFT_EN |
+-			      MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
+-}
+-
+-static int aqr107_get_tunable(struct phy_device *phydev,
+-			      struct ethtool_tunable *tuna, void *data)
+-{
+-	switch (tuna->id) {
+-	case ETHTOOL_PHY_DOWNSHIFT:
+-		return aqr107_get_downshift(phydev, data);
+-	default:
+-		return -EOPNOTSUPP;
+-	}
+-}
+-
+-static int aqr107_set_tunable(struct phy_device *phydev,
+-			      struct ethtool_tunable *tuna, const void *data)
+-{
+-	switch (tuna->id) {
+-	case ETHTOOL_PHY_DOWNSHIFT:
+-		return aqr107_set_downshift(phydev, *(const u8 *)data);
+-	default:
+-		return -EOPNOTSUPP;
+-	}
+-}
+-
+-/* If we configure settings whilst firmware is still initializing the chip,
+- * then these settings may be overwritten. Therefore make sure chip
+- * initialization has completed. Use presence of the firmware ID as
+- * indicator for initialization having completed.
+- * The chip also provides a "reset completed" bit, but it's cleared after
+- * read. Therefore function would time out if called again.
+- */
+-static int aqr107_wait_reset_complete(struct phy_device *phydev)
+-{
+-	int val;
+-
+-	return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+-					 VEND1_GLOBAL_FW_ID, val, val != 0,
+-					 20000, 2000000, false);
+-}
+-
+-static void aqr107_chip_info(struct phy_device *phydev)
+-{
+-	u8 fw_major, fw_minor, build_id, prov_id;
+-	int val;
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_FW_ID);
+-	if (val < 0)
+-		return;
+-
+-	fw_major = FIELD_GET(VEND1_GLOBAL_FW_ID_MAJOR, val);
+-	fw_minor = FIELD_GET(VEND1_GLOBAL_FW_ID_MINOR, val);
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT1);
+-	if (val < 0)
+-		return;
+-
+-	build_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID, val);
+-	prov_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_PROV_ID, val);
+-
+-	phydev_dbg(phydev, "FW %u.%u, Build %u, Provisioning %u\n",
+-		   fw_major, fw_minor, build_id, prov_id);
+-}
+-
+-static int aqr107_config_init(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	/* Check that the PHY interface type is compatible */
+-	if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
+-	    phydev->interface != PHY_INTERFACE_MODE_1000BASEKX &&
+-	    phydev->interface != PHY_INTERFACE_MODE_2500BASEX &&
+-	    phydev->interface != PHY_INTERFACE_MODE_XGMII &&
+-	    phydev->interface != PHY_INTERFACE_MODE_USXGMII &&
+-	    phydev->interface != PHY_INTERFACE_MODE_10GKR &&
+-	    phydev->interface != PHY_INTERFACE_MODE_10GBASER &&
+-	    phydev->interface != PHY_INTERFACE_MODE_XAUI &&
+-	    phydev->interface != PHY_INTERFACE_MODE_RXAUI)
+-		return -ENODEV;
+-
+-	WARN(phydev->interface == PHY_INTERFACE_MODE_XGMII,
+-	     "Your devicetree is out of date, please update it. The AQR107 family doesn't support XGMII, maybe you mean USXGMII.\n");
+-
+-	ret = aqr107_wait_reset_complete(phydev);
+-	if (!ret)
+-		aqr107_chip_info(phydev);
+-
+-	return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
+-}
+-
+-static int aqcs109_config_init(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	/* Check that the PHY interface type is compatible */
+-	if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
+-	    phydev->interface != PHY_INTERFACE_MODE_2500BASEX)
+-		return -ENODEV;
+-
+-	ret = aqr107_wait_reset_complete(phydev);
+-	if (!ret)
+-		aqr107_chip_info(phydev);
+-
+-	/* AQCS109 belongs to a chip family partially supporting 10G and 5G.
+-	 * PMA speed ability bits are the same for all members of the family,
+-	 * AQCS109 however supports speeds up to 2.5G only.
+-	 */
+-	phy_set_max_speed(phydev, SPEED_2500);
+-
+-	return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
+-}
+-
+-static void aqr107_link_change_notify(struct phy_device *phydev)
+-{
+-	u8 fw_major, fw_minor;
+-	bool downshift, short_reach, afr;
+-	int mode, val;
+-
+-	if (phydev->state != PHY_RUNNING || phydev->autoneg == AUTONEG_DISABLE)
+-		return;
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
+-	/* call failed or link partner is no Aquantia PHY */
+-	if (val < 0 || !(val & MDIO_AN_RX_LP_STAT1_AQ_PHY))
+-		return;
+-
+-	short_reach = val & MDIO_AN_RX_LP_STAT1_SHORT_REACH;
+-	downshift = val & MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT;
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT4);
+-	if (val < 0)
+-		return;
+-
+-	fw_major = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MAJOR, val);
+-	fw_minor = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MINOR, val);
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_VEND_STAT3);
+-	if (val < 0)
+-		return;
+-
+-	afr = val & MDIO_AN_RX_VEND_STAT3_AFR;
+-
+-	phydev_dbg(phydev, "Link partner is Aquantia PHY, FW %u.%u%s%s%s\n",
+-		   fw_major, fw_minor,
+-		   short_reach ? ", short reach mode" : "",
+-		   downshift ? ", fast-retrain downshift advertised" : "",
+-		   afr ? ", fast reframe advertised" : "");
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT9);
+-	if (val < 0)
+-		return;
+-
+-	mode = FIELD_GET(VEND1_GLOBAL_RSVD_STAT9_MODE, val);
+-	if (mode == VEND1_GLOBAL_RSVD_STAT9_1000BT2)
+-		phydev_info(phydev, "Aquantia 1000Base-T2 mode active\n");
+-}
+-
+-static int aqr107_wait_processor_intensive_op(struct phy_device *phydev)
+-{
+-	int val, err;
+-
+-	/* The datasheet notes to wait at least 1ms after issuing a
+-	 * processor intensive operation before checking.
+-	 * We cannot use the 'sleep_before_read' parameter of read_poll_timeout
+-	 * because that just determines the maximum time slept, not the minimum.
+-	 */
+-	usleep_range(1000, 5000);
+-
+-	err = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+-					VEND1_GLOBAL_GEN_STAT2, val,
+-					!(val & VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG),
+-					AQR107_OP_IN_PROG_SLEEP,
+-					AQR107_OP_IN_PROG_TIMEOUT, false);
+-	if (err) {
+-		phydev_err(phydev, "timeout: processor-intensive MDIO operation\n");
+-		return err;
+-	}
+-
+-	return 0;
+-}
+-
+-static int aqr107_get_rate_matching(struct phy_device *phydev,
+-				    phy_interface_t iface)
+-{
+-	if (iface == PHY_INTERFACE_MODE_10GBASER ||
+-	    iface == PHY_INTERFACE_MODE_2500BASEX ||
+-	    iface == PHY_INTERFACE_MODE_NA)
+-		return RATE_MATCH_PAUSE;
+-	return RATE_MATCH_NONE;
+-}
+-
+-static int aqr107_suspend(struct phy_device *phydev)
+-{
+-	int err;
+-
+-	err = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
+-			       MDIO_CTRL1_LPOWER);
+-	if (err)
+-		return err;
+-
+-	return aqr107_wait_processor_intensive_op(phydev);
+-}
+-
+-static int aqr107_resume(struct phy_device *phydev)
+-{
+-	int err;
+-
+-	err = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
+-				 MDIO_CTRL1_LPOWER);
+-	if (err)
+-		return err;
+-
+-	return aqr107_wait_processor_intensive_op(phydev);
+-}
+-
+-static int aqr107_probe(struct phy_device *phydev)
+-{
+-	phydev->priv = devm_kzalloc(&phydev->mdio.dev,
+-				    sizeof(struct aqr107_priv), GFP_KERNEL);
+-	if (!phydev->priv)
+-		return -ENOMEM;
+-
+-	return aqr_hwmon_probe(phydev);
+-}
+-
+-static struct phy_driver aqr_driver[] = {
+-{
+-	PHY_ID_MATCH_MODEL(PHY_ID_AQ1202),
+-	.name		= "Aquantia AQ1202",
+-	.config_aneg    = aqr_config_aneg,
+-	.config_intr	= aqr_config_intr,
+-	.handle_interrupt = aqr_handle_interrupt,
+-	.read_status	= aqr_read_status,
+-},
+-{
+-	PHY_ID_MATCH_MODEL(PHY_ID_AQ2104),
+-	.name		= "Aquantia AQ2104",
+-	.config_aneg    = aqr_config_aneg,
+-	.config_intr	= aqr_config_intr,
+-	.handle_interrupt = aqr_handle_interrupt,
+-	.read_status	= aqr_read_status,
+-},
+-{
+-	PHY_ID_MATCH_MODEL(PHY_ID_AQR105),
+-	.name		= "Aquantia AQR105",
+-	.config_aneg    = aqr_config_aneg,
+-	.config_intr	= aqr_config_intr,
+-	.handle_interrupt = aqr_handle_interrupt,
+-	.read_status	= aqr_read_status,
+-	.suspend	= aqr107_suspend,
+-	.resume		= aqr107_resume,
+-},
+-{
+-	PHY_ID_MATCH_MODEL(PHY_ID_AQR106),
+-	.name		= "Aquantia AQR106",
+-	.config_aneg    = aqr_config_aneg,
+-	.config_intr	= aqr_config_intr,
+-	.handle_interrupt = aqr_handle_interrupt,
+-	.read_status	= aqr_read_status,
+-},
+-{
+-	PHY_ID_MATCH_MODEL(PHY_ID_AQR107),
+-	.name		= "Aquantia AQR107",
+-	.probe		= aqr107_probe,
+-	.get_rate_matching = aqr107_get_rate_matching,
+-	.config_init	= aqr107_config_init,
+-	.config_aneg    = aqr_config_aneg,
+-	.config_intr	= aqr_config_intr,
+-	.handle_interrupt = aqr_handle_interrupt,
+-	.read_status	= aqr107_read_status,
+-	.get_tunable    = aqr107_get_tunable,
+-	.set_tunable    = aqr107_set_tunable,
+-	.suspend	= aqr107_suspend,
+-	.resume		= aqr107_resume,
+-	.get_sset_count	= aqr107_get_sset_count,
+-	.get_strings	= aqr107_get_strings,
+-	.get_stats	= aqr107_get_stats,
+-	.link_change_notify = aqr107_link_change_notify,
+-},
+-{
+-	PHY_ID_MATCH_MODEL(PHY_ID_AQCS109),
+-	.name		= "Aquantia AQCS109",
+-	.probe		= aqr107_probe,
+-	.get_rate_matching = aqr107_get_rate_matching,
+-	.config_init	= aqcs109_config_init,
+-	.config_aneg    = aqr_config_aneg,
+-	.config_intr	= aqr_config_intr,
+-	.handle_interrupt = aqr_handle_interrupt,
+-	.read_status	= aqr107_read_status,
+-	.get_tunable    = aqr107_get_tunable,
+-	.set_tunable    = aqr107_set_tunable,
+-	.suspend	= aqr107_suspend,
+-	.resume		= aqr107_resume,
+-	.get_sset_count	= aqr107_get_sset_count,
+-	.get_strings	= aqr107_get_strings,
+-	.get_stats	= aqr107_get_stats,
+-	.link_change_notify = aqr107_link_change_notify,
+-},
+-{
+-	PHY_ID_MATCH_MODEL(PHY_ID_AQR405),
+-	.name		= "Aquantia AQR405",
+-	.config_aneg    = aqr_config_aneg,
+-	.config_intr	= aqr_config_intr,
+-	.handle_interrupt = aqr_handle_interrupt,
+-	.read_status	= aqr_read_status,
+-},
+-{
+-	PHY_ID_MATCH_MODEL(PHY_ID_AQR113C),
+-	.name           = "Aquantia AQR113C",
+-	.probe          = aqr107_probe,
+-	.get_rate_matching = aqr107_get_rate_matching,
+-	.config_init    = aqr107_config_init,
+-	.config_aneg    = aqr_config_aneg,
+-	.config_intr    = aqr_config_intr,
+-	.handle_interrupt       = aqr_handle_interrupt,
+-	.read_status    = aqr107_read_status,
+-	.get_tunable    = aqr107_get_tunable,
+-	.set_tunable    = aqr107_set_tunable,
+-	.suspend        = aqr107_suspend,
+-	.resume         = aqr107_resume,
+-	.get_sset_count = aqr107_get_sset_count,
+-	.get_strings    = aqr107_get_strings,
+-	.get_stats      = aqr107_get_stats,
+-	.link_change_notify = aqr107_link_change_notify,
+-},
+-};
+-
+-module_phy_driver(aqr_driver);
+-
+-static struct mdio_device_id __maybe_unused aqr_tbl[] = {
+-	{ PHY_ID_MATCH_MODEL(PHY_ID_AQ1202) },
+-	{ PHY_ID_MATCH_MODEL(PHY_ID_AQ2104) },
+-	{ PHY_ID_MATCH_MODEL(PHY_ID_AQR105) },
+-	{ PHY_ID_MATCH_MODEL(PHY_ID_AQR106) },
+-	{ PHY_ID_MATCH_MODEL(PHY_ID_AQR107) },
+-	{ PHY_ID_MATCH_MODEL(PHY_ID_AQCS109) },
+-	{ PHY_ID_MATCH_MODEL(PHY_ID_AQR405) },
+-	{ PHY_ID_MATCH_MODEL(PHY_ID_AQR113C) },
+-	{ }
+-};
+-
+-MODULE_DEVICE_TABLE(mdio, aqr_tbl);
+-
+-MODULE_DESCRIPTION("Aquantia PHY driver");
+-MODULE_AUTHOR("Shaohui Xie <[email protected]>");
+-MODULE_LICENSE("GPL v2");

+ 183 - 0
target/linux/generic/backport-6.6/702-02-v6.7-net-phy-aquantia-move-MMD_VEND-define-to-header.patch

@@ -0,0 +1,183 @@
+From e1fbfa4a995d42e02e22b0dff2f8b4fdee1504b3 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Tue, 14 Nov 2023 15:08:42 +0100
+Subject: [PATCH 2/3] net: phy: aquantia: move MMD_VEND define to header
+
+Move MMD_VEND define to header to clean things up and in preparation for
+firmware loading support that require some define placed in
+aquantia_main.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/aquantia/aquantia.h       | 69 +++++++++++++++++++++++
+ drivers/net/phy/aquantia/aquantia_hwmon.c | 14 -----
+ drivers/net/phy/aquantia/aquantia_main.c  | 55 ------------------
+ 3 files changed, 69 insertions(+), 69 deletions(-)
+
+--- a/drivers/net/phy/aquantia/aquantia.h
++++ b/drivers/net/phy/aquantia/aquantia.h
+@@ -9,6 +9,75 @@
+ #include <linux/device.h>
+ #include <linux/phy.h>
+ 
++/* Vendor specific 1, MDIO_MMD_VEND1 */
++#define VEND1_GLOBAL_FW_ID			0x0020
++#define VEND1_GLOBAL_FW_ID_MAJOR		GENMASK(15, 8)
++#define VEND1_GLOBAL_FW_ID_MINOR		GENMASK(7, 0)
++
++/* The following registers all have similar layouts; first the registers... */
++#define VEND1_GLOBAL_CFG_10M			0x0310
++#define VEND1_GLOBAL_CFG_100M			0x031b
++#define VEND1_GLOBAL_CFG_1G			0x031c
++#define VEND1_GLOBAL_CFG_2_5G			0x031d
++#define VEND1_GLOBAL_CFG_5G			0x031e
++#define VEND1_GLOBAL_CFG_10G			0x031f
++/* ...and now the fields */
++#define VEND1_GLOBAL_CFG_RATE_ADAPT		GENMASK(8, 7)
++#define VEND1_GLOBAL_CFG_RATE_ADAPT_NONE	0
++#define VEND1_GLOBAL_CFG_RATE_ADAPT_USX		1
++#define VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE	2
++
++/* Vendor specific 1, MDIO_MMD_VEND2 */
++#define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL	0xc421
++#define VEND1_THERMAL_PROV_LOW_TEMP_FAIL	0xc422
++#define VEND1_THERMAL_PROV_HIGH_TEMP_WARN	0xc423
++#define VEND1_THERMAL_PROV_LOW_TEMP_WARN	0xc424
++#define VEND1_THERMAL_STAT1			0xc820
++#define VEND1_THERMAL_STAT2			0xc821
++#define VEND1_THERMAL_STAT2_VALID		BIT(0)
++#define VEND1_GENERAL_STAT1			0xc830
++#define VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL	BIT(14)
++#define VEND1_GENERAL_STAT1_LOW_TEMP_FAIL	BIT(13)
++#define VEND1_GENERAL_STAT1_HIGH_TEMP_WARN	BIT(12)
++#define VEND1_GENERAL_STAT1_LOW_TEMP_WARN	BIT(11)
++
++#define VEND1_GLOBAL_GEN_STAT2			0xc831
++#define VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG	BIT(15)
++
++#define VEND1_GLOBAL_RSVD_STAT1			0xc885
++#define VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID	GENMASK(7, 4)
++#define VEND1_GLOBAL_RSVD_STAT1_PROV_ID		GENMASK(3, 0)
++
++#define VEND1_GLOBAL_RSVD_STAT9			0xc88d
++#define VEND1_GLOBAL_RSVD_STAT9_MODE		GENMASK(7, 0)
++#define VEND1_GLOBAL_RSVD_STAT9_1000BT2		0x23
++
++#define VEND1_GLOBAL_INT_STD_STATUS		0xfc00
++#define VEND1_GLOBAL_INT_VEND_STATUS		0xfc01
++
++#define VEND1_GLOBAL_INT_STD_MASK		0xff00
++#define VEND1_GLOBAL_INT_STD_MASK_PMA1		BIT(15)
++#define VEND1_GLOBAL_INT_STD_MASK_PMA2		BIT(14)
++#define VEND1_GLOBAL_INT_STD_MASK_PCS1		BIT(13)
++#define VEND1_GLOBAL_INT_STD_MASK_PCS2		BIT(12)
++#define VEND1_GLOBAL_INT_STD_MASK_PCS3		BIT(11)
++#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS1	BIT(10)
++#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS2	BIT(9)
++#define VEND1_GLOBAL_INT_STD_MASK_AN1		BIT(8)
++#define VEND1_GLOBAL_INT_STD_MASK_AN2		BIT(7)
++#define VEND1_GLOBAL_INT_STD_MASK_GBE		BIT(6)
++#define VEND1_GLOBAL_INT_STD_MASK_ALL		BIT(0)
++
++#define VEND1_GLOBAL_INT_VEND_MASK		0xff01
++#define VEND1_GLOBAL_INT_VEND_MASK_PMA		BIT(15)
++#define VEND1_GLOBAL_INT_VEND_MASK_PCS		BIT(14)
++#define VEND1_GLOBAL_INT_VEND_MASK_PHY_XS	BIT(13)
++#define VEND1_GLOBAL_INT_VEND_MASK_AN		BIT(12)
++#define VEND1_GLOBAL_INT_VEND_MASK_GBE		BIT(11)
++#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL1	BIT(2)
++#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL2	BIT(1)
++#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3	BIT(0)
++
+ #if IS_REACHABLE(CONFIG_HWMON)
+ int aqr_hwmon_probe(struct phy_device *phydev);
+ #else
+--- a/drivers/net/phy/aquantia/aquantia_hwmon.c
++++ b/drivers/net/phy/aquantia/aquantia_hwmon.c
+@@ -13,20 +13,6 @@
+ 
+ #include "aquantia.h"
+ 
+-/* Vendor specific 1, MDIO_MMD_VEND2 */
+-#define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL	0xc421
+-#define VEND1_THERMAL_PROV_LOW_TEMP_FAIL	0xc422
+-#define VEND1_THERMAL_PROV_HIGH_TEMP_WARN	0xc423
+-#define VEND1_THERMAL_PROV_LOW_TEMP_WARN	0xc424
+-#define VEND1_THERMAL_STAT1			0xc820
+-#define VEND1_THERMAL_STAT2			0xc821
+-#define VEND1_THERMAL_STAT2_VALID		BIT(0)
+-#define VEND1_GENERAL_STAT1			0xc830
+-#define VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL	BIT(14)
+-#define VEND1_GENERAL_STAT1_LOW_TEMP_FAIL	BIT(13)
+-#define VEND1_GENERAL_STAT1_HIGH_TEMP_WARN	BIT(12)
+-#define VEND1_GENERAL_STAT1_LOW_TEMP_WARN	BIT(11)
+-
+ #if IS_REACHABLE(CONFIG_HWMON)
+ 
+ static umode_t aqr_hwmon_is_visible(const void *data,
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -89,61 +89,6 @@
+ #define MDIO_C22EXT_STAT_SGMII_TX_FRAME_ALIGN_ERR	0xd31a
+ #define MDIO_C22EXT_STAT_SGMII_TX_RUNT_FRAMES		0xd31b
+ 
+-/* Vendor specific 1, MDIO_MMD_VEND1 */
+-#define VEND1_GLOBAL_FW_ID			0x0020
+-#define VEND1_GLOBAL_FW_ID_MAJOR		GENMASK(15, 8)
+-#define VEND1_GLOBAL_FW_ID_MINOR		GENMASK(7, 0)
+-
+-#define VEND1_GLOBAL_GEN_STAT2			0xc831
+-#define VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG	BIT(15)
+-
+-/* The following registers all have similar layouts; first the registers... */
+-#define VEND1_GLOBAL_CFG_10M			0x0310
+-#define VEND1_GLOBAL_CFG_100M			0x031b
+-#define VEND1_GLOBAL_CFG_1G			0x031c
+-#define VEND1_GLOBAL_CFG_2_5G			0x031d
+-#define VEND1_GLOBAL_CFG_5G			0x031e
+-#define VEND1_GLOBAL_CFG_10G			0x031f
+-/* ...and now the fields */
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT		GENMASK(8, 7)
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT_NONE	0
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT_USX		1
+-#define VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE	2
+-
+-#define VEND1_GLOBAL_RSVD_STAT1			0xc885
+-#define VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID	GENMASK(7, 4)
+-#define VEND1_GLOBAL_RSVD_STAT1_PROV_ID		GENMASK(3, 0)
+-
+-#define VEND1_GLOBAL_RSVD_STAT9			0xc88d
+-#define VEND1_GLOBAL_RSVD_STAT9_MODE		GENMASK(7, 0)
+-#define VEND1_GLOBAL_RSVD_STAT9_1000BT2		0x23
+-
+-#define VEND1_GLOBAL_INT_STD_STATUS		0xfc00
+-#define VEND1_GLOBAL_INT_VEND_STATUS		0xfc01
+-
+-#define VEND1_GLOBAL_INT_STD_MASK		0xff00
+-#define VEND1_GLOBAL_INT_STD_MASK_PMA1		BIT(15)
+-#define VEND1_GLOBAL_INT_STD_MASK_PMA2		BIT(14)
+-#define VEND1_GLOBAL_INT_STD_MASK_PCS1		BIT(13)
+-#define VEND1_GLOBAL_INT_STD_MASK_PCS2		BIT(12)
+-#define VEND1_GLOBAL_INT_STD_MASK_PCS3		BIT(11)
+-#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS1	BIT(10)
+-#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS2	BIT(9)
+-#define VEND1_GLOBAL_INT_STD_MASK_AN1		BIT(8)
+-#define VEND1_GLOBAL_INT_STD_MASK_AN2		BIT(7)
+-#define VEND1_GLOBAL_INT_STD_MASK_GBE		BIT(6)
+-#define VEND1_GLOBAL_INT_STD_MASK_ALL		BIT(0)
+-
+-#define VEND1_GLOBAL_INT_VEND_MASK		0xff01
+-#define VEND1_GLOBAL_INT_VEND_MASK_PMA		BIT(15)
+-#define VEND1_GLOBAL_INT_VEND_MASK_PCS		BIT(14)
+-#define VEND1_GLOBAL_INT_VEND_MASK_PHY_XS	BIT(13)
+-#define VEND1_GLOBAL_INT_VEND_MASK_AN		BIT(12)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GBE		BIT(11)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL1	BIT(2)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL2	BIT(1)
+-#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3	BIT(0)
+-
+ /* Sleep and timeout for checking if the Processor-Intensive
+  * MDIO operation is finished
+  */

+ 504 - 0
target/linux/generic/backport-6.6/702-03-v6.7-net-phy-aquantia-add-firmware-load-support.patch

@@ -0,0 +1,504 @@
+From e93984ebc1c82bd34f7a1b3391efaceee0a8ae96 Mon Sep 17 00:00:00 2001
+From: Robert Marko <[email protected]>
+Date: Tue, 14 Nov 2023 15:08:43 +0100
+Subject: [PATCH 3/3] net: phy: aquantia: add firmware load support
+
+Aquantia PHY-s require firmware to be loaded before they start operating.
+It can be automatically loaded in case when there is a SPI-NOR connected
+to Aquantia PHY-s or can be loaded from the host via MDIO.
+
+This patch adds support for loading the firmware via MDIO as in most cases
+there is no SPI-NOR being used to save on cost.
+Firmware loading code itself is ported from mainline U-boot with cleanups.
+
+The firmware has mixed values both in big and little endian.
+PHY core itself is big-endian but it expects values to be in little-endian.
+The firmware is little-endian but CRC-16 value for it is stored at the end
+of firmware in big-endian.
+
+It seems the PHY does the conversion internally from firmware that is
+little-endian to the PHY that is big-endian on using the mailbox
+but mailbox returns a big-endian CRC-16 to verify the written data
+integrity.
+
+Co-developed-by: Christian Marangi <[email protected]>
+Signed-off-by: Robert Marko <[email protected]>
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/aquantia/Kconfig             |   1 +
+ drivers/net/phy/aquantia/Makefile            |   2 +-
+ drivers/net/phy/aquantia/aquantia.h          |  32 ++
+ drivers/net/phy/aquantia/aquantia_firmware.c | 370 +++++++++++++++++++
+ drivers/net/phy/aquantia/aquantia_main.c     |   6 +
+ 5 files changed, 410 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/phy/aquantia/aquantia_firmware.c
+
+--- a/drivers/net/phy/aquantia/Kconfig
++++ b/drivers/net/phy/aquantia/Kconfig
+@@ -1,5 +1,6 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+ config AQUANTIA_PHY
+ 	tristate "Aquantia PHYs"
++	select CRC_CCITT
+ 	help
+ 	  Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405
+--- a/drivers/net/phy/aquantia/Makefile
++++ b/drivers/net/phy/aquantia/Makefile
+@@ -1,5 +1,5 @@
+ # SPDX-License-Identifier: GPL-2.0
+-aquantia-objs			+= aquantia_main.o
++aquantia-objs			+= aquantia_main.o aquantia_firmware.o
+ ifdef CONFIG_HWMON
+ aquantia-objs			+= aquantia_hwmon.o
+ endif
+--- a/drivers/net/phy/aquantia/aquantia.h
++++ b/drivers/net/phy/aquantia/aquantia.h
+@@ -10,10 +10,35 @@
+ #include <linux/phy.h>
+ 
+ /* Vendor specific 1, MDIO_MMD_VEND1 */
++#define VEND1_GLOBAL_SC				0x0
++#define VEND1_GLOBAL_SC_SOFT_RESET		BIT(15)
++#define VEND1_GLOBAL_SC_LOW_POWER		BIT(11)
++
+ #define VEND1_GLOBAL_FW_ID			0x0020
+ #define VEND1_GLOBAL_FW_ID_MAJOR		GENMASK(15, 8)
+ #define VEND1_GLOBAL_FW_ID_MINOR		GENMASK(7, 0)
+ 
++#define VEND1_GLOBAL_MAILBOX_INTERFACE1			0x0200
++#define VEND1_GLOBAL_MAILBOX_INTERFACE1_EXECUTE		BIT(15)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE1_WRITE		BIT(14)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE1_CRC_RESET	BIT(12)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE1_BUSY		BIT(8)
++
++#define VEND1_GLOBAL_MAILBOX_INTERFACE2			0x0201
++#define VEND1_GLOBAL_MAILBOX_INTERFACE3			0x0202
++#define VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR_MASK	GENMASK(15, 0)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR(x)	FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR_MASK, (u16)((x) >> 16))
++#define VEND1_GLOBAL_MAILBOX_INTERFACE4			0x0203
++#define VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR_MASK	GENMASK(15, 2)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR(x)	FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR_MASK, (u16)(x))
++
++#define VEND1_GLOBAL_MAILBOX_INTERFACE5			0x0204
++#define VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA_MASK	GENMASK(15, 0)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA(x)	FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA_MASK, (u16)((x) >> 16))
++#define VEND1_GLOBAL_MAILBOX_INTERFACE6			0x0205
++#define VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA_MASK	GENMASK(15, 0)
++#define VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA(x)	FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA_MASK, (u16)(x))
++
+ /* The following registers all have similar layouts; first the registers... */
+ #define VEND1_GLOBAL_CFG_10M			0x0310
+ #define VEND1_GLOBAL_CFG_100M			0x031b
+@@ -28,6 +53,11 @@
+ #define VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE	2
+ 
+ /* Vendor specific 1, MDIO_MMD_VEND2 */
++#define VEND1_GLOBAL_CONTROL2			0xc001
++#define VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_RST	BIT(15)
++#define VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD	BIT(6)
++#define VEND1_GLOBAL_CONTROL2_UP_RUN_STALL	BIT(0)
++
+ #define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL	0xc421
+ #define VEND1_THERMAL_PROV_LOW_TEMP_FAIL	0xc422
+ #define VEND1_THERMAL_PROV_HIGH_TEMP_WARN	0xc423
+@@ -83,3 +113,5 @@ int aqr_hwmon_probe(struct phy_device *p
+ #else
+ static inline int aqr_hwmon_probe(struct phy_device *phydev) { return 0; }
+ #endif
++
++int aqr_firmware_load(struct phy_device *phydev);
+--- /dev/null
++++ b/drivers/net/phy/aquantia/aquantia_firmware.c
+@@ -0,0 +1,370 @@
++// SPDX-License-Identifier: GPL-2.0
++
++#include <linux/bitfield.h>
++#include <linux/of.h>
++#include <linux/firmware.h>
++#include <linux/crc-ccitt.h>
++#include <linux/nvmem-consumer.h>
++
++#include <asm/unaligned.h>
++
++#include "aquantia.h"
++
++#define UP_RESET_SLEEP		100
++
++/* addresses of memory segments in the phy */
++#define DRAM_BASE_ADDR		0x3FFE0000
++#define IRAM_BASE_ADDR		0x40000000
++
++/* firmware image format constants */
++#define VERSION_STRING_SIZE		0x40
++#define VERSION_STRING_OFFSET		0x0200
++/* primary offset is written at an offset from the start of the fw blob */
++#define PRIMARY_OFFSET_OFFSET		0x8
++/* primary offset needs to be then added to a base offset */
++#define PRIMARY_OFFSET_SHIFT		12
++#define PRIMARY_OFFSET(x)		((x) << PRIMARY_OFFSET_SHIFT)
++#define HEADER_OFFSET			0x300
++
++struct aqr_fw_header {
++	u32 padding;
++	u8 iram_offset[3];
++	u8 iram_size[3];
++	u8 dram_offset[3];
++	u8 dram_size[3];
++} __packed;
++
++enum aqr_fw_src {
++	AQR_FW_SRC_NVMEM = 0,
++	AQR_FW_SRC_FS,
++};
++
++static const char * const aqr_fw_src_string[] = {
++	[AQR_FW_SRC_NVMEM] = "NVMEM",
++	[AQR_FW_SRC_FS] = "FS",
++};
++
++/* AQR firmware doesn't have fixed offsets for iram and dram section
++ * but instead provide an header with the offset to use on reading
++ * and parsing the firmware.
++ *
++ * AQR firmware can't be trusted and each offset is validated to be
++ * not negative and be in the size of the firmware itself.
++ */
++static bool aqr_fw_validate_get(size_t size, size_t offset, size_t get_size)
++{
++	return offset + get_size <= size;
++}
++
++static int aqr_fw_get_be16(const u8 *data, size_t offset, size_t size, u16 *value)
++{
++	if (!aqr_fw_validate_get(size, offset, sizeof(u16)))
++		return -EINVAL;
++
++	*value = get_unaligned_be16(data + offset);
++
++	return 0;
++}
++
++static int aqr_fw_get_le16(const u8 *data, size_t offset, size_t size, u16 *value)
++{
++	if (!aqr_fw_validate_get(size, offset, sizeof(u16)))
++		return -EINVAL;
++
++	*value = get_unaligned_le16(data + offset);
++
++	return 0;
++}
++
++static int aqr_fw_get_le24(const u8 *data, size_t offset, size_t size, u32 *value)
++{
++	if (!aqr_fw_validate_get(size, offset, sizeof(u8) * 3))
++		return -EINVAL;
++
++	*value = get_unaligned_le24(data + offset);
++
++	return 0;
++}
++
++/* load data into the phy's memory */
++static int aqr_fw_load_memory(struct phy_device *phydev, u32 addr,
++			      const u8 *data, size_t len)
++{
++	u16 crc = 0, up_crc;
++	size_t pos;
++
++	/* PHY expect addr in LE */
++	addr = (__force u32)cpu_to_le32(addr);
++
++	phy_write_mmd(phydev, MDIO_MMD_VEND1,
++		      VEND1_GLOBAL_MAILBOX_INTERFACE1,
++		      VEND1_GLOBAL_MAILBOX_INTERFACE1_CRC_RESET);
++	phy_write_mmd(phydev, MDIO_MMD_VEND1,
++		      VEND1_GLOBAL_MAILBOX_INTERFACE3,
++		      VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR(addr));
++	phy_write_mmd(phydev, MDIO_MMD_VEND1,
++		      VEND1_GLOBAL_MAILBOX_INTERFACE4,
++		      VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR(addr));
++
++	/* We assume and enforce the size to be word aligned.
++	 * If a firmware that is not word aligned is found, please report upstream.
++	 */
++	for (pos = 0; pos < len; pos += sizeof(u32)) {
++		u32 word;
++
++		/* FW data is always stored in little-endian */
++		word = get_unaligned((const u32 *)(data + pos));
++
++		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE5,
++			      VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA(word));
++		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE6,
++			      VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA(word));
++
++		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE1,
++			      VEND1_GLOBAL_MAILBOX_INTERFACE1_EXECUTE |
++			      VEND1_GLOBAL_MAILBOX_INTERFACE1_WRITE);
++
++		/* calculate CRC as we load data to the mailbox.
++		 * We convert word to big-endian as PHY is BE and mailbox will
++		 * return a BE CRC.
++		 */
++		word = (__force u32)cpu_to_be32(word);
++		crc = crc_ccitt_false(crc, (u8 *)&word, sizeof(word));
++	}
++
++	up_crc = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE2);
++	if (crc != up_crc) {
++		phydev_err(phydev, "CRC mismatch: calculated 0x%04x PHY 0x%04x\n",
++			   crc, up_crc);
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
++static int aqr_fw_boot(struct phy_device *phydev, const u8 *data, size_t size,
++		       enum aqr_fw_src fw_src)
++{
++	u16 calculated_crc, read_crc, read_primary_offset;
++	u32 iram_offset = 0, iram_size = 0;
++	u32 dram_offset = 0, dram_size = 0;
++	char version[VERSION_STRING_SIZE];
++	u32 primary_offset = 0;
++	int ret;
++
++	/* extract saved CRC at the end of the fw
++	 * CRC is saved in big-endian as PHY is BE
++	 */
++	ret = aqr_fw_get_be16(data, size - sizeof(u16), size, &read_crc);
++	if (ret) {
++		phydev_err(phydev, "bad firmware CRC in firmware\n");
++		return ret;
++	}
++	calculated_crc = crc_ccitt_false(0, data, size - sizeof(u16));
++	if (read_crc != calculated_crc) {
++		phydev_err(phydev, "bad firmware CRC: file 0x%04x calculated 0x%04x\n",
++			   read_crc, calculated_crc);
++		return -EINVAL;
++	}
++
++	/* Get the primary offset to extract DRAM and IRAM sections. */
++	ret = aqr_fw_get_le16(data, PRIMARY_OFFSET_OFFSET, size, &read_primary_offset);
++	if (ret) {
++		phydev_err(phydev, "bad primary offset in firmware\n");
++		return ret;
++	}
++	primary_offset = PRIMARY_OFFSET(read_primary_offset);
++
++	/* Find the DRAM and IRAM sections within the firmware file.
++	 * Make sure the fw_header is correctly in the firmware.
++	 */
++	if (!aqr_fw_validate_get(size, primary_offset + HEADER_OFFSET,
++				 sizeof(struct aqr_fw_header))) {
++		phydev_err(phydev, "bad fw_header in firmware\n");
++		return -EINVAL;
++	}
++
++	/* offset are in LE and values needs to be converted to cpu endian */
++	ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET +
++			      offsetof(struct aqr_fw_header, iram_offset),
++			      size, &iram_offset);
++	if (ret) {
++		phydev_err(phydev, "bad iram offset in firmware\n");
++		return ret;
++	}
++	ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET +
++			      offsetof(struct aqr_fw_header, iram_size),
++			      size, &iram_size);
++	if (ret) {
++		phydev_err(phydev, "invalid iram size in firmware\n");
++		return ret;
++	}
++	ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET +
++			      offsetof(struct aqr_fw_header, dram_offset),
++			      size, &dram_offset);
++	if (ret) {
++		phydev_err(phydev, "bad dram offset in firmware\n");
++		return ret;
++	}
++	ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET +
++			      offsetof(struct aqr_fw_header, dram_size),
++			      size, &dram_size);
++	if (ret) {
++		phydev_err(phydev, "invalid dram size in firmware\n");
++		return ret;
++	}
++
++	/* Increment the offset with the primary offset.
++	 * Validate iram/dram offset and size.
++	 */
++	iram_offset += primary_offset;
++	if (iram_size % sizeof(u32)) {
++		phydev_err(phydev, "iram size if not aligned to word size. Please report this upstream!\n");
++		return -EINVAL;
++	}
++	if (!aqr_fw_validate_get(size, iram_offset, iram_size)) {
++		phydev_err(phydev, "invalid iram offset for iram size\n");
++		return -EINVAL;
++	}
++
++	dram_offset += primary_offset;
++	if (dram_size % sizeof(u32)) {
++		phydev_err(phydev, "dram size if not aligned to word size. Please report this upstream!\n");
++		return -EINVAL;
++	}
++	if (!aqr_fw_validate_get(size, dram_offset, dram_size)) {
++		phydev_err(phydev, "invalid iram offset for iram size\n");
++		return -EINVAL;
++	}
++
++	phydev_dbg(phydev, "primary %d IRAM offset=%d size=%d DRAM offset=%d size=%d\n",
++		   primary_offset, iram_offset, iram_size, dram_offset, dram_size);
++
++	if (!aqr_fw_validate_get(size, dram_offset + VERSION_STRING_OFFSET,
++				 VERSION_STRING_SIZE)) {
++		phydev_err(phydev, "invalid version in firmware\n");
++		return -EINVAL;
++	}
++	strscpy(version, (char *)data + dram_offset + VERSION_STRING_OFFSET,
++		VERSION_STRING_SIZE);
++	if (version[0] == '\0') {
++		phydev_err(phydev, "invalid version in firmware\n");
++		return -EINVAL;
++	}
++	phydev_info(phydev, "loading firmware version '%s' from '%s'\n", version,
++		    aqr_fw_src_string[fw_src]);
++
++	/* stall the microcprocessor */
++	phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_CONTROL2,
++		      VEND1_GLOBAL_CONTROL2_UP_RUN_STALL | VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD);
++
++	phydev_dbg(phydev, "loading DRAM 0x%08x from offset=%d size=%d\n",
++		   DRAM_BASE_ADDR, dram_offset, dram_size);
++	ret = aqr_fw_load_memory(phydev, DRAM_BASE_ADDR, data + dram_offset,
++				 dram_size);
++	if (ret)
++		return ret;
++
++	phydev_dbg(phydev, "loading IRAM 0x%08x from offset=%d size=%d\n",
++		   IRAM_BASE_ADDR, iram_offset, iram_size);
++	ret = aqr_fw_load_memory(phydev, IRAM_BASE_ADDR, data + iram_offset,
++				 iram_size);
++	if (ret)
++		return ret;
++
++	/* make sure soft reset and low power mode are clear */
++	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_SC,
++			   VEND1_GLOBAL_SC_SOFT_RESET | VEND1_GLOBAL_SC_LOW_POWER);
++
++	/* Release the microprocessor. UP_RESET must be held for 100 usec. */
++	phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_CONTROL2,
++		      VEND1_GLOBAL_CONTROL2_UP_RUN_STALL |
++		      VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD |
++		      VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_RST);
++	usleep_range(UP_RESET_SLEEP, UP_RESET_SLEEP * 2);
++
++	phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_CONTROL2,
++		      VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD);
++
++	return 0;
++}
++
++static int aqr_firmware_load_nvmem(struct phy_device *phydev)
++{
++	struct nvmem_cell *cell;
++	size_t size;
++	u8 *buf;
++	int ret;
++
++	cell = nvmem_cell_get(&phydev->mdio.dev, "firmware");
++	if (IS_ERR(cell))
++		return PTR_ERR(cell);
++
++	buf = nvmem_cell_read(cell, &size);
++	if (IS_ERR(buf)) {
++		ret = PTR_ERR(buf);
++		goto exit;
++	}
++
++	ret = aqr_fw_boot(phydev, buf, size, AQR_FW_SRC_NVMEM);
++	if (ret)
++		phydev_err(phydev, "firmware loading failed: %d\n", ret);
++
++	kfree(buf);
++exit:
++	nvmem_cell_put(cell);
++
++	return ret;
++}
++
++static int aqr_firmware_load_fs(struct phy_device *phydev)
++{
++	struct device *dev = &phydev->mdio.dev;
++	const struct firmware *fw;
++	const char *fw_name;
++	int ret;
++
++	ret = of_property_read_string(dev->of_node, "firmware-name",
++				      &fw_name);
++	if (ret)
++		return ret;
++
++	ret = request_firmware(&fw, fw_name, dev);
++	if (ret) {
++		phydev_err(phydev, "failed to find FW file %s (%d)\n",
++			   fw_name, ret);
++		return ret;
++	}
++
++	ret = aqr_fw_boot(phydev, fw->data, fw->size, AQR_FW_SRC_FS);
++	if (ret)
++		phydev_err(phydev, "firmware loading failed: %d\n", ret);
++
++	release_firmware(fw);
++
++	return ret;
++}
++
++int aqr_firmware_load(struct phy_device *phydev)
++{
++	int ret;
++
++	/* Check if the firmware is not already loaded by pooling
++	 * the current version returned by the PHY. If 0 is returned,
++	 * no firmware is loaded.
++	 */
++	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_FW_ID);
++	if (ret > 0)
++		goto exit;
++
++	ret = aqr_firmware_load_nvmem(phydev);
++	if (!ret)
++		goto exit;
++
++	ret = aqr_firmware_load_fs(phydev);
++	if (ret)
++		return ret;
++
++exit:
++	return 0;
++}
+--- a/drivers/net/phy/aquantia/aquantia_main.c
++++ b/drivers/net/phy/aquantia/aquantia_main.c
+@@ -656,11 +656,17 @@ static int aqr107_resume(struct phy_devi
+ 
+ static int aqr107_probe(struct phy_device *phydev)
+ {
++	int ret;
++
+ 	phydev->priv = devm_kzalloc(&phydev->mdio.dev,
+ 				    sizeof(struct aqr107_priv), GFP_KERNEL);
+ 	if (!phydev->priv)
+ 		return -ENOMEM;
+ 
++	ret = aqr_firmware_load(phydev);
++	if (ret)
++		return ret;
++
+ 	return aqr_hwmon_probe(phydev);
+ }
+ 

+ 326 - 0
target/linux/generic/backport-6.6/703-v6.3-net-mdio-Add-dedicated-C45-API-to-MDIO-bus-drivers.patch

@@ -0,0 +1,326 @@
+From 4e4aafcddbbfcdd6eed5780e190fcbfac8b4685a Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <[email protected]>
+Date: Mon, 9 Jan 2023 16:30:41 +0100
+Subject: [PATCH] net: mdio: Add dedicated C45 API to MDIO bus drivers
+
+Currently C22 and C45 transactions are mixed over a combined API calls
+which make use of a special bit in the reg address to indicate if a
+C45 transaction should be performed. This makes it impossible to know
+if the bus driver actually supports C45. Additionally, many C22 only
+drivers don't return -EOPNOTSUPP when asked to perform a C45
+transaction, they mistaking perform a C22 transaction.
+
+This is the first step to cleanly separate C22 from C45. To maintain
+backwards compatibility until all drivers which are capable of
+performing C45 are converted to this new API, the helper functions
+will fall back to the older API if the new API is not
+supported. Eventually this fallback will be removed.
+
+Signed-off-by: Andrew Lunn <[email protected]>
+Signed-off-by: Michael Walle <[email protected]>
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/mdio_bus.c | 189 +++++++++++++++++++++++++++++++++++++
+ include/linux/mdio.h       |  39 ++++----
+ include/linux/phy.h        |   5 +
+ 3 files changed, 214 insertions(+), 19 deletions(-)
+
+--- a/drivers/net/phy/mdio_bus.c
++++ b/drivers/net/phy/mdio_bus.c
+@@ -832,6 +832,100 @@ int __mdiobus_modify_changed(struct mii_
+ EXPORT_SYMBOL_GPL(__mdiobus_modify_changed);
+ 
+ /**
++ * __mdiobus_c45_read - Unlocked version of the mdiobus_c45_read function
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to read
++ *
++ * Read a MDIO bus register. Caller must hold the mdio bus lock.
++ *
++ * NOTE: MUST NOT be called from interrupt context.
++ */
++int __mdiobus_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum)
++{
++	int retval;
++
++	lockdep_assert_held_once(&bus->mdio_lock);
++
++	if (bus->read_c45)
++		retval = bus->read_c45(bus, addr, devad, regnum);
++	else
++		retval = bus->read(bus, addr, mdiobus_c45_addr(devad, regnum));
++
++	trace_mdio_access(bus, 1, addr, regnum, retval, retval);
++	mdiobus_stats_acct(&bus->stats[addr], true, retval);
++
++	return retval;
++}
++EXPORT_SYMBOL(__mdiobus_c45_read);
++
++/**
++ * __mdiobus_c45_write - Unlocked version of the mdiobus_write function
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to write
++ * @val: value to write to @regnum
++ *
++ * Write a MDIO bus register. Caller must hold the mdio bus lock.
++ *
++ * NOTE: MUST NOT be called from interrupt context.
++ */
++int __mdiobus_c45_write(struct mii_bus *bus, int addr, int devad, u32 regnum,
++			u16 val)
++{
++	int err;
++
++	lockdep_assert_held_once(&bus->mdio_lock);
++
++	if (bus->write_c45)
++		err = bus->write_c45(bus, addr, devad, regnum, val);
++	else
++		err = bus->write(bus, addr, mdiobus_c45_addr(devad, regnum),
++				 val);
++
++	trace_mdio_access(bus, 0, addr, regnum, val, err);
++	mdiobus_stats_acct(&bus->stats[addr], false, err);
++
++	return err;
++}
++EXPORT_SYMBOL(__mdiobus_c45_write);
++
++/**
++ * __mdiobus_c45_modify_changed - Unlocked version of the mdiobus_modify function
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to modify
++ * @mask: bit mask of bits to clear
++ * @set: bit mask of bits to set
++ *
++ * Read, modify, and if any change, write the register value back to the
++ * device. Any error returns a negative number.
++ *
++ * NOTE: MUST NOT be called from interrupt context.
++ */
++static int __mdiobus_c45_modify_changed(struct mii_bus *bus, int addr,
++					int devad, u32 regnum, u16 mask,
++					u16 set)
++{
++	int new, ret;
++
++	ret = __mdiobus_c45_read(bus, addr, devad, regnum);
++	if (ret < 0)
++		return ret;
++
++	new = (ret & ~mask) | set;
++	if (new == ret)
++		return 0;
++
++	ret = __mdiobus_c45_write(bus, addr, devad, regnum, new);
++
++	return ret < 0 ? ret : 1;
++}
++
++/**
+  * mdiobus_read_nested - Nested version of the mdiobus_read function
+  * @bus: the mii_bus struct
+  * @addr: the phy address
+@@ -879,6 +973,29 @@ int mdiobus_read(struct mii_bus *bus, in
+ EXPORT_SYMBOL(mdiobus_read);
+ 
+ /**
++ * mdiobus_c45_read - Convenience function for reading a given MII mgmt register
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to read
++ *
++ * NOTE: MUST NOT be called from interrupt context,
++ * because the bus read/write functions may wait for an interrupt
++ * to conclude the operation.
++ */
++int mdiobus_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum)
++{
++	int retval;
++
++	mutex_lock(&bus->mdio_lock);
++	retval = __mdiobus_c45_read(bus, addr, devad, regnum);
++	mutex_unlock(&bus->mdio_lock);
++
++	return retval;
++}
++EXPORT_SYMBOL(mdiobus_c45_read);
++
++/**
+  * mdiobus_write_nested - Nested version of the mdiobus_write function
+  * @bus: the mii_bus struct
+  * @addr: the phy address
+@@ -928,6 +1045,31 @@ int mdiobus_write(struct mii_bus *bus, i
+ EXPORT_SYMBOL(mdiobus_write);
+ 
+ /**
++ * mdiobus_c45_write - Convenience function for writing a given MII mgmt register
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to write
++ * @val: value to write to @regnum
++ *
++ * NOTE: MUST NOT be called from interrupt context,
++ * because the bus read/write functions may wait for an interrupt
++ * to conclude the operation.
++ */
++int mdiobus_c45_write(struct mii_bus *bus, int addr, int devad, u32 regnum,
++		      u16 val)
++{
++	int err;
++
++	mutex_lock(&bus->mdio_lock);
++	err = __mdiobus_c45_write(bus, addr, devad, regnum, val);
++	mutex_unlock(&bus->mdio_lock);
++
++	return err;
++}
++EXPORT_SYMBOL(mdiobus_c45_write);
++
++/**
+  * mdiobus_modify - Convenience function for modifying a given mdio device
+  *	register
+  * @bus: the mii_bus struct
+@@ -949,6 +1091,30 @@ int mdiobus_modify(struct mii_bus *bus,
+ EXPORT_SYMBOL_GPL(mdiobus_modify);
+ 
+ /**
++ * mdiobus_c45_modify - Convenience function for modifying a given mdio device
++ *	register
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to write
++ * @mask: bit mask of bits to clear
++ * @set: bit mask of bits to set
++ */
++int mdiobus_c45_modify(struct mii_bus *bus, int addr, int devad, u32 regnum,
++		       u16 mask, u16 set)
++{
++	int err;
++
++	mutex_lock(&bus->mdio_lock);
++	err = __mdiobus_c45_modify_changed(bus, addr, devad, regnum,
++					   mask, set);
++	mutex_unlock(&bus->mdio_lock);
++
++	return err < 0 ? err : 0;
++}
++EXPORT_SYMBOL_GPL(mdiobus_c45_modify);
++
++/**
+  * mdiobus_modify_changed - Convenience function for modifying a given mdio
+  *	device register and returning if it changed
+  * @bus: the mii_bus struct
+@@ -971,6 +1137,29 @@ int mdiobus_modify_changed(struct mii_bu
+ EXPORT_SYMBOL_GPL(mdiobus_modify_changed);
+ 
+ /**
++ * mdiobus_c45_modify_changed - Convenience function for modifying a given mdio
++ *	device register and returning if it changed
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @devad: device address to read
++ * @regnum: register number to write
++ * @mask: bit mask of bits to clear
++ * @set: bit mask of bits to set
++ */
++int mdiobus_c45_modify_changed(struct mii_bus *bus, int devad, int addr,
++			       u32 regnum, u16 mask, u16 set)
++{
++	int err;
++
++	mutex_lock(&bus->mdio_lock);
++	err = __mdiobus_c45_modify_changed(bus, addr, devad, regnum, mask, set);
++	mutex_unlock(&bus->mdio_lock);
++
++	return err;
++}
++EXPORT_SYMBOL_GPL(mdiobus_c45_modify_changed);
++
++/**
+  * mdio_bus_match - determine if given MDIO driver supports the given
+  *		    MDIO device
+  * @dev: target MDIO device
+--- a/include/linux/mdio.h
++++ b/include/linux/mdio.h
+@@ -423,6 +423,17 @@ int mdiobus_modify(struct mii_bus *bus,
+ 		   u16 set);
+ int mdiobus_modify_changed(struct mii_bus *bus, int addr, u32 regnum,
+ 			   u16 mask, u16 set);
++int __mdiobus_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum);
++int mdiobus_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum);
++int __mdiobus_c45_write(struct mii_bus *bus, int addr,  int devad, u32 regnum,
++			u16 val);
++int mdiobus_c45_write(struct mii_bus *bus, int addr,  int devad, u32 regnum,
++		      u16 val);
++int mdiobus_c45_modify(struct mii_bus *bus, int addr, int devad, u32 regnum,
++		       u16 mask, u16 set);
++
++int mdiobus_c45_modify_changed(struct mii_bus *bus, int addr, int devad,
++			       u32 regnum, u16 mask, u16 set);
+ 
+ static inline int mdiodev_read(struct mdio_device *mdiodev, u32 regnum)
+ {
+@@ -463,29 +474,19 @@ static inline u16 mdiobus_c45_devad(u32
+ 	return FIELD_GET(MII_DEVADDR_C45_MASK, regnum);
+ }
+ 
+-static inline int __mdiobus_c45_read(struct mii_bus *bus, int prtad, int devad,
+-				     u16 regnum)
+-{
+-	return __mdiobus_read(bus, prtad, mdiobus_c45_addr(devad, regnum));
+-}
+-
+-static inline int __mdiobus_c45_write(struct mii_bus *bus, int prtad, int devad,
+-				      u16 regnum, u16 val)
+-{
+-	return __mdiobus_write(bus, prtad, mdiobus_c45_addr(devad, regnum),
+-			       val);
+-}
+-
+-static inline int mdiobus_c45_read(struct mii_bus *bus, int prtad, int devad,
+-				   u16 regnum)
++static inline int mdiodev_c45_modify(struct mdio_device *mdiodev, int devad,
++				     u32 regnum, u16 mask, u16 set)
+ {
+-	return mdiobus_read(bus, prtad, mdiobus_c45_addr(devad, regnum));
++	return mdiobus_c45_modify(mdiodev->bus, mdiodev->addr, devad, regnum,
++				  mask, set);
+ }
+ 
+-static inline int mdiobus_c45_write(struct mii_bus *bus, int prtad, int devad,
+-				    u16 regnum, u16 val)
++static inline int mdiodev_c45_modify_changed(struct mdio_device *mdiodev,
++					     int devad, u32 regnum, u16 mask,
++					     u16 set)
+ {
+-	return mdiobus_write(bus, prtad, mdiobus_c45_addr(devad, regnum), val);
++	return mdiobus_c45_modify_changed(mdiodev->bus, mdiodev->addr, devad,
++					  regnum, mask, set);
+ }
+ 
+ int mdiobus_register_device(struct mdio_device *mdiodev);
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -364,6 +364,11 @@ struct mii_bus {
+ 	int (*read)(struct mii_bus *bus, int addr, int regnum);
+ 	/** @write: Perform a write transfer on the bus */
+ 	int (*write)(struct mii_bus *bus, int addr, int regnum, u16 val);
++	/** @read_c45: Perform a C45 read transfer on the bus */
++	int (*read_c45)(struct mii_bus *bus, int addr, int devnum, int regnum);
++	/** @write_c45: Perform a C45 write transfer on the bus */
++	int (*write_c45)(struct mii_bus *bus, int addr, int devnum,
++			 int regnum, u16 val);
+ 	/** @reset: Perform a reset of the bus */
+ 	int (*reset)(struct mii_bus *bus);
+ 

+ 105 - 0
target/linux/generic/backport-6.6/704-v6.6-net-phy-Introduce-PSGMII-PHY-interface-mode.patch

@@ -0,0 +1,105 @@
+From 9a0e95e34e9c0a713ddfd48c3a88a20d2bdfd514 Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <[email protected]>
+Date: Fri, 11 Aug 2023 13:10:07 +0200
+Subject: [PATCH] net: phy: Introduce PSGMII PHY interface mode
+
+The PSGMII interface is similar to QSGMII. The main difference
+is that the PSGMII interface combines five SGMII lines into a
+single link while in QSGMII only four lines are combined.
+
+Similarly to the QSGMII, this interface mode might also needs
+special handling within the MAC driver.
+
+It is commonly used by Qualcomm with their QCA807x PHY series and
+modern WiSoC-s.
+
+Add definitions for the PHY layer to allow to express this type
+of connection between the MAC and PHY.
+
+Signed-off-by: Gabor Juhos <[email protected]>
+Signed-off-by: Robert Marko <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ Documentation/networking/phy.rst | 4 ++++
+ drivers/net/phy/phy-core.c       | 2 ++
+ drivers/net/phy/phylink.c        | 3 +++
+ include/linux/phy.h              | 4 ++++
+ 4 files changed, 13 insertions(+)
+
+--- a/Documentation/networking/phy.rst
++++ b/Documentation/networking/phy.rst
+@@ -323,6 +323,10 @@ Some of the interface modes are describe
+     contrast with the 1000BASE-X phy mode used for Clause 38 and 39 PMDs, this
+     interface mode has different autonegotiation and only supports full duplex.
+ 
++``PHY_INTERFACE_MODE_PSGMII``
++    This is the Penta SGMII mode, it is similar to QSGMII but it combines 5
++    SGMII lines into a single link compared to 4 on QSGMII.
++
+ Pause frames / flow control
+ ===========================
+ 
+--- a/drivers/net/phy/phy-core.c
++++ b/drivers/net/phy/phy-core.c
+@@ -140,6 +140,8 @@ int phy_interface_num_ports(phy_interfac
+ 	case PHY_INTERFACE_MODE_QSGMII:
+ 	case PHY_INTERFACE_MODE_QUSGMII:
+ 		return 4;
++	case PHY_INTERFACE_MODE_PSGMII:
++		return 5;
+ 	case PHY_INTERFACE_MODE_MAX:
+ 		WARN_ONCE(1, "PHY_INTERFACE_MODE_MAX isn't a valid interface mode");
+ 		return 0;
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -187,6 +187,7 @@ static int phylink_interface_max_speed(p
+ 	case PHY_INTERFACE_MODE_RGMII_RXID:
+ 	case PHY_INTERFACE_MODE_RGMII_ID:
+ 	case PHY_INTERFACE_MODE_RGMII:
++	case PHY_INTERFACE_MODE_PSGMII:
+ 	case PHY_INTERFACE_MODE_QSGMII:
+ 	case PHY_INTERFACE_MODE_QUSGMII:
+ 	case PHY_INTERFACE_MODE_SGMII:
+@@ -448,6 +449,7 @@ unsigned long phylink_get_capabilities(p
+ 	case PHY_INTERFACE_MODE_RGMII_RXID:
+ 	case PHY_INTERFACE_MODE_RGMII_ID:
+ 	case PHY_INTERFACE_MODE_RGMII:
++	case PHY_INTERFACE_MODE_PSGMII:
+ 	case PHY_INTERFACE_MODE_QSGMII:
+ 	case PHY_INTERFACE_MODE_QUSGMII:
+ 	case PHY_INTERFACE_MODE_SGMII:
+@@ -814,6 +816,7 @@ static int phylink_parse_mode(struct phy
+ 
+ 		switch (pl->link_config.interface) {
+ 		case PHY_INTERFACE_MODE_SGMII:
++		case PHY_INTERFACE_MODE_PSGMII:
+ 		case PHY_INTERFACE_MODE_QSGMII:
+ 		case PHY_INTERFACE_MODE_QUSGMII:
+ 		case PHY_INTERFACE_MODE_RGMII:
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -103,6 +103,7 @@ extern const int phy_10gbit_features_arr
+  * @PHY_INTERFACE_MODE_XGMII: 10 gigabit media-independent interface
+  * @PHY_INTERFACE_MODE_XLGMII:40 gigabit media-independent interface
+  * @PHY_INTERFACE_MODE_MOCA: Multimedia over Coax
++ * @PHY_INTERFACE_MODE_PSGMII: Penta SGMII
+  * @PHY_INTERFACE_MODE_QSGMII: Quad SGMII
+  * @PHY_INTERFACE_MODE_TRGMII: Turbo RGMII
+  * @PHY_INTERFACE_MODE_100BASEX: 100 BaseX
+@@ -140,6 +141,7 @@ typedef enum {
+ 	PHY_INTERFACE_MODE_XGMII,
+ 	PHY_INTERFACE_MODE_XLGMII,
+ 	PHY_INTERFACE_MODE_MOCA,
++	PHY_INTERFACE_MODE_PSGMII,
+ 	PHY_INTERFACE_MODE_QSGMII,
+ 	PHY_INTERFACE_MODE_TRGMII,
+ 	PHY_INTERFACE_MODE_100BASEX,
+@@ -247,6 +249,8 @@ static inline const char *phy_modes(phy_
+ 		return "xlgmii";
+ 	case PHY_INTERFACE_MODE_MOCA:
+ 		return "moca";
++	case PHY_INTERFACE_MODE_PSGMII:
++		return "psgmii";
+ 	case PHY_INTERFACE_MODE_QSGMII:
+ 		return "qsgmii";
+ 	case PHY_INTERFACE_MODE_TRGMII:

+ 32 - 0
target/linux/generic/backport-6.6/705-v6.4-net-phy-at803x-Replace-of_gpio.h-with-what-indeed-is.patch

@@ -0,0 +1,32 @@
+From a593a2fcfdfb92cfd0ffc54bc81b07e6bfaaaf46 Mon Sep 17 00:00:00 2001
+From: Andy Shevchenko <[email protected]>
+Date: Thu, 16 Mar 2023 14:08:26 +0200
+Subject: [PATCH] net: phy: at803x: Replace of_gpio.h with what indeed is used
+
+of_gpio.h in this driver is solely used as a proxy to other headers.
+This is incorrect usage of the of_gpio.h. Replace it .h with what
+indeed is used in the code.
+
+Signed-off-by: Andy Shevchenko <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -13,12 +13,11 @@
+ #include <linux/netdevice.h>
+ #include <linux/etherdevice.h>
+ #include <linux/ethtool_netlink.h>
+-#include <linux/of_gpio.h>
+ #include <linux/bitfield.h>
+-#include <linux/gpio/consumer.h>
+ #include <linux/regulator/of_regulator.h>
+ #include <linux/regulator/driver.h>
+ #include <linux/regulator/consumer.h>
++#include <linux/of.h>
+ #include <linux/phylink.h>
+ #include <linux/sfp.h>
+ #include <dt-bindings/net/qca-ar803x.h>

+ 70 - 0
target/linux/generic/backport-6.6/706-v6.6-01-net-phy-at803x-support-qca8081-genphy_c45_pma_read_a.patch

@@ -0,0 +1,70 @@
+From 8b8bc13d89a7d23d14b0e041c73f037c9db997b1 Mon Sep 17 00:00:00 2001
+From: Luo Jie <[email protected]>
+Date: Sun, 16 Jul 2023 16:49:19 +0800
+Subject: [PATCH 1/6] net: phy: at803x: support qca8081
+ genphy_c45_pma_read_abilities
+
+qca8081 PHY supports to use genphy_c45_pma_read_abilities for
+getting the PHY features supported except for the autoneg ability
+
+but autoneg ability exists in MDIO_STAT1 instead of MMD7.1, add it
+manually after calling genphy_c45_pma_read_abilities.
+
+Signed-off-by: Luo Jie <[email protected]>
+Reviewed-by: Russell King (Oracle) <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 28 ++++++++++++++++++----------
+ 1 file changed, 18 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -902,15 +902,6 @@ static int at803x_get_features(struct ph
+ 	if (err)
+ 		return err;
+ 
+-	if (phydev->drv->phy_id == QCA8081_PHY_ID) {
+-		err = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_NG_EXTABLE);
+-		if (err < 0)
+-			return err;
+-
+-		linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported,
+-				err & MDIO_PMA_NG_EXTABLE_2_5GBT);
+-	}
+-
+ 	if (phydev->drv->phy_id != ATH8031_PHY_ID)
+ 		return 0;
+ 
+@@ -1996,6 +1987,23 @@ static int qca808x_cable_test_get_status
+ 	return 0;
+ }
+ 
++static int qca808x_get_features(struct phy_device *phydev)
++{
++	int ret;
++
++	ret = genphy_c45_pma_read_abilities(phydev);
++	if (ret)
++		return ret;
++
++	/* The autoneg ability is not existed in bit3 of MMD7.1,
++	 * but it is supported by qca808x PHY, so we add it here
++	 * manually.
++	 */
++	linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
++
++	return 0;
++}
++
+ static struct phy_driver at803x_driver[] = {
+ {
+ 	/* Qualcomm Atheros AR8035 */
+@@ -2163,7 +2171,7 @@ static struct phy_driver at803x_driver[]
+ 	.set_tunable		= at803x_set_tunable,
+ 	.set_wol		= at803x_set_wol,
+ 	.get_wol		= at803x_get_wol,
+-	.get_features		= at803x_get_features,
++	.get_features		= qca808x_get_features,
+ 	.config_aneg		= at803x_config_aneg,
+ 	.suspend		= genphy_suspend,
+ 	.resume			= genphy_resume,

+ 73 - 0
target/linux/generic/backport-6.6/706-v6.6-02-net-phy-at803x-merge-qca8081-slave-seed-function.patch

@@ -0,0 +1,73 @@
+From f3db55ae860a82e1224a909072783ef850e5d228 Mon Sep 17 00:00:00 2001
+From: Luo Jie <[email protected]>
+Date: Sun, 16 Jul 2023 16:49:20 +0800
+Subject: [PATCH 2/6] net: phy: at803x: merge qca8081 slave seed function
+
+merge the seed enablement and seed value configuration into
+one function, since the random seed value is needed to be
+configured when the seed is enabled.
+
+Signed-off-by: Luo Jie <[email protected]>
+Reviewed-by: Russell King (Oracle) <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 29 +++++++++--------------------
+ 1 file changed, 9 insertions(+), 20 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1730,24 +1730,19 @@ static int qca808x_phy_fast_retrain_conf
+ 	return 0;
+ }
+ 
+-static int qca808x_phy_ms_random_seed_set(struct phy_device *phydev)
+-{
+-	u16 seed_value = prandom_u32_max(QCA808X_MASTER_SLAVE_SEED_RANGE);
+-
+-	return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+-			QCA808X_MASTER_SLAVE_SEED_CFG,
+-			FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value));
+-}
+-
+ static int qca808x_phy_ms_seed_enable(struct phy_device *phydev, bool enable)
+ {
+-	u16 seed_enable = 0;
++	u16 seed_value;
+ 
+-	if (enable)
+-		seed_enable = QCA808X_MASTER_SLAVE_SEED_ENABLE;
++	if (!enable)
++		return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
++				QCA808X_MASTER_SLAVE_SEED_ENABLE, 0);
+ 
++	seed_value = prandom_u32_max(QCA808X_MASTER_SLAVE_SEED_RANGE);
+ 	return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+-			QCA808X_MASTER_SLAVE_SEED_ENABLE, seed_enable);
++			QCA808X_MASTER_SLAVE_SEED_CFG | QCA808X_MASTER_SLAVE_SEED_ENABLE,
++			FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value) |
++			QCA808X_MASTER_SLAVE_SEED_ENABLE);
+ }
+ 
+ static int qca808x_config_init(struct phy_device *phydev)
+@@ -1771,12 +1766,7 @@ static int qca808x_config_init(struct ph
+ 	if (ret)
+ 		return ret;
+ 
+-	/* Configure lower ramdom seed to make phy linked as slave mode */
+-	ret = qca808x_phy_ms_random_seed_set(phydev);
+-	if (ret)
+-		return ret;
+-
+-	/* Enable seed */
++	/* Enable seed and configure lower ramdom seed to make phy linked as slave mode */
+ 	ret = qca808x_phy_ms_seed_enable(phydev, true);
+ 	if (ret)
+ 		return ret;
+@@ -1821,7 +1811,6 @@ static int qca808x_read_status(struct ph
+ 		if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR) {
+ 			qca808x_phy_ms_seed_enable(phydev, false);
+ 		} else {
+-			qca808x_phy_ms_random_seed_set(phydev);
+ 			qca808x_phy_ms_seed_enable(phydev, true);
+ 		}
+ 	}

+ 76 - 0
target/linux/generic/backport-6.6/706-v6.6-03-net-phy-at803x-enable-qca8081-slave-seed-conditional.patch

@@ -0,0 +1,76 @@
+From 7cc3209558002d95c0d45a1276ba4f5f741eec42 Mon Sep 17 00:00:00 2001
+From: Luo Jie <[email protected]>
+Date: Sun, 16 Jul 2023 16:49:21 +0800
+Subject: [PATCH 3/6] net: phy: at803x: enable qca8081 slave seed conditionally
+
+qca8081 is the single port PHY, the slave prefer mode is used
+by default.
+
+if the phy master perfer mode is configured, the slave seed
+configuration should not be enabled, since the slave seed
+enablement is for making PHY linked as slave mode easily.
+
+disable slave seed if the master mode is preferred.
+
+Signed-off-by: Luo Jie <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 25 ++++++++++++++++++++-----
+ 1 file changed, 20 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1745,6 +1745,12 @@ static int qca808x_phy_ms_seed_enable(st
+ 			QCA808X_MASTER_SLAVE_SEED_ENABLE);
+ }
+ 
++static bool qca808x_is_prefer_master(struct phy_device *phydev)
++{
++	return (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_FORCE) ||
++		(phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
++}
++
+ static int qca808x_config_init(struct phy_device *phydev)
+ {
+ 	int ret;
+@@ -1766,11 +1772,17 @@ static int qca808x_config_init(struct ph
+ 	if (ret)
+ 		return ret;
+ 
+-	/* Enable seed and configure lower ramdom seed to make phy linked as slave mode */
+-	ret = qca808x_phy_ms_seed_enable(phydev, true);
+-	if (ret)
++	ret = genphy_read_master_slave(phydev);
++	if (ret < 0)
+ 		return ret;
+ 
++	if (!qca808x_is_prefer_master(phydev)) {
++		/* Enable seed and configure lower ramdom seed to make phy linked as slave mode */
++		ret = qca808x_phy_ms_seed_enable(phydev, true);
++		if (ret)
++			return ret;
++	}
++
+ 	/* Configure adc threshold as 100mv for the link 10M */
+ 	return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
+ 			QCA808X_ADC_THRESHOLD_MASK, QCA808X_ADC_THRESHOLD_100MV);
+@@ -1802,13 +1814,16 @@ static int qca808x_read_status(struct ph
+ 			phydev->interface = PHY_INTERFACE_MODE_SGMII;
+ 	} else {
+ 		/* generate seed as a lower random value to make PHY linked as SLAVE easily,
+-		 * except for master/slave configuration fault detected.
++		 * except for master/slave configuration fault detected or the master mode
++		 * preferred.
++		 *
+ 		 * the reason for not putting this code into the function link_change_notify is
+ 		 * the corner case where the link partner is also the qca8081 PHY and the seed
+ 		 * value is configured as the same value, the link can't be up and no link change
+ 		 * occurs.
+ 		 */
+-		if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR) {
++		if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
++				qca808x_is_prefer_master(phydev)) {
+ 			qca808x_phy_ms_seed_enable(phydev, false);
+ 		} else {
+ 			qca808x_phy_ms_seed_enable(phydev, true);

+ 48 - 0
target/linux/generic/backport-6.6/706-v6.6-04-net-phy-at803x-support-qca8081-1G-chip-type.patch

@@ -0,0 +1,48 @@
+From fea7cfb83d1a2782e39cd101dd44ed2548539de5 Mon Sep 17 00:00:00 2001
+From: Luo Jie <[email protected]>
+Date: Sun, 16 Jul 2023 16:49:22 +0800
+Subject: [PATCH 4/6] net: phy: at803x: support qca8081 1G chip type
+
+The qca8081 1G chip version does not support 2.5 capability, which
+is distinguished from qca8081 2.5G chip according to the bit0 of
+register mmd7.0x901d, the 1G version chip also has the same PHY ID
+as the normal qca8081 2.5G chip.
+
+Signed-off-by: Luo Jie <[email protected]>
+Reviewed-by: Russell King (Oracle) <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -272,6 +272,10 @@
+ #define QCA808X_CDT_STATUS_STAT_OPEN		2
+ #define QCA808X_CDT_STATUS_STAT_SHORT		3
+ 
++/* QCA808X 1G chip type */
++#define QCA808X_PHY_MMD7_CHIP_TYPE		0x901d
++#define QCA808X_PHY_CHIP_TYPE_1G		BIT(0)
++
+ MODULE_DESCRIPTION("Qualcomm Atheros AR803x and QCA808X PHY driver");
+ MODULE_AUTHOR("Matus Ujhelyi");
+ MODULE_LICENSE("GPL");
+@@ -2005,6 +2009,17 @@ static int qca808x_get_features(struct p
+ 	 */
+ 	linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+ 
++	/* As for the qca8081 1G version chip, the 2500baseT ability is also
++	 * existed in the bit0 of MMD1.21, we need to remove it manually if
++	 * it is the qca8081 1G chip according to the bit0 of MMD7.0x901d.
++	 */
++	ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE);
++	if (ret < 0)
++		return ret;
++
++	if (QCA808X_PHY_CHIP_TYPE_1G & ret)
++		linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
++
+ 	return 0;
+ }
+ 

+ 98 - 0
target/linux/generic/backport-6.6/706-v6.6-05-net-phy-at803x-remove-qca8081-1G-fast-retrain-and-sl.patch

@@ -0,0 +1,98 @@
+From df9401ff3e6eeaa42bfb06761967f1b71f5afce7 Mon Sep 17 00:00:00 2001
+From: Luo Jie <[email protected]>
+Date: Sun, 16 Jul 2023 16:49:23 +0800
+Subject: [PATCH 5/6] net: phy: at803x: remove qca8081 1G fast retrain and
+ slave seed config
+
+The fast retrain and slave seed configs are only applicable when the 2.5G
+ability is supported.
+
+Signed-off-by: Luo Jie <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 50 +++++++++++++++++++++++++---------------
+ 1 file changed, 32 insertions(+), 18 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1755,6 +1755,11 @@ static bool qca808x_is_prefer_master(str
+ 		(phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
+ }
+ 
++static bool qca808x_has_fast_retrain_or_slave_seed(struct phy_device *phydev)
++{
++	return linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
++}
++
+ static int qca808x_config_init(struct phy_device *phydev)
+ {
+ 	int ret;
+@@ -1771,20 +1776,24 @@ static int qca808x_config_init(struct ph
+ 	if (ret)
+ 		return ret;
+ 
+-	/* Config the fast retrain for the link 2500M */
+-	ret = qca808x_phy_fast_retrain_config(phydev);
+-	if (ret)
+-		return ret;
+-
+-	ret = genphy_read_master_slave(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	if (!qca808x_is_prefer_master(phydev)) {
+-		/* Enable seed and configure lower ramdom seed to make phy linked as slave mode */
+-		ret = qca808x_phy_ms_seed_enable(phydev, true);
++	if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
++		/* Config the fast retrain for the link 2500M */
++		ret = qca808x_phy_fast_retrain_config(phydev);
+ 		if (ret)
+ 			return ret;
++
++		ret = genphy_read_master_slave(phydev);
++		if (ret < 0)
++			return ret;
++
++		if (!qca808x_is_prefer_master(phydev)) {
++			/* Enable seed and configure lower ramdom seed to make phy
++			 * linked as slave mode.
++			 */
++			ret = qca808x_phy_ms_seed_enable(phydev, true);
++			if (ret)
++				return ret;
++		}
+ 	}
+ 
+ 	/* Configure adc threshold as 100mv for the link 10M */
+@@ -1826,11 +1835,13 @@ static int qca808x_read_status(struct ph
+ 		 * value is configured as the same value, the link can't be up and no link change
+ 		 * occurs.
+ 		 */
+-		if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
+-				qca808x_is_prefer_master(phydev)) {
+-			qca808x_phy_ms_seed_enable(phydev, false);
+-		} else {
+-			qca808x_phy_ms_seed_enable(phydev, true);
++		if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
++			if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
++					qca808x_is_prefer_master(phydev)) {
++				qca808x_phy_ms_seed_enable(phydev, false);
++			} else {
++				qca808x_phy_ms_seed_enable(phydev, true);
++			}
+ 		}
+ 	}
+ 
+@@ -1845,7 +1856,10 @@ static int qca808x_soft_reset(struct phy
+ 	if (ret < 0)
+ 		return ret;
+ 
+-	return qca808x_phy_ms_seed_enable(phydev, true);
++	if (qca808x_has_fast_retrain_or_slave_seed(phydev))
++		ret = qca808x_phy_ms_seed_enable(phydev, true);
++
++	return ret;
+ }
+ 
+ static bool qca808x_cdt_fault_length_valid(int cdt_code)

+ 55 - 0
target/linux/generic/backport-6.6/706-v6.6-06-net-phy-at803x-add-qca8081-fifo-reset-on-the-link-ch.patch

@@ -0,0 +1,55 @@
+From 723970affdd8766fa0d91cd34bf2ffc861538b5f Mon Sep 17 00:00:00 2001
+From: Luo Jie <[email protected]>
+Date: Sun, 16 Jul 2023 16:49:24 +0800
+Subject: [PATCH 6/6] net: phy: at803x: add qca8081 fifo reset on the link
+ changed
+
+The qca8081 sgmii fifo needs to be reset on link down and
+released on the link up in case of any abnormal issue
+such as the packet blocked on the PHY.
+
+Signed-off-by: Luo Jie <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Reviewed-by: Russell King (Oracle) <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -276,6 +276,9 @@
+ #define QCA808X_PHY_MMD7_CHIP_TYPE		0x901d
+ #define QCA808X_PHY_CHIP_TYPE_1G		BIT(0)
+ 
++#define QCA8081_PHY_SERDES_MMD1_FIFO_CTRL	0x9072
++#define QCA8081_PHY_FIFO_RSTN			BIT(11)
++
+ MODULE_DESCRIPTION("Qualcomm Atheros AR803x and QCA808X PHY driver");
+ MODULE_AUTHOR("Matus Ujhelyi");
+ MODULE_LICENSE("GPL");
+@@ -2037,6 +2040,16 @@ static int qca808x_get_features(struct p
+ 	return 0;
+ }
+ 
++static void qca808x_link_change_notify(struct phy_device *phydev)
++{
++	/* Assert interface sgmii fifo on link down, deassert it on link up,
++	 * the interface device address is always phy address added by 1.
++	 */
++	mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
++			MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
++			QCA8081_PHY_FIFO_RSTN, phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
++}
++
+ static struct phy_driver at803x_driver[] = {
+ {
+ 	/* Qualcomm Atheros AR8035 */
+@@ -2213,6 +2226,7 @@ static struct phy_driver at803x_driver[]
+ 	.soft_reset		= qca808x_soft_reset,
+ 	.cable_test_start	= qca808x_cable_test_start,
+ 	.cable_test_get_status	= qca808x_cable_test_get_status,
++	.link_change_notify	= qca808x_link_change_notify,
+ }, };
+ 
+ module_phy_driver(at803x_driver);

+ 69 - 0
target/linux/generic/backport-6.6/707-v6.8-02-net-phy-at803x-move-disable-WOL-to-specific-at8031-p.patch

@@ -0,0 +1,69 @@
+From 6a3b8c573b5a152a6aa7a0b54c5e18b84c6ba6f5 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 8 Dec 2023 15:51:49 +0100
+Subject: [PATCH 02/13] net: phy: at803x: move disable WOL to specific at8031
+ probe
+
+Move the WOL disable call to specific at8031 probe to make at803x_probe
+more generic and drop extra check for PHY ID.
+
+Keep the same previous behaviour by first calling at803x_probe and then
+disabling WOL.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 27 +++++++++++++++++----------
+ 1 file changed, 17 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -886,15 +886,6 @@ static int at803x_probe(struct phy_devic
+ 			priv->is_fiber = true;
+ 			break;
+ 		}
+-
+-		/* Disable WoL in 1588 register which is enabled
+-		 * by default
+-		 */
+-		ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
+-				     AT803X_PHY_MMD3_WOL_CTRL,
+-				     AT803X_WOL_EN, 0);
+-		if (ret)
+-			return ret;
+ 	}
+ 
+ 	return 0;
+@@ -1591,6 +1582,22 @@ static int at803x_cable_test_start(struc
+ 	return 0;
+ }
+ 
++static int at8031_probe(struct phy_device *phydev)
++{
++	int ret;
++
++	ret = at803x_probe(phydev);
++	if (ret)
++		return ret;
++
++	/* Disable WoL in 1588 register which is enabled
++	 * by default
++	 */
++	return phy_modify_mmd(phydev, MDIO_MMD_PCS,
++			      AT803X_PHY_MMD3_WOL_CTRL,
++			      AT803X_WOL_EN, 0);
++}
++
+ static int qca83xx_config_init(struct phy_device *phydev)
+ {
+ 	u8 switch_revision;
+@@ -2092,7 +2099,7 @@ static struct phy_driver at803x_driver[]
+ 	PHY_ID_MATCH_EXACT(ATH8031_PHY_ID),
+ 	.name			= "Qualcomm Atheros AR8031/AR8033",
+ 	.flags			= PHY_POLL_CABLE_TEST,
+-	.probe			= at803x_probe,
++	.probe			= at8031_probe,
+ 	.config_init		= at803x_config_init,
+ 	.config_aneg		= at803x_config_aneg,
+ 	.soft_reset		= genphy_soft_reset,

+ 129 - 0
target/linux/generic/backport-6.6/707-v6.8-03-net-phy-at803x-raname-hw_stats-functions-to-qca83xx-.patch

@@ -0,0 +1,129 @@
+From 07b1ad83b9ed6db1735ba10baf67b7a565ac0cef Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 8 Dec 2023 15:51:50 +0100
+Subject: [PATCH 03/13] net: phy: at803x: raname hw_stats functions to qca83xx
+ specific name
+
+The function and the struct related to hw_stats were specific to qca83xx
+PHY but were called following the convention in the driver of calling
+everything with at803x prefix.
+
+To better organize the code, rename these function a more specific name
+to better describe that they are specific to 83xx PHY family.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 44 ++++++++++++++++++++--------------------
+ 1 file changed, 22 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -295,7 +295,7 @@ struct at803x_hw_stat {
+ 	enum stat_access_type access_type;
+ };
+ 
+-static struct at803x_hw_stat at803x_hw_stats[] = {
++static struct at803x_hw_stat qca83xx_hw_stats[] = {
+ 	{ "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
+ 	{ "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
+ 	{ "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
+@@ -311,7 +311,7 @@ struct at803x_priv {
+ 	bool is_1000basex;
+ 	struct regulator_dev *vddio_rdev;
+ 	struct regulator_dev *vddh_rdev;
+-	u64 stats[ARRAY_SIZE(at803x_hw_stats)];
++	u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
+ };
+ 
+ struct at803x_context {
+@@ -529,24 +529,24 @@ static void at803x_get_wol(struct phy_de
+ 		wol->wolopts |= WAKE_MAGIC;
+ }
+ 
+-static int at803x_get_sset_count(struct phy_device *phydev)
++static int qca83xx_get_sset_count(struct phy_device *phydev)
+ {
+-	return ARRAY_SIZE(at803x_hw_stats);
++	return ARRAY_SIZE(qca83xx_hw_stats);
+ }
+ 
+-static void at803x_get_strings(struct phy_device *phydev, u8 *data)
++static void qca83xx_get_strings(struct phy_device *phydev, u8 *data)
+ {
+ 	int i;
+ 
+-	for (i = 0; i < ARRAY_SIZE(at803x_hw_stats); i++) {
++	for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) {
+ 		strscpy(data + i * ETH_GSTRING_LEN,
+-			at803x_hw_stats[i].string, ETH_GSTRING_LEN);
++			qca83xx_hw_stats[i].string, ETH_GSTRING_LEN);
+ 	}
+ }
+ 
+-static u64 at803x_get_stat(struct phy_device *phydev, int i)
++static u64 qca83xx_get_stat(struct phy_device *phydev, int i)
+ {
+-	struct at803x_hw_stat stat = at803x_hw_stats[i];
++	struct at803x_hw_stat stat = qca83xx_hw_stats[i];
+ 	struct at803x_priv *priv = phydev->priv;
+ 	int val;
+ 	u64 ret;
+@@ -567,13 +567,13 @@ static u64 at803x_get_stat(struct phy_de
+ 	return ret;
+ }
+ 
+-static void at803x_get_stats(struct phy_device *phydev,
+-			     struct ethtool_stats *stats, u64 *data)
++static void qca83xx_get_stats(struct phy_device *phydev,
++			      struct ethtool_stats *stats, u64 *data)
+ {
+ 	int i;
+ 
+-	for (i = 0; i < ARRAY_SIZE(at803x_hw_stats); i++)
+-		data[i] = at803x_get_stat(phydev, i);
++	for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++)
++		data[i] = qca83xx_get_stat(phydev, i);
+ }
+ 
+ static int at803x_suspend(struct phy_device *phydev)
+@@ -2175,9 +2175,9 @@ static struct phy_driver at803x_driver[]
+ 	.flags			= PHY_IS_INTERNAL,
+ 	.config_init		= qca83xx_config_init,
+ 	.soft_reset		= genphy_soft_reset,
+-	.get_sset_count		= at803x_get_sset_count,
+-	.get_strings		= at803x_get_strings,
+-	.get_stats		= at803x_get_stats,
++	.get_sset_count		= qca83xx_get_sset_count,
++	.get_strings		= qca83xx_get_strings,
++	.get_stats		= qca83xx_get_stats,
+ 	.suspend		= qca83xx_suspend,
+ 	.resume			= qca83xx_resume,
+ }, {
+@@ -2191,9 +2191,9 @@ static struct phy_driver at803x_driver[]
+ 	.flags			= PHY_IS_INTERNAL,
+ 	.config_init		= qca83xx_config_init,
+ 	.soft_reset		= genphy_soft_reset,
+-	.get_sset_count		= at803x_get_sset_count,
+-	.get_strings		= at803x_get_strings,
+-	.get_stats		= at803x_get_stats,
++	.get_sset_count		= qca83xx_get_sset_count,
++	.get_strings		= qca83xx_get_strings,
++	.get_stats		= qca83xx_get_stats,
+ 	.suspend		= qca83xx_suspend,
+ 	.resume			= qca83xx_resume,
+ }, {
+@@ -2207,9 +2207,9 @@ static struct phy_driver at803x_driver[]
+ 	.flags			= PHY_IS_INTERNAL,
+ 	.config_init		= qca83xx_config_init,
+ 	.soft_reset		= genphy_soft_reset,
+-	.get_sset_count		= at803x_get_sset_count,
+-	.get_strings		= at803x_get_strings,
+-	.get_stats		= at803x_get_stats,
++	.get_sset_count		= qca83xx_get_sset_count,
++	.get_strings		= qca83xx_get_strings,
++	.get_stats		= qca83xx_get_stats,
+ 	.suspend		= qca83xx_suspend,
+ 	.resume			= qca83xx_resume,
+ }, {

+ 155 - 0
target/linux/generic/backport-6.6/707-v6.8-04-net-phy-at803x-move-qca83xx-specific-check-in-dedica.patch

@@ -0,0 +1,155 @@
+From d43cff3f82336c0bd965ea552232d9f4ddac71a6 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 8 Dec 2023 15:51:51 +0100
+Subject: [PATCH 04/13] net: phy: at803x: move qca83xx specific check in
+ dedicated functions
+
+Rework qca83xx specific check to dedicated function to tidy things up
+and drop useless phy_id check.
+
+Also drop an useless link_change_notify for QCA8337 as it did nothing an
+returned early.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 68 ++++++++++++++++++++++------------------
+ 1 file changed, 37 insertions(+), 31 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1623,27 +1623,26 @@ static int qca83xx_config_init(struct ph
+ 		break;
+ 	}
+ 
++	/* Following original QCA sourcecode set port to prefer master */
++	phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
++
++	return 0;
++}
++
++static int qca8327_config_init(struct phy_device *phydev)
++{
+ 	/* QCA8327 require DAC amplitude adjustment for 100m set to +6%.
+ 	 * Disable on init and enable only with 100m speed following
+ 	 * qca original source code.
+ 	 */
+-	if (phydev->drv->phy_id == QCA8327_A_PHY_ID ||
+-	    phydev->drv->phy_id == QCA8327_B_PHY_ID)
+-		at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+-				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
++	at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++			      QCA8327_DEBUG_MANU_CTRL_EN, 0);
+ 
+-	/* Following original QCA sourcecode set port to prefer master */
+-	phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
+-
+-	return 0;
++	return qca83xx_config_init(phydev);
+ }
+ 
+ static void qca83xx_link_change_notify(struct phy_device *phydev)
+ {
+-	/* QCA8337 doesn't require DAC Amplitude adjustement */
+-	if (phydev->drv->phy_id == QCA8337_PHY_ID)
+-		return;
+-
+ 	/* Set DAC Amplitude adjustment to +6% for 100m on link running */
+ 	if (phydev->state == PHY_RUNNING) {
+ 		if (phydev->speed == SPEED_100)
+@@ -1686,19 +1685,6 @@ static int qca83xx_resume(struct phy_dev
+ 
+ static int qca83xx_suspend(struct phy_device *phydev)
+ {
+-	u16 mask = 0;
+-
+-	/* Only QCA8337 support actual suspend.
+-	 * QCA8327 cause port unreliability when phy suspend
+-	 * is set.
+-	 */
+-	if (phydev->drv->phy_id == QCA8337_PHY_ID) {
+-		genphy_suspend(phydev);
+-	} else {
+-		mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
+-		phy_modify(phydev, MII_BMCR, mask, 0);
+-	}
+-
+ 	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_GREEN,
+ 			      AT803X_DEBUG_GATE_CLK_IN1000, 0);
+ 
+@@ -1709,6 +1695,27 @@ static int qca83xx_suspend(struct phy_de
+ 	return 0;
+ }
+ 
++static int qca8337_suspend(struct phy_device *phydev)
++{
++	/* Only QCA8337 support actual suspend. */
++	genphy_suspend(phydev);
++
++	return qca83xx_suspend(phydev);
++}
++
++static int qca8327_suspend(struct phy_device *phydev)
++{
++	u16 mask = 0;
++
++	/* QCA8327 cause port unreliability when phy suspend
++	 * is set.
++	 */
++	mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
++	phy_modify(phydev, MII_BMCR, mask, 0);
++
++	return qca83xx_suspend(phydev);
++}
++
+ static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
+ {
+ 	int ret;
+@@ -2170,7 +2177,6 @@ static struct phy_driver at803x_driver[]
+ 	.phy_id_mask		= QCA8K_PHY_ID_MASK,
+ 	.name			= "Qualcomm Atheros 8337 internal PHY",
+ 	/* PHY_GBIT_FEATURES */
+-	.link_change_notify	= qca83xx_link_change_notify,
+ 	.probe			= at803x_probe,
+ 	.flags			= PHY_IS_INTERNAL,
+ 	.config_init		= qca83xx_config_init,
+@@ -2178,7 +2184,7 @@ static struct phy_driver at803x_driver[]
+ 	.get_sset_count		= qca83xx_get_sset_count,
+ 	.get_strings		= qca83xx_get_strings,
+ 	.get_stats		= qca83xx_get_stats,
+-	.suspend		= qca83xx_suspend,
++	.suspend		= qca8337_suspend,
+ 	.resume			= qca83xx_resume,
+ }, {
+ 	/* QCA8327-A from switch QCA8327-AL1A */
+@@ -2189,12 +2195,12 @@ static struct phy_driver at803x_driver[]
+ 	.link_change_notify	= qca83xx_link_change_notify,
+ 	.probe			= at803x_probe,
+ 	.flags			= PHY_IS_INTERNAL,
+-	.config_init		= qca83xx_config_init,
++	.config_init		= qca8327_config_init,
+ 	.soft_reset		= genphy_soft_reset,
+ 	.get_sset_count		= qca83xx_get_sset_count,
+ 	.get_strings		= qca83xx_get_strings,
+ 	.get_stats		= qca83xx_get_stats,
+-	.suspend		= qca83xx_suspend,
++	.suspend		= qca8327_suspend,
+ 	.resume			= qca83xx_resume,
+ }, {
+ 	/* QCA8327-B from switch QCA8327-BL1A */
+@@ -2205,12 +2211,12 @@ static struct phy_driver at803x_driver[]
+ 	.link_change_notify	= qca83xx_link_change_notify,
+ 	.probe			= at803x_probe,
+ 	.flags			= PHY_IS_INTERNAL,
+-	.config_init		= qca83xx_config_init,
++	.config_init		= qca8327_config_init,
+ 	.soft_reset		= genphy_soft_reset,
+ 	.get_sset_count		= qca83xx_get_sset_count,
+ 	.get_strings		= qca83xx_get_strings,
+ 	.get_stats		= qca83xx_get_stats,
+-	.suspend		= qca83xx_suspend,
++	.suspend		= qca8327_suspend,
+ 	.resume			= qca83xx_resume,
+ }, {
+ 	/* Qualcomm QCA8081 */

+ 94 - 0
target/linux/generic/backport-6.6/707-v6.8-05-net-phy-at803x-move-specific-DT-option-for-at8031-to.patch

@@ -0,0 +1,94 @@
+From 900eef75cc5018e149c52fe305c9c3fe424c52a7 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 8 Dec 2023 15:51:52 +0100
+Subject: [PATCH 05/13] net: phy: at803x: move specific DT option for at8031 to
+ specific probe
+
+Move specific DT options for at8031 to specific probe to tidy things up
+and make at803x_parse_dt more generic.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 55 ++++++++++++++++++++++------------------
+ 1 file changed, 31 insertions(+), 24 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -825,30 +825,6 @@ static int at803x_parse_dt(struct phy_de
+ 		}
+ 	}
+ 
+-	/* Only supported on AR8031/AR8033, the AR8030/AR8035 use strapping
+-	 * options.
+-	 */
+-	if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+-		if (of_property_read_bool(node, "qca,keep-pll-enabled"))
+-			priv->flags |= AT803X_KEEP_PLL_ENABLED;
+-
+-		ret = at8031_register_regulators(phydev);
+-		if (ret < 0)
+-			return ret;
+-
+-		ret = devm_regulator_get_enable_optional(&phydev->mdio.dev,
+-							 "vddio");
+-		if (ret) {
+-			phydev_err(phydev, "failed to get VDDIO regulator\n");
+-			return ret;
+-		}
+-
+-		/* Only AR8031/8033 support 1000Base-X for SFP modules */
+-		ret = phy_sfp_probe(phydev, &at803x_sfp_ops);
+-		if (ret < 0)
+-			return ret;
+-	}
+-
+ 	return 0;
+ }
+ 
+@@ -1582,6 +1558,30 @@ static int at803x_cable_test_start(struc
+ 	return 0;
+ }
+ 
++static int at8031_parse_dt(struct phy_device *phydev)
++{
++	struct device_node *node = phydev->mdio.dev.of_node;
++	struct at803x_priv *priv = phydev->priv;
++	int ret;
++
++	if (of_property_read_bool(node, "qca,keep-pll-enabled"))
++		priv->flags |= AT803X_KEEP_PLL_ENABLED;
++
++	ret = at8031_register_regulators(phydev);
++	if (ret < 0)
++		return ret;
++
++	ret = devm_regulator_get_enable_optional(&phydev->mdio.dev,
++						 "vddio");
++	if (ret) {
++		phydev_err(phydev, "failed to get VDDIO regulator\n");
++		return ret;
++	}
++
++	/* Only AR8031/8033 support 1000Base-X for SFP modules */
++	return phy_sfp_probe(phydev, &at803x_sfp_ops);
++}
++
+ static int at8031_probe(struct phy_device *phydev)
+ {
+ 	int ret;
+@@ -1590,6 +1590,13 @@ static int at8031_probe(struct phy_devic
+ 	if (ret)
+ 		return ret;
+ 
++	/* Only supported on AR8031/AR8033, the AR8030/AR8035 use strapping
++	 * options.
++	 */
++	ret = at8031_parse_dt(phydev);
++	if (ret)
++		return ret;
++
+ 	/* Disable WoL in 1588 register which is enabled
+ 	 * by default
+ 	 */

+ 78 - 0
target/linux/generic/backport-6.6/707-v6.8-06-net-phy-at803x-move-specific-at8031-probe-mode-check.patch

@@ -0,0 +1,78 @@
+From 25d2ba94005fac18fe68878cddff59a67e115554 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 8 Dec 2023 15:51:53 +0100
+Subject: [PATCH 06/13] net: phy: at803x: move specific at8031 probe mode check
+ to dedicated probe
+
+Move specific at8031 probe mode check to dedicated probe to make
+at803x_probe more generic and keep code tidy.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 39 +++++++++++++++++++--------------------
+ 1 file changed, 19 insertions(+), 20 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -844,26 +844,6 @@ static int at803x_probe(struct phy_devic
+ 	if (ret)
+ 		return ret;
+ 
+-	if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+-		int ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
+-		int mode_cfg;
+-
+-		if (ccr < 0)
+-			return ccr;
+-		mode_cfg = ccr & AT803X_MODE_CFG_MASK;
+-
+-		switch (mode_cfg) {
+-		case AT803X_MODE_CFG_BX1000_RGMII_50OHM:
+-		case AT803X_MODE_CFG_BX1000_RGMII_75OHM:
+-			priv->is_1000basex = true;
+-			fallthrough;
+-		case AT803X_MODE_CFG_FX100_RGMII_50OHM:
+-		case AT803X_MODE_CFG_FX100_RGMII_75OHM:
+-			priv->is_fiber = true;
+-			break;
+-		}
+-	}
+-
+ 	return 0;
+ }
+ 
+@@ -1584,6 +1564,9 @@ static int at8031_parse_dt(struct phy_de
+ 
+ static int at8031_probe(struct phy_device *phydev)
+ {
++	struct at803x_priv *priv = phydev->priv;
++	int mode_cfg;
++	int ccr;
+ 	int ret;
+ 
+ 	ret = at803x_probe(phydev);
+@@ -1597,6 +1580,22 @@ static int at8031_probe(struct phy_devic
+ 	if (ret)
+ 		return ret;
+ 
++	ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
++	if (ccr < 0)
++		return ccr;
++	mode_cfg = ccr & AT803X_MODE_CFG_MASK;
++
++	switch (mode_cfg) {
++	case AT803X_MODE_CFG_BX1000_RGMII_50OHM:
++	case AT803X_MODE_CFG_BX1000_RGMII_75OHM:
++		priv->is_1000basex = true;
++		fallthrough;
++	case AT803X_MODE_CFG_FX100_RGMII_50OHM:
++	case AT803X_MODE_CFG_FX100_RGMII_75OHM:
++		priv->is_fiber = true;
++		break;
++	}
++
+ 	/* Disable WoL in 1588 register which is enabled
+ 	 * by default
+ 	 */

+ 86 - 0
target/linux/generic/backport-6.6/707-v6.8-07-net-phy-at803x-move-specific-at8031-config_init-to-d.patch

@@ -0,0 +1,86 @@
+From 3ae3bc426eaf57ca8f53d75777d9a5ef779bc7b7 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 8 Dec 2023 15:51:54 +0100
+Subject: [PATCH 07/13] net: phy: at803x: move specific at8031 config_init to
+ dedicated function
+
+Move specific at8031 config_init to dedicated function to make
+at803x_config_init more generic and tidy things up.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 45 ++++++++++++++++++++++------------------
+ 1 file changed, 25 insertions(+), 20 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -951,27 +951,8 @@ static int at803x_hibernation_mode_confi
+ 
+ static int at803x_config_init(struct phy_device *phydev)
+ {
+-	struct at803x_priv *priv = phydev->priv;
+ 	int ret;
+ 
+-	if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+-		/* Some bootloaders leave the fiber page selected.
+-		 * Switch to the appropriate page (fiber or copper), as otherwise we
+-		 * read the PHY capabilities from the wrong page.
+-		 */
+-		phy_lock_mdio_bus(phydev);
+-		ret = at803x_write_page(phydev,
+-					priv->is_fiber ? AT803X_PAGE_FIBER :
+-							 AT803X_PAGE_COPPER);
+-		phy_unlock_mdio_bus(phydev);
+-		if (ret)
+-			return ret;
+-
+-		ret = at8031_pll_config(phydev);
+-		if (ret < 0)
+-			return ret;
+-	}
+-
+ 	/* The RX and TX delay default is:
+ 	 *   after HW reset: RX delay enabled and TX delay disabled
+ 	 *   after SW reset: RX delay enabled, while TX delay retains the
+@@ -1604,6 +1585,30 @@ static int at8031_probe(struct phy_devic
+ 			      AT803X_WOL_EN, 0);
+ }
+ 
++static int at8031_config_init(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++	int ret;
++
++	/* Some bootloaders leave the fiber page selected.
++	 * Switch to the appropriate page (fiber or copper), as otherwise we
++	 * read the PHY capabilities from the wrong page.
++	 */
++	phy_lock_mdio_bus(phydev);
++	ret = at803x_write_page(phydev,
++				priv->is_fiber ? AT803X_PAGE_FIBER :
++						 AT803X_PAGE_COPPER);
++	phy_unlock_mdio_bus(phydev);
++	if (ret)
++		return ret;
++
++	ret = at8031_pll_config(phydev);
++	if (ret < 0)
++		return ret;
++
++	return at803x_config_init(phydev);
++}
++
+ static int qca83xx_config_init(struct phy_device *phydev)
+ {
+ 	u8 switch_revision;
+@@ -2113,7 +2118,7 @@ static struct phy_driver at803x_driver[]
+ 	.name			= "Qualcomm Atheros AR8031/AR8033",
+ 	.flags			= PHY_POLL_CABLE_TEST,
+ 	.probe			= at8031_probe,
+-	.config_init		= at803x_config_init,
++	.config_init		= at8031_config_init,
+ 	.config_aneg		= at803x_config_aneg,
+ 	.soft_reset		= genphy_soft_reset,
+ 	.set_wol		= at803x_set_wol,

+ 92 - 0
target/linux/generic/backport-6.6/707-v6.8-08-net-phy-at803x-move-specific-at8031-WOL-bits-to-dedi.patch

@@ -0,0 +1,92 @@
+From 27b89c9dc1b0393090d68d651b82f30ad2696baa Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 8 Dec 2023 15:51:55 +0100
+Subject: [PATCH 08/13] net: phy: at803x: move specific at8031 WOL bits to
+ dedicated function
+
+Move specific at8031 WOL enable/disable to dedicated function to make
+at803x_set_wol more generic.
+
+This is needed in preparation for PHY driver split as qca8081 share the
+same function to toggle WOL settings.
+
+In this new implementation WOL module in at8031 is enabled after the
+generic interrupt is setup. This should not cause any problem as the
+WOL_INT has a separate implementation and only relay on MAC bits.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 42 ++++++++++++++++++++++++----------------
+ 1 file changed, 25 insertions(+), 17 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -466,27 +466,11 @@ static int at803x_set_wol(struct phy_dev
+ 			phy_write_mmd(phydev, MDIO_MMD_PCS, offsets[i],
+ 				      mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
+ 
+-		/* Enable WOL function for 1588 */
+-		if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+-			ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
+-					     AT803X_PHY_MMD3_WOL_CTRL,
+-					     0, AT803X_WOL_EN);
+-			if (ret)
+-				return ret;
+-		}
+ 		/* Enable WOL interrupt */
+ 		ret = phy_modify(phydev, AT803X_INTR_ENABLE, 0, AT803X_INTR_ENABLE_WOL);
+ 		if (ret)
+ 			return ret;
+ 	} else {
+-		/* Disable WoL function for 1588 */
+-		if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+-			ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
+-					     AT803X_PHY_MMD3_WOL_CTRL,
+-					     AT803X_WOL_EN, 0);
+-			if (ret)
+-				return ret;
+-		}
+ 		/* Disable WOL interrupt */
+ 		ret = phy_modify(phydev, AT803X_INTR_ENABLE, AT803X_INTR_ENABLE_WOL, 0);
+ 		if (ret)
+@@ -1609,6 +1593,30 @@ static int at8031_config_init(struct phy
+ 	return at803x_config_init(phydev);
+ }
+ 
++static int at8031_set_wol(struct phy_device *phydev,
++			  struct ethtool_wolinfo *wol)
++{
++	int ret;
++
++	/* First setup MAC address and enable WOL interrupt */
++	ret = at803x_set_wol(phydev, wol);
++	if (ret)
++		return ret;
++
++	if (wol->wolopts & WAKE_MAGIC)
++		/* Enable WOL function for 1588 */
++		ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
++				     AT803X_PHY_MMD3_WOL_CTRL,
++				     0, AT803X_WOL_EN);
++	else
++		/* Disable WoL function for 1588 */
++		ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
++				     AT803X_PHY_MMD3_WOL_CTRL,
++				     AT803X_WOL_EN, 0);
++
++	return ret;
++}
++
+ static int qca83xx_config_init(struct phy_device *phydev)
+ {
+ 	u8 switch_revision;
+@@ -2121,7 +2129,7 @@ static struct phy_driver at803x_driver[]
+ 	.config_init		= at8031_config_init,
+ 	.config_aneg		= at803x_config_aneg,
+ 	.soft_reset		= genphy_soft_reset,
+-	.set_wol		= at803x_set_wol,
++	.set_wol		= at8031_set_wol,
+ 	.get_wol		= at803x_get_wol,
+ 	.suspend		= at803x_suspend,
+ 	.resume			= at803x_resume,

+ 78 - 0
target/linux/generic/backport-6.6/707-v6.8-09-net-phy-at803x-move-specific-at8031-config_intr-to-d.patch

@@ -0,0 +1,78 @@
+From 30dd62191d3dd97c08f7f9dc9ce77ffab457e4fb Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 8 Dec 2023 15:51:56 +0100
+Subject: [PATCH 09/13] net: phy: at803x: move specific at8031 config_intr to
+ dedicated function
+
+Move specific at8031 config_intr bits to dedicated function to make
+at803x_config_initr more generic.
+
+This is needed in preparation for PHY driver split as qca8081 share the
+same function to setup interrupts.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 30 ++++++++++++++++++++++++------
+ 1 file changed, 24 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -990,7 +990,6 @@ static int at803x_ack_interrupt(struct p
+ 
+ static int at803x_config_intr(struct phy_device *phydev)
+ {
+-	struct at803x_priv *priv = phydev->priv;
+ 	int err;
+ 	int value;
+ 
+@@ -1007,10 +1006,6 @@ static int at803x_config_intr(struct phy
+ 		value |= AT803X_INTR_ENABLE_DUPLEX_CHANGED;
+ 		value |= AT803X_INTR_ENABLE_LINK_FAIL;
+ 		value |= AT803X_INTR_ENABLE_LINK_SUCCESS;
+-		if (priv->is_fiber) {
+-			value |= AT803X_INTR_ENABLE_LINK_FAIL_BX;
+-			value |= AT803X_INTR_ENABLE_LINK_SUCCESS_BX;
+-		}
+ 
+ 		err = phy_write(phydev, AT803X_INTR_ENABLE, value);
+ 	} else {
+@@ -1617,6 +1612,29 @@ static int at8031_set_wol(struct phy_dev
+ 	return ret;
+ }
+ 
++static int at8031_config_intr(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++	int err, value = 0;
++
++	if (phydev->interrupts == PHY_INTERRUPT_ENABLED &&
++	    priv->is_fiber) {
++		/* Clear any pending interrupts */
++		err = at803x_ack_interrupt(phydev);
++		if (err)
++			return err;
++
++		value |= AT803X_INTR_ENABLE_LINK_FAIL_BX;
++		value |= AT803X_INTR_ENABLE_LINK_SUCCESS_BX;
++
++		err = phy_set_bits(phydev, AT803X_INTR_ENABLE, value);
++		if (err)
++			return err;
++	}
++
++	return at803x_config_intr(phydev);
++}
++
+ static int qca83xx_config_init(struct phy_device *phydev)
+ {
+ 	u8 switch_revision;
+@@ -2137,7 +2155,7 @@ static struct phy_driver at803x_driver[]
+ 	.write_page		= at803x_write_page,
+ 	.get_features		= at803x_get_features,
+ 	.read_status		= at803x_read_status,
+-	.config_intr		= at803x_config_intr,
++	.config_intr		= at8031_config_intr,
+ 	.handle_interrupt	= at803x_handle_interrupt,
+ 	.get_tunable		= at803x_get_tunable,
+ 	.set_tunable		= at803x_set_tunable,

+ 78 - 0
target/linux/generic/backport-6.6/707-v6.8-10-net-phy-at803x-make-at8031-related-DT-functions-name.patch

@@ -0,0 +1,78 @@
+From a5ab9d8e7ae0da8328ac1637a9755311508dc8ab Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 8 Dec 2023 15:51:57 +0100
+Subject: [PATCH 10/13] net: phy: at803x: make at8031 related DT functions name
+ more specific
+
+Rename at8031 related DT function name to a more specific name
+referencing they are only related to at8031 and not to the generic
+at803x PHY family.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 16 ++++++++--------
+ 1 file changed, 8 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -583,7 +583,7 @@ static int at803x_resume(struct phy_devi
+ 	return phy_modify(phydev, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, 0);
+ }
+ 
+-static int at803x_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev,
++static int at8031_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev,
+ 					    unsigned int selector)
+ {
+ 	struct phy_device *phydev = rdev_get_drvdata(rdev);
+@@ -596,7 +596,7 @@ static int at803x_rgmii_reg_set_voltage_
+ 					     AT803X_DEBUG_RGMII_1V8, 0);
+ }
+ 
+-static int at803x_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev)
++static int at8031_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev)
+ {
+ 	struct phy_device *phydev = rdev_get_drvdata(rdev);
+ 	int val;
+@@ -610,8 +610,8 @@ static int at803x_rgmii_reg_get_voltage_
+ 
+ static const struct regulator_ops vddio_regulator_ops = {
+ 	.list_voltage = regulator_list_voltage_table,
+-	.set_voltage_sel = at803x_rgmii_reg_set_voltage_sel,
+-	.get_voltage_sel = at803x_rgmii_reg_get_voltage_sel,
++	.set_voltage_sel = at8031_rgmii_reg_set_voltage_sel,
++	.get_voltage_sel = at8031_rgmii_reg_get_voltage_sel,
+ };
+ 
+ static const unsigned int vddio_voltage_table[] = {
+@@ -666,7 +666,7 @@ static int at8031_register_regulators(st
+ 	return 0;
+ }
+ 
+-static int at803x_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
++static int at8031_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
+ {
+ 	struct phy_device *phydev = upstream;
+ 	__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_support);
+@@ -710,10 +710,10 @@ static int at803x_sfp_insert(void *upstr
+ 	return 0;
+ }
+ 
+-static const struct sfp_upstream_ops at803x_sfp_ops = {
++static const struct sfp_upstream_ops at8031_sfp_ops = {
+ 	.attach = phy_sfp_attach,
+ 	.detach = phy_sfp_detach,
+-	.module_insert = at803x_sfp_insert,
++	.module_insert = at8031_sfp_insert,
+ };
+ 
+ static int at803x_parse_dt(struct phy_device *phydev)
+@@ -1519,7 +1519,7 @@ static int at8031_parse_dt(struct phy_de
+ 	}
+ 
+ 	/* Only AR8031/8033 support 1000Base-X for SFP modules */
+-	return phy_sfp_probe(phydev, &at803x_sfp_ops);
++	return phy_sfp_probe(phydev, &at8031_sfp_ops);
+ }
+ 
+ static int at8031_probe(struct phy_device *phydev)

+ 297 - 0
target/linux/generic/backport-6.6/707-v6.8-11-net-phy-at803x-move-at8031-functions-in-dedicated-se.patch

@@ -0,0 +1,297 @@
+From f932a6dc8bae0dae9645b5b1b4c65aed8a8acb2a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 8 Dec 2023 15:51:58 +0100
+Subject: [PATCH 11/13] net: phy: at803x: move at8031 functions in dedicated
+ section
+
+Move at8031 functions in dedicated section with dedicated at8031
+parse_dt and probe.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 266 +++++++++++++++++++--------------------
+ 1 file changed, 133 insertions(+), 133 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -583,139 +583,6 @@ static int at803x_resume(struct phy_devi
+ 	return phy_modify(phydev, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, 0);
+ }
+ 
+-static int at8031_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev,
+-					    unsigned int selector)
+-{
+-	struct phy_device *phydev = rdev_get_drvdata(rdev);
+-
+-	if (selector)
+-		return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+-					     0, AT803X_DEBUG_RGMII_1V8);
+-	else
+-		return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+-					     AT803X_DEBUG_RGMII_1V8, 0);
+-}
+-
+-static int at8031_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev)
+-{
+-	struct phy_device *phydev = rdev_get_drvdata(rdev);
+-	int val;
+-
+-	val = at803x_debug_reg_read(phydev, AT803X_DEBUG_REG_1F);
+-	if (val < 0)
+-		return val;
+-
+-	return (val & AT803X_DEBUG_RGMII_1V8) ? 1 : 0;
+-}
+-
+-static const struct regulator_ops vddio_regulator_ops = {
+-	.list_voltage = regulator_list_voltage_table,
+-	.set_voltage_sel = at8031_rgmii_reg_set_voltage_sel,
+-	.get_voltage_sel = at8031_rgmii_reg_get_voltage_sel,
+-};
+-
+-static const unsigned int vddio_voltage_table[] = {
+-	1500000,
+-	1800000,
+-};
+-
+-static const struct regulator_desc vddio_desc = {
+-	.name = "vddio",
+-	.of_match = of_match_ptr("vddio-regulator"),
+-	.n_voltages = ARRAY_SIZE(vddio_voltage_table),
+-	.volt_table = vddio_voltage_table,
+-	.ops = &vddio_regulator_ops,
+-	.type = REGULATOR_VOLTAGE,
+-	.owner = THIS_MODULE,
+-};
+-
+-static const struct regulator_ops vddh_regulator_ops = {
+-};
+-
+-static const struct regulator_desc vddh_desc = {
+-	.name = "vddh",
+-	.of_match = of_match_ptr("vddh-regulator"),
+-	.n_voltages = 1,
+-	.fixed_uV = 2500000,
+-	.ops = &vddh_regulator_ops,
+-	.type = REGULATOR_VOLTAGE,
+-	.owner = THIS_MODULE,
+-};
+-
+-static int at8031_register_regulators(struct phy_device *phydev)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-	struct device *dev = &phydev->mdio.dev;
+-	struct regulator_config config = { };
+-
+-	config.dev = dev;
+-	config.driver_data = phydev;
+-
+-	priv->vddio_rdev = devm_regulator_register(dev, &vddio_desc, &config);
+-	if (IS_ERR(priv->vddio_rdev)) {
+-		phydev_err(phydev, "failed to register VDDIO regulator\n");
+-		return PTR_ERR(priv->vddio_rdev);
+-	}
+-
+-	priv->vddh_rdev = devm_regulator_register(dev, &vddh_desc, &config);
+-	if (IS_ERR(priv->vddh_rdev)) {
+-		phydev_err(phydev, "failed to register VDDH regulator\n");
+-		return PTR_ERR(priv->vddh_rdev);
+-	}
+-
+-	return 0;
+-}
+-
+-static int at8031_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
+-{
+-	struct phy_device *phydev = upstream;
+-	__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_support);
+-	__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
+-	DECLARE_PHY_INTERFACE_MASK(interfaces);
+-	phy_interface_t iface;
+-
+-	linkmode_zero(phy_support);
+-	phylink_set(phy_support, 1000baseX_Full);
+-	phylink_set(phy_support, 1000baseT_Full);
+-	phylink_set(phy_support, Autoneg);
+-	phylink_set(phy_support, Pause);
+-	phylink_set(phy_support, Asym_Pause);
+-
+-	linkmode_zero(sfp_support);
+-	sfp_parse_support(phydev->sfp_bus, id, sfp_support, interfaces);
+-	/* Some modules support 10G modes as well as others we support.
+-	 * Mask out non-supported modes so the correct interface is picked.
+-	 */
+-	linkmode_and(sfp_support, phy_support, sfp_support);
+-
+-	if (linkmode_empty(sfp_support)) {
+-		dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
+-		return -EINVAL;
+-	}
+-
+-	iface = sfp_select_interface(phydev->sfp_bus, sfp_support);
+-
+-	/* Only 1000Base-X is supported by AR8031/8033 as the downstream SerDes
+-	 * interface for use with SFP modules.
+-	 * However, some copper modules detected as having a preferred SGMII
+-	 * interface do default to and function in 1000Base-X mode, so just
+-	 * print a warning and allow such modules, as they may have some chance
+-	 * of working.
+-	 */
+-	if (iface == PHY_INTERFACE_MODE_SGMII)
+-		dev_warn(&phydev->mdio.dev, "module may not function if 1000Base-X not supported\n");
+-	else if (iface != PHY_INTERFACE_MODE_1000BASEX)
+-		return -EINVAL;
+-
+-	return 0;
+-}
+-
+-static const struct sfp_upstream_ops at8031_sfp_ops = {
+-	.attach = phy_sfp_attach,
+-	.detach = phy_sfp_detach,
+-	.module_insert = at8031_sfp_insert,
+-};
+-
+ static int at803x_parse_dt(struct phy_device *phydev)
+ {
+ 	struct device_node *node = phydev->mdio.dev.of_node;
+@@ -1498,6 +1365,139 @@ static int at803x_cable_test_start(struc
+ 	return 0;
+ }
+ 
++static int at8031_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev,
++					    unsigned int selector)
++{
++	struct phy_device *phydev = rdev_get_drvdata(rdev);
++
++	if (selector)
++		return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
++					     0, AT803X_DEBUG_RGMII_1V8);
++	else
++		return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
++					     AT803X_DEBUG_RGMII_1V8, 0);
++}
++
++static int at8031_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev)
++{
++	struct phy_device *phydev = rdev_get_drvdata(rdev);
++	int val;
++
++	val = at803x_debug_reg_read(phydev, AT803X_DEBUG_REG_1F);
++	if (val < 0)
++		return val;
++
++	return (val & AT803X_DEBUG_RGMII_1V8) ? 1 : 0;
++}
++
++static const struct regulator_ops vddio_regulator_ops = {
++	.list_voltage = regulator_list_voltage_table,
++	.set_voltage_sel = at8031_rgmii_reg_set_voltage_sel,
++	.get_voltage_sel = at8031_rgmii_reg_get_voltage_sel,
++};
++
++static const unsigned int vddio_voltage_table[] = {
++	1500000,
++	1800000,
++};
++
++static const struct regulator_desc vddio_desc = {
++	.name = "vddio",
++	.of_match = of_match_ptr("vddio-regulator"),
++	.n_voltages = ARRAY_SIZE(vddio_voltage_table),
++	.volt_table = vddio_voltage_table,
++	.ops = &vddio_regulator_ops,
++	.type = REGULATOR_VOLTAGE,
++	.owner = THIS_MODULE,
++};
++
++static const struct regulator_ops vddh_regulator_ops = {
++};
++
++static const struct regulator_desc vddh_desc = {
++	.name = "vddh",
++	.of_match = of_match_ptr("vddh-regulator"),
++	.n_voltages = 1,
++	.fixed_uV = 2500000,
++	.ops = &vddh_regulator_ops,
++	.type = REGULATOR_VOLTAGE,
++	.owner = THIS_MODULE,
++};
++
++static int at8031_register_regulators(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++	struct device *dev = &phydev->mdio.dev;
++	struct regulator_config config = { };
++
++	config.dev = dev;
++	config.driver_data = phydev;
++
++	priv->vddio_rdev = devm_regulator_register(dev, &vddio_desc, &config);
++	if (IS_ERR(priv->vddio_rdev)) {
++		phydev_err(phydev, "failed to register VDDIO regulator\n");
++		return PTR_ERR(priv->vddio_rdev);
++	}
++
++	priv->vddh_rdev = devm_regulator_register(dev, &vddh_desc, &config);
++	if (IS_ERR(priv->vddh_rdev)) {
++		phydev_err(phydev, "failed to register VDDH regulator\n");
++		return PTR_ERR(priv->vddh_rdev);
++	}
++
++	return 0;
++}
++
++static int at8031_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
++{
++	struct phy_device *phydev = upstream;
++	__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_support);
++	__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
++	DECLARE_PHY_INTERFACE_MASK(interfaces);
++	phy_interface_t iface;
++
++	linkmode_zero(phy_support);
++	phylink_set(phy_support, 1000baseX_Full);
++	phylink_set(phy_support, 1000baseT_Full);
++	phylink_set(phy_support, Autoneg);
++	phylink_set(phy_support, Pause);
++	phylink_set(phy_support, Asym_Pause);
++
++	linkmode_zero(sfp_support);
++	sfp_parse_support(phydev->sfp_bus, id, sfp_support, interfaces);
++	/* Some modules support 10G modes as well as others we support.
++	 * Mask out non-supported modes so the correct interface is picked.
++	 */
++	linkmode_and(sfp_support, phy_support, sfp_support);
++
++	if (linkmode_empty(sfp_support)) {
++		dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
++		return -EINVAL;
++	}
++
++	iface = sfp_select_interface(phydev->sfp_bus, sfp_support);
++
++	/* Only 1000Base-X is supported by AR8031/8033 as the downstream SerDes
++	 * interface for use with SFP modules.
++	 * However, some copper modules detected as having a preferred SGMII
++	 * interface do default to and function in 1000Base-X mode, so just
++	 * print a warning and allow such modules, as they may have some chance
++	 * of working.
++	 */
++	if (iface == PHY_INTERFACE_MODE_SGMII)
++		dev_warn(&phydev->mdio.dev, "module may not function if 1000Base-X not supported\n");
++	else if (iface != PHY_INTERFACE_MODE_1000BASEX)
++		return -EINVAL;
++
++	return 0;
++}
++
++static const struct sfp_upstream_ops at8031_sfp_ops = {
++	.attach = phy_sfp_attach,
++	.detach = phy_sfp_detach,
++	.module_insert = at8031_sfp_insert,
++};
++
+ static int at8031_parse_dt(struct phy_device *phydev)
+ {
+ 	struct device_node *node = phydev->mdio.dev.of_node;

+ 114 - 0
target/linux/generic/backport-6.6/707-v6.8-12-net-phy-at803x-move-at8035-specific-DT-parse-to-dedi.patch

@@ -0,0 +1,114 @@
+From 21a2802a8365cfa82cc02187c1f95136d85592ad Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 8 Dec 2023 15:51:59 +0100
+Subject: [PATCH 12/13] net: phy: at803x: move at8035 specific DT parse to
+ dedicated probe
+
+Move at8035 specific DT parse for clock out frequency to dedicated probe
+to make at803x probe function more generic.
+
+This is to tidy code and no behaviour change are intended.
+
+Detection logic is changed, we check if the clk 25m mask is set and if
+it's not zero, we assume the qca,clk-out-frequency property is set.
+
+The property is checked in the generic at803x_parse_dt called by
+at803x_probe.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 60 +++++++++++++++++++++++++++-------------
+ 1 file changed, 41 insertions(+), 19 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -638,23 +638,6 @@ static int at803x_parse_dt(struct phy_de
+ 
+ 		priv->clk_25m_reg |= FIELD_PREP(AT803X_CLK_OUT_MASK, sel);
+ 		priv->clk_25m_mask |= AT803X_CLK_OUT_MASK;
+-
+-		/* Fixup for the AR8030/AR8035. This chip has another mask and
+-		 * doesn't support the DSP reference. Eg. the lowest bit of the
+-		 * mask. The upper two bits select the same frequencies. Mask
+-		 * the lowest bit here.
+-		 *
+-		 * Warning:
+-		 *   There was no datasheet for the AR8030 available so this is
+-		 *   just a guess. But the AR8035 is listed as pin compatible
+-		 *   to the AR8030 so there might be a good chance it works on
+-		 *   the AR8030 too.
+-		 */
+-		if (phydev->drv->phy_id == ATH8030_PHY_ID ||
+-		    phydev->drv->phy_id == ATH8035_PHY_ID) {
+-			priv->clk_25m_reg &= AT8035_CLK_OUT_MASK;
+-			priv->clk_25m_mask &= AT8035_CLK_OUT_MASK;
+-		}
+ 	}
+ 
+ 	ret = of_property_read_u32(node, "qca,clk-out-strength", &strength);
+@@ -1635,6 +1618,45 @@ static int at8031_config_intr(struct phy
+ 	return at803x_config_intr(phydev);
+ }
+ 
++static int at8035_parse_dt(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++
++	/* Mask is set by the generic at803x_parse_dt
++	 * if property is set. Assume property is set
++	 * with the mask not zero.
++	 */
++	if (priv->clk_25m_mask) {
++		/* Fixup for the AR8030/AR8035. This chip has another mask and
++		 * doesn't support the DSP reference. Eg. the lowest bit of the
++		 * mask. The upper two bits select the same frequencies. Mask
++		 * the lowest bit here.
++		 *
++		 * Warning:
++		 *   There was no datasheet for the AR8030 available so this is
++		 *   just a guess. But the AR8035 is listed as pin compatible
++		 *   to the AR8030 so there might be a good chance it works on
++		 *   the AR8030 too.
++		 */
++		priv->clk_25m_reg &= AT8035_CLK_OUT_MASK;
++		priv->clk_25m_mask &= AT8035_CLK_OUT_MASK;
++	}
++
++	return 0;
++}
++
++/* AR8030 and AR8035 shared the same special mask for clk_25m */
++static int at8035_probe(struct phy_device *phydev)
++{
++	int ret;
++
++	ret = at803x_probe(phydev);
++	if (ret)
++		return ret;
++
++	return at8035_parse_dt(phydev);
++}
++
+ static int qca83xx_config_init(struct phy_device *phydev)
+ {
+ 	u8 switch_revision;
+@@ -2107,7 +2129,7 @@ static struct phy_driver at803x_driver[]
+ 	PHY_ID_MATCH_EXACT(ATH8035_PHY_ID),
+ 	.name			= "Qualcomm Atheros AR8035",
+ 	.flags			= PHY_POLL_CABLE_TEST,
+-	.probe			= at803x_probe,
++	.probe			= at8035_probe,
+ 	.config_aneg		= at803x_config_aneg,
+ 	.config_init		= at803x_config_init,
+ 	.soft_reset		= genphy_soft_reset,
+@@ -2128,7 +2150,7 @@ static struct phy_driver at803x_driver[]
+ 	.phy_id			= ATH8030_PHY_ID,
+ 	.name			= "Qualcomm Atheros AR8030",
+ 	.phy_id_mask		= AT8030_PHY_ID_MASK,
+-	.probe			= at803x_probe,
++	.probe			= at8035_probe,
+ 	.config_init		= at803x_config_init,
+ 	.link_change_notify	= at803x_link_change_notify,
+ 	.set_wol		= at803x_set_wol,

+ 219 - 0
target/linux/generic/backport-6.6/707-v6.8-13-net-phy-at803x-drop-specific-PHY-ID-check-from-cable.patch

@@ -0,0 +1,219 @@
+From ef9df47b449e32e06501a11272809be49019bdb6 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 8 Dec 2023 15:52:00 +0100
+Subject: [PATCH 13/13] net: phy: at803x: drop specific PHY ID check from cable
+ test functions
+
+Drop specific PHY ID check for cable test functions for at803x. This is
+done to make functions more generic. While at it better describe what
+the functions does by using more symbolic function names.
+
+PHYs that requires to set additional reg are moved to specific function
+calling the more generic one.
+
+cdt_start and cdt_wait_for_completion are changed to take an additional
+arg to pass specific values specific to the PHY.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 95 +++++++++++++++++++++-------------------
+ 1 file changed, 50 insertions(+), 45 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1222,31 +1222,16 @@ static int at803x_cdt_fault_length(u16 s
+ 	return (dt * 824) / 10;
+ }
+ 
+-static int at803x_cdt_start(struct phy_device *phydev, int pair)
++static int at803x_cdt_start(struct phy_device *phydev,
++			    u32 cdt_start)
+ {
+-	u16 cdt;
+-
+-	/* qca8081 takes the different bit 15 to enable CDT test */
+-	if (phydev->drv->phy_id == QCA8081_PHY_ID)
+-		cdt = QCA808X_CDT_ENABLE_TEST |
+-			QCA808X_CDT_LENGTH_UNIT |
+-			QCA808X_CDT_INTER_CHECK_DIS;
+-	else
+-		cdt = FIELD_PREP(AT803X_CDT_MDI_PAIR_MASK, pair) |
+-			AT803X_CDT_ENABLE_TEST;
+-
+-	return phy_write(phydev, AT803X_CDT, cdt);
++	return phy_write(phydev, AT803X_CDT, cdt_start);
+ }
+ 
+-static int at803x_cdt_wait_for_completion(struct phy_device *phydev)
++static int at803x_cdt_wait_for_completion(struct phy_device *phydev,
++					  u32 cdt_en)
+ {
+ 	int val, ret;
+-	u16 cdt_en;
+-
+-	if (phydev->drv->phy_id == QCA8081_PHY_ID)
+-		cdt_en = QCA808X_CDT_ENABLE_TEST;
+-	else
+-		cdt_en = AT803X_CDT_ENABLE_TEST;
+ 
+ 	/* One test run takes about 25ms */
+ 	ret = phy_read_poll_timeout(phydev, AT803X_CDT, val,
+@@ -1266,11 +1251,13 @@ static int at803x_cable_test_one_pair(st
+ 	};
+ 	int ret, val;
+ 
+-	ret = at803x_cdt_start(phydev, pair);
++	val = FIELD_PREP(AT803X_CDT_MDI_PAIR_MASK, pair) |
++	      AT803X_CDT_ENABLE_TEST;
++	ret = at803x_cdt_start(phydev, val);
+ 	if (ret)
+ 		return ret;
+ 
+-	ret = at803x_cdt_wait_for_completion(phydev);
++	ret = at803x_cdt_wait_for_completion(phydev, AT803X_CDT_ENABLE_TEST);
+ 	if (ret)
+ 		return ret;
+ 
+@@ -1292,19 +1279,11 @@ static int at803x_cable_test_one_pair(st
+ }
+ 
+ static int at803x_cable_test_get_status(struct phy_device *phydev,
+-					bool *finished)
++					bool *finished, unsigned long pair_mask)
+ {
+-	unsigned long pair_mask;
+ 	int retries = 20;
+ 	int pair, ret;
+ 
+-	if (phydev->phy_id == ATH9331_PHY_ID ||
+-	    phydev->phy_id == ATH8032_PHY_ID ||
+-	    phydev->phy_id == QCA9561_PHY_ID)
+-		pair_mask = 0x3;
+-	else
+-		pair_mask = 0xf;
+-
+ 	*finished = false;
+ 
+ 	/* According to the datasheet the CDT can be performed when
+@@ -1331,7 +1310,7 @@ static int at803x_cable_test_get_status(
+ 	return 0;
+ }
+ 
+-static int at803x_cable_test_start(struct phy_device *phydev)
++static void at803x_cable_test_autoneg(struct phy_device *phydev)
+ {
+ 	/* Enable auto-negotiation, but advertise no capabilities, no link
+ 	 * will be established. A restart of the auto-negotiation is not
+@@ -1339,11 +1318,11 @@ static int at803x_cable_test_start(struc
+ 	 */
+ 	phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
+ 	phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA);
+-	if (phydev->phy_id != ATH9331_PHY_ID &&
+-	    phydev->phy_id != ATH8032_PHY_ID &&
+-	    phydev->phy_id != QCA9561_PHY_ID)
+-		phy_write(phydev, MII_CTRL1000, 0);
++}
+ 
++static int at803x_cable_test_start(struct phy_device *phydev)
++{
++	at803x_cable_test_autoneg(phydev);
+ 	/* we do all the (time consuming) work later */
+ 	return 0;
+ }
+@@ -1618,6 +1597,29 @@ static int at8031_config_intr(struct phy
+ 	return at803x_config_intr(phydev);
+ }
+ 
++/* AR8031 and AR8035 share the same cable test get status reg */
++static int at8031_cable_test_get_status(struct phy_device *phydev,
++					bool *finished)
++{
++	return at803x_cable_test_get_status(phydev, finished, 0xf);
++}
++
++/* AR8031 and AR8035 share the same cable test start logic */
++static int at8031_cable_test_start(struct phy_device *phydev)
++{
++	at803x_cable_test_autoneg(phydev);
++	phy_write(phydev, MII_CTRL1000, 0);
++	/* we do all the (time consuming) work later */
++	return 0;
++}
++
++/* AR8032, AR9331 and QCA9561 share the same cable test get status reg */
++static int at8032_cable_test_get_status(struct phy_device *phydev,
++					bool *finished)
++{
++	return at803x_cable_test_get_status(phydev, finished, 0x3);
++}
++
+ static int at8035_parse_dt(struct phy_device *phydev)
+ {
+ 	struct at803x_priv *priv = phydev->priv;
+@@ -2041,11 +2043,14 @@ static int qca808x_cable_test_get_status
+ 
+ 	*finished = false;
+ 
+-	ret = at803x_cdt_start(phydev, 0);
++	val = QCA808X_CDT_ENABLE_TEST |
++	      QCA808X_CDT_LENGTH_UNIT |
++	      QCA808X_CDT_INTER_CHECK_DIS;
++	ret = at803x_cdt_start(phydev, val);
+ 	if (ret)
+ 		return ret;
+ 
+-	ret = at803x_cdt_wait_for_completion(phydev);
++	ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
+ 	if (ret)
+ 		return ret;
+ 
+@@ -2143,8 +2148,8 @@ static struct phy_driver at803x_driver[]
+ 	.handle_interrupt	= at803x_handle_interrupt,
+ 	.get_tunable		= at803x_get_tunable,
+ 	.set_tunable		= at803x_set_tunable,
+-	.cable_test_start	= at803x_cable_test_start,
+-	.cable_test_get_status	= at803x_cable_test_get_status,
++	.cable_test_start	= at8031_cable_test_start,
++	.cable_test_get_status	= at8031_cable_test_get_status,
+ }, {
+ 	/* Qualcomm Atheros AR8030 */
+ 	.phy_id			= ATH8030_PHY_ID,
+@@ -2181,8 +2186,8 @@ static struct phy_driver at803x_driver[]
+ 	.handle_interrupt	= at803x_handle_interrupt,
+ 	.get_tunable		= at803x_get_tunable,
+ 	.set_tunable		= at803x_set_tunable,
+-	.cable_test_start	= at803x_cable_test_start,
+-	.cable_test_get_status	= at803x_cable_test_get_status,
++	.cable_test_start	= at8031_cable_test_start,
++	.cable_test_get_status	= at8031_cable_test_get_status,
+ }, {
+ 	/* Qualcomm Atheros AR8032 */
+ 	PHY_ID_MATCH_EXACT(ATH8032_PHY_ID),
+@@ -2197,7 +2202,7 @@ static struct phy_driver at803x_driver[]
+ 	.config_intr		= at803x_config_intr,
+ 	.handle_interrupt	= at803x_handle_interrupt,
+ 	.cable_test_start	= at803x_cable_test_start,
+-	.cable_test_get_status	= at803x_cable_test_get_status,
++	.cable_test_get_status	= at8032_cable_test_get_status,
+ }, {
+ 	/* ATHEROS AR9331 */
+ 	PHY_ID_MATCH_EXACT(ATH9331_PHY_ID),
+@@ -2210,7 +2215,7 @@ static struct phy_driver at803x_driver[]
+ 	.config_intr		= at803x_config_intr,
+ 	.handle_interrupt	= at803x_handle_interrupt,
+ 	.cable_test_start	= at803x_cable_test_start,
+-	.cable_test_get_status	= at803x_cable_test_get_status,
++	.cable_test_get_status	= at8032_cable_test_get_status,
+ 	.read_status		= at803x_read_status,
+ 	.soft_reset		= genphy_soft_reset,
+ 	.config_aneg		= at803x_config_aneg,
+@@ -2226,7 +2231,7 @@ static struct phy_driver at803x_driver[]
+ 	.config_intr		= at803x_config_intr,
+ 	.handle_interrupt	= at803x_handle_interrupt,
+ 	.cable_test_start	= at803x_cable_test_start,
+-	.cable_test_get_status	= at803x_cable_test_get_status,
++	.cable_test_get_status	= at8032_cable_test_get_status,
+ 	.read_status		= at803x_read_status,
+ 	.soft_reset		= genphy_soft_reset,
+ 	.config_aneg		= at803x_config_aneg,

+ 116 - 0
target/linux/generic/backport-6.6/708-v6.8-01-net-phy-at803x-move-specific-qca808x-config_aneg-to-.patch

@@ -0,0 +1,116 @@
+From 8e732f1c6f2dc5e18f766d0f1b11df9db2dd044a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Thu, 14 Dec 2023 01:44:31 +0100
+Subject: [PATCH 1/2] net: phy: at803x: move specific qca808x config_aneg to
+ dedicated function
+
+Move specific qca808x config_aneg to dedicated function to permit easier
+split of qca808x portion from at803x driver.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 66 ++++++++++++++++++++++++----------------
+ 1 file changed, 40 insertions(+), 26 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1045,9 +1045,8 @@ static int at803x_config_mdix(struct phy
+ 			  FIELD_PREP(AT803X_SFC_MDI_CROSSOVER_MODE_M, val));
+ }
+ 
+-static int at803x_config_aneg(struct phy_device *phydev)
++static int at803x_prepare_config_aneg(struct phy_device *phydev)
+ {
+-	struct at803x_priv *priv = phydev->priv;
+ 	int ret;
+ 
+ 	ret = at803x_config_mdix(phydev, phydev->mdix_ctrl);
+@@ -1064,33 +1063,22 @@ static int at803x_config_aneg(struct phy
+ 			return ret;
+ 	}
+ 
+-	if (priv->is_1000basex)
+-		return genphy_c37_config_aneg(phydev);
+-
+-	/* Do not restart auto-negotiation by setting ret to 0 defautly,
+-	 * when calling __genphy_config_aneg later.
+-	 */
+-	ret = 0;
+-
+-	if (phydev->drv->phy_id == QCA8081_PHY_ID) {
+-		int phy_ctrl = 0;
++	return 0;
++}
+ 
+-		/* The reg MII_BMCR also needs to be configured for force mode, the
+-		 * genphy_config_aneg is also needed.
+-		 */
+-		if (phydev->autoneg == AUTONEG_DISABLE)
+-			genphy_c45_pma_setup_forced(phydev);
++static int at803x_config_aneg(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++	int ret;
+ 
+-		if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising))
+-			phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
++	ret = at803x_prepare_config_aneg(phydev);
++	if (ret)
++		return ret;
+ 
+-		ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+-				MDIO_AN_10GBT_CTRL_ADV2_5G, phy_ctrl);
+-		if (ret < 0)
+-			return ret;
+-	}
++	if (priv->is_1000basex)
++		return genphy_c37_config_aneg(phydev);
+ 
+-	return __genphy_config_aneg(phydev, ret);
++	return genphy_config_aneg(phydev);
+ }
+ 
+ static int at803x_get_downshift(struct phy_device *phydev, u8 *d)
+@@ -2118,6 +2106,32 @@ static int qca808x_get_features(struct p
+ 	return 0;
+ }
+ 
++static int qca808x_config_aneg(struct phy_device *phydev)
++{
++	int phy_ctrl = 0;
++	int ret;
++
++	ret = at803x_prepare_config_aneg(phydev);
++	if (ret)
++		return ret;
++
++	/* The reg MII_BMCR also needs to be configured for force mode, the
++	 * genphy_config_aneg is also needed.
++	 */
++	if (phydev->autoneg == AUTONEG_DISABLE)
++		genphy_c45_pma_setup_forced(phydev);
++
++	if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising))
++		phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
++
++	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
++				     MDIO_AN_10GBT_CTRL_ADV2_5G, phy_ctrl);
++	if (ret < 0)
++		return ret;
++
++	return __genphy_config_aneg(phydev, ret);
++}
++
+ static void qca808x_link_change_notify(struct phy_device *phydev)
+ {
+ 	/* Assert interface sgmii fifo on link down, deassert it on link up,
+@@ -2295,7 +2309,7 @@ static struct phy_driver at803x_driver[]
+ 	.set_wol		= at803x_set_wol,
+ 	.get_wol		= at803x_get_wol,
+ 	.get_features		= qca808x_get_features,
+-	.config_aneg		= at803x_config_aneg,
++	.config_aneg		= qca808x_config_aneg,
+ 	.suspend		= genphy_suspend,
+ 	.resume			= genphy_resume,
+ 	.read_status		= qca808x_read_status,

+ 97 - 0
target/linux/generic/backport-6.6/708-v6.8-02-net-phy-at803x-make-read-specific-status-function-mo.patch

@@ -0,0 +1,97 @@
+From 38eb804e8458ba181a03a0498ce4bf84eebd1931 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Thu, 14 Dec 2023 01:44:32 +0100
+Subject: [PATCH 2/2] net: phy: at803x: make read specific status function more
+ generic
+
+Rework read specific status function to be more generic. The function
+apply different speed mask based on the PHY ID. Make it more generic by
+adding an additional arg to pass the specific speed (ss) mask and use
+the provided mask to parse the speed value.
+
+This is needed to permit an easier deatch of qca808x code from the
+at803x driver.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 26 ++++++++++++++++++--------
+ 1 file changed, 18 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -301,6 +301,11 @@ static struct at803x_hw_stat qca83xx_hw_
+ 	{ "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
+ };
+ 
++struct at803x_ss_mask {
++	u16 speed_mask;
++	u8 speed_shift;
++};
++
+ struct at803x_priv {
+ 	int flags;
+ 	u16 clk_25m_reg;
+@@ -921,7 +926,8 @@ static void at803x_link_change_notify(st
+ 	}
+ }
+ 
+-static int at803x_read_specific_status(struct phy_device *phydev)
++static int at803x_read_specific_status(struct phy_device *phydev,
++				       struct at803x_ss_mask ss_mask)
+ {
+ 	int ss;
+ 
+@@ -940,11 +946,8 @@ static int at803x_read_specific_status(s
+ 		if (sfc < 0)
+ 			return sfc;
+ 
+-		/* qca8081 takes the different bits for speed value from at803x */
+-		if (phydev->drv->phy_id == QCA8081_PHY_ID)
+-			speed = FIELD_GET(QCA808X_SS_SPEED_MASK, ss);
+-		else
+-			speed = FIELD_GET(AT803X_SS_SPEED_MASK, ss);
++		speed = ss & ss_mask.speed_mask;
++		speed >>= ss_mask.speed_shift;
+ 
+ 		switch (speed) {
+ 		case AT803X_SS_SPEED_10:
+@@ -989,6 +992,7 @@ static int at803x_read_specific_status(s
+ static int at803x_read_status(struct phy_device *phydev)
+ {
+ 	struct at803x_priv *priv = phydev->priv;
++	struct at803x_ss_mask ss_mask = { 0 };
+ 	int err, old_link = phydev->link;
+ 
+ 	if (priv->is_1000basex)
+@@ -1012,7 +1016,9 @@ static int at803x_read_status(struct phy
+ 	if (err < 0)
+ 		return err;
+ 
+-	err = at803x_read_specific_status(phydev);
++	ss_mask.speed_mask = AT803X_SS_SPEED_MASK;
++	ss_mask.speed_shift = __bf_shf(AT803X_SS_SPEED_MASK);
++	err = at803x_read_specific_status(phydev, ss_mask);
+ 	if (err < 0)
+ 		return err;
+ 
+@@ -1869,6 +1875,7 @@ static int qca808x_config_init(struct ph
+ 
+ static int qca808x_read_status(struct phy_device *phydev)
+ {
++	struct at803x_ss_mask ss_mask = { 0 };
+ 	int ret;
+ 
+ 	ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+@@ -1882,7 +1889,10 @@ static int qca808x_read_status(struct ph
+ 	if (ret)
+ 		return ret;
+ 
+-	ret = at803x_read_specific_status(phydev);
++	/* qca8081 takes the different bits for speed value from at803x */
++	ss_mask.speed_mask = QCA808X_SS_SPEED_MASK;
++	ss_mask.speed_shift = __bf_shf(QCA808X_SS_SPEED_MASK);
++	ret = at803x_read_specific_status(phydev, ss_mask);
+ 	if (ret < 0)
+ 		return ret;
+ 

+ 27 - 0
target/linux/generic/backport-6.6/709-v6.8-01-net-phy-at803x-remove-extra-space-after-cast.patch

@@ -0,0 +1,27 @@
+From fc9d7264ddc32eaa647d6bfcdc25cdf9f786fde0 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Mon, 18 Dec 2023 00:27:39 +0100
+Subject: [PATCH 1/2] net: phy: at803x: remove extra space after cast
+
+Remove extra space after cast as reported by checkpatch to keep code
+clean.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/phy/at803x.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -462,7 +462,7 @@ static int at803x_set_wol(struct phy_dev
+ 		if (!ndev)
+ 			return -ENODEV;
+ 
+-		mac = (const u8 *) ndev->dev_addr;
++		mac = (const u8 *)ndev->dev_addr;
+ 
+ 		if (!is_valid_ether_addr(mac))
+ 			return -EINVAL;

+ 38 - 0
target/linux/generic/backport-6.6/709-v6.8-02-net-phy-at803x-replace-msleep-1-with-usleep_range.patch

@@ -0,0 +1,38 @@
+From 3ab5720881a924fb6405d9e6a3b09f1026467c47 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Mon, 18 Dec 2023 00:25:08 +0100
+Subject: [PATCH 2/2] net: phy: at803x: replace msleep(1) with usleep_range
+
+Replace msleep(1) with usleep_range as suggested by timers-howto guide.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/phy/at803x.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -916,9 +916,9 @@ static void at803x_link_change_notify(st
+ 		at803x_context_save(phydev, &context);
+ 
+ 		phy_device_reset(phydev, 1);
+-		msleep(1);
++		usleep_range(1000, 2000);
+ 		phy_device_reset(phydev, 0);
+-		msleep(1);
++		usleep_range(1000, 2000);
+ 
+ 		at803x_context_restore(phydev, &context);
+ 
+@@ -1733,7 +1733,7 @@ static int qca83xx_resume(struct phy_dev
+ 	if (ret)
+ 		return ret;
+ 
+-	msleep(1);
++	usleep_range(1000, 2000);
+ 
+ 	return 0;
+ }

+ 152 - 0
target/linux/generic/backport-6.6/710-v6.8-net-phy-at803x-better-align-function-varibles-to-ope.patch

@@ -0,0 +1,152 @@
+From 7961ef1fa10ec35ad6923fb5751877116e4b035b Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Tue, 19 Dec 2023 21:21:24 +0100
+Subject: [PATCH] net: phy: at803x: better align function varibles to open
+ parenthesis
+
+Better align function variables to open parenthesis as suggested by
+checkpatch script for qca808x function to make code cleaner.
+
+For cable_test_get_status function some additional rework was needed to
+handle too long functions.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 67 ++++++++++++++++++++++------------------
+ 1 file changed, 37 insertions(+), 30 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1781,27 +1781,27 @@ static int qca808x_phy_fast_retrain_conf
+ 		return ret;
+ 
+ 	phy_write_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_TOP_OPTION1,
+-			QCA808X_TOP_OPTION1_DATA);
++		      QCA808X_TOP_OPTION1_DATA);
+ 	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB,
+-			QCA808X_MSE_THRESHOLD_20DB_VALUE);
++		      QCA808X_MSE_THRESHOLD_20DB_VALUE);
+ 	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB,
+-			QCA808X_MSE_THRESHOLD_17DB_VALUE);
++		      QCA808X_MSE_THRESHOLD_17DB_VALUE);
+ 	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB,
+-			QCA808X_MSE_THRESHOLD_27DB_VALUE);
++		      QCA808X_MSE_THRESHOLD_27DB_VALUE);
+ 	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB,
+-			QCA808X_MSE_THRESHOLD_28DB_VALUE);
++		      QCA808X_MSE_THRESHOLD_28DB_VALUE);
+ 	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_1,
+-			QCA808X_MMD3_DEBUG_1_VALUE);
++		      QCA808X_MMD3_DEBUG_1_VALUE);
+ 	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_4,
+-			QCA808X_MMD3_DEBUG_4_VALUE);
++		      QCA808X_MMD3_DEBUG_4_VALUE);
+ 	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_5,
+-			QCA808X_MMD3_DEBUG_5_VALUE);
++		      QCA808X_MMD3_DEBUG_5_VALUE);
+ 	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_3,
+-			QCA808X_MMD3_DEBUG_3_VALUE);
++		      QCA808X_MMD3_DEBUG_3_VALUE);
+ 	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_6,
+-			QCA808X_MMD3_DEBUG_6_VALUE);
++		      QCA808X_MMD3_DEBUG_6_VALUE);
+ 	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_2,
+-			QCA808X_MMD3_DEBUG_2_VALUE);
++		      QCA808X_MMD3_DEBUG_2_VALUE);
+ 
+ 	return 0;
+ }
+@@ -1838,13 +1838,14 @@ static int qca808x_config_init(struct ph
+ 
+ 	/* Active adc&vga on 802.3az for the link 1000M and 100M */
+ 	ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_ADDR_CLD_CTRL7,
+-			QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
++			     QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
+ 	if (ret)
+ 		return ret;
+ 
+ 	/* Adjust the threshold on 802.3az for the link 1000M */
+ 	ret = phy_write_mmd(phydev, MDIO_MMD_PCS,
+-			QCA808X_PHY_MMD3_AZ_TRAINING_CTRL, QCA808X_MMD3_AZ_TRAINING_VAL);
++			    QCA808X_PHY_MMD3_AZ_TRAINING_CTRL,
++			    QCA808X_MMD3_AZ_TRAINING_VAL);
+ 	if (ret)
+ 		return ret;
+ 
+@@ -1870,7 +1871,8 @@ static int qca808x_config_init(struct ph
+ 
+ 	/* Configure adc threshold as 100mv for the link 10M */
+ 	return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
+-			QCA808X_ADC_THRESHOLD_MASK, QCA808X_ADC_THRESHOLD_100MV);
++				     QCA808X_ADC_THRESHOLD_MASK,
++				     QCA808X_ADC_THRESHOLD_100MV);
+ }
+ 
+ static int qca808x_read_status(struct phy_device *phydev)
+@@ -1883,7 +1885,7 @@ static int qca808x_read_status(struct ph
+ 		return ret;
+ 
+ 	linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->lp_advertising,
+-			ret & MDIO_AN_10GBT_STAT_LP2_5G);
++			 ret & MDIO_AN_10GBT_STAT_LP2_5G);
+ 
+ 	ret = genphy_read_status(phydev);
+ 	if (ret)
+@@ -1913,7 +1915,7 @@ static int qca808x_read_status(struct ph
+ 		 */
+ 		if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+ 			if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
+-					qca808x_is_prefer_master(phydev)) {
++			    qca808x_is_prefer_master(phydev)) {
+ 				qca808x_phy_ms_seed_enable(phydev, false);
+ 			} else {
+ 				qca808x_phy_ms_seed_enable(phydev, true);
+@@ -2070,18 +2072,22 @@ static int qca808x_cable_test_get_status
+ 	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
+ 				qca808x_cable_test_result_trans(pair_d));
+ 
+-	if (qca808x_cdt_fault_length_valid(pair_a))
+-		ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A,
+-				qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A));
+-	if (qca808x_cdt_fault_length_valid(pair_b))
+-		ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B,
+-				qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B));
+-	if (qca808x_cdt_fault_length_valid(pair_c))
+-		ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C,
+-				qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C));
+-	if (qca808x_cdt_fault_length_valid(pair_d))
+-		ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D,
+-				qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D));
++	if (qca808x_cdt_fault_length_valid(pair_a)) {
++		val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A);
++		ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
++	}
++	if (qca808x_cdt_fault_length_valid(pair_b)) {
++		val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B);
++		ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
++	}
++	if (qca808x_cdt_fault_length_valid(pair_c)) {
++		val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C);
++		ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
++	}
++	if (qca808x_cdt_fault_length_valid(pair_d)) {
++		val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D);
++		ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
++	}
+ 
+ 	*finished = true;
+ 
+@@ -2148,8 +2154,9 @@ static void qca808x_link_change_notify(s
+ 	 * the interface device address is always phy address added by 1.
+ 	 */
+ 	mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
+-			MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
+-			QCA8081_PHY_FIFO_RSTN, phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
++				   MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
++				   QCA8081_PHY_FIFO_RSTN,
++				   phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
+ }
+ 
+ static struct phy_driver at803x_driver[] = {

+ 62 - 0
target/linux/generic/backport-6.6/711-v6.8-01-net-phy-at803x-generalize-cdt-fault-length-function.patch

@@ -0,0 +1,62 @@
+From 22eb276098da820d9440fad22901f9b74ed4d659 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Thu, 4 Jan 2024 22:30:38 +0100
+Subject: [PATCH 1/4] net: phy: at803x: generalize cdt fault length function
+
+Generalize cable test fault length function since they all base on the
+same magic values (already reverse engineered to understand the meaning
+of it) to have consistenct values on every PHY.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Simon Horman <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 13 ++++++-------
+ 1 file changed, 6 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1192,10 +1192,8 @@ static bool at803x_cdt_fault_length_vali
+ 	return false;
+ }
+ 
+-static int at803x_cdt_fault_length(u16 status)
++static int at803x_cdt_fault_length(int dt)
+ {
+-	int dt;
+-
+ 	/* According to the datasheet the distance to the fault is
+ 	 * DELTA_TIME * 0.824 meters.
+ 	 *
+@@ -1211,8 +1209,6 @@ static int at803x_cdt_fault_length(u16 s
+ 	 * With a VF of 0.69 we get the factor 0.824 mentioned in the
+ 	 * datasheet.
+ 	 */
+-	dt = FIELD_GET(AT803X_CDT_STATUS_DELTA_TIME_MASK, status);
+-
+ 	return (dt * 824) / 10;
+ }
+ 
+@@ -1265,9 +1261,11 @@ static int at803x_cable_test_one_pair(st
+ 	ethnl_cable_test_result(phydev, ethtool_pair[pair],
+ 				at803x_cable_test_result_trans(val));
+ 
+-	if (at803x_cdt_fault_length_valid(val))
++	if (at803x_cdt_fault_length_valid(val)) {
++		val = FIELD_GET(AT803X_CDT_STATUS_DELTA_TIME_MASK, val);
+ 		ethnl_cable_test_fault_length(phydev, ethtool_pair[pair],
+ 					      at803x_cdt_fault_length(val));
++	}
+ 
+ 	return 1;
+ }
+@@ -1992,7 +1990,8 @@ static int qca808x_cdt_fault_length(stru
+ 	if (val < 0)
+ 		return val;
+ 
+-	return (FIELD_GET(QCA808X_CDT_DIAG_LENGTH, val) * 824) / 10;
++	val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH, val);
++	return at803x_cdt_fault_length(val);
+ }
+ 
+ static int qca808x_cable_test_start(struct phy_device *phydev)

+ 118 - 0
target/linux/generic/backport-6.6/711-v6.8-02-net-phy-at803x-refactor-qca808x-cable-test-get-statu.patch

@@ -0,0 +1,118 @@
+From e0e9ada1df6133513249861c1d91c1dbefd9383b Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Thu, 4 Jan 2024 22:30:39 +0100
+Subject: [PATCH 2/4] net: phy: at803x: refactor qca808x cable test get status
+ function
+
+Refactor qca808x cable test get status function to remove code
+duplication and clean things up.
+
+The same logic is applied to each pair hence it can be generalized and
+moved to a common function.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Simon Horman <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 80 ++++++++++++++++++++++++----------------
+ 1 file changed, 49 insertions(+), 31 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -2035,10 +2035,43 @@ static int qca808x_cable_test_start(stru
+ 	return 0;
+ }
+ 
++static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
++					      u16 status)
++{
++	u16 pair_code;
++	int length;
++
++	switch (pair) {
++	case ETHTOOL_A_CABLE_PAIR_A:
++		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
++		break;
++	case ETHTOOL_A_CABLE_PAIR_B:
++		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
++		break;
++	case ETHTOOL_A_CABLE_PAIR_C:
++		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
++		break;
++	case ETHTOOL_A_CABLE_PAIR_D:
++		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	ethnl_cable_test_result(phydev, pair,
++				qca808x_cable_test_result_trans(pair_code));
++
++	if (qca808x_cdt_fault_length_valid(pair_code)) {
++		length = qca808x_cdt_fault_length(phydev, pair);
++		ethnl_cable_test_fault_length(phydev, pair, length);
++	}
++
++	return 0;
++}
++
+ static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
+ {
+ 	int ret, val;
+-	int pair_a, pair_b, pair_c, pair_d;
+ 
+ 	*finished = false;
+ 
+@@ -2057,36 +2090,21 @@ static int qca808x_cable_test_get_status
+ 	if (val < 0)
+ 		return val;
+ 
+-	pair_a = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, val);
+-	pair_b = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, val);
+-	pair_c = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, val);
+-	pair_d = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, val);
+-
+-	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
+-				qca808x_cable_test_result_trans(pair_a));
+-	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B,
+-				qca808x_cable_test_result_trans(pair_b));
+-	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C,
+-				qca808x_cable_test_result_trans(pair_c));
+-	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
+-				qca808x_cable_test_result_trans(pair_d));
+-
+-	if (qca808x_cdt_fault_length_valid(pair_a)) {
+-		val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A);
+-		ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
+-	}
+-	if (qca808x_cdt_fault_length_valid(pair_b)) {
+-		val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B);
+-		ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
+-	}
+-	if (qca808x_cdt_fault_length_valid(pair_c)) {
+-		val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C);
+-		ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
+-	}
+-	if (qca808x_cdt_fault_length_valid(pair_d)) {
+-		val = qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D);
+-		ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
+-	}
++	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
++	if (ret)
++		return ret;
++
++	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
++	if (ret)
++		return ret;
++
++	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
++	if (ret)
++		return ret;
++
++	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
++	if (ret)
++		return ret;
+ 
+ 	*finished = true;
+ 

+ 182 - 0
target/linux/generic/backport-6.6/711-v6.8-03-net-phy-at803x-add-support-for-cdt-cross-short-test-.patch

@@ -0,0 +1,182 @@
+From ea73e5ea442ee2aade67b1fb1233ccb3cbea2ceb Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Thu, 4 Jan 2024 22:30:40 +0100
+Subject: [PATCH 3/4] net: phy: at803x: add support for cdt cross short test
+ for qca808x
+
+QCA808x PHY Family supports Cable Diagnostic Test also for Cross Pair
+Short.
+
+Add all the define to make enable and support these additional tests.
+
+Cross Short test was previously disabled by default, this is now changed
+and enabled by default. In this mode, the mask changed a bit and length
+is shifted based on the fault condition.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Simon Horman <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 86 ++++++++++++++++++++++++++++++++--------
+ 1 file changed, 69 insertions(+), 17 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -254,6 +254,7 @@
+ 
+ #define QCA808X_CDT_ENABLE_TEST			BIT(15)
+ #define QCA808X_CDT_INTER_CHECK_DIS		BIT(13)
++#define QCA808X_CDT_STATUS			BIT(11)
+ #define QCA808X_CDT_LENGTH_UNIT			BIT(10)
+ 
+ #define QCA808X_MMD3_CDT_STATUS			0x8064
+@@ -261,16 +262,44 @@
+ #define QCA808X_MMD3_CDT_DIAG_PAIR_B		0x8066
+ #define QCA808X_MMD3_CDT_DIAG_PAIR_C		0x8067
+ #define QCA808X_MMD3_CDT_DIAG_PAIR_D		0x8068
+-#define QCA808X_CDT_DIAG_LENGTH			GENMASK(7, 0)
++#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT	GENMASK(15, 8)
++#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT	GENMASK(7, 0)
+ 
+ #define QCA808X_CDT_CODE_PAIR_A			GENMASK(15, 12)
+ #define QCA808X_CDT_CODE_PAIR_B			GENMASK(11, 8)
+ #define QCA808X_CDT_CODE_PAIR_C			GENMASK(7, 4)
+ #define QCA808X_CDT_CODE_PAIR_D			GENMASK(3, 0)
+-#define QCA808X_CDT_STATUS_STAT_FAIL		0
+-#define QCA808X_CDT_STATUS_STAT_NORMAL		1
+-#define QCA808X_CDT_STATUS_STAT_OPEN		2
+-#define QCA808X_CDT_STATUS_STAT_SHORT		3
++
++#define QCA808X_CDT_STATUS_STAT_TYPE		GENMASK(1, 0)
++#define QCA808X_CDT_STATUS_STAT_FAIL		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
++#define QCA808X_CDT_STATUS_STAT_NORMAL		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
++#define QCA808X_CDT_STATUS_STAT_SAME_OPEN	FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
++#define QCA808X_CDT_STATUS_STAT_SAME_SHORT	FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
++
++#define QCA808X_CDT_STATUS_STAT_MDI		GENMASK(3, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI1		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
++#define QCA808X_CDT_STATUS_STAT_MDI2		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI3		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
++
++/* NORMAL are MDI with type set to 0 */
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI1
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++									 QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++									 QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI2
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++									 QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++									 QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI3
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++									 QCA808X_CDT_STATUS_STAT_MDI3)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++									 QCA808X_CDT_STATUS_STAT_MDI3)
++
++/* Added for reference of existence but should be handled by wait_for_completion already */
++#define QCA808X_CDT_STATUS_STAT_BUSY		(BIT(1) | BIT(3))
+ 
+ /* QCA808X 1G chip type */
+ #define QCA808X_PHY_MMD7_CHIP_TYPE		0x901d
+@@ -1941,8 +1970,17 @@ static int qca808x_soft_reset(struct phy
+ static bool qca808x_cdt_fault_length_valid(int cdt_code)
+ {
+ 	switch (cdt_code) {
+-	case QCA808X_CDT_STATUS_STAT_SHORT:
+-	case QCA808X_CDT_STATUS_STAT_OPEN:
++	case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+ 		return true;
+ 	default:
+ 		return false;
+@@ -1954,17 +1992,28 @@ static int qca808x_cable_test_result_tra
+ 	switch (cdt_code) {
+ 	case QCA808X_CDT_STATUS_STAT_NORMAL:
+ 		return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+-	case QCA808X_CDT_STATUS_STAT_SHORT:
++	case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+ 		return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+-	case QCA808X_CDT_STATUS_STAT_OPEN:
++	case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+ 		return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++		return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+ 	case QCA808X_CDT_STATUS_STAT_FAIL:
+ 	default:
+ 		return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+ 	}
+ }
+ 
+-static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair)
++static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
++				    int result)
+ {
+ 	int val;
+ 	u32 cdt_length_reg = 0;
+@@ -1990,7 +2039,11 @@ static int qca808x_cdt_fault_length(stru
+ 	if (val < 0)
+ 		return val;
+ 
+-	val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH, val);
++	if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
++		val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
++	else
++		val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
++
+ 	return at803x_cdt_fault_length(val);
+ }
+ 
+@@ -2038,8 +2091,8 @@ static int qca808x_cable_test_start(stru
+ static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
+ 					      u16 status)
+ {
++	int length, result;
+ 	u16 pair_code;
+-	int length;
+ 
+ 	switch (pair) {
+ 	case ETHTOOL_A_CABLE_PAIR_A:
+@@ -2058,11 +2111,11 @@ static int qca808x_cable_test_get_pair_s
+ 		return -EINVAL;
+ 	}
+ 
+-	ethnl_cable_test_result(phydev, pair,
+-				qca808x_cable_test_result_trans(pair_code));
++	result = qca808x_cable_test_result_trans(pair_code);
++	ethnl_cable_test_result(phydev, pair, result);
+ 
+ 	if (qca808x_cdt_fault_length_valid(pair_code)) {
+-		length = qca808x_cdt_fault_length(phydev, pair);
++		length = qca808x_cdt_fault_length(phydev, pair, result);
+ 		ethnl_cable_test_fault_length(phydev, pair, length);
+ 	}
+ 
+@@ -2076,8 +2129,7 @@ static int qca808x_cable_test_get_status
+ 	*finished = false;
+ 
+ 	val = QCA808X_CDT_ENABLE_TEST |
+-	      QCA808X_CDT_LENGTH_UNIT |
+-	      QCA808X_CDT_INTER_CHECK_DIS;
++	      QCA808X_CDT_LENGTH_UNIT;
+ 	ret = at803x_cdt_start(phydev, val);
+ 	if (ret)
+ 		return ret;

+ 62 - 0
target/linux/generic/backport-6.6/711-v6.8-04-net-phy-at803x-make-read_status-more-generic.patch

@@ -0,0 +1,62 @@
+From c34d9452d4e5d98a655d7b625e85466320885416 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Thu, 4 Jan 2024 22:30:41 +0100
+Subject: [PATCH 4/4] net: phy: at803x: make read_status more generic
+
+Make read_status more generic in preparation on moving it to shared
+library as other PHY Family Driver will have the exact same
+implementation.
+
+The only specific part was a check for AR8031/33 if 1000basex was used.
+The check is moved to a dedicated function specific for those PHYs.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Simon Horman <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/at803x.c | 17 ++++++++++++-----
+ 1 file changed, 12 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -1020,13 +1020,9 @@ static int at803x_read_specific_status(s
+ 
+ static int at803x_read_status(struct phy_device *phydev)
+ {
+-	struct at803x_priv *priv = phydev->priv;
+ 	struct at803x_ss_mask ss_mask = { 0 };
+ 	int err, old_link = phydev->link;
+ 
+-	if (priv->is_1000basex)
+-		return genphy_c37_read_status(phydev);
+-
+ 	/* Update the link, but return if there was an error */
+ 	err = genphy_update_link(phydev);
+ 	if (err)
+@@ -1618,6 +1614,17 @@ static int at8031_config_intr(struct phy
+ 	return at803x_config_intr(phydev);
+ }
+ 
++/* AR8031 and AR8033 share the same read status logic */
++static int at8031_read_status(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++
++	if (priv->is_1000basex)
++		return genphy_c37_read_status(phydev);
++
++	return at803x_read_status(phydev);
++}
++
+ /* AR8031 and AR8035 share the same cable test get status reg */
+ static int at8031_cable_test_get_status(struct phy_device *phydev,
+ 					bool *finished)
+@@ -2281,7 +2288,7 @@ static struct phy_driver at803x_driver[]
+ 	.read_page		= at803x_read_page,
+ 	.write_page		= at803x_write_page,
+ 	.get_features		= at803x_get_features,
+-	.read_status		= at803x_read_status,
++	.read_status		= at8031_read_status,
+ 	.config_intr		= at8031_config_intr,
+ 	.handle_interrupt	= at803x_handle_interrupt,
+ 	.get_tunable		= at803x_get_tunable,

+ 408 - 0
target/linux/generic/backport-6.6/712-v6.9-net-phy-at803x-add-LED-support-for-qca808x.patch

@@ -0,0 +1,408 @@
+From 7196062b64ee470b91015f3d2e82d225948258ea Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Thu, 25 Jan 2024 21:37:01 +0100
+Subject: [PATCH 5/5] net: phy: at803x: add LED support for qca808x
+
+Add LED support for QCA8081 PHY.
+
+Documentation for this LEDs PHY is very scarce even with NDA access
+to Documentation for OEMs. Only the blink pattern are documented and are
+very confusing most of the time. No documentation is present about
+forcing the LED on/off or to always blink.
+
+Those settings were reversed by poking the regs and trying to find the
+correct bits to trigger these modes. Some bits mode are not clear and
+maybe the documentation option are not 100% correct. For the sake of LED
+support the reversed option are enough to add support for current LED
+APIs.
+
+Supported HW control modes are:
+- tx
+- rx
+- link_10
+- link_100
+- link_1000
+- link_2500
+- half_duplex
+- full_duplex
+
+Also add support for LED polarity set to set LED polarity to active
+high or low. QSDK sets this value to high by default but PHY reset value
+doesn't have this enabled by default.
+
+QSDK also sets 2 additional bits but their usage is not clear, info about
+this is added in the header. It was verified that for correct function
+of the LED if active high is needed, only BIT 6 is needed.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/at803x.c | 327 +++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 327 insertions(+)
+
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -301,6 +301,87 @@
+ /* Added for reference of existence but should be handled by wait_for_completion already */
+ #define QCA808X_CDT_STATUS_STAT_BUSY		(BIT(1) | BIT(3))
+ 
++#define QCA808X_MMD7_LED_GLOBAL			0x8073
++#define QCA808X_LED_BLINK_1			GENMASK(11, 6)
++#define QCA808X_LED_BLINK_2			GENMASK(5, 0)
++/* Values are the same for both BLINK_1 and BLINK_2 */
++#define QCA808X_LED_BLINK_FREQ_MASK		GENMASK(5, 3)
++#define QCA808X_LED_BLINK_FREQ_2HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
++#define QCA808X_LED_BLINK_FREQ_4HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
++#define QCA808X_LED_BLINK_FREQ_8HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
++#define QCA808X_LED_BLINK_FREQ_16HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
++#define QCA808X_LED_BLINK_FREQ_32HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
++#define QCA808X_LED_BLINK_FREQ_64HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
++#define QCA808X_LED_BLINK_FREQ_128HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
++#define QCA808X_LED_BLINK_FREQ_256HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
++#define QCA808X_LED_BLINK_DUTY_MASK		GENMASK(2, 0)
++#define QCA808X_LED_BLINK_DUTY_50_50		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
++#define QCA808X_LED_BLINK_DUTY_75_25		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
++#define QCA808X_LED_BLINK_DUTY_25_75		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
++#define QCA808X_LED_BLINK_DUTY_33_67		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
++#define QCA808X_LED_BLINK_DUTY_67_33		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
++#define QCA808X_LED_BLINK_DUTY_17_83		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
++#define QCA808X_LED_BLINK_DUTY_83_17		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
++#define QCA808X_LED_BLINK_DUTY_8_92		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
++
++#define QCA808X_MMD7_LED2_CTRL			0x8074
++#define QCA808X_MMD7_LED2_FORCE_CTRL		0x8075
++#define QCA808X_MMD7_LED1_CTRL			0x8076
++#define QCA808X_MMD7_LED1_FORCE_CTRL		0x8077
++#define QCA808X_MMD7_LED0_CTRL			0x8078
++#define QCA808X_MMD7_LED_CTRL(x)		(0x8078 - ((x) * 2))
++
++/* LED hw control pattern is the same for every LED */
++#define QCA808X_LED_PATTERN_MASK		GENMASK(15, 0)
++#define QCA808X_LED_SPEED2500_ON		BIT(15)
++#define QCA808X_LED_SPEED2500_BLINK		BIT(14)
++/* Follow blink trigger even if duplex or speed condition doesn't match */
++#define QCA808X_LED_BLINK_CHECK_BYPASS		BIT(13)
++#define QCA808X_LED_FULL_DUPLEX_ON		BIT(12)
++#define QCA808X_LED_HALF_DUPLEX_ON		BIT(11)
++#define QCA808X_LED_TX_BLINK			BIT(10)
++#define QCA808X_LED_RX_BLINK			BIT(9)
++#define QCA808X_LED_TX_ON_10MS			BIT(8)
++#define QCA808X_LED_RX_ON_10MS			BIT(7)
++#define QCA808X_LED_SPEED1000_ON		BIT(6)
++#define QCA808X_LED_SPEED100_ON			BIT(5)
++#define QCA808X_LED_SPEED10_ON			BIT(4)
++#define QCA808X_LED_COLLISION_BLINK		BIT(3)
++#define QCA808X_LED_SPEED1000_BLINK		BIT(2)
++#define QCA808X_LED_SPEED100_BLINK		BIT(1)
++#define QCA808X_LED_SPEED10_BLINK		BIT(0)
++
++#define QCA808X_MMD7_LED0_FORCE_CTRL		0x8079
++#define QCA808X_MMD7_LED_FORCE_CTRL(x)		(0x8079 - ((x) * 2))
++
++/* LED force ctrl is the same for every LED
++ * No documentation exist for this, not even internal one
++ * with NDA as QCOM gives only info about configuring
++ * hw control pattern rules and doesn't indicate any way
++ * to force the LED to specific mode.
++ * These define comes from reverse and testing and maybe
++ * lack of some info or some info are not entirely correct.
++ * For the basic LED control and hw control these finding
++ * are enough to support LED control in all the required APIs.
++ *
++ * On doing some comparison with implementation with qca807x,
++ * it was found that it's 1:1 equal to it and confirms all the
++ * reverse done. It was also found further specification with the
++ * force mode and the blink modes.
++ */
++#define QCA808X_LED_FORCE_EN			BIT(15)
++#define QCA808X_LED_FORCE_MODE_MASK		GENMASK(14, 13)
++#define QCA808X_LED_FORCE_BLINK_1		FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
++#define QCA808X_LED_FORCE_BLINK_2		FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
++#define QCA808X_LED_FORCE_ON			FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
++#define QCA808X_LED_FORCE_OFF			FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
++
++#define QCA808X_MMD7_LED_POLARITY_CTRL		0x901a
++/* QSDK sets by default 0x46 to this reg that sets BIT 6 for
++ * LED to active high. It's not clear what BIT 3 and BIT 4 does.
++ */
++#define QCA808X_LED_ACTIVE_HIGH			BIT(6)
++
+ /* QCA808X 1G chip type */
+ #define QCA808X_PHY_MMD7_CHIP_TYPE		0x901d
+ #define QCA808X_PHY_CHIP_TYPE_1G		BIT(0)
+@@ -346,6 +427,7 @@ struct at803x_priv {
+ 	struct regulator_dev *vddio_rdev;
+ 	struct regulator_dev *vddh_rdev;
+ 	u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
++	int led_polarity_mode;
+ };
+ 
+ struct at803x_context {
+@@ -706,6 +788,9 @@ static int at803x_probe(struct phy_devic
+ 	if (!priv)
+ 		return -ENOMEM;
+ 
++	/* Init LED polarity mode to -1 */
++	priv->led_polarity_mode = -1;
++
+ 	phydev->priv = priv;
+ 
+ 	ret = at803x_parse_dt(phydev);
+@@ -2235,6 +2320,242 @@ static void qca808x_link_change_notify(s
+ 				   phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
+ }
+ 
++static int qca808x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
++				    u16 *offload_trigger)
++{
++	/* Parsing specific to netdev trigger */
++	if (test_bit(TRIGGER_NETDEV_TX, &rules))
++		*offload_trigger |= QCA808X_LED_TX_BLINK;
++	if (test_bit(TRIGGER_NETDEV_RX, &rules))
++		*offload_trigger |= QCA808X_LED_RX_BLINK;
++	if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
++		*offload_trigger |= QCA808X_LED_SPEED10_ON;
++	if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++		*offload_trigger |= QCA808X_LED_SPEED100_ON;
++	if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++		*offload_trigger |= QCA808X_LED_SPEED1000_ON;
++	if (test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
++		*offload_trigger |= QCA808X_LED_SPEED2500_ON;
++	if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
++		*offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
++	if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
++		*offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
++
++	if (rules && !*offload_trigger)
++		return -EOPNOTSUPP;
++
++	/* Enable BLINK_CHECK_BYPASS by default to make the LED
++	 * blink even with duplex or speed mode not enabled.
++	 */
++	*offload_trigger |= QCA808X_LED_BLINK_CHECK_BYPASS;
++
++	return 0;
++}
++
++static int qca808x_led_hw_control_enable(struct phy_device *phydev, u8 index)
++{
++	u16 reg;
++
++	if (index > 2)
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++				  QCA808X_LED_FORCE_EN);
++}
++
++static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
++				       unsigned long rules)
++{
++	u16 offload_trigger = 0;
++
++	if (index > 2)
++		return -EINVAL;
++
++	return qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
++}
++
++static int qca808x_led_hw_control_set(struct phy_device *phydev, u8 index,
++				      unsigned long rules)
++{
++	u16 reg, offload_trigger = 0;
++	int ret;
++
++	if (index > 2)
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_CTRL(index);
++
++	ret = qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
++	if (ret)
++		return ret;
++
++	ret = qca808x_led_hw_control_enable(phydev, index);
++	if (ret)
++		return ret;
++
++	return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++			      QCA808X_LED_PATTERN_MASK,
++			      offload_trigger);
++}
++
++static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
++{
++	u16 reg;
++	int val;
++
++	if (index > 2)
++		return false;
++
++	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++	val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++
++	return !(val & QCA808X_LED_FORCE_EN);
++}
++
++static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
++				      unsigned long *rules)
++{
++	u16 reg;
++	int val;
++
++	if (index > 2)
++		return -EINVAL;
++
++	/* Check if we have hw control enabled */
++	if (qca808x_led_hw_control_status(phydev, index))
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_CTRL(index);
++
++	val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++	if (val & QCA808X_LED_TX_BLINK)
++		set_bit(TRIGGER_NETDEV_TX, rules);
++	if (val & QCA808X_LED_RX_BLINK)
++		set_bit(TRIGGER_NETDEV_RX, rules);
++	if (val & QCA808X_LED_SPEED10_ON)
++		set_bit(TRIGGER_NETDEV_LINK_10, rules);
++	if (val & QCA808X_LED_SPEED100_ON)
++		set_bit(TRIGGER_NETDEV_LINK_100, rules);
++	if (val & QCA808X_LED_SPEED1000_ON)
++		set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++	if (val & QCA808X_LED_SPEED2500_ON)
++		set_bit(TRIGGER_NETDEV_LINK_2500, rules);
++	if (val & QCA808X_LED_HALF_DUPLEX_ON)
++		set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
++	if (val & QCA808X_LED_FULL_DUPLEX_ON)
++		set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
++
++	return 0;
++}
++
++static int qca808x_led_hw_control_reset(struct phy_device *phydev, u8 index)
++{
++	u16 reg;
++
++	if (index > 2)
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_CTRL(index);
++
++	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++				  QCA808X_LED_PATTERN_MASK);
++}
++
++static int qca808x_led_brightness_set(struct phy_device *phydev,
++				      u8 index, enum led_brightness value)
++{
++	u16 reg;
++	int ret;
++
++	if (index > 2)
++		return -EINVAL;
++
++	if (!value) {
++		ret = qca808x_led_hw_control_reset(phydev, index);
++		if (ret)
++			return ret;
++	}
++
++	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++	return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++			      QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++			      QCA808X_LED_FORCE_EN | value ? QCA808X_LED_FORCE_ON :
++							     QCA808X_LED_FORCE_OFF);
++}
++
++static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
++				 unsigned long *delay_on,
++				 unsigned long *delay_off)
++{
++	int ret;
++	u16 reg;
++
++	if (index > 2)
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++	/* Set blink to 50% off, 50% on at 4Hz by default */
++	ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
++			     QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
++			     QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
++	if (ret)
++		return ret;
++
++	/* We use BLINK_1 for normal blinking */
++	ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++			     QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++			     QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
++	if (ret)
++		return ret;
++
++	/* We set blink to 4Hz, aka 250ms */
++	*delay_on = 250 / 2;
++	*delay_off = 250 / 2;
++
++	return 0;
++}
++
++static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
++				    unsigned long modes)
++{
++	struct at803x_priv *priv = phydev->priv;
++	bool active_low = false;
++	u32 mode;
++
++	for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
++		switch (mode) {
++		case PHY_LED_ACTIVE_LOW:
++			active_low = true;
++			break;
++		default:
++			return -EINVAL;
++		}
++	}
++
++	/* PHY polarity is global and can't be set per LED.
++	 * To detect this, check if last requested polarity mode
++	 * match the new one.
++	 */
++	if (priv->led_polarity_mode >= 0 &&
++	    priv->led_polarity_mode != active_low) {
++		phydev_err(phydev, "PHY polarity is global. Mismatched polarity on different LED\n");
++		return -EINVAL;
++	}
++
++	/* Save the last PHY polarity mode */
++	priv->led_polarity_mode = active_low;
++
++	return phy_modify_mmd(phydev, MDIO_MMD_AN,
++			      QCA808X_MMD7_LED_POLARITY_CTRL,
++			      QCA808X_LED_ACTIVE_HIGH,
++			      active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
++}
++
+ static struct phy_driver at803x_driver[] = {
+ {
+ 	/* Qualcomm Atheros AR8035 */
+@@ -2411,6 +2732,12 @@ static struct phy_driver at803x_driver[]
+ 	.cable_test_start	= qca808x_cable_test_start,
+ 	.cable_test_get_status	= qca808x_cable_test_get_status,
+ 	.link_change_notify	= qca808x_link_change_notify,
++	.led_brightness_set	= qca808x_led_brightness_set,
++	.led_blink_set		= qca808x_led_blink_set,
++	.led_hw_is_supported	= qca808x_led_hw_is_supported,
++	.led_hw_control_set	= qca808x_led_hw_control_set,
++	.led_hw_control_get	= qca808x_led_hw_control_get,
++	.led_polarity_set	= qca808x_led_polarity_set,
+ }, };
+ 
+ module_phy_driver(at803x_driver);

+ 5598 - 0
target/linux/generic/backport-6.6/713-v6.9-01-net-phy-move-at803x-PHY-driver-to-dedicated-director.patch

@@ -0,0 +1,5598 @@
+From 9e56ff53b4115875667760445b028357848b4748 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Mon, 29 Jan 2024 15:15:19 +0100
+Subject: [PATCH 1/5] net: phy: move at803x PHY driver to dedicated directory
+
+In preparation for addition of other Qcom PHY and to tidy things up,
+move the at803x PHY driver to dedicated directory.
+
+The same order in the Kconfig selection is saved.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/Kconfig             | 7 +------
+ drivers/net/phy/Makefile            | 2 +-
+ drivers/net/phy/qcom/Kconfig        | 7 +++++++
+ drivers/net/phy/qcom/Makefile       | 2 ++
+ drivers/net/phy/{ => qcom}/at803x.c | 0
+ 5 files changed, 11 insertions(+), 7 deletions(-)
+ create mode 100644 drivers/net/phy/qcom/Kconfig
+ create mode 100644 drivers/net/phy/qcom/Makefile
+ rename drivers/net/phy/{ => qcom}/at803x.c (100%)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -277,12 +277,7 @@ config NXP_TJA11XX_PHY
+ 	help
+ 	  Currently supports the NXP TJA1100 and TJA1101 PHY.
+ 
+-config AT803X_PHY
+-	tristate "Qualcomm Atheros AR803X PHYs and QCA833x PHYs"
+-	depends on REGULATOR
+-	help
+-	  Currently supports the AR8030, AR8031, AR8033, AR8035 and internal
+-	  QCA8337(Internal qca8k PHY) model
++source "drivers/net/phy/qcom/Kconfig"
+ 
+ config QSEMI_PHY
+ 	tristate "Quality Semiconductor PHYs"
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -34,7 +34,6 @@ obj-$(CONFIG_ADIN_PHY)		+= adin.o
+ obj-$(CONFIG_ADIN1100_PHY)	+= adin1100.o
+ obj-$(CONFIG_AMD_PHY)		+= amd.o
+ obj-$(CONFIG_AQUANTIA_PHY)	+= aquantia/
+-obj-$(CONFIG_AT803X_PHY)	+= at803x.o
+ obj-$(CONFIG_AX88796B_PHY)	+= ax88796b.o
+ obj-$(CONFIG_BCM54140_PHY)	+= bcm54140.o
+ obj-$(CONFIG_BCM63XX_PHY)	+= bcm63xx.o
+@@ -75,6 +74,7 @@ obj-$(CONFIG_MOTORCOMM_PHY)	+= motorcomm
+ obj-$(CONFIG_NATIONAL_PHY)	+= national.o
+ obj-$(CONFIG_NXP_C45_TJA11XX_PHY)	+= nxp-c45-tja11xx.o
+ obj-$(CONFIG_NXP_TJA11XX_PHY)	+= nxp-tja11xx.o
++obj-y				+= qcom/
+ obj-$(CONFIG_QSEMI_PHY)		+= qsemi.o
+ obj-$(CONFIG_REALTEK_PHY)	+= realtek.o
+ obj-$(CONFIG_RENESAS_PHY)	+= uPD60620.o
+--- /dev/null
++++ b/drivers/net/phy/qcom/Kconfig
+@@ -0,0 +1,7 @@
++# SPDX-License-Identifier: GPL-2.0-only
++config AT803X_PHY
++	tristate "Qualcomm Atheros AR803X PHYs and QCA833x PHYs"
++	depends on REGULATOR
++	help
++	  Currently supports the AR8030, AR8031, AR8033, AR8035 and internal
++	  QCA8337(Internal qca8k PHY) model
+--- /dev/null
++++ b/drivers/net/phy/qcom/Makefile
+@@ -0,0 +1,2 @@
++# SPDX-License-Identifier: GPL-2.0
++obj-$(CONFIG_AT803X_PHY)	+= at803x.o
+--- a/drivers/net/phy/at803x.c
++++ /dev/null
+@@ -1,2759 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0+
+-/*
+- * drivers/net/phy/at803x.c
+- *
+- * Driver for Qualcomm Atheros AR803x PHY
+- *
+- * Author: Matus Ujhelyi <[email protected]>
+- */
+-
+-#include <linux/phy.h>
+-#include <linux/module.h>
+-#include <linux/string.h>
+-#include <linux/netdevice.h>
+-#include <linux/etherdevice.h>
+-#include <linux/ethtool_netlink.h>
+-#include <linux/bitfield.h>
+-#include <linux/regulator/of_regulator.h>
+-#include <linux/regulator/driver.h>
+-#include <linux/regulator/consumer.h>
+-#include <linux/of.h>
+-#include <linux/phylink.h>
+-#include <linux/sfp.h>
+-#include <dt-bindings/net/qca-ar803x.h>
+-
+-#define AT803X_SPECIFIC_FUNCTION_CONTROL	0x10
+-#define AT803X_SFC_ASSERT_CRS			BIT(11)
+-#define AT803X_SFC_FORCE_LINK			BIT(10)
+-#define AT803X_SFC_MDI_CROSSOVER_MODE_M		GENMASK(6, 5)
+-#define AT803X_SFC_AUTOMATIC_CROSSOVER		0x3
+-#define AT803X_SFC_MANUAL_MDIX			0x1
+-#define AT803X_SFC_MANUAL_MDI			0x0
+-#define AT803X_SFC_SQE_TEST			BIT(2)
+-#define AT803X_SFC_POLARITY_REVERSAL		BIT(1)
+-#define AT803X_SFC_DISABLE_JABBER		BIT(0)
+-
+-#define AT803X_SPECIFIC_STATUS			0x11
+-#define AT803X_SS_SPEED_MASK			GENMASK(15, 14)
+-#define AT803X_SS_SPEED_1000			2
+-#define AT803X_SS_SPEED_100			1
+-#define AT803X_SS_SPEED_10			0
+-#define AT803X_SS_DUPLEX			BIT(13)
+-#define AT803X_SS_SPEED_DUPLEX_RESOLVED		BIT(11)
+-#define AT803X_SS_MDIX				BIT(6)
+-
+-#define QCA808X_SS_SPEED_MASK			GENMASK(9, 7)
+-#define QCA808X_SS_SPEED_2500			4
+-
+-#define AT803X_INTR_ENABLE			0x12
+-#define AT803X_INTR_ENABLE_AUTONEG_ERR		BIT(15)
+-#define AT803X_INTR_ENABLE_SPEED_CHANGED	BIT(14)
+-#define AT803X_INTR_ENABLE_DUPLEX_CHANGED	BIT(13)
+-#define AT803X_INTR_ENABLE_PAGE_RECEIVED	BIT(12)
+-#define AT803X_INTR_ENABLE_LINK_FAIL		BIT(11)
+-#define AT803X_INTR_ENABLE_LINK_SUCCESS		BIT(10)
+-#define AT803X_INTR_ENABLE_LINK_FAIL_BX		BIT(8)
+-#define AT803X_INTR_ENABLE_LINK_SUCCESS_BX	BIT(7)
+-#define AT803X_INTR_ENABLE_WIRESPEED_DOWNGRADE	BIT(5)
+-#define AT803X_INTR_ENABLE_POLARITY_CHANGED	BIT(1)
+-#define AT803X_INTR_ENABLE_WOL			BIT(0)
+-
+-#define AT803X_INTR_STATUS			0x13
+-
+-#define AT803X_SMART_SPEED			0x14
+-#define AT803X_SMART_SPEED_ENABLE		BIT(5)
+-#define AT803X_SMART_SPEED_RETRY_LIMIT_MASK	GENMASK(4, 2)
+-#define AT803X_SMART_SPEED_BYPASS_TIMER		BIT(1)
+-#define AT803X_CDT				0x16
+-#define AT803X_CDT_MDI_PAIR_MASK		GENMASK(9, 8)
+-#define AT803X_CDT_ENABLE_TEST			BIT(0)
+-#define AT803X_CDT_STATUS			0x1c
+-#define AT803X_CDT_STATUS_STAT_NORMAL		0
+-#define AT803X_CDT_STATUS_STAT_SHORT		1
+-#define AT803X_CDT_STATUS_STAT_OPEN		2
+-#define AT803X_CDT_STATUS_STAT_FAIL		3
+-#define AT803X_CDT_STATUS_STAT_MASK		GENMASK(9, 8)
+-#define AT803X_CDT_STATUS_DELTA_TIME_MASK	GENMASK(7, 0)
+-#define AT803X_LED_CONTROL			0x18
+-
+-#define AT803X_PHY_MMD3_WOL_CTRL		0x8012
+-#define AT803X_WOL_EN				BIT(5)
+-#define AT803X_LOC_MAC_ADDR_0_15_OFFSET		0x804C
+-#define AT803X_LOC_MAC_ADDR_16_31_OFFSET	0x804B
+-#define AT803X_LOC_MAC_ADDR_32_47_OFFSET	0x804A
+-#define AT803X_REG_CHIP_CONFIG			0x1f
+-#define AT803X_BT_BX_REG_SEL			0x8000
+-
+-#define AT803X_DEBUG_ADDR			0x1D
+-#define AT803X_DEBUG_DATA			0x1E
+-
+-#define AT803X_MODE_CFG_MASK			0x0F
+-#define AT803X_MODE_CFG_BASET_RGMII		0x00
+-#define AT803X_MODE_CFG_BASET_SGMII		0x01
+-#define AT803X_MODE_CFG_BX1000_RGMII_50OHM	0x02
+-#define AT803X_MODE_CFG_BX1000_RGMII_75OHM	0x03
+-#define AT803X_MODE_CFG_BX1000_CONV_50OHM	0x04
+-#define AT803X_MODE_CFG_BX1000_CONV_75OHM	0x05
+-#define AT803X_MODE_CFG_FX100_RGMII_50OHM	0x06
+-#define AT803X_MODE_CFG_FX100_CONV_50OHM	0x07
+-#define AT803X_MODE_CFG_RGMII_AUTO_MDET		0x0B
+-#define AT803X_MODE_CFG_FX100_RGMII_75OHM	0x0E
+-#define AT803X_MODE_CFG_FX100_CONV_75OHM	0x0F
+-
+-#define AT803X_PSSR				0x11	/*PHY-Specific Status Register*/
+-#define AT803X_PSSR_MR_AN_COMPLETE		0x0200
+-
+-#define AT803X_DEBUG_ANALOG_TEST_CTRL		0x00
+-#define QCA8327_DEBUG_MANU_CTRL_EN		BIT(2)
+-#define QCA8337_DEBUG_MANU_CTRL_EN		GENMASK(3, 2)
+-#define AT803X_DEBUG_RX_CLK_DLY_EN		BIT(15)
+-
+-#define AT803X_DEBUG_SYSTEM_CTRL_MODE		0x05
+-#define AT803X_DEBUG_TX_CLK_DLY_EN		BIT(8)
+-
+-#define AT803X_DEBUG_REG_HIB_CTRL		0x0b
+-#define   AT803X_DEBUG_HIB_CTRL_SEL_RST_80U	BIT(10)
+-#define   AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE	BIT(13)
+-#define   AT803X_DEBUG_HIB_CTRL_PS_HIB_EN	BIT(15)
+-
+-#define AT803X_DEBUG_REG_3C			0x3C
+-
+-#define AT803X_DEBUG_REG_GREEN			0x3D
+-#define   AT803X_DEBUG_GATE_CLK_IN1000		BIT(6)
+-
+-#define AT803X_DEBUG_REG_1F			0x1F
+-#define AT803X_DEBUG_PLL_ON			BIT(2)
+-#define AT803X_DEBUG_RGMII_1V8			BIT(3)
+-
+-#define MDIO_AZ_DEBUG				0x800D
+-
+-/* AT803x supports either the XTAL input pad, an internal PLL or the
+- * DSP as clock reference for the clock output pad. The XTAL reference
+- * is only used for 25 MHz output, all other frequencies need the PLL.
+- * The DSP as a clock reference is used in synchronous ethernet
+- * applications.
+- *
+- * By default the PLL is only enabled if there is a link. Otherwise
+- * the PHY will go into low power state and disabled the PLL. You can
+- * set the PLL_ON bit (see debug register 0x1f) to keep the PLL always
+- * enabled.
+- */
+-#define AT803X_MMD7_CLK25M			0x8016
+-#define AT803X_CLK_OUT_MASK			GENMASK(4, 2)
+-#define AT803X_CLK_OUT_25MHZ_XTAL		0
+-#define AT803X_CLK_OUT_25MHZ_DSP		1
+-#define AT803X_CLK_OUT_50MHZ_PLL		2
+-#define AT803X_CLK_OUT_50MHZ_DSP		3
+-#define AT803X_CLK_OUT_62_5MHZ_PLL		4
+-#define AT803X_CLK_OUT_62_5MHZ_DSP		5
+-#define AT803X_CLK_OUT_125MHZ_PLL		6
+-#define AT803X_CLK_OUT_125MHZ_DSP		7
+-
+-/* The AR8035 has another mask which is compatible with the AR8031/AR8033 mask
+- * but doesn't support choosing between XTAL/PLL and DSP.
+- */
+-#define AT8035_CLK_OUT_MASK			GENMASK(4, 3)
+-
+-#define AT803X_CLK_OUT_STRENGTH_MASK		GENMASK(8, 7)
+-#define AT803X_CLK_OUT_STRENGTH_FULL		0
+-#define AT803X_CLK_OUT_STRENGTH_HALF		1
+-#define AT803X_CLK_OUT_STRENGTH_QUARTER		2
+-
+-#define AT803X_DEFAULT_DOWNSHIFT		5
+-#define AT803X_MIN_DOWNSHIFT			2
+-#define AT803X_MAX_DOWNSHIFT			9
+-
+-#define AT803X_MMD3_SMARTEEE_CTL1		0x805b
+-#define AT803X_MMD3_SMARTEEE_CTL2		0x805c
+-#define AT803X_MMD3_SMARTEEE_CTL3		0x805d
+-#define AT803X_MMD3_SMARTEEE_CTL3_LPI_EN	BIT(8)
+-
+-#define ATH9331_PHY_ID				0x004dd041
+-#define ATH8030_PHY_ID				0x004dd076
+-#define ATH8031_PHY_ID				0x004dd074
+-#define ATH8032_PHY_ID				0x004dd023
+-#define ATH8035_PHY_ID				0x004dd072
+-#define AT8030_PHY_ID_MASK			0xffffffef
+-
+-#define QCA8081_PHY_ID				0x004dd101
+-
+-#define QCA8327_A_PHY_ID			0x004dd033
+-#define QCA8327_B_PHY_ID			0x004dd034
+-#define QCA8337_PHY_ID				0x004dd036
+-#define QCA9561_PHY_ID				0x004dd042
+-#define QCA8K_PHY_ID_MASK			0xffffffff
+-
+-#define QCA8K_DEVFLAGS_REVISION_MASK		GENMASK(2, 0)
+-
+-#define AT803X_PAGE_FIBER			0
+-#define AT803X_PAGE_COPPER			1
+-
+-/* don't turn off internal PLL */
+-#define AT803X_KEEP_PLL_ENABLED			BIT(0)
+-#define AT803X_DISABLE_SMARTEEE			BIT(1)
+-
+-/* disable hibernation mode */
+-#define AT803X_DISABLE_HIBERNATION_MODE		BIT(2)
+-
+-/* ADC threshold */
+-#define QCA808X_PHY_DEBUG_ADC_THRESHOLD		0x2c80
+-#define QCA808X_ADC_THRESHOLD_MASK		GENMASK(7, 0)
+-#define QCA808X_ADC_THRESHOLD_80MV		0
+-#define QCA808X_ADC_THRESHOLD_100MV		0xf0
+-#define QCA808X_ADC_THRESHOLD_200MV		0x0f
+-#define QCA808X_ADC_THRESHOLD_300MV		0xff
+-
+-/* CLD control */
+-#define QCA808X_PHY_MMD3_ADDR_CLD_CTRL7		0x8007
+-#define QCA808X_8023AZ_AFE_CTRL_MASK		GENMASK(8, 4)
+-#define QCA808X_8023AZ_AFE_EN			0x90
+-
+-/* AZ control */
+-#define QCA808X_PHY_MMD3_AZ_TRAINING_CTRL	0x8008
+-#define QCA808X_MMD3_AZ_TRAINING_VAL		0x1c32
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB	0x8014
+-#define QCA808X_MSE_THRESHOLD_20DB_VALUE	0x529
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB	0x800E
+-#define QCA808X_MSE_THRESHOLD_17DB_VALUE	0x341
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB	0x801E
+-#define QCA808X_MSE_THRESHOLD_27DB_VALUE	0x419
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB	0x8020
+-#define QCA808X_MSE_THRESHOLD_28DB_VALUE	0x341
+-
+-#define QCA808X_PHY_MMD7_TOP_OPTION1		0x901c
+-#define QCA808X_TOP_OPTION1_DATA		0x0
+-
+-#define QCA808X_PHY_MMD3_DEBUG_1		0xa100
+-#define QCA808X_MMD3_DEBUG_1_VALUE		0x9203
+-#define QCA808X_PHY_MMD3_DEBUG_2		0xa101
+-#define QCA808X_MMD3_DEBUG_2_VALUE		0x48ad
+-#define QCA808X_PHY_MMD3_DEBUG_3		0xa103
+-#define QCA808X_MMD3_DEBUG_3_VALUE		0x1698
+-#define QCA808X_PHY_MMD3_DEBUG_4		0xa105
+-#define QCA808X_MMD3_DEBUG_4_VALUE		0x8001
+-#define QCA808X_PHY_MMD3_DEBUG_5		0xa106
+-#define QCA808X_MMD3_DEBUG_5_VALUE		0x1111
+-#define QCA808X_PHY_MMD3_DEBUG_6		0xa011
+-#define QCA808X_MMD3_DEBUG_6_VALUE		0x5f85
+-
+-/* master/slave seed config */
+-#define QCA808X_PHY_DEBUG_LOCAL_SEED		9
+-#define QCA808X_MASTER_SLAVE_SEED_ENABLE	BIT(1)
+-#define QCA808X_MASTER_SLAVE_SEED_CFG		GENMASK(12, 2)
+-#define QCA808X_MASTER_SLAVE_SEED_RANGE		0x32
+-
+-/* Hibernation yields lower power consumpiton in contrast with normal operation mode.
+- * when the copper cable is unplugged, the PHY enters into hibernation mode in about 10s.
+- */
+-#define QCA808X_DBG_AN_TEST			0xb
+-#define QCA808X_HIBERNATION_EN			BIT(15)
+-
+-#define QCA808X_CDT_ENABLE_TEST			BIT(15)
+-#define QCA808X_CDT_INTER_CHECK_DIS		BIT(13)
+-#define QCA808X_CDT_STATUS			BIT(11)
+-#define QCA808X_CDT_LENGTH_UNIT			BIT(10)
+-
+-#define QCA808X_MMD3_CDT_STATUS			0x8064
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_A		0x8065
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_B		0x8066
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_C		0x8067
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_D		0x8068
+-#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT	GENMASK(15, 8)
+-#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT	GENMASK(7, 0)
+-
+-#define QCA808X_CDT_CODE_PAIR_A			GENMASK(15, 12)
+-#define QCA808X_CDT_CODE_PAIR_B			GENMASK(11, 8)
+-#define QCA808X_CDT_CODE_PAIR_C			GENMASK(7, 4)
+-#define QCA808X_CDT_CODE_PAIR_D			GENMASK(3, 0)
+-
+-#define QCA808X_CDT_STATUS_STAT_TYPE		GENMASK(1, 0)
+-#define QCA808X_CDT_STATUS_STAT_FAIL		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
+-#define QCA808X_CDT_STATUS_STAT_NORMAL		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
+-#define QCA808X_CDT_STATUS_STAT_SAME_OPEN	FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
+-#define QCA808X_CDT_STATUS_STAT_SAME_SHORT	FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
+-
+-#define QCA808X_CDT_STATUS_STAT_MDI		GENMASK(3, 2)
+-#define QCA808X_CDT_STATUS_STAT_MDI1		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
+-#define QCA808X_CDT_STATUS_STAT_MDI2		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
+-#define QCA808X_CDT_STATUS_STAT_MDI3		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
+-
+-/* NORMAL are MDI with type set to 0 */
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI1
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+-									 QCA808X_CDT_STATUS_STAT_MDI1)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+-									 QCA808X_CDT_STATUS_STAT_MDI1)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI2
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+-									 QCA808X_CDT_STATUS_STAT_MDI2)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+-									 QCA808X_CDT_STATUS_STAT_MDI2)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI3
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+-									 QCA808X_CDT_STATUS_STAT_MDI3)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+-									 QCA808X_CDT_STATUS_STAT_MDI3)
+-
+-/* Added for reference of existence but should be handled by wait_for_completion already */
+-#define QCA808X_CDT_STATUS_STAT_BUSY		(BIT(1) | BIT(3))
+-
+-#define QCA808X_MMD7_LED_GLOBAL			0x8073
+-#define QCA808X_LED_BLINK_1			GENMASK(11, 6)
+-#define QCA808X_LED_BLINK_2			GENMASK(5, 0)
+-/* Values are the same for both BLINK_1 and BLINK_2 */
+-#define QCA808X_LED_BLINK_FREQ_MASK		GENMASK(5, 3)
+-#define QCA808X_LED_BLINK_FREQ_2HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
+-#define QCA808X_LED_BLINK_FREQ_4HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
+-#define QCA808X_LED_BLINK_FREQ_8HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
+-#define QCA808X_LED_BLINK_FREQ_16HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
+-#define QCA808X_LED_BLINK_FREQ_32HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
+-#define QCA808X_LED_BLINK_FREQ_64HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
+-#define QCA808X_LED_BLINK_FREQ_128HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
+-#define QCA808X_LED_BLINK_FREQ_256HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
+-#define QCA808X_LED_BLINK_DUTY_MASK		GENMASK(2, 0)
+-#define QCA808X_LED_BLINK_DUTY_50_50		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
+-#define QCA808X_LED_BLINK_DUTY_75_25		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
+-#define QCA808X_LED_BLINK_DUTY_25_75		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
+-#define QCA808X_LED_BLINK_DUTY_33_67		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
+-#define QCA808X_LED_BLINK_DUTY_67_33		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
+-#define QCA808X_LED_BLINK_DUTY_17_83		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
+-#define QCA808X_LED_BLINK_DUTY_83_17		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
+-#define QCA808X_LED_BLINK_DUTY_8_92		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
+-
+-#define QCA808X_MMD7_LED2_CTRL			0x8074
+-#define QCA808X_MMD7_LED2_FORCE_CTRL		0x8075
+-#define QCA808X_MMD7_LED1_CTRL			0x8076
+-#define QCA808X_MMD7_LED1_FORCE_CTRL		0x8077
+-#define QCA808X_MMD7_LED0_CTRL			0x8078
+-#define QCA808X_MMD7_LED_CTRL(x)		(0x8078 - ((x) * 2))
+-
+-/* LED hw control pattern is the same for every LED */
+-#define QCA808X_LED_PATTERN_MASK		GENMASK(15, 0)
+-#define QCA808X_LED_SPEED2500_ON		BIT(15)
+-#define QCA808X_LED_SPEED2500_BLINK		BIT(14)
+-/* Follow blink trigger even if duplex or speed condition doesn't match */
+-#define QCA808X_LED_BLINK_CHECK_BYPASS		BIT(13)
+-#define QCA808X_LED_FULL_DUPLEX_ON		BIT(12)
+-#define QCA808X_LED_HALF_DUPLEX_ON		BIT(11)
+-#define QCA808X_LED_TX_BLINK			BIT(10)
+-#define QCA808X_LED_RX_BLINK			BIT(9)
+-#define QCA808X_LED_TX_ON_10MS			BIT(8)
+-#define QCA808X_LED_RX_ON_10MS			BIT(7)
+-#define QCA808X_LED_SPEED1000_ON		BIT(6)
+-#define QCA808X_LED_SPEED100_ON			BIT(5)
+-#define QCA808X_LED_SPEED10_ON			BIT(4)
+-#define QCA808X_LED_COLLISION_BLINK		BIT(3)
+-#define QCA808X_LED_SPEED1000_BLINK		BIT(2)
+-#define QCA808X_LED_SPEED100_BLINK		BIT(1)
+-#define QCA808X_LED_SPEED10_BLINK		BIT(0)
+-
+-#define QCA808X_MMD7_LED0_FORCE_CTRL		0x8079
+-#define QCA808X_MMD7_LED_FORCE_CTRL(x)		(0x8079 - ((x) * 2))
+-
+-/* LED force ctrl is the same for every LED
+- * No documentation exist for this, not even internal one
+- * with NDA as QCOM gives only info about configuring
+- * hw control pattern rules and doesn't indicate any way
+- * to force the LED to specific mode.
+- * These define comes from reverse and testing and maybe
+- * lack of some info or some info are not entirely correct.
+- * For the basic LED control and hw control these finding
+- * are enough to support LED control in all the required APIs.
+- *
+- * On doing some comparison with implementation with qca807x,
+- * it was found that it's 1:1 equal to it and confirms all the
+- * reverse done. It was also found further specification with the
+- * force mode and the blink modes.
+- */
+-#define QCA808X_LED_FORCE_EN			BIT(15)
+-#define QCA808X_LED_FORCE_MODE_MASK		GENMASK(14, 13)
+-#define QCA808X_LED_FORCE_BLINK_1		FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
+-#define QCA808X_LED_FORCE_BLINK_2		FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
+-#define QCA808X_LED_FORCE_ON			FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
+-#define QCA808X_LED_FORCE_OFF			FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
+-
+-#define QCA808X_MMD7_LED_POLARITY_CTRL		0x901a
+-/* QSDK sets by default 0x46 to this reg that sets BIT 6 for
+- * LED to active high. It's not clear what BIT 3 and BIT 4 does.
+- */
+-#define QCA808X_LED_ACTIVE_HIGH			BIT(6)
+-
+-/* QCA808X 1G chip type */
+-#define QCA808X_PHY_MMD7_CHIP_TYPE		0x901d
+-#define QCA808X_PHY_CHIP_TYPE_1G		BIT(0)
+-
+-#define QCA8081_PHY_SERDES_MMD1_FIFO_CTRL	0x9072
+-#define QCA8081_PHY_FIFO_RSTN			BIT(11)
+-
+-MODULE_DESCRIPTION("Qualcomm Atheros AR803x and QCA808X PHY driver");
+-MODULE_AUTHOR("Matus Ujhelyi");
+-MODULE_LICENSE("GPL");
+-
+-enum stat_access_type {
+-	PHY,
+-	MMD
+-};
+-
+-struct at803x_hw_stat {
+-	const char *string;
+-	u8 reg;
+-	u32 mask;
+-	enum stat_access_type access_type;
+-};
+-
+-static struct at803x_hw_stat qca83xx_hw_stats[] = {
+-	{ "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
+-	{ "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
+-	{ "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
+-};
+-
+-struct at803x_ss_mask {
+-	u16 speed_mask;
+-	u8 speed_shift;
+-};
+-
+-struct at803x_priv {
+-	int flags;
+-	u16 clk_25m_reg;
+-	u16 clk_25m_mask;
+-	u8 smarteee_lpi_tw_1g;
+-	u8 smarteee_lpi_tw_100m;
+-	bool is_fiber;
+-	bool is_1000basex;
+-	struct regulator_dev *vddio_rdev;
+-	struct regulator_dev *vddh_rdev;
+-	u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
+-	int led_polarity_mode;
+-};
+-
+-struct at803x_context {
+-	u16 bmcr;
+-	u16 advertise;
+-	u16 control1000;
+-	u16 int_enable;
+-	u16 smart_speed;
+-	u16 led_control;
+-};
+-
+-static int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data)
+-{
+-	int ret;
+-
+-	ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
+-	if (ret < 0)
+-		return ret;
+-
+-	return phy_write(phydev, AT803X_DEBUG_DATA, data);
+-}
+-
+-static int at803x_debug_reg_read(struct phy_device *phydev, u16 reg)
+-{
+-	int ret;
+-
+-	ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
+-	if (ret < 0)
+-		return ret;
+-
+-	return phy_read(phydev, AT803X_DEBUG_DATA);
+-}
+-
+-static int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,
+-				 u16 clear, u16 set)
+-{
+-	u16 val;
+-	int ret;
+-
+-	ret = at803x_debug_reg_read(phydev, reg);
+-	if (ret < 0)
+-		return ret;
+-
+-	val = ret & 0xffff;
+-	val &= ~clear;
+-	val |= set;
+-
+-	return phy_write(phydev, AT803X_DEBUG_DATA, val);
+-}
+-
+-static int at803x_write_page(struct phy_device *phydev, int page)
+-{
+-	int mask;
+-	int set;
+-
+-	if (page == AT803X_PAGE_COPPER) {
+-		set = AT803X_BT_BX_REG_SEL;
+-		mask = 0;
+-	} else {
+-		set = 0;
+-		mask = AT803X_BT_BX_REG_SEL;
+-	}
+-
+-	return __phy_modify(phydev, AT803X_REG_CHIP_CONFIG, mask, set);
+-}
+-
+-static int at803x_read_page(struct phy_device *phydev)
+-{
+-	int ccr = __phy_read(phydev, AT803X_REG_CHIP_CONFIG);
+-
+-	if (ccr < 0)
+-		return ccr;
+-
+-	if (ccr & AT803X_BT_BX_REG_SEL)
+-		return AT803X_PAGE_COPPER;
+-
+-	return AT803X_PAGE_FIBER;
+-}
+-
+-static int at803x_enable_rx_delay(struct phy_device *phydev)
+-{
+-	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0,
+-				     AT803X_DEBUG_RX_CLK_DLY_EN);
+-}
+-
+-static int at803x_enable_tx_delay(struct phy_device *phydev)
+-{
+-	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0,
+-				     AT803X_DEBUG_TX_CLK_DLY_EN);
+-}
+-
+-static int at803x_disable_rx_delay(struct phy_device *phydev)
+-{
+-	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+-				     AT803X_DEBUG_RX_CLK_DLY_EN, 0);
+-}
+-
+-static int at803x_disable_tx_delay(struct phy_device *phydev)
+-{
+-	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE,
+-				     AT803X_DEBUG_TX_CLK_DLY_EN, 0);
+-}
+-
+-/* save relevant PHY registers to private copy */
+-static void at803x_context_save(struct phy_device *phydev,
+-				struct at803x_context *context)
+-{
+-	context->bmcr = phy_read(phydev, MII_BMCR);
+-	context->advertise = phy_read(phydev, MII_ADVERTISE);
+-	context->control1000 = phy_read(phydev, MII_CTRL1000);
+-	context->int_enable = phy_read(phydev, AT803X_INTR_ENABLE);
+-	context->smart_speed = phy_read(phydev, AT803X_SMART_SPEED);
+-	context->led_control = phy_read(phydev, AT803X_LED_CONTROL);
+-}
+-
+-/* restore relevant PHY registers from private copy */
+-static void at803x_context_restore(struct phy_device *phydev,
+-				   const struct at803x_context *context)
+-{
+-	phy_write(phydev, MII_BMCR, context->bmcr);
+-	phy_write(phydev, MII_ADVERTISE, context->advertise);
+-	phy_write(phydev, MII_CTRL1000, context->control1000);
+-	phy_write(phydev, AT803X_INTR_ENABLE, context->int_enable);
+-	phy_write(phydev, AT803X_SMART_SPEED, context->smart_speed);
+-	phy_write(phydev, AT803X_LED_CONTROL, context->led_control);
+-}
+-
+-static int at803x_set_wol(struct phy_device *phydev,
+-			  struct ethtool_wolinfo *wol)
+-{
+-	int ret, irq_enabled;
+-
+-	if (wol->wolopts & WAKE_MAGIC) {
+-		struct net_device *ndev = phydev->attached_dev;
+-		const u8 *mac;
+-		unsigned int i;
+-		static const unsigned int offsets[] = {
+-			AT803X_LOC_MAC_ADDR_32_47_OFFSET,
+-			AT803X_LOC_MAC_ADDR_16_31_OFFSET,
+-			AT803X_LOC_MAC_ADDR_0_15_OFFSET,
+-		};
+-
+-		if (!ndev)
+-			return -ENODEV;
+-
+-		mac = (const u8 *)ndev->dev_addr;
+-
+-		if (!is_valid_ether_addr(mac))
+-			return -EINVAL;
+-
+-		for (i = 0; i < 3; i++)
+-			phy_write_mmd(phydev, MDIO_MMD_PCS, offsets[i],
+-				      mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
+-
+-		/* Enable WOL interrupt */
+-		ret = phy_modify(phydev, AT803X_INTR_ENABLE, 0, AT803X_INTR_ENABLE_WOL);
+-		if (ret)
+-			return ret;
+-	} else {
+-		/* Disable WOL interrupt */
+-		ret = phy_modify(phydev, AT803X_INTR_ENABLE, AT803X_INTR_ENABLE_WOL, 0);
+-		if (ret)
+-			return ret;
+-	}
+-
+-	/* Clear WOL status */
+-	ret = phy_read(phydev, AT803X_INTR_STATUS);
+-	if (ret < 0)
+-		return ret;
+-
+-	/* Check if there are other interrupts except for WOL triggered when PHY is
+-	 * in interrupt mode, only the interrupts enabled by AT803X_INTR_ENABLE can
+-	 * be passed up to the interrupt PIN.
+-	 */
+-	irq_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
+-	if (irq_enabled < 0)
+-		return irq_enabled;
+-
+-	irq_enabled &= ~AT803X_INTR_ENABLE_WOL;
+-	if (ret & irq_enabled && !phy_polling_mode(phydev))
+-		phy_trigger_machine(phydev);
+-
+-	return 0;
+-}
+-
+-static void at803x_get_wol(struct phy_device *phydev,
+-			   struct ethtool_wolinfo *wol)
+-{
+-	int value;
+-
+-	wol->supported = WAKE_MAGIC;
+-	wol->wolopts = 0;
+-
+-	value = phy_read(phydev, AT803X_INTR_ENABLE);
+-	if (value < 0)
+-		return;
+-
+-	if (value & AT803X_INTR_ENABLE_WOL)
+-		wol->wolopts |= WAKE_MAGIC;
+-}
+-
+-static int qca83xx_get_sset_count(struct phy_device *phydev)
+-{
+-	return ARRAY_SIZE(qca83xx_hw_stats);
+-}
+-
+-static void qca83xx_get_strings(struct phy_device *phydev, u8 *data)
+-{
+-	int i;
+-
+-	for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) {
+-		strscpy(data + i * ETH_GSTRING_LEN,
+-			qca83xx_hw_stats[i].string, ETH_GSTRING_LEN);
+-	}
+-}
+-
+-static u64 qca83xx_get_stat(struct phy_device *phydev, int i)
+-{
+-	struct at803x_hw_stat stat = qca83xx_hw_stats[i];
+-	struct at803x_priv *priv = phydev->priv;
+-	int val;
+-	u64 ret;
+-
+-	if (stat.access_type == MMD)
+-		val = phy_read_mmd(phydev, MDIO_MMD_PCS, stat.reg);
+-	else
+-		val = phy_read(phydev, stat.reg);
+-
+-	if (val < 0) {
+-		ret = U64_MAX;
+-	} else {
+-		val = val & stat.mask;
+-		priv->stats[i] += val;
+-		ret = priv->stats[i];
+-	}
+-
+-	return ret;
+-}
+-
+-static void qca83xx_get_stats(struct phy_device *phydev,
+-			      struct ethtool_stats *stats, u64 *data)
+-{
+-	int i;
+-
+-	for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++)
+-		data[i] = qca83xx_get_stat(phydev, i);
+-}
+-
+-static int at803x_suspend(struct phy_device *phydev)
+-{
+-	int value;
+-	int wol_enabled;
+-
+-	value = phy_read(phydev, AT803X_INTR_ENABLE);
+-	wol_enabled = value & AT803X_INTR_ENABLE_WOL;
+-
+-	if (wol_enabled)
+-		value = BMCR_ISOLATE;
+-	else
+-		value = BMCR_PDOWN;
+-
+-	phy_modify(phydev, MII_BMCR, 0, value);
+-
+-	return 0;
+-}
+-
+-static int at803x_resume(struct phy_device *phydev)
+-{
+-	return phy_modify(phydev, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, 0);
+-}
+-
+-static int at803x_parse_dt(struct phy_device *phydev)
+-{
+-	struct device_node *node = phydev->mdio.dev.of_node;
+-	struct at803x_priv *priv = phydev->priv;
+-	u32 freq, strength, tw;
+-	unsigned int sel;
+-	int ret;
+-
+-	if (!IS_ENABLED(CONFIG_OF_MDIO))
+-		return 0;
+-
+-	if (of_property_read_bool(node, "qca,disable-smarteee"))
+-		priv->flags |= AT803X_DISABLE_SMARTEEE;
+-
+-	if (of_property_read_bool(node, "qca,disable-hibernation-mode"))
+-		priv->flags |= AT803X_DISABLE_HIBERNATION_MODE;
+-
+-	if (!of_property_read_u32(node, "qca,smarteee-tw-us-1g", &tw)) {
+-		if (!tw || tw > 255) {
+-			phydev_err(phydev, "invalid qca,smarteee-tw-us-1g\n");
+-			return -EINVAL;
+-		}
+-		priv->smarteee_lpi_tw_1g = tw;
+-	}
+-
+-	if (!of_property_read_u32(node, "qca,smarteee-tw-us-100m", &tw)) {
+-		if (!tw || tw > 255) {
+-			phydev_err(phydev, "invalid qca,smarteee-tw-us-100m\n");
+-			return -EINVAL;
+-		}
+-		priv->smarteee_lpi_tw_100m = tw;
+-	}
+-
+-	ret = of_property_read_u32(node, "qca,clk-out-frequency", &freq);
+-	if (!ret) {
+-		switch (freq) {
+-		case 25000000:
+-			sel = AT803X_CLK_OUT_25MHZ_XTAL;
+-			break;
+-		case 50000000:
+-			sel = AT803X_CLK_OUT_50MHZ_PLL;
+-			break;
+-		case 62500000:
+-			sel = AT803X_CLK_OUT_62_5MHZ_PLL;
+-			break;
+-		case 125000000:
+-			sel = AT803X_CLK_OUT_125MHZ_PLL;
+-			break;
+-		default:
+-			phydev_err(phydev, "invalid qca,clk-out-frequency\n");
+-			return -EINVAL;
+-		}
+-
+-		priv->clk_25m_reg |= FIELD_PREP(AT803X_CLK_OUT_MASK, sel);
+-		priv->clk_25m_mask |= AT803X_CLK_OUT_MASK;
+-	}
+-
+-	ret = of_property_read_u32(node, "qca,clk-out-strength", &strength);
+-	if (!ret) {
+-		priv->clk_25m_mask |= AT803X_CLK_OUT_STRENGTH_MASK;
+-		switch (strength) {
+-		case AR803X_STRENGTH_FULL:
+-			priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_FULL;
+-			break;
+-		case AR803X_STRENGTH_HALF:
+-			priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_HALF;
+-			break;
+-		case AR803X_STRENGTH_QUARTER:
+-			priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_QUARTER;
+-			break;
+-		default:
+-			phydev_err(phydev, "invalid qca,clk-out-strength\n");
+-			return -EINVAL;
+-		}
+-	}
+-
+-	return 0;
+-}
+-
+-static int at803x_probe(struct phy_device *phydev)
+-{
+-	struct device *dev = &phydev->mdio.dev;
+-	struct at803x_priv *priv;
+-	int ret;
+-
+-	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+-	if (!priv)
+-		return -ENOMEM;
+-
+-	/* Init LED polarity mode to -1 */
+-	priv->led_polarity_mode = -1;
+-
+-	phydev->priv = priv;
+-
+-	ret = at803x_parse_dt(phydev);
+-	if (ret)
+-		return ret;
+-
+-	return 0;
+-}
+-
+-static int at803x_get_features(struct phy_device *phydev)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-	int err;
+-
+-	err = genphy_read_abilities(phydev);
+-	if (err)
+-		return err;
+-
+-	if (phydev->drv->phy_id != ATH8031_PHY_ID)
+-		return 0;
+-
+-	/* AR8031/AR8033 have different status registers
+-	 * for copper and fiber operation. However, the
+-	 * extended status register is the same for both
+-	 * operation modes.
+-	 *
+-	 * As a result of that, ESTATUS_1000_XFULL is set
+-	 * to 1 even when operating in copper TP mode.
+-	 *
+-	 * Remove this mode from the supported link modes
+-	 * when not operating in 1000BaseX mode.
+-	 */
+-	if (!priv->is_1000basex)
+-		linkmode_clear_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+-				   phydev->supported);
+-
+-	return 0;
+-}
+-
+-static int at803x_smarteee_config(struct phy_device *phydev)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-	u16 mask = 0, val = 0;
+-	int ret;
+-
+-	if (priv->flags & AT803X_DISABLE_SMARTEEE)
+-		return phy_modify_mmd(phydev, MDIO_MMD_PCS,
+-				      AT803X_MMD3_SMARTEEE_CTL3,
+-				      AT803X_MMD3_SMARTEEE_CTL3_LPI_EN, 0);
+-
+-	if (priv->smarteee_lpi_tw_1g) {
+-		mask |= 0xff00;
+-		val |= priv->smarteee_lpi_tw_1g << 8;
+-	}
+-	if (priv->smarteee_lpi_tw_100m) {
+-		mask |= 0x00ff;
+-		val |= priv->smarteee_lpi_tw_100m;
+-	}
+-	if (!mask)
+-		return 0;
+-
+-	ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, AT803X_MMD3_SMARTEEE_CTL1,
+-			     mask, val);
+-	if (ret)
+-		return ret;
+-
+-	return phy_modify_mmd(phydev, MDIO_MMD_PCS, AT803X_MMD3_SMARTEEE_CTL3,
+-			      AT803X_MMD3_SMARTEEE_CTL3_LPI_EN,
+-			      AT803X_MMD3_SMARTEEE_CTL3_LPI_EN);
+-}
+-
+-static int at803x_clk_out_config(struct phy_device *phydev)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-
+-	if (!priv->clk_25m_mask)
+-		return 0;
+-
+-	return phy_modify_mmd(phydev, MDIO_MMD_AN, AT803X_MMD7_CLK25M,
+-			      priv->clk_25m_mask, priv->clk_25m_reg);
+-}
+-
+-static int at8031_pll_config(struct phy_device *phydev)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-
+-	/* The default after hardware reset is PLL OFF. After a soft reset, the
+-	 * values are retained.
+-	 */
+-	if (priv->flags & AT803X_KEEP_PLL_ENABLED)
+-		return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+-					     0, AT803X_DEBUG_PLL_ON);
+-	else
+-		return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+-					     AT803X_DEBUG_PLL_ON, 0);
+-}
+-
+-static int at803x_hibernation_mode_config(struct phy_device *phydev)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-
+-	/* The default after hardware reset is hibernation mode enabled. After
+-	 * software reset, the value is retained.
+-	 */
+-	if (!(priv->flags & AT803X_DISABLE_HIBERNATION_MODE))
+-		return 0;
+-
+-	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
+-					 AT803X_DEBUG_HIB_CTRL_PS_HIB_EN, 0);
+-}
+-
+-static int at803x_config_init(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	/* The RX and TX delay default is:
+-	 *   after HW reset: RX delay enabled and TX delay disabled
+-	 *   after SW reset: RX delay enabled, while TX delay retains the
+-	 *   value before reset.
+-	 */
+-	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+-	    phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
+-		ret = at803x_enable_rx_delay(phydev);
+-	else
+-		ret = at803x_disable_rx_delay(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+-	    phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+-		ret = at803x_enable_tx_delay(phydev);
+-	else
+-		ret = at803x_disable_tx_delay(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	ret = at803x_smarteee_config(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	ret = at803x_clk_out_config(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	ret = at803x_hibernation_mode_config(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	/* Ar803x extended next page bit is enabled by default. Cisco
+-	 * multigig switches read this bit and attempt to negotiate 10Gbps
+-	 * rates even if the next page bit is disabled. This is incorrect
+-	 * behaviour but we still need to accommodate it. XNP is only needed
+-	 * for 10Gbps support, so disable XNP.
+-	 */
+-	return phy_modify(phydev, MII_ADVERTISE, MDIO_AN_CTRL1_XNP, 0);
+-}
+-
+-static int at803x_ack_interrupt(struct phy_device *phydev)
+-{
+-	int err;
+-
+-	err = phy_read(phydev, AT803X_INTR_STATUS);
+-
+-	return (err < 0) ? err : 0;
+-}
+-
+-static int at803x_config_intr(struct phy_device *phydev)
+-{
+-	int err;
+-	int value;
+-
+-	value = phy_read(phydev, AT803X_INTR_ENABLE);
+-
+-	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+-		/* Clear any pending interrupts */
+-		err = at803x_ack_interrupt(phydev);
+-		if (err)
+-			return err;
+-
+-		value |= AT803X_INTR_ENABLE_AUTONEG_ERR;
+-		value |= AT803X_INTR_ENABLE_SPEED_CHANGED;
+-		value |= AT803X_INTR_ENABLE_DUPLEX_CHANGED;
+-		value |= AT803X_INTR_ENABLE_LINK_FAIL;
+-		value |= AT803X_INTR_ENABLE_LINK_SUCCESS;
+-
+-		err = phy_write(phydev, AT803X_INTR_ENABLE, value);
+-	} else {
+-		err = phy_write(phydev, AT803X_INTR_ENABLE, 0);
+-		if (err)
+-			return err;
+-
+-		/* Clear any pending interrupts */
+-		err = at803x_ack_interrupt(phydev);
+-	}
+-
+-	return err;
+-}
+-
+-static irqreturn_t at803x_handle_interrupt(struct phy_device *phydev)
+-{
+-	int irq_status, int_enabled;
+-
+-	irq_status = phy_read(phydev, AT803X_INTR_STATUS);
+-	if (irq_status < 0) {
+-		phy_error(phydev);
+-		return IRQ_NONE;
+-	}
+-
+-	/* Read the current enabled interrupts */
+-	int_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
+-	if (int_enabled < 0) {
+-		phy_error(phydev);
+-		return IRQ_NONE;
+-	}
+-
+-	/* See if this was one of our enabled interrupts */
+-	if (!(irq_status & int_enabled))
+-		return IRQ_NONE;
+-
+-	phy_trigger_machine(phydev);
+-
+-	return IRQ_HANDLED;
+-}
+-
+-static void at803x_link_change_notify(struct phy_device *phydev)
+-{
+-	/*
+-	 * Conduct a hardware reset for AT8030 every time a link loss is
+-	 * signalled. This is necessary to circumvent a hardware bug that
+-	 * occurs when the cable is unplugged while TX packets are pending
+-	 * in the FIFO. In such cases, the FIFO enters an error mode it
+-	 * cannot recover from by software.
+-	 */
+-	if (phydev->state == PHY_NOLINK && phydev->mdio.reset_gpio) {
+-		struct at803x_context context;
+-
+-		at803x_context_save(phydev, &context);
+-
+-		phy_device_reset(phydev, 1);
+-		usleep_range(1000, 2000);
+-		phy_device_reset(phydev, 0);
+-		usleep_range(1000, 2000);
+-
+-		at803x_context_restore(phydev, &context);
+-
+-		phydev_dbg(phydev, "%s(): phy was reset\n", __func__);
+-	}
+-}
+-
+-static int at803x_read_specific_status(struct phy_device *phydev,
+-				       struct at803x_ss_mask ss_mask)
+-{
+-	int ss;
+-
+-	/* Read the AT8035 PHY-Specific Status register, which indicates the
+-	 * speed and duplex that the PHY is actually using, irrespective of
+-	 * whether we are in autoneg mode or not.
+-	 */
+-	ss = phy_read(phydev, AT803X_SPECIFIC_STATUS);
+-	if (ss < 0)
+-		return ss;
+-
+-	if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) {
+-		int sfc, speed;
+-
+-		sfc = phy_read(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL);
+-		if (sfc < 0)
+-			return sfc;
+-
+-		speed = ss & ss_mask.speed_mask;
+-		speed >>= ss_mask.speed_shift;
+-
+-		switch (speed) {
+-		case AT803X_SS_SPEED_10:
+-			phydev->speed = SPEED_10;
+-			break;
+-		case AT803X_SS_SPEED_100:
+-			phydev->speed = SPEED_100;
+-			break;
+-		case AT803X_SS_SPEED_1000:
+-			phydev->speed = SPEED_1000;
+-			break;
+-		case QCA808X_SS_SPEED_2500:
+-			phydev->speed = SPEED_2500;
+-			break;
+-		}
+-		if (ss & AT803X_SS_DUPLEX)
+-			phydev->duplex = DUPLEX_FULL;
+-		else
+-			phydev->duplex = DUPLEX_HALF;
+-
+-		if (ss & AT803X_SS_MDIX)
+-			phydev->mdix = ETH_TP_MDI_X;
+-		else
+-			phydev->mdix = ETH_TP_MDI;
+-
+-		switch (FIELD_GET(AT803X_SFC_MDI_CROSSOVER_MODE_M, sfc)) {
+-		case AT803X_SFC_MANUAL_MDI:
+-			phydev->mdix_ctrl = ETH_TP_MDI;
+-			break;
+-		case AT803X_SFC_MANUAL_MDIX:
+-			phydev->mdix_ctrl = ETH_TP_MDI_X;
+-			break;
+-		case AT803X_SFC_AUTOMATIC_CROSSOVER:
+-			phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+-			break;
+-		}
+-	}
+-
+-	return 0;
+-}
+-
+-static int at803x_read_status(struct phy_device *phydev)
+-{
+-	struct at803x_ss_mask ss_mask = { 0 };
+-	int err, old_link = phydev->link;
+-
+-	/* Update the link, but return if there was an error */
+-	err = genphy_update_link(phydev);
+-	if (err)
+-		return err;
+-
+-	/* why bother the PHY if nothing can have changed */
+-	if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
+-		return 0;
+-
+-	phydev->speed = SPEED_UNKNOWN;
+-	phydev->duplex = DUPLEX_UNKNOWN;
+-	phydev->pause = 0;
+-	phydev->asym_pause = 0;
+-
+-	err = genphy_read_lpa(phydev);
+-	if (err < 0)
+-		return err;
+-
+-	ss_mask.speed_mask = AT803X_SS_SPEED_MASK;
+-	ss_mask.speed_shift = __bf_shf(AT803X_SS_SPEED_MASK);
+-	err = at803x_read_specific_status(phydev, ss_mask);
+-	if (err < 0)
+-		return err;
+-
+-	if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)
+-		phy_resolve_aneg_pause(phydev);
+-
+-	return 0;
+-}
+-
+-static int at803x_config_mdix(struct phy_device *phydev, u8 ctrl)
+-{
+-	u16 val;
+-
+-	switch (ctrl) {
+-	case ETH_TP_MDI:
+-		val = AT803X_SFC_MANUAL_MDI;
+-		break;
+-	case ETH_TP_MDI_X:
+-		val = AT803X_SFC_MANUAL_MDIX;
+-		break;
+-	case ETH_TP_MDI_AUTO:
+-		val = AT803X_SFC_AUTOMATIC_CROSSOVER;
+-		break;
+-	default:
+-		return 0;
+-	}
+-
+-	return phy_modify_changed(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL,
+-			  AT803X_SFC_MDI_CROSSOVER_MODE_M,
+-			  FIELD_PREP(AT803X_SFC_MDI_CROSSOVER_MODE_M, val));
+-}
+-
+-static int at803x_prepare_config_aneg(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	ret = at803x_config_mdix(phydev, phydev->mdix_ctrl);
+-	if (ret < 0)
+-		return ret;
+-
+-	/* Changes of the midx bits are disruptive to the normal operation;
+-	 * therefore any changes to these registers must be followed by a
+-	 * software reset to take effect.
+-	 */
+-	if (ret == 1) {
+-		ret = genphy_soft_reset(phydev);
+-		if (ret < 0)
+-			return ret;
+-	}
+-
+-	return 0;
+-}
+-
+-static int at803x_config_aneg(struct phy_device *phydev)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-	int ret;
+-
+-	ret = at803x_prepare_config_aneg(phydev);
+-	if (ret)
+-		return ret;
+-
+-	if (priv->is_1000basex)
+-		return genphy_c37_config_aneg(phydev);
+-
+-	return genphy_config_aneg(phydev);
+-}
+-
+-static int at803x_get_downshift(struct phy_device *phydev, u8 *d)
+-{
+-	int val;
+-
+-	val = phy_read(phydev, AT803X_SMART_SPEED);
+-	if (val < 0)
+-		return val;
+-
+-	if (val & AT803X_SMART_SPEED_ENABLE)
+-		*d = FIELD_GET(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, val) + 2;
+-	else
+-		*d = DOWNSHIFT_DEV_DISABLE;
+-
+-	return 0;
+-}
+-
+-static int at803x_set_downshift(struct phy_device *phydev, u8 cnt)
+-{
+-	u16 mask, set;
+-	int ret;
+-
+-	switch (cnt) {
+-	case DOWNSHIFT_DEV_DEFAULT_COUNT:
+-		cnt = AT803X_DEFAULT_DOWNSHIFT;
+-		fallthrough;
+-	case AT803X_MIN_DOWNSHIFT ... AT803X_MAX_DOWNSHIFT:
+-		set = AT803X_SMART_SPEED_ENABLE |
+-		      AT803X_SMART_SPEED_BYPASS_TIMER |
+-		      FIELD_PREP(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, cnt - 2);
+-		mask = AT803X_SMART_SPEED_RETRY_LIMIT_MASK;
+-		break;
+-	case DOWNSHIFT_DEV_DISABLE:
+-		set = 0;
+-		mask = AT803X_SMART_SPEED_ENABLE |
+-		       AT803X_SMART_SPEED_BYPASS_TIMER;
+-		break;
+-	default:
+-		return -EINVAL;
+-	}
+-
+-	ret = phy_modify_changed(phydev, AT803X_SMART_SPEED, mask, set);
+-
+-	/* After changing the smart speed settings, we need to perform a
+-	 * software reset, use phy_init_hw() to make sure we set the
+-	 * reapply any values which might got lost during software reset.
+-	 */
+-	if (ret == 1)
+-		ret = phy_init_hw(phydev);
+-
+-	return ret;
+-}
+-
+-static int at803x_get_tunable(struct phy_device *phydev,
+-			      struct ethtool_tunable *tuna, void *data)
+-{
+-	switch (tuna->id) {
+-	case ETHTOOL_PHY_DOWNSHIFT:
+-		return at803x_get_downshift(phydev, data);
+-	default:
+-		return -EOPNOTSUPP;
+-	}
+-}
+-
+-static int at803x_set_tunable(struct phy_device *phydev,
+-			      struct ethtool_tunable *tuna, const void *data)
+-{
+-	switch (tuna->id) {
+-	case ETHTOOL_PHY_DOWNSHIFT:
+-		return at803x_set_downshift(phydev, *(const u8 *)data);
+-	default:
+-		return -EOPNOTSUPP;
+-	}
+-}
+-
+-static int at803x_cable_test_result_trans(u16 status)
+-{
+-	switch (FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status)) {
+-	case AT803X_CDT_STATUS_STAT_NORMAL:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+-	case AT803X_CDT_STATUS_STAT_SHORT:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+-	case AT803X_CDT_STATUS_STAT_OPEN:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+-	case AT803X_CDT_STATUS_STAT_FAIL:
+-	default:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+-	}
+-}
+-
+-static bool at803x_cdt_test_failed(u16 status)
+-{
+-	return FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status) ==
+-		AT803X_CDT_STATUS_STAT_FAIL;
+-}
+-
+-static bool at803x_cdt_fault_length_valid(u16 status)
+-{
+-	switch (FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status)) {
+-	case AT803X_CDT_STATUS_STAT_OPEN:
+-	case AT803X_CDT_STATUS_STAT_SHORT:
+-		return true;
+-	}
+-	return false;
+-}
+-
+-static int at803x_cdt_fault_length(int dt)
+-{
+-	/* According to the datasheet the distance to the fault is
+-	 * DELTA_TIME * 0.824 meters.
+-	 *
+-	 * The author suspect the correct formula is:
+-	 *
+-	 *   fault_distance = DELTA_TIME * (c * VF) / 125MHz / 2
+-	 *
+-	 * where c is the speed of light, VF is the velocity factor of
+-	 * the twisted pair cable, 125MHz the counter frequency and
+-	 * we need to divide by 2 because the hardware will measure the
+-	 * round trip time to the fault and back to the PHY.
+-	 *
+-	 * With a VF of 0.69 we get the factor 0.824 mentioned in the
+-	 * datasheet.
+-	 */
+-	return (dt * 824) / 10;
+-}
+-
+-static int at803x_cdt_start(struct phy_device *phydev,
+-			    u32 cdt_start)
+-{
+-	return phy_write(phydev, AT803X_CDT, cdt_start);
+-}
+-
+-static int at803x_cdt_wait_for_completion(struct phy_device *phydev,
+-					  u32 cdt_en)
+-{
+-	int val, ret;
+-
+-	/* One test run takes about 25ms */
+-	ret = phy_read_poll_timeout(phydev, AT803X_CDT, val,
+-				    !(val & cdt_en),
+-				    30000, 100000, true);
+-
+-	return ret < 0 ? ret : 0;
+-}
+-
+-static int at803x_cable_test_one_pair(struct phy_device *phydev, int pair)
+-{
+-	static const int ethtool_pair[] = {
+-		ETHTOOL_A_CABLE_PAIR_A,
+-		ETHTOOL_A_CABLE_PAIR_B,
+-		ETHTOOL_A_CABLE_PAIR_C,
+-		ETHTOOL_A_CABLE_PAIR_D,
+-	};
+-	int ret, val;
+-
+-	val = FIELD_PREP(AT803X_CDT_MDI_PAIR_MASK, pair) |
+-	      AT803X_CDT_ENABLE_TEST;
+-	ret = at803x_cdt_start(phydev, val);
+-	if (ret)
+-		return ret;
+-
+-	ret = at803x_cdt_wait_for_completion(phydev, AT803X_CDT_ENABLE_TEST);
+-	if (ret)
+-		return ret;
+-
+-	val = phy_read(phydev, AT803X_CDT_STATUS);
+-	if (val < 0)
+-		return val;
+-
+-	if (at803x_cdt_test_failed(val))
+-		return 0;
+-
+-	ethnl_cable_test_result(phydev, ethtool_pair[pair],
+-				at803x_cable_test_result_trans(val));
+-
+-	if (at803x_cdt_fault_length_valid(val)) {
+-		val = FIELD_GET(AT803X_CDT_STATUS_DELTA_TIME_MASK, val);
+-		ethnl_cable_test_fault_length(phydev, ethtool_pair[pair],
+-					      at803x_cdt_fault_length(val));
+-	}
+-
+-	return 1;
+-}
+-
+-static int at803x_cable_test_get_status(struct phy_device *phydev,
+-					bool *finished, unsigned long pair_mask)
+-{
+-	int retries = 20;
+-	int pair, ret;
+-
+-	*finished = false;
+-
+-	/* According to the datasheet the CDT can be performed when
+-	 * there is no link partner or when the link partner is
+-	 * auto-negotiating. Starting the test will restart the AN
+-	 * automatically. It seems that doing this repeatedly we will
+-	 * get a slot where our link partner won't disturb our
+-	 * measurement.
+-	 */
+-	while (pair_mask && retries--) {
+-		for_each_set_bit(pair, &pair_mask, 4) {
+-			ret = at803x_cable_test_one_pair(phydev, pair);
+-			if (ret < 0)
+-				return ret;
+-			if (ret)
+-				clear_bit(pair, &pair_mask);
+-		}
+-		if (pair_mask)
+-			msleep(250);
+-	}
+-
+-	*finished = true;
+-
+-	return 0;
+-}
+-
+-static void at803x_cable_test_autoneg(struct phy_device *phydev)
+-{
+-	/* Enable auto-negotiation, but advertise no capabilities, no link
+-	 * will be established. A restart of the auto-negotiation is not
+-	 * required, because the cable test will automatically break the link.
+-	 */
+-	phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
+-	phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA);
+-}
+-
+-static int at803x_cable_test_start(struct phy_device *phydev)
+-{
+-	at803x_cable_test_autoneg(phydev);
+-	/* we do all the (time consuming) work later */
+-	return 0;
+-}
+-
+-static int at8031_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev,
+-					    unsigned int selector)
+-{
+-	struct phy_device *phydev = rdev_get_drvdata(rdev);
+-
+-	if (selector)
+-		return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+-					     0, AT803X_DEBUG_RGMII_1V8);
+-	else
+-		return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+-					     AT803X_DEBUG_RGMII_1V8, 0);
+-}
+-
+-static int at8031_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev)
+-{
+-	struct phy_device *phydev = rdev_get_drvdata(rdev);
+-	int val;
+-
+-	val = at803x_debug_reg_read(phydev, AT803X_DEBUG_REG_1F);
+-	if (val < 0)
+-		return val;
+-
+-	return (val & AT803X_DEBUG_RGMII_1V8) ? 1 : 0;
+-}
+-
+-static const struct regulator_ops vddio_regulator_ops = {
+-	.list_voltage = regulator_list_voltage_table,
+-	.set_voltage_sel = at8031_rgmii_reg_set_voltage_sel,
+-	.get_voltage_sel = at8031_rgmii_reg_get_voltage_sel,
+-};
+-
+-static const unsigned int vddio_voltage_table[] = {
+-	1500000,
+-	1800000,
+-};
+-
+-static const struct regulator_desc vddio_desc = {
+-	.name = "vddio",
+-	.of_match = of_match_ptr("vddio-regulator"),
+-	.n_voltages = ARRAY_SIZE(vddio_voltage_table),
+-	.volt_table = vddio_voltage_table,
+-	.ops = &vddio_regulator_ops,
+-	.type = REGULATOR_VOLTAGE,
+-	.owner = THIS_MODULE,
+-};
+-
+-static const struct regulator_ops vddh_regulator_ops = {
+-};
+-
+-static const struct regulator_desc vddh_desc = {
+-	.name = "vddh",
+-	.of_match = of_match_ptr("vddh-regulator"),
+-	.n_voltages = 1,
+-	.fixed_uV = 2500000,
+-	.ops = &vddh_regulator_ops,
+-	.type = REGULATOR_VOLTAGE,
+-	.owner = THIS_MODULE,
+-};
+-
+-static int at8031_register_regulators(struct phy_device *phydev)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-	struct device *dev = &phydev->mdio.dev;
+-	struct regulator_config config = { };
+-
+-	config.dev = dev;
+-	config.driver_data = phydev;
+-
+-	priv->vddio_rdev = devm_regulator_register(dev, &vddio_desc, &config);
+-	if (IS_ERR(priv->vddio_rdev)) {
+-		phydev_err(phydev, "failed to register VDDIO regulator\n");
+-		return PTR_ERR(priv->vddio_rdev);
+-	}
+-
+-	priv->vddh_rdev = devm_regulator_register(dev, &vddh_desc, &config);
+-	if (IS_ERR(priv->vddh_rdev)) {
+-		phydev_err(phydev, "failed to register VDDH regulator\n");
+-		return PTR_ERR(priv->vddh_rdev);
+-	}
+-
+-	return 0;
+-}
+-
+-static int at8031_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
+-{
+-	struct phy_device *phydev = upstream;
+-	__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_support);
+-	__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
+-	DECLARE_PHY_INTERFACE_MASK(interfaces);
+-	phy_interface_t iface;
+-
+-	linkmode_zero(phy_support);
+-	phylink_set(phy_support, 1000baseX_Full);
+-	phylink_set(phy_support, 1000baseT_Full);
+-	phylink_set(phy_support, Autoneg);
+-	phylink_set(phy_support, Pause);
+-	phylink_set(phy_support, Asym_Pause);
+-
+-	linkmode_zero(sfp_support);
+-	sfp_parse_support(phydev->sfp_bus, id, sfp_support, interfaces);
+-	/* Some modules support 10G modes as well as others we support.
+-	 * Mask out non-supported modes so the correct interface is picked.
+-	 */
+-	linkmode_and(sfp_support, phy_support, sfp_support);
+-
+-	if (linkmode_empty(sfp_support)) {
+-		dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
+-		return -EINVAL;
+-	}
+-
+-	iface = sfp_select_interface(phydev->sfp_bus, sfp_support);
+-
+-	/* Only 1000Base-X is supported by AR8031/8033 as the downstream SerDes
+-	 * interface for use with SFP modules.
+-	 * However, some copper modules detected as having a preferred SGMII
+-	 * interface do default to and function in 1000Base-X mode, so just
+-	 * print a warning and allow such modules, as they may have some chance
+-	 * of working.
+-	 */
+-	if (iface == PHY_INTERFACE_MODE_SGMII)
+-		dev_warn(&phydev->mdio.dev, "module may not function if 1000Base-X not supported\n");
+-	else if (iface != PHY_INTERFACE_MODE_1000BASEX)
+-		return -EINVAL;
+-
+-	return 0;
+-}
+-
+-static const struct sfp_upstream_ops at8031_sfp_ops = {
+-	.attach = phy_sfp_attach,
+-	.detach = phy_sfp_detach,
+-	.module_insert = at8031_sfp_insert,
+-};
+-
+-static int at8031_parse_dt(struct phy_device *phydev)
+-{
+-	struct device_node *node = phydev->mdio.dev.of_node;
+-	struct at803x_priv *priv = phydev->priv;
+-	int ret;
+-
+-	if (of_property_read_bool(node, "qca,keep-pll-enabled"))
+-		priv->flags |= AT803X_KEEP_PLL_ENABLED;
+-
+-	ret = at8031_register_regulators(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	ret = devm_regulator_get_enable_optional(&phydev->mdio.dev,
+-						 "vddio");
+-	if (ret) {
+-		phydev_err(phydev, "failed to get VDDIO regulator\n");
+-		return ret;
+-	}
+-
+-	/* Only AR8031/8033 support 1000Base-X for SFP modules */
+-	return phy_sfp_probe(phydev, &at8031_sfp_ops);
+-}
+-
+-static int at8031_probe(struct phy_device *phydev)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-	int mode_cfg;
+-	int ccr;
+-	int ret;
+-
+-	ret = at803x_probe(phydev);
+-	if (ret)
+-		return ret;
+-
+-	/* Only supported on AR8031/AR8033, the AR8030/AR8035 use strapping
+-	 * options.
+-	 */
+-	ret = at8031_parse_dt(phydev);
+-	if (ret)
+-		return ret;
+-
+-	ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
+-	if (ccr < 0)
+-		return ccr;
+-	mode_cfg = ccr & AT803X_MODE_CFG_MASK;
+-
+-	switch (mode_cfg) {
+-	case AT803X_MODE_CFG_BX1000_RGMII_50OHM:
+-	case AT803X_MODE_CFG_BX1000_RGMII_75OHM:
+-		priv->is_1000basex = true;
+-		fallthrough;
+-	case AT803X_MODE_CFG_FX100_RGMII_50OHM:
+-	case AT803X_MODE_CFG_FX100_RGMII_75OHM:
+-		priv->is_fiber = true;
+-		break;
+-	}
+-
+-	/* Disable WoL in 1588 register which is enabled
+-	 * by default
+-	 */
+-	return phy_modify_mmd(phydev, MDIO_MMD_PCS,
+-			      AT803X_PHY_MMD3_WOL_CTRL,
+-			      AT803X_WOL_EN, 0);
+-}
+-
+-static int at8031_config_init(struct phy_device *phydev)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-	int ret;
+-
+-	/* Some bootloaders leave the fiber page selected.
+-	 * Switch to the appropriate page (fiber or copper), as otherwise we
+-	 * read the PHY capabilities from the wrong page.
+-	 */
+-	phy_lock_mdio_bus(phydev);
+-	ret = at803x_write_page(phydev,
+-				priv->is_fiber ? AT803X_PAGE_FIBER :
+-						 AT803X_PAGE_COPPER);
+-	phy_unlock_mdio_bus(phydev);
+-	if (ret)
+-		return ret;
+-
+-	ret = at8031_pll_config(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	return at803x_config_init(phydev);
+-}
+-
+-static int at8031_set_wol(struct phy_device *phydev,
+-			  struct ethtool_wolinfo *wol)
+-{
+-	int ret;
+-
+-	/* First setup MAC address and enable WOL interrupt */
+-	ret = at803x_set_wol(phydev, wol);
+-	if (ret)
+-		return ret;
+-
+-	if (wol->wolopts & WAKE_MAGIC)
+-		/* Enable WOL function for 1588 */
+-		ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
+-				     AT803X_PHY_MMD3_WOL_CTRL,
+-				     0, AT803X_WOL_EN);
+-	else
+-		/* Disable WoL function for 1588 */
+-		ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
+-				     AT803X_PHY_MMD3_WOL_CTRL,
+-				     AT803X_WOL_EN, 0);
+-
+-	return ret;
+-}
+-
+-static int at8031_config_intr(struct phy_device *phydev)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-	int err, value = 0;
+-
+-	if (phydev->interrupts == PHY_INTERRUPT_ENABLED &&
+-	    priv->is_fiber) {
+-		/* Clear any pending interrupts */
+-		err = at803x_ack_interrupt(phydev);
+-		if (err)
+-			return err;
+-
+-		value |= AT803X_INTR_ENABLE_LINK_FAIL_BX;
+-		value |= AT803X_INTR_ENABLE_LINK_SUCCESS_BX;
+-
+-		err = phy_set_bits(phydev, AT803X_INTR_ENABLE, value);
+-		if (err)
+-			return err;
+-	}
+-
+-	return at803x_config_intr(phydev);
+-}
+-
+-/* AR8031 and AR8033 share the same read status logic */
+-static int at8031_read_status(struct phy_device *phydev)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-
+-	if (priv->is_1000basex)
+-		return genphy_c37_read_status(phydev);
+-
+-	return at803x_read_status(phydev);
+-}
+-
+-/* AR8031 and AR8035 share the same cable test get status reg */
+-static int at8031_cable_test_get_status(struct phy_device *phydev,
+-					bool *finished)
+-{
+-	return at803x_cable_test_get_status(phydev, finished, 0xf);
+-}
+-
+-/* AR8031 and AR8035 share the same cable test start logic */
+-static int at8031_cable_test_start(struct phy_device *phydev)
+-{
+-	at803x_cable_test_autoneg(phydev);
+-	phy_write(phydev, MII_CTRL1000, 0);
+-	/* we do all the (time consuming) work later */
+-	return 0;
+-}
+-
+-/* AR8032, AR9331 and QCA9561 share the same cable test get status reg */
+-static int at8032_cable_test_get_status(struct phy_device *phydev,
+-					bool *finished)
+-{
+-	return at803x_cable_test_get_status(phydev, finished, 0x3);
+-}
+-
+-static int at8035_parse_dt(struct phy_device *phydev)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-
+-	/* Mask is set by the generic at803x_parse_dt
+-	 * if property is set. Assume property is set
+-	 * with the mask not zero.
+-	 */
+-	if (priv->clk_25m_mask) {
+-		/* Fixup for the AR8030/AR8035. This chip has another mask and
+-		 * doesn't support the DSP reference. Eg. the lowest bit of the
+-		 * mask. The upper two bits select the same frequencies. Mask
+-		 * the lowest bit here.
+-		 *
+-		 * Warning:
+-		 *   There was no datasheet for the AR8030 available so this is
+-		 *   just a guess. But the AR8035 is listed as pin compatible
+-		 *   to the AR8030 so there might be a good chance it works on
+-		 *   the AR8030 too.
+-		 */
+-		priv->clk_25m_reg &= AT8035_CLK_OUT_MASK;
+-		priv->clk_25m_mask &= AT8035_CLK_OUT_MASK;
+-	}
+-
+-	return 0;
+-}
+-
+-/* AR8030 and AR8035 shared the same special mask for clk_25m */
+-static int at8035_probe(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	ret = at803x_probe(phydev);
+-	if (ret)
+-		return ret;
+-
+-	return at8035_parse_dt(phydev);
+-}
+-
+-static int qca83xx_config_init(struct phy_device *phydev)
+-{
+-	u8 switch_revision;
+-
+-	switch_revision = phydev->dev_flags & QCA8K_DEVFLAGS_REVISION_MASK;
+-
+-	switch (switch_revision) {
+-	case 1:
+-		/* For 100M waveform */
+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0x02ea);
+-		/* Turn on Gigabit clock */
+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x68a0);
+-		break;
+-
+-	case 2:
+-		phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0);
+-		fallthrough;
+-	case 4:
+-		phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f);
+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x6860);
+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0x2c46);
+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3C, 0x6000);
+-		break;
+-	}
+-
+-	/* Following original QCA sourcecode set port to prefer master */
+-	phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
+-
+-	return 0;
+-}
+-
+-static int qca8327_config_init(struct phy_device *phydev)
+-{
+-	/* QCA8327 require DAC amplitude adjustment for 100m set to +6%.
+-	 * Disable on init and enable only with 100m speed following
+-	 * qca original source code.
+-	 */
+-	at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+-			      QCA8327_DEBUG_MANU_CTRL_EN, 0);
+-
+-	return qca83xx_config_init(phydev);
+-}
+-
+-static void qca83xx_link_change_notify(struct phy_device *phydev)
+-{
+-	/* Set DAC Amplitude adjustment to +6% for 100m on link running */
+-	if (phydev->state == PHY_RUNNING) {
+-		if (phydev->speed == SPEED_100)
+-			at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+-					      QCA8327_DEBUG_MANU_CTRL_EN,
+-					      QCA8327_DEBUG_MANU_CTRL_EN);
+-	} else {
+-		/* Reset DAC Amplitude adjustment */
+-		at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+-				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
+-	}
+-}
+-
+-static int qca83xx_resume(struct phy_device *phydev)
+-{
+-	int ret, val;
+-
+-	/* Skip reset if not suspended */
+-	if (!phydev->suspended)
+-		return 0;
+-
+-	/* Reinit the port, reset values set by suspend */
+-	qca83xx_config_init(phydev);
+-
+-	/* Reset the port on port resume */
+-	phy_set_bits(phydev, MII_BMCR, BMCR_RESET | BMCR_ANENABLE);
+-
+-	/* On resume from suspend the switch execute a reset and
+-	 * restart auto-negotiation. Wait for reset to complete.
+-	 */
+-	ret = phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET),
+-				    50000, 600000, true);
+-	if (ret)
+-		return ret;
+-
+-	usleep_range(1000, 2000);
+-
+-	return 0;
+-}
+-
+-static int qca83xx_suspend(struct phy_device *phydev)
+-{
+-	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_GREEN,
+-			      AT803X_DEBUG_GATE_CLK_IN1000, 0);
+-
+-	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
+-			      AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE |
+-			      AT803X_DEBUG_HIB_CTRL_SEL_RST_80U, 0);
+-
+-	return 0;
+-}
+-
+-static int qca8337_suspend(struct phy_device *phydev)
+-{
+-	/* Only QCA8337 support actual suspend. */
+-	genphy_suspend(phydev);
+-
+-	return qca83xx_suspend(phydev);
+-}
+-
+-static int qca8327_suspend(struct phy_device *phydev)
+-{
+-	u16 mask = 0;
+-
+-	/* QCA8327 cause port unreliability when phy suspend
+-	 * is set.
+-	 */
+-	mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
+-	phy_modify(phydev, MII_BMCR, mask, 0);
+-
+-	return qca83xx_suspend(phydev);
+-}
+-
+-static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	/* Enable fast retrain */
+-	ret = genphy_c45_fast_retrain(phydev, true);
+-	if (ret)
+-		return ret;
+-
+-	phy_write_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_TOP_OPTION1,
+-		      QCA808X_TOP_OPTION1_DATA);
+-	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB,
+-		      QCA808X_MSE_THRESHOLD_20DB_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB,
+-		      QCA808X_MSE_THRESHOLD_17DB_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB,
+-		      QCA808X_MSE_THRESHOLD_27DB_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB,
+-		      QCA808X_MSE_THRESHOLD_28DB_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_1,
+-		      QCA808X_MMD3_DEBUG_1_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_4,
+-		      QCA808X_MMD3_DEBUG_4_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_5,
+-		      QCA808X_MMD3_DEBUG_5_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_3,
+-		      QCA808X_MMD3_DEBUG_3_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_6,
+-		      QCA808X_MMD3_DEBUG_6_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_2,
+-		      QCA808X_MMD3_DEBUG_2_VALUE);
+-
+-	return 0;
+-}
+-
+-static int qca808x_phy_ms_seed_enable(struct phy_device *phydev, bool enable)
+-{
+-	u16 seed_value;
+-
+-	if (!enable)
+-		return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+-				QCA808X_MASTER_SLAVE_SEED_ENABLE, 0);
+-
+-	seed_value = prandom_u32_max(QCA808X_MASTER_SLAVE_SEED_RANGE);
+-	return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+-			QCA808X_MASTER_SLAVE_SEED_CFG | QCA808X_MASTER_SLAVE_SEED_ENABLE,
+-			FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value) |
+-			QCA808X_MASTER_SLAVE_SEED_ENABLE);
+-}
+-
+-static bool qca808x_is_prefer_master(struct phy_device *phydev)
+-{
+-	return (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_FORCE) ||
+-		(phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
+-}
+-
+-static bool qca808x_has_fast_retrain_or_slave_seed(struct phy_device *phydev)
+-{
+-	return linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
+-}
+-
+-static int qca808x_config_init(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	/* Active adc&vga on 802.3az for the link 1000M and 100M */
+-	ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_ADDR_CLD_CTRL7,
+-			     QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
+-	if (ret)
+-		return ret;
+-
+-	/* Adjust the threshold on 802.3az for the link 1000M */
+-	ret = phy_write_mmd(phydev, MDIO_MMD_PCS,
+-			    QCA808X_PHY_MMD3_AZ_TRAINING_CTRL,
+-			    QCA808X_MMD3_AZ_TRAINING_VAL);
+-	if (ret)
+-		return ret;
+-
+-	if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+-		/* Config the fast retrain for the link 2500M */
+-		ret = qca808x_phy_fast_retrain_config(phydev);
+-		if (ret)
+-			return ret;
+-
+-		ret = genphy_read_master_slave(phydev);
+-		if (ret < 0)
+-			return ret;
+-
+-		if (!qca808x_is_prefer_master(phydev)) {
+-			/* Enable seed and configure lower ramdom seed to make phy
+-			 * linked as slave mode.
+-			 */
+-			ret = qca808x_phy_ms_seed_enable(phydev, true);
+-			if (ret)
+-				return ret;
+-		}
+-	}
+-
+-	/* Configure adc threshold as 100mv for the link 10M */
+-	return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
+-				     QCA808X_ADC_THRESHOLD_MASK,
+-				     QCA808X_ADC_THRESHOLD_100MV);
+-}
+-
+-static int qca808x_read_status(struct phy_device *phydev)
+-{
+-	struct at803x_ss_mask ss_mask = { 0 };
+-	int ret;
+-
+-	ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+-	if (ret < 0)
+-		return ret;
+-
+-	linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->lp_advertising,
+-			 ret & MDIO_AN_10GBT_STAT_LP2_5G);
+-
+-	ret = genphy_read_status(phydev);
+-	if (ret)
+-		return ret;
+-
+-	/* qca8081 takes the different bits for speed value from at803x */
+-	ss_mask.speed_mask = QCA808X_SS_SPEED_MASK;
+-	ss_mask.speed_shift = __bf_shf(QCA808X_SS_SPEED_MASK);
+-	ret = at803x_read_specific_status(phydev, ss_mask);
+-	if (ret < 0)
+-		return ret;
+-
+-	if (phydev->link) {
+-		if (phydev->speed == SPEED_2500)
+-			phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+-		else
+-			phydev->interface = PHY_INTERFACE_MODE_SGMII;
+-	} else {
+-		/* generate seed as a lower random value to make PHY linked as SLAVE easily,
+-		 * except for master/slave configuration fault detected or the master mode
+-		 * preferred.
+-		 *
+-		 * the reason for not putting this code into the function link_change_notify is
+-		 * the corner case where the link partner is also the qca8081 PHY and the seed
+-		 * value is configured as the same value, the link can't be up and no link change
+-		 * occurs.
+-		 */
+-		if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+-			if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
+-			    qca808x_is_prefer_master(phydev)) {
+-				qca808x_phy_ms_seed_enable(phydev, false);
+-			} else {
+-				qca808x_phy_ms_seed_enable(phydev, true);
+-			}
+-		}
+-	}
+-
+-	return 0;
+-}
+-
+-static int qca808x_soft_reset(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	ret = genphy_soft_reset(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	if (qca808x_has_fast_retrain_or_slave_seed(phydev))
+-		ret = qca808x_phy_ms_seed_enable(phydev, true);
+-
+-	return ret;
+-}
+-
+-static bool qca808x_cdt_fault_length_valid(int cdt_code)
+-{
+-	switch (cdt_code) {
+-	case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+-	case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+-		return true;
+-	default:
+-		return false;
+-	}
+-}
+-
+-static int qca808x_cable_test_result_trans(int cdt_code)
+-{
+-	switch (cdt_code) {
+-	case QCA808X_CDT_STATUS_STAT_NORMAL:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+-	case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+-	case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+-	case QCA808X_CDT_STATUS_STAT_FAIL:
+-	default:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+-	}
+-}
+-
+-static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
+-				    int result)
+-{
+-	int val;
+-	u32 cdt_length_reg = 0;
+-
+-	switch (pair) {
+-	case ETHTOOL_A_CABLE_PAIR_A:
+-		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;
+-		break;
+-	case ETHTOOL_A_CABLE_PAIR_B:
+-		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;
+-		break;
+-	case ETHTOOL_A_CABLE_PAIR_C:
+-		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;
+-		break;
+-	case ETHTOOL_A_CABLE_PAIR_D:
+-		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;
+-		break;
+-	default:
+-		return -EINVAL;
+-	}
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);
+-	if (val < 0)
+-		return val;
+-
+-	if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
+-		val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
+-	else
+-		val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
+-
+-	return at803x_cdt_fault_length(val);
+-}
+-
+-static int qca808x_cable_test_start(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	/* perform CDT with the following configs:
+-	 * 1. disable hibernation.
+-	 * 2. force PHY working in MDI mode.
+-	 * 3. for PHY working in 1000BaseT.
+-	 * 4. configure the threshold.
+-	 */
+-
+-	ret = at803x_debug_reg_mask(phydev, QCA808X_DBG_AN_TEST, QCA808X_HIBERNATION_EN, 0);
+-	if (ret < 0)
+-		return ret;
+-
+-	ret = at803x_config_mdix(phydev, ETH_TP_MDI);
+-	if (ret < 0)
+-		return ret;
+-
+-	/* Force 1000base-T needs to configure PMA/PMD and MII_BMCR */
+-	phydev->duplex = DUPLEX_FULL;
+-	phydev->speed = SPEED_1000;
+-	ret = genphy_c45_pma_setup_forced(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	ret = genphy_setup_forced(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	/* configure the thresholds for open, short, pair ok test */
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8074, 0xc040);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8076, 0xc040);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8077, 0xa060);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8078, 0xc050);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807a, 0xc060);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807e, 0xb060);
+-
+-	return 0;
+-}
+-
+-static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
+-					      u16 status)
+-{
+-	int length, result;
+-	u16 pair_code;
+-
+-	switch (pair) {
+-	case ETHTOOL_A_CABLE_PAIR_A:
+-		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
+-		break;
+-	case ETHTOOL_A_CABLE_PAIR_B:
+-		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
+-		break;
+-	case ETHTOOL_A_CABLE_PAIR_C:
+-		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
+-		break;
+-	case ETHTOOL_A_CABLE_PAIR_D:
+-		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
+-		break;
+-	default:
+-		return -EINVAL;
+-	}
+-
+-	result = qca808x_cable_test_result_trans(pair_code);
+-	ethnl_cable_test_result(phydev, pair, result);
+-
+-	if (qca808x_cdt_fault_length_valid(pair_code)) {
+-		length = qca808x_cdt_fault_length(phydev, pair, result);
+-		ethnl_cable_test_fault_length(phydev, pair, length);
+-	}
+-
+-	return 0;
+-}
+-
+-static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
+-{
+-	int ret, val;
+-
+-	*finished = false;
+-
+-	val = QCA808X_CDT_ENABLE_TEST |
+-	      QCA808X_CDT_LENGTH_UNIT;
+-	ret = at803x_cdt_start(phydev, val);
+-	if (ret)
+-		return ret;
+-
+-	ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
+-	if (ret)
+-		return ret;
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);
+-	if (val < 0)
+-		return val;
+-
+-	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
+-	if (ret)
+-		return ret;
+-
+-	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
+-	if (ret)
+-		return ret;
+-
+-	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
+-	if (ret)
+-		return ret;
+-
+-	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
+-	if (ret)
+-		return ret;
+-
+-	*finished = true;
+-
+-	return 0;
+-}
+-
+-static int qca808x_get_features(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	ret = genphy_c45_pma_read_abilities(phydev);
+-	if (ret)
+-		return ret;
+-
+-	/* The autoneg ability is not existed in bit3 of MMD7.1,
+-	 * but it is supported by qca808x PHY, so we add it here
+-	 * manually.
+-	 */
+-	linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+-
+-	/* As for the qca8081 1G version chip, the 2500baseT ability is also
+-	 * existed in the bit0 of MMD1.21, we need to remove it manually if
+-	 * it is the qca8081 1G chip according to the bit0 of MMD7.0x901d.
+-	 */
+-	ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE);
+-	if (ret < 0)
+-		return ret;
+-
+-	if (QCA808X_PHY_CHIP_TYPE_1G & ret)
+-		linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
+-
+-	return 0;
+-}
+-
+-static int qca808x_config_aneg(struct phy_device *phydev)
+-{
+-	int phy_ctrl = 0;
+-	int ret;
+-
+-	ret = at803x_prepare_config_aneg(phydev);
+-	if (ret)
+-		return ret;
+-
+-	/* The reg MII_BMCR also needs to be configured for force mode, the
+-	 * genphy_config_aneg is also needed.
+-	 */
+-	if (phydev->autoneg == AUTONEG_DISABLE)
+-		genphy_c45_pma_setup_forced(phydev);
+-
+-	if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising))
+-		phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
+-
+-	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+-				     MDIO_AN_10GBT_CTRL_ADV2_5G, phy_ctrl);
+-	if (ret < 0)
+-		return ret;
+-
+-	return __genphy_config_aneg(phydev, ret);
+-}
+-
+-static void qca808x_link_change_notify(struct phy_device *phydev)
+-{
+-	/* Assert interface sgmii fifo on link down, deassert it on link up,
+-	 * the interface device address is always phy address added by 1.
+-	 */
+-	mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
+-				   MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
+-				   QCA8081_PHY_FIFO_RSTN,
+-				   phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
+-}
+-
+-static int qca808x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
+-				    u16 *offload_trigger)
+-{
+-	/* Parsing specific to netdev trigger */
+-	if (test_bit(TRIGGER_NETDEV_TX, &rules))
+-		*offload_trigger |= QCA808X_LED_TX_BLINK;
+-	if (test_bit(TRIGGER_NETDEV_RX, &rules))
+-		*offload_trigger |= QCA808X_LED_RX_BLINK;
+-	if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
+-		*offload_trigger |= QCA808X_LED_SPEED10_ON;
+-	if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
+-		*offload_trigger |= QCA808X_LED_SPEED100_ON;
+-	if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
+-		*offload_trigger |= QCA808X_LED_SPEED1000_ON;
+-	if (test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
+-		*offload_trigger |= QCA808X_LED_SPEED2500_ON;
+-	if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
+-		*offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
+-	if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
+-		*offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
+-
+-	if (rules && !*offload_trigger)
+-		return -EOPNOTSUPP;
+-
+-	/* Enable BLINK_CHECK_BYPASS by default to make the LED
+-	 * blink even with duplex or speed mode not enabled.
+-	 */
+-	*offload_trigger |= QCA808X_LED_BLINK_CHECK_BYPASS;
+-
+-	return 0;
+-}
+-
+-static int qca808x_led_hw_control_enable(struct phy_device *phydev, u8 index)
+-{
+-	u16 reg;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+-	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
+-				  QCA808X_LED_FORCE_EN);
+-}
+-
+-static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
+-				       unsigned long rules)
+-{
+-	u16 offload_trigger = 0;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	return qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
+-}
+-
+-static int qca808x_led_hw_control_set(struct phy_device *phydev, u8 index,
+-				      unsigned long rules)
+-{
+-	u16 reg, offload_trigger = 0;
+-	int ret;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	reg = QCA808X_MMD7_LED_CTRL(index);
+-
+-	ret = qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
+-	if (ret)
+-		return ret;
+-
+-	ret = qca808x_led_hw_control_enable(phydev, index);
+-	if (ret)
+-		return ret;
+-
+-	return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+-			      QCA808X_LED_PATTERN_MASK,
+-			      offload_trigger);
+-}
+-
+-static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
+-{
+-	u16 reg;
+-	int val;
+-
+-	if (index > 2)
+-		return false;
+-
+-	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+-
+-	return !(val & QCA808X_LED_FORCE_EN);
+-}
+-
+-static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
+-				      unsigned long *rules)
+-{
+-	u16 reg;
+-	int val;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	/* Check if we have hw control enabled */
+-	if (qca808x_led_hw_control_status(phydev, index))
+-		return -EINVAL;
+-
+-	reg = QCA808X_MMD7_LED_CTRL(index);
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+-	if (val & QCA808X_LED_TX_BLINK)
+-		set_bit(TRIGGER_NETDEV_TX, rules);
+-	if (val & QCA808X_LED_RX_BLINK)
+-		set_bit(TRIGGER_NETDEV_RX, rules);
+-	if (val & QCA808X_LED_SPEED10_ON)
+-		set_bit(TRIGGER_NETDEV_LINK_10, rules);
+-	if (val & QCA808X_LED_SPEED100_ON)
+-		set_bit(TRIGGER_NETDEV_LINK_100, rules);
+-	if (val & QCA808X_LED_SPEED1000_ON)
+-		set_bit(TRIGGER_NETDEV_LINK_1000, rules);
+-	if (val & QCA808X_LED_SPEED2500_ON)
+-		set_bit(TRIGGER_NETDEV_LINK_2500, rules);
+-	if (val & QCA808X_LED_HALF_DUPLEX_ON)
+-		set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
+-	if (val & QCA808X_LED_FULL_DUPLEX_ON)
+-		set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
+-
+-	return 0;
+-}
+-
+-static int qca808x_led_hw_control_reset(struct phy_device *phydev, u8 index)
+-{
+-	u16 reg;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	reg = QCA808X_MMD7_LED_CTRL(index);
+-
+-	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
+-				  QCA808X_LED_PATTERN_MASK);
+-}
+-
+-static int qca808x_led_brightness_set(struct phy_device *phydev,
+-				      u8 index, enum led_brightness value)
+-{
+-	u16 reg;
+-	int ret;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	if (!value) {
+-		ret = qca808x_led_hw_control_reset(phydev, index);
+-		if (ret)
+-			return ret;
+-	}
+-
+-	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+-	return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+-			      QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
+-			      QCA808X_LED_FORCE_EN | value ? QCA808X_LED_FORCE_ON :
+-							     QCA808X_LED_FORCE_OFF);
+-}
+-
+-static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
+-				 unsigned long *delay_on,
+-				 unsigned long *delay_off)
+-{
+-	int ret;
+-	u16 reg;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+-	/* Set blink to 50% off, 50% on at 4Hz by default */
+-	ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
+-			     QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
+-			     QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
+-	if (ret)
+-		return ret;
+-
+-	/* We use BLINK_1 for normal blinking */
+-	ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+-			     QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
+-			     QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
+-	if (ret)
+-		return ret;
+-
+-	/* We set blink to 4Hz, aka 250ms */
+-	*delay_on = 250 / 2;
+-	*delay_off = 250 / 2;
+-
+-	return 0;
+-}
+-
+-static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
+-				    unsigned long modes)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-	bool active_low = false;
+-	u32 mode;
+-
+-	for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
+-		switch (mode) {
+-		case PHY_LED_ACTIVE_LOW:
+-			active_low = true;
+-			break;
+-		default:
+-			return -EINVAL;
+-		}
+-	}
+-
+-	/* PHY polarity is global and can't be set per LED.
+-	 * To detect this, check if last requested polarity mode
+-	 * match the new one.
+-	 */
+-	if (priv->led_polarity_mode >= 0 &&
+-	    priv->led_polarity_mode != active_low) {
+-		phydev_err(phydev, "PHY polarity is global. Mismatched polarity on different LED\n");
+-		return -EINVAL;
+-	}
+-
+-	/* Save the last PHY polarity mode */
+-	priv->led_polarity_mode = active_low;
+-
+-	return phy_modify_mmd(phydev, MDIO_MMD_AN,
+-			      QCA808X_MMD7_LED_POLARITY_CTRL,
+-			      QCA808X_LED_ACTIVE_HIGH,
+-			      active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
+-}
+-
+-static struct phy_driver at803x_driver[] = {
+-{
+-	/* Qualcomm Atheros AR8035 */
+-	PHY_ID_MATCH_EXACT(ATH8035_PHY_ID),
+-	.name			= "Qualcomm Atheros AR8035",
+-	.flags			= PHY_POLL_CABLE_TEST,
+-	.probe			= at8035_probe,
+-	.config_aneg		= at803x_config_aneg,
+-	.config_init		= at803x_config_init,
+-	.soft_reset		= genphy_soft_reset,
+-	.set_wol		= at803x_set_wol,
+-	.get_wol		= at803x_get_wol,
+-	.suspend		= at803x_suspend,
+-	.resume			= at803x_resume,
+-	/* PHY_GBIT_FEATURES */
+-	.read_status		= at803x_read_status,
+-	.config_intr		= at803x_config_intr,
+-	.handle_interrupt	= at803x_handle_interrupt,
+-	.get_tunable		= at803x_get_tunable,
+-	.set_tunable		= at803x_set_tunable,
+-	.cable_test_start	= at8031_cable_test_start,
+-	.cable_test_get_status	= at8031_cable_test_get_status,
+-}, {
+-	/* Qualcomm Atheros AR8030 */
+-	.phy_id			= ATH8030_PHY_ID,
+-	.name			= "Qualcomm Atheros AR8030",
+-	.phy_id_mask		= AT8030_PHY_ID_MASK,
+-	.probe			= at8035_probe,
+-	.config_init		= at803x_config_init,
+-	.link_change_notify	= at803x_link_change_notify,
+-	.set_wol		= at803x_set_wol,
+-	.get_wol		= at803x_get_wol,
+-	.suspend		= at803x_suspend,
+-	.resume			= at803x_resume,
+-	/* PHY_BASIC_FEATURES */
+-	.config_intr		= at803x_config_intr,
+-	.handle_interrupt	= at803x_handle_interrupt,
+-}, {
+-	/* Qualcomm Atheros AR8031/AR8033 */
+-	PHY_ID_MATCH_EXACT(ATH8031_PHY_ID),
+-	.name			= "Qualcomm Atheros AR8031/AR8033",
+-	.flags			= PHY_POLL_CABLE_TEST,
+-	.probe			= at8031_probe,
+-	.config_init		= at8031_config_init,
+-	.config_aneg		= at803x_config_aneg,
+-	.soft_reset		= genphy_soft_reset,
+-	.set_wol		= at8031_set_wol,
+-	.get_wol		= at803x_get_wol,
+-	.suspend		= at803x_suspend,
+-	.resume			= at803x_resume,
+-	.read_page		= at803x_read_page,
+-	.write_page		= at803x_write_page,
+-	.get_features		= at803x_get_features,
+-	.read_status		= at8031_read_status,
+-	.config_intr		= at8031_config_intr,
+-	.handle_interrupt	= at803x_handle_interrupt,
+-	.get_tunable		= at803x_get_tunable,
+-	.set_tunable		= at803x_set_tunable,
+-	.cable_test_start	= at8031_cable_test_start,
+-	.cable_test_get_status	= at8031_cable_test_get_status,
+-}, {
+-	/* Qualcomm Atheros AR8032 */
+-	PHY_ID_MATCH_EXACT(ATH8032_PHY_ID),
+-	.name			= "Qualcomm Atheros AR8032",
+-	.probe			= at803x_probe,
+-	.flags			= PHY_POLL_CABLE_TEST,
+-	.config_init		= at803x_config_init,
+-	.link_change_notify	= at803x_link_change_notify,
+-	.suspend		= at803x_suspend,
+-	.resume			= at803x_resume,
+-	/* PHY_BASIC_FEATURES */
+-	.config_intr		= at803x_config_intr,
+-	.handle_interrupt	= at803x_handle_interrupt,
+-	.cable_test_start	= at803x_cable_test_start,
+-	.cable_test_get_status	= at8032_cable_test_get_status,
+-}, {
+-	/* ATHEROS AR9331 */
+-	PHY_ID_MATCH_EXACT(ATH9331_PHY_ID),
+-	.name			= "Qualcomm Atheros AR9331 built-in PHY",
+-	.probe			= at803x_probe,
+-	.suspend		= at803x_suspend,
+-	.resume			= at803x_resume,
+-	.flags			= PHY_POLL_CABLE_TEST,
+-	/* PHY_BASIC_FEATURES */
+-	.config_intr		= at803x_config_intr,
+-	.handle_interrupt	= at803x_handle_interrupt,
+-	.cable_test_start	= at803x_cable_test_start,
+-	.cable_test_get_status	= at8032_cable_test_get_status,
+-	.read_status		= at803x_read_status,
+-	.soft_reset		= genphy_soft_reset,
+-	.config_aneg		= at803x_config_aneg,
+-}, {
+-	/* Qualcomm Atheros QCA9561 */
+-	PHY_ID_MATCH_EXACT(QCA9561_PHY_ID),
+-	.name			= "Qualcomm Atheros QCA9561 built-in PHY",
+-	.probe			= at803x_probe,
+-	.suspend		= at803x_suspend,
+-	.resume			= at803x_resume,
+-	.flags			= PHY_POLL_CABLE_TEST,
+-	/* PHY_BASIC_FEATURES */
+-	.config_intr		= at803x_config_intr,
+-	.handle_interrupt	= at803x_handle_interrupt,
+-	.cable_test_start	= at803x_cable_test_start,
+-	.cable_test_get_status	= at8032_cable_test_get_status,
+-	.read_status		= at803x_read_status,
+-	.soft_reset		= genphy_soft_reset,
+-	.config_aneg		= at803x_config_aneg,
+-}, {
+-	/* QCA8337 */
+-	.phy_id			= QCA8337_PHY_ID,
+-	.phy_id_mask		= QCA8K_PHY_ID_MASK,
+-	.name			= "Qualcomm Atheros 8337 internal PHY",
+-	/* PHY_GBIT_FEATURES */
+-	.probe			= at803x_probe,
+-	.flags			= PHY_IS_INTERNAL,
+-	.config_init		= qca83xx_config_init,
+-	.soft_reset		= genphy_soft_reset,
+-	.get_sset_count		= qca83xx_get_sset_count,
+-	.get_strings		= qca83xx_get_strings,
+-	.get_stats		= qca83xx_get_stats,
+-	.suspend		= qca8337_suspend,
+-	.resume			= qca83xx_resume,
+-}, {
+-	/* QCA8327-A from switch QCA8327-AL1A */
+-	.phy_id			= QCA8327_A_PHY_ID,
+-	.phy_id_mask		= QCA8K_PHY_ID_MASK,
+-	.name			= "Qualcomm Atheros 8327-A internal PHY",
+-	/* PHY_GBIT_FEATURES */
+-	.link_change_notify	= qca83xx_link_change_notify,
+-	.probe			= at803x_probe,
+-	.flags			= PHY_IS_INTERNAL,
+-	.config_init		= qca8327_config_init,
+-	.soft_reset		= genphy_soft_reset,
+-	.get_sset_count		= qca83xx_get_sset_count,
+-	.get_strings		= qca83xx_get_strings,
+-	.get_stats		= qca83xx_get_stats,
+-	.suspend		= qca8327_suspend,
+-	.resume			= qca83xx_resume,
+-}, {
+-	/* QCA8327-B from switch QCA8327-BL1A */
+-	.phy_id			= QCA8327_B_PHY_ID,
+-	.phy_id_mask		= QCA8K_PHY_ID_MASK,
+-	.name			= "Qualcomm Atheros 8327-B internal PHY",
+-	/* PHY_GBIT_FEATURES */
+-	.link_change_notify	= qca83xx_link_change_notify,
+-	.probe			= at803x_probe,
+-	.flags			= PHY_IS_INTERNAL,
+-	.config_init		= qca8327_config_init,
+-	.soft_reset		= genphy_soft_reset,
+-	.get_sset_count		= qca83xx_get_sset_count,
+-	.get_strings		= qca83xx_get_strings,
+-	.get_stats		= qca83xx_get_stats,
+-	.suspend		= qca8327_suspend,
+-	.resume			= qca83xx_resume,
+-}, {
+-	/* Qualcomm QCA8081 */
+-	PHY_ID_MATCH_EXACT(QCA8081_PHY_ID),
+-	.name			= "Qualcomm QCA8081",
+-	.flags			= PHY_POLL_CABLE_TEST,
+-	.probe			= at803x_probe,
+-	.config_intr		= at803x_config_intr,
+-	.handle_interrupt	= at803x_handle_interrupt,
+-	.get_tunable		= at803x_get_tunable,
+-	.set_tunable		= at803x_set_tunable,
+-	.set_wol		= at803x_set_wol,
+-	.get_wol		= at803x_get_wol,
+-	.get_features		= qca808x_get_features,
+-	.config_aneg		= qca808x_config_aneg,
+-	.suspend		= genphy_suspend,
+-	.resume			= genphy_resume,
+-	.read_status		= qca808x_read_status,
+-	.config_init		= qca808x_config_init,
+-	.soft_reset		= qca808x_soft_reset,
+-	.cable_test_start	= qca808x_cable_test_start,
+-	.cable_test_get_status	= qca808x_cable_test_get_status,
+-	.link_change_notify	= qca808x_link_change_notify,
+-	.led_brightness_set	= qca808x_led_brightness_set,
+-	.led_blink_set		= qca808x_led_blink_set,
+-	.led_hw_is_supported	= qca808x_led_hw_is_supported,
+-	.led_hw_control_set	= qca808x_led_hw_control_set,
+-	.led_hw_control_get	= qca808x_led_hw_control_get,
+-	.led_polarity_set	= qca808x_led_polarity_set,
+-}, };
+-
+-module_phy_driver(at803x_driver);
+-
+-static struct mdio_device_id __maybe_unused atheros_tbl[] = {
+-	{ ATH8030_PHY_ID, AT8030_PHY_ID_MASK },
+-	{ PHY_ID_MATCH_EXACT(ATH8031_PHY_ID) },
+-	{ PHY_ID_MATCH_EXACT(ATH8032_PHY_ID) },
+-	{ PHY_ID_MATCH_EXACT(ATH8035_PHY_ID) },
+-	{ PHY_ID_MATCH_EXACT(ATH9331_PHY_ID) },
+-	{ PHY_ID_MATCH_EXACT(QCA8337_PHY_ID) },
+-	{ PHY_ID_MATCH_EXACT(QCA8327_A_PHY_ID) },
+-	{ PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) },
+-	{ PHY_ID_MATCH_EXACT(QCA9561_PHY_ID) },
+-	{ PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) },
+-	{ }
+-};
+-
+-MODULE_DEVICE_TABLE(mdio, atheros_tbl);
+--- /dev/null
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -0,0 +1,2759 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * drivers/net/phy/at803x.c
++ *
++ * Driver for Qualcomm Atheros AR803x PHY
++ *
++ * Author: Matus Ujhelyi <[email protected]>
++ */
++
++#include <linux/phy.h>
++#include <linux/module.h>
++#include <linux/string.h>
++#include <linux/netdevice.h>
++#include <linux/etherdevice.h>
++#include <linux/ethtool_netlink.h>
++#include <linux/bitfield.h>
++#include <linux/regulator/of_regulator.h>
++#include <linux/regulator/driver.h>
++#include <linux/regulator/consumer.h>
++#include <linux/of.h>
++#include <linux/phylink.h>
++#include <linux/sfp.h>
++#include <dt-bindings/net/qca-ar803x.h>
++
++#define AT803X_SPECIFIC_FUNCTION_CONTROL	0x10
++#define AT803X_SFC_ASSERT_CRS			BIT(11)
++#define AT803X_SFC_FORCE_LINK			BIT(10)
++#define AT803X_SFC_MDI_CROSSOVER_MODE_M		GENMASK(6, 5)
++#define AT803X_SFC_AUTOMATIC_CROSSOVER		0x3
++#define AT803X_SFC_MANUAL_MDIX			0x1
++#define AT803X_SFC_MANUAL_MDI			0x0
++#define AT803X_SFC_SQE_TEST			BIT(2)
++#define AT803X_SFC_POLARITY_REVERSAL		BIT(1)
++#define AT803X_SFC_DISABLE_JABBER		BIT(0)
++
++#define AT803X_SPECIFIC_STATUS			0x11
++#define AT803X_SS_SPEED_MASK			GENMASK(15, 14)
++#define AT803X_SS_SPEED_1000			2
++#define AT803X_SS_SPEED_100			1
++#define AT803X_SS_SPEED_10			0
++#define AT803X_SS_DUPLEX			BIT(13)
++#define AT803X_SS_SPEED_DUPLEX_RESOLVED		BIT(11)
++#define AT803X_SS_MDIX				BIT(6)
++
++#define QCA808X_SS_SPEED_MASK			GENMASK(9, 7)
++#define QCA808X_SS_SPEED_2500			4
++
++#define AT803X_INTR_ENABLE			0x12
++#define AT803X_INTR_ENABLE_AUTONEG_ERR		BIT(15)
++#define AT803X_INTR_ENABLE_SPEED_CHANGED	BIT(14)
++#define AT803X_INTR_ENABLE_DUPLEX_CHANGED	BIT(13)
++#define AT803X_INTR_ENABLE_PAGE_RECEIVED	BIT(12)
++#define AT803X_INTR_ENABLE_LINK_FAIL		BIT(11)
++#define AT803X_INTR_ENABLE_LINK_SUCCESS		BIT(10)
++#define AT803X_INTR_ENABLE_LINK_FAIL_BX		BIT(8)
++#define AT803X_INTR_ENABLE_LINK_SUCCESS_BX	BIT(7)
++#define AT803X_INTR_ENABLE_WIRESPEED_DOWNGRADE	BIT(5)
++#define AT803X_INTR_ENABLE_POLARITY_CHANGED	BIT(1)
++#define AT803X_INTR_ENABLE_WOL			BIT(0)
++
++#define AT803X_INTR_STATUS			0x13
++
++#define AT803X_SMART_SPEED			0x14
++#define AT803X_SMART_SPEED_ENABLE		BIT(5)
++#define AT803X_SMART_SPEED_RETRY_LIMIT_MASK	GENMASK(4, 2)
++#define AT803X_SMART_SPEED_BYPASS_TIMER		BIT(1)
++#define AT803X_CDT				0x16
++#define AT803X_CDT_MDI_PAIR_MASK		GENMASK(9, 8)
++#define AT803X_CDT_ENABLE_TEST			BIT(0)
++#define AT803X_CDT_STATUS			0x1c
++#define AT803X_CDT_STATUS_STAT_NORMAL		0
++#define AT803X_CDT_STATUS_STAT_SHORT		1
++#define AT803X_CDT_STATUS_STAT_OPEN		2
++#define AT803X_CDT_STATUS_STAT_FAIL		3
++#define AT803X_CDT_STATUS_STAT_MASK		GENMASK(9, 8)
++#define AT803X_CDT_STATUS_DELTA_TIME_MASK	GENMASK(7, 0)
++#define AT803X_LED_CONTROL			0x18
++
++#define AT803X_PHY_MMD3_WOL_CTRL		0x8012
++#define AT803X_WOL_EN				BIT(5)
++#define AT803X_LOC_MAC_ADDR_0_15_OFFSET		0x804C
++#define AT803X_LOC_MAC_ADDR_16_31_OFFSET	0x804B
++#define AT803X_LOC_MAC_ADDR_32_47_OFFSET	0x804A
++#define AT803X_REG_CHIP_CONFIG			0x1f
++#define AT803X_BT_BX_REG_SEL			0x8000
++
++#define AT803X_DEBUG_ADDR			0x1D
++#define AT803X_DEBUG_DATA			0x1E
++
++#define AT803X_MODE_CFG_MASK			0x0F
++#define AT803X_MODE_CFG_BASET_RGMII		0x00
++#define AT803X_MODE_CFG_BASET_SGMII		0x01
++#define AT803X_MODE_CFG_BX1000_RGMII_50OHM	0x02
++#define AT803X_MODE_CFG_BX1000_RGMII_75OHM	0x03
++#define AT803X_MODE_CFG_BX1000_CONV_50OHM	0x04
++#define AT803X_MODE_CFG_BX1000_CONV_75OHM	0x05
++#define AT803X_MODE_CFG_FX100_RGMII_50OHM	0x06
++#define AT803X_MODE_CFG_FX100_CONV_50OHM	0x07
++#define AT803X_MODE_CFG_RGMII_AUTO_MDET		0x0B
++#define AT803X_MODE_CFG_FX100_RGMII_75OHM	0x0E
++#define AT803X_MODE_CFG_FX100_CONV_75OHM	0x0F
++
++#define AT803X_PSSR				0x11	/*PHY-Specific Status Register*/
++#define AT803X_PSSR_MR_AN_COMPLETE		0x0200
++
++#define AT803X_DEBUG_ANALOG_TEST_CTRL		0x00
++#define QCA8327_DEBUG_MANU_CTRL_EN		BIT(2)
++#define QCA8337_DEBUG_MANU_CTRL_EN		GENMASK(3, 2)
++#define AT803X_DEBUG_RX_CLK_DLY_EN		BIT(15)
++
++#define AT803X_DEBUG_SYSTEM_CTRL_MODE		0x05
++#define AT803X_DEBUG_TX_CLK_DLY_EN		BIT(8)
++
++#define AT803X_DEBUG_REG_HIB_CTRL		0x0b
++#define   AT803X_DEBUG_HIB_CTRL_SEL_RST_80U	BIT(10)
++#define   AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE	BIT(13)
++#define   AT803X_DEBUG_HIB_CTRL_PS_HIB_EN	BIT(15)
++
++#define AT803X_DEBUG_REG_3C			0x3C
++
++#define AT803X_DEBUG_REG_GREEN			0x3D
++#define   AT803X_DEBUG_GATE_CLK_IN1000		BIT(6)
++
++#define AT803X_DEBUG_REG_1F			0x1F
++#define AT803X_DEBUG_PLL_ON			BIT(2)
++#define AT803X_DEBUG_RGMII_1V8			BIT(3)
++
++#define MDIO_AZ_DEBUG				0x800D
++
++/* AT803x supports either the XTAL input pad, an internal PLL or the
++ * DSP as clock reference for the clock output pad. The XTAL reference
++ * is only used for 25 MHz output, all other frequencies need the PLL.
++ * The DSP as a clock reference is used in synchronous ethernet
++ * applications.
++ *
++ * By default the PLL is only enabled if there is a link. Otherwise
++ * the PHY will go into low power state and disabled the PLL. You can
++ * set the PLL_ON bit (see debug register 0x1f) to keep the PLL always
++ * enabled.
++ */
++#define AT803X_MMD7_CLK25M			0x8016
++#define AT803X_CLK_OUT_MASK			GENMASK(4, 2)
++#define AT803X_CLK_OUT_25MHZ_XTAL		0
++#define AT803X_CLK_OUT_25MHZ_DSP		1
++#define AT803X_CLK_OUT_50MHZ_PLL		2
++#define AT803X_CLK_OUT_50MHZ_DSP		3
++#define AT803X_CLK_OUT_62_5MHZ_PLL		4
++#define AT803X_CLK_OUT_62_5MHZ_DSP		5
++#define AT803X_CLK_OUT_125MHZ_PLL		6
++#define AT803X_CLK_OUT_125MHZ_DSP		7
++
++/* The AR8035 has another mask which is compatible with the AR8031/AR8033 mask
++ * but doesn't support choosing between XTAL/PLL and DSP.
++ */
++#define AT8035_CLK_OUT_MASK			GENMASK(4, 3)
++
++#define AT803X_CLK_OUT_STRENGTH_MASK		GENMASK(8, 7)
++#define AT803X_CLK_OUT_STRENGTH_FULL		0
++#define AT803X_CLK_OUT_STRENGTH_HALF		1
++#define AT803X_CLK_OUT_STRENGTH_QUARTER		2
++
++#define AT803X_DEFAULT_DOWNSHIFT		5
++#define AT803X_MIN_DOWNSHIFT			2
++#define AT803X_MAX_DOWNSHIFT			9
++
++#define AT803X_MMD3_SMARTEEE_CTL1		0x805b
++#define AT803X_MMD3_SMARTEEE_CTL2		0x805c
++#define AT803X_MMD3_SMARTEEE_CTL3		0x805d
++#define AT803X_MMD3_SMARTEEE_CTL3_LPI_EN	BIT(8)
++
++#define ATH9331_PHY_ID				0x004dd041
++#define ATH8030_PHY_ID				0x004dd076
++#define ATH8031_PHY_ID				0x004dd074
++#define ATH8032_PHY_ID				0x004dd023
++#define ATH8035_PHY_ID				0x004dd072
++#define AT8030_PHY_ID_MASK			0xffffffef
++
++#define QCA8081_PHY_ID				0x004dd101
++
++#define QCA8327_A_PHY_ID			0x004dd033
++#define QCA8327_B_PHY_ID			0x004dd034
++#define QCA8337_PHY_ID				0x004dd036
++#define QCA9561_PHY_ID				0x004dd042
++#define QCA8K_PHY_ID_MASK			0xffffffff
++
++#define QCA8K_DEVFLAGS_REVISION_MASK		GENMASK(2, 0)
++
++#define AT803X_PAGE_FIBER			0
++#define AT803X_PAGE_COPPER			1
++
++/* don't turn off internal PLL */
++#define AT803X_KEEP_PLL_ENABLED			BIT(0)
++#define AT803X_DISABLE_SMARTEEE			BIT(1)
++
++/* disable hibernation mode */
++#define AT803X_DISABLE_HIBERNATION_MODE		BIT(2)
++
++/* ADC threshold */
++#define QCA808X_PHY_DEBUG_ADC_THRESHOLD		0x2c80
++#define QCA808X_ADC_THRESHOLD_MASK		GENMASK(7, 0)
++#define QCA808X_ADC_THRESHOLD_80MV		0
++#define QCA808X_ADC_THRESHOLD_100MV		0xf0
++#define QCA808X_ADC_THRESHOLD_200MV		0x0f
++#define QCA808X_ADC_THRESHOLD_300MV		0xff
++
++/* CLD control */
++#define QCA808X_PHY_MMD3_ADDR_CLD_CTRL7		0x8007
++#define QCA808X_8023AZ_AFE_CTRL_MASK		GENMASK(8, 4)
++#define QCA808X_8023AZ_AFE_EN			0x90
++
++/* AZ control */
++#define QCA808X_PHY_MMD3_AZ_TRAINING_CTRL	0x8008
++#define QCA808X_MMD3_AZ_TRAINING_VAL		0x1c32
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB	0x8014
++#define QCA808X_MSE_THRESHOLD_20DB_VALUE	0x529
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB	0x800E
++#define QCA808X_MSE_THRESHOLD_17DB_VALUE	0x341
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB	0x801E
++#define QCA808X_MSE_THRESHOLD_27DB_VALUE	0x419
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB	0x8020
++#define QCA808X_MSE_THRESHOLD_28DB_VALUE	0x341
++
++#define QCA808X_PHY_MMD7_TOP_OPTION1		0x901c
++#define QCA808X_TOP_OPTION1_DATA		0x0
++
++#define QCA808X_PHY_MMD3_DEBUG_1		0xa100
++#define QCA808X_MMD3_DEBUG_1_VALUE		0x9203
++#define QCA808X_PHY_MMD3_DEBUG_2		0xa101
++#define QCA808X_MMD3_DEBUG_2_VALUE		0x48ad
++#define QCA808X_PHY_MMD3_DEBUG_3		0xa103
++#define QCA808X_MMD3_DEBUG_3_VALUE		0x1698
++#define QCA808X_PHY_MMD3_DEBUG_4		0xa105
++#define QCA808X_MMD3_DEBUG_4_VALUE		0x8001
++#define QCA808X_PHY_MMD3_DEBUG_5		0xa106
++#define QCA808X_MMD3_DEBUG_5_VALUE		0x1111
++#define QCA808X_PHY_MMD3_DEBUG_6		0xa011
++#define QCA808X_MMD3_DEBUG_6_VALUE		0x5f85
++
++/* master/slave seed config */
++#define QCA808X_PHY_DEBUG_LOCAL_SEED		9
++#define QCA808X_MASTER_SLAVE_SEED_ENABLE	BIT(1)
++#define QCA808X_MASTER_SLAVE_SEED_CFG		GENMASK(12, 2)
++#define QCA808X_MASTER_SLAVE_SEED_RANGE		0x32
++
++/* Hibernation yields lower power consumpiton in contrast with normal operation mode.
++ * when the copper cable is unplugged, the PHY enters into hibernation mode in about 10s.
++ */
++#define QCA808X_DBG_AN_TEST			0xb
++#define QCA808X_HIBERNATION_EN			BIT(15)
++
++#define QCA808X_CDT_ENABLE_TEST			BIT(15)
++#define QCA808X_CDT_INTER_CHECK_DIS		BIT(13)
++#define QCA808X_CDT_STATUS			BIT(11)
++#define QCA808X_CDT_LENGTH_UNIT			BIT(10)
++
++#define QCA808X_MMD3_CDT_STATUS			0x8064
++#define QCA808X_MMD3_CDT_DIAG_PAIR_A		0x8065
++#define QCA808X_MMD3_CDT_DIAG_PAIR_B		0x8066
++#define QCA808X_MMD3_CDT_DIAG_PAIR_C		0x8067
++#define QCA808X_MMD3_CDT_DIAG_PAIR_D		0x8068
++#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT	GENMASK(15, 8)
++#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT	GENMASK(7, 0)
++
++#define QCA808X_CDT_CODE_PAIR_A			GENMASK(15, 12)
++#define QCA808X_CDT_CODE_PAIR_B			GENMASK(11, 8)
++#define QCA808X_CDT_CODE_PAIR_C			GENMASK(7, 4)
++#define QCA808X_CDT_CODE_PAIR_D			GENMASK(3, 0)
++
++#define QCA808X_CDT_STATUS_STAT_TYPE		GENMASK(1, 0)
++#define QCA808X_CDT_STATUS_STAT_FAIL		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
++#define QCA808X_CDT_STATUS_STAT_NORMAL		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
++#define QCA808X_CDT_STATUS_STAT_SAME_OPEN	FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
++#define QCA808X_CDT_STATUS_STAT_SAME_SHORT	FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
++
++#define QCA808X_CDT_STATUS_STAT_MDI		GENMASK(3, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI1		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
++#define QCA808X_CDT_STATUS_STAT_MDI2		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI3		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
++
++/* NORMAL are MDI with type set to 0 */
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI1
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++									 QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++									 QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI2
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++									 QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++									 QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI3
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++									 QCA808X_CDT_STATUS_STAT_MDI3)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++									 QCA808X_CDT_STATUS_STAT_MDI3)
++
++/* Added for reference of existence but should be handled by wait_for_completion already */
++#define QCA808X_CDT_STATUS_STAT_BUSY		(BIT(1) | BIT(3))
++
++#define QCA808X_MMD7_LED_GLOBAL			0x8073
++#define QCA808X_LED_BLINK_1			GENMASK(11, 6)
++#define QCA808X_LED_BLINK_2			GENMASK(5, 0)
++/* Values are the same for both BLINK_1 and BLINK_2 */
++#define QCA808X_LED_BLINK_FREQ_MASK		GENMASK(5, 3)
++#define QCA808X_LED_BLINK_FREQ_2HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
++#define QCA808X_LED_BLINK_FREQ_4HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
++#define QCA808X_LED_BLINK_FREQ_8HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
++#define QCA808X_LED_BLINK_FREQ_16HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
++#define QCA808X_LED_BLINK_FREQ_32HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
++#define QCA808X_LED_BLINK_FREQ_64HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
++#define QCA808X_LED_BLINK_FREQ_128HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
++#define QCA808X_LED_BLINK_FREQ_256HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
++#define QCA808X_LED_BLINK_DUTY_MASK		GENMASK(2, 0)
++#define QCA808X_LED_BLINK_DUTY_50_50		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
++#define QCA808X_LED_BLINK_DUTY_75_25		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
++#define QCA808X_LED_BLINK_DUTY_25_75		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
++#define QCA808X_LED_BLINK_DUTY_33_67		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
++#define QCA808X_LED_BLINK_DUTY_67_33		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
++#define QCA808X_LED_BLINK_DUTY_17_83		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
++#define QCA808X_LED_BLINK_DUTY_83_17		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
++#define QCA808X_LED_BLINK_DUTY_8_92		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
++
++#define QCA808X_MMD7_LED2_CTRL			0x8074
++#define QCA808X_MMD7_LED2_FORCE_CTRL		0x8075
++#define QCA808X_MMD7_LED1_CTRL			0x8076
++#define QCA808X_MMD7_LED1_FORCE_CTRL		0x8077
++#define QCA808X_MMD7_LED0_CTRL			0x8078
++#define QCA808X_MMD7_LED_CTRL(x)		(0x8078 - ((x) * 2))
++
++/* LED hw control pattern is the same for every LED */
++#define QCA808X_LED_PATTERN_MASK		GENMASK(15, 0)
++#define QCA808X_LED_SPEED2500_ON		BIT(15)
++#define QCA808X_LED_SPEED2500_BLINK		BIT(14)
++/* Follow blink trigger even if duplex or speed condition doesn't match */
++#define QCA808X_LED_BLINK_CHECK_BYPASS		BIT(13)
++#define QCA808X_LED_FULL_DUPLEX_ON		BIT(12)
++#define QCA808X_LED_HALF_DUPLEX_ON		BIT(11)
++#define QCA808X_LED_TX_BLINK			BIT(10)
++#define QCA808X_LED_RX_BLINK			BIT(9)
++#define QCA808X_LED_TX_ON_10MS			BIT(8)
++#define QCA808X_LED_RX_ON_10MS			BIT(7)
++#define QCA808X_LED_SPEED1000_ON		BIT(6)
++#define QCA808X_LED_SPEED100_ON			BIT(5)
++#define QCA808X_LED_SPEED10_ON			BIT(4)
++#define QCA808X_LED_COLLISION_BLINK		BIT(3)
++#define QCA808X_LED_SPEED1000_BLINK		BIT(2)
++#define QCA808X_LED_SPEED100_BLINK		BIT(1)
++#define QCA808X_LED_SPEED10_BLINK		BIT(0)
++
++#define QCA808X_MMD7_LED0_FORCE_CTRL		0x8079
++#define QCA808X_MMD7_LED_FORCE_CTRL(x)		(0x8079 - ((x) * 2))
++
++/* LED force ctrl is the same for every LED
++ * No documentation exist for this, not even internal one
++ * with NDA as QCOM gives only info about configuring
++ * hw control pattern rules and doesn't indicate any way
++ * to force the LED to specific mode.
++ * These define comes from reverse and testing and maybe
++ * lack of some info or some info are not entirely correct.
++ * For the basic LED control and hw control these finding
++ * are enough to support LED control in all the required APIs.
++ *
++ * On doing some comparison with implementation with qca807x,
++ * it was found that it's 1:1 equal to it and confirms all the
++ * reverse done. It was also found further specification with the
++ * force mode and the blink modes.
++ */
++#define QCA808X_LED_FORCE_EN			BIT(15)
++#define QCA808X_LED_FORCE_MODE_MASK		GENMASK(14, 13)
++#define QCA808X_LED_FORCE_BLINK_1		FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
++#define QCA808X_LED_FORCE_BLINK_2		FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
++#define QCA808X_LED_FORCE_ON			FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
++#define QCA808X_LED_FORCE_OFF			FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
++
++#define QCA808X_MMD7_LED_POLARITY_CTRL		0x901a
++/* QSDK sets by default 0x46 to this reg that sets BIT 6 for
++ * LED to active high. It's not clear what BIT 3 and BIT 4 does.
++ */
++#define QCA808X_LED_ACTIVE_HIGH			BIT(6)
++
++/* QCA808X 1G chip type */
++#define QCA808X_PHY_MMD7_CHIP_TYPE		0x901d
++#define QCA808X_PHY_CHIP_TYPE_1G		BIT(0)
++
++#define QCA8081_PHY_SERDES_MMD1_FIFO_CTRL	0x9072
++#define QCA8081_PHY_FIFO_RSTN			BIT(11)
++
++MODULE_DESCRIPTION("Qualcomm Atheros AR803x and QCA808X PHY driver");
++MODULE_AUTHOR("Matus Ujhelyi");
++MODULE_LICENSE("GPL");
++
++enum stat_access_type {
++	PHY,
++	MMD
++};
++
++struct at803x_hw_stat {
++	const char *string;
++	u8 reg;
++	u32 mask;
++	enum stat_access_type access_type;
++};
++
++static struct at803x_hw_stat qca83xx_hw_stats[] = {
++	{ "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
++	{ "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
++	{ "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
++};
++
++struct at803x_ss_mask {
++	u16 speed_mask;
++	u8 speed_shift;
++};
++
++struct at803x_priv {
++	int flags;
++	u16 clk_25m_reg;
++	u16 clk_25m_mask;
++	u8 smarteee_lpi_tw_1g;
++	u8 smarteee_lpi_tw_100m;
++	bool is_fiber;
++	bool is_1000basex;
++	struct regulator_dev *vddio_rdev;
++	struct regulator_dev *vddh_rdev;
++	u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
++	int led_polarity_mode;
++};
++
++struct at803x_context {
++	u16 bmcr;
++	u16 advertise;
++	u16 control1000;
++	u16 int_enable;
++	u16 smart_speed;
++	u16 led_control;
++};
++
++static int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data)
++{
++	int ret;
++
++	ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
++	if (ret < 0)
++		return ret;
++
++	return phy_write(phydev, AT803X_DEBUG_DATA, data);
++}
++
++static int at803x_debug_reg_read(struct phy_device *phydev, u16 reg)
++{
++	int ret;
++
++	ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
++	if (ret < 0)
++		return ret;
++
++	return phy_read(phydev, AT803X_DEBUG_DATA);
++}
++
++static int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,
++				 u16 clear, u16 set)
++{
++	u16 val;
++	int ret;
++
++	ret = at803x_debug_reg_read(phydev, reg);
++	if (ret < 0)
++		return ret;
++
++	val = ret & 0xffff;
++	val &= ~clear;
++	val |= set;
++
++	return phy_write(phydev, AT803X_DEBUG_DATA, val);
++}
++
++static int at803x_write_page(struct phy_device *phydev, int page)
++{
++	int mask;
++	int set;
++
++	if (page == AT803X_PAGE_COPPER) {
++		set = AT803X_BT_BX_REG_SEL;
++		mask = 0;
++	} else {
++		set = 0;
++		mask = AT803X_BT_BX_REG_SEL;
++	}
++
++	return __phy_modify(phydev, AT803X_REG_CHIP_CONFIG, mask, set);
++}
++
++static int at803x_read_page(struct phy_device *phydev)
++{
++	int ccr = __phy_read(phydev, AT803X_REG_CHIP_CONFIG);
++
++	if (ccr < 0)
++		return ccr;
++
++	if (ccr & AT803X_BT_BX_REG_SEL)
++		return AT803X_PAGE_COPPER;
++
++	return AT803X_PAGE_FIBER;
++}
++
++static int at803x_enable_rx_delay(struct phy_device *phydev)
++{
++	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0,
++				     AT803X_DEBUG_RX_CLK_DLY_EN);
++}
++
++static int at803x_enable_tx_delay(struct phy_device *phydev)
++{
++	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0,
++				     AT803X_DEBUG_TX_CLK_DLY_EN);
++}
++
++static int at803x_disable_rx_delay(struct phy_device *phydev)
++{
++	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++				     AT803X_DEBUG_RX_CLK_DLY_EN, 0);
++}
++
++static int at803x_disable_tx_delay(struct phy_device *phydev)
++{
++	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE,
++				     AT803X_DEBUG_TX_CLK_DLY_EN, 0);
++}
++
++/* save relevant PHY registers to private copy */
++static void at803x_context_save(struct phy_device *phydev,
++				struct at803x_context *context)
++{
++	context->bmcr = phy_read(phydev, MII_BMCR);
++	context->advertise = phy_read(phydev, MII_ADVERTISE);
++	context->control1000 = phy_read(phydev, MII_CTRL1000);
++	context->int_enable = phy_read(phydev, AT803X_INTR_ENABLE);
++	context->smart_speed = phy_read(phydev, AT803X_SMART_SPEED);
++	context->led_control = phy_read(phydev, AT803X_LED_CONTROL);
++}
++
++/* restore relevant PHY registers from private copy */
++static void at803x_context_restore(struct phy_device *phydev,
++				   const struct at803x_context *context)
++{
++	phy_write(phydev, MII_BMCR, context->bmcr);
++	phy_write(phydev, MII_ADVERTISE, context->advertise);
++	phy_write(phydev, MII_CTRL1000, context->control1000);
++	phy_write(phydev, AT803X_INTR_ENABLE, context->int_enable);
++	phy_write(phydev, AT803X_SMART_SPEED, context->smart_speed);
++	phy_write(phydev, AT803X_LED_CONTROL, context->led_control);
++}
++
++static int at803x_set_wol(struct phy_device *phydev,
++			  struct ethtool_wolinfo *wol)
++{
++	int ret, irq_enabled;
++
++	if (wol->wolopts & WAKE_MAGIC) {
++		struct net_device *ndev = phydev->attached_dev;
++		const u8 *mac;
++		unsigned int i;
++		static const unsigned int offsets[] = {
++			AT803X_LOC_MAC_ADDR_32_47_OFFSET,
++			AT803X_LOC_MAC_ADDR_16_31_OFFSET,
++			AT803X_LOC_MAC_ADDR_0_15_OFFSET,
++		};
++
++		if (!ndev)
++			return -ENODEV;
++
++		mac = (const u8 *)ndev->dev_addr;
++
++		if (!is_valid_ether_addr(mac))
++			return -EINVAL;
++
++		for (i = 0; i < 3; i++)
++			phy_write_mmd(phydev, MDIO_MMD_PCS, offsets[i],
++				      mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
++
++		/* Enable WOL interrupt */
++		ret = phy_modify(phydev, AT803X_INTR_ENABLE, 0, AT803X_INTR_ENABLE_WOL);
++		if (ret)
++			return ret;
++	} else {
++		/* Disable WOL interrupt */
++		ret = phy_modify(phydev, AT803X_INTR_ENABLE, AT803X_INTR_ENABLE_WOL, 0);
++		if (ret)
++			return ret;
++	}
++
++	/* Clear WOL status */
++	ret = phy_read(phydev, AT803X_INTR_STATUS);
++	if (ret < 0)
++		return ret;
++
++	/* Check if there are other interrupts except for WOL triggered when PHY is
++	 * in interrupt mode, only the interrupts enabled by AT803X_INTR_ENABLE can
++	 * be passed up to the interrupt PIN.
++	 */
++	irq_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
++	if (irq_enabled < 0)
++		return irq_enabled;
++
++	irq_enabled &= ~AT803X_INTR_ENABLE_WOL;
++	if (ret & irq_enabled && !phy_polling_mode(phydev))
++		phy_trigger_machine(phydev);
++
++	return 0;
++}
++
++static void at803x_get_wol(struct phy_device *phydev,
++			   struct ethtool_wolinfo *wol)
++{
++	int value;
++
++	wol->supported = WAKE_MAGIC;
++	wol->wolopts = 0;
++
++	value = phy_read(phydev, AT803X_INTR_ENABLE);
++	if (value < 0)
++		return;
++
++	if (value & AT803X_INTR_ENABLE_WOL)
++		wol->wolopts |= WAKE_MAGIC;
++}
++
++static int qca83xx_get_sset_count(struct phy_device *phydev)
++{
++	return ARRAY_SIZE(qca83xx_hw_stats);
++}
++
++static void qca83xx_get_strings(struct phy_device *phydev, u8 *data)
++{
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) {
++		strscpy(data + i * ETH_GSTRING_LEN,
++			qca83xx_hw_stats[i].string, ETH_GSTRING_LEN);
++	}
++}
++
++static u64 qca83xx_get_stat(struct phy_device *phydev, int i)
++{
++	struct at803x_hw_stat stat = qca83xx_hw_stats[i];
++	struct at803x_priv *priv = phydev->priv;
++	int val;
++	u64 ret;
++
++	if (stat.access_type == MMD)
++		val = phy_read_mmd(phydev, MDIO_MMD_PCS, stat.reg);
++	else
++		val = phy_read(phydev, stat.reg);
++
++	if (val < 0) {
++		ret = U64_MAX;
++	} else {
++		val = val & stat.mask;
++		priv->stats[i] += val;
++		ret = priv->stats[i];
++	}
++
++	return ret;
++}
++
++static void qca83xx_get_stats(struct phy_device *phydev,
++			      struct ethtool_stats *stats, u64 *data)
++{
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++)
++		data[i] = qca83xx_get_stat(phydev, i);
++}
++
++static int at803x_suspend(struct phy_device *phydev)
++{
++	int value;
++	int wol_enabled;
++
++	value = phy_read(phydev, AT803X_INTR_ENABLE);
++	wol_enabled = value & AT803X_INTR_ENABLE_WOL;
++
++	if (wol_enabled)
++		value = BMCR_ISOLATE;
++	else
++		value = BMCR_PDOWN;
++
++	phy_modify(phydev, MII_BMCR, 0, value);
++
++	return 0;
++}
++
++static int at803x_resume(struct phy_device *phydev)
++{
++	return phy_modify(phydev, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, 0);
++}
++
++static int at803x_parse_dt(struct phy_device *phydev)
++{
++	struct device_node *node = phydev->mdio.dev.of_node;
++	struct at803x_priv *priv = phydev->priv;
++	u32 freq, strength, tw;
++	unsigned int sel;
++	int ret;
++
++	if (!IS_ENABLED(CONFIG_OF_MDIO))
++		return 0;
++
++	if (of_property_read_bool(node, "qca,disable-smarteee"))
++		priv->flags |= AT803X_DISABLE_SMARTEEE;
++
++	if (of_property_read_bool(node, "qca,disable-hibernation-mode"))
++		priv->flags |= AT803X_DISABLE_HIBERNATION_MODE;
++
++	if (!of_property_read_u32(node, "qca,smarteee-tw-us-1g", &tw)) {
++		if (!tw || tw > 255) {
++			phydev_err(phydev, "invalid qca,smarteee-tw-us-1g\n");
++			return -EINVAL;
++		}
++		priv->smarteee_lpi_tw_1g = tw;
++	}
++
++	if (!of_property_read_u32(node, "qca,smarteee-tw-us-100m", &tw)) {
++		if (!tw || tw > 255) {
++			phydev_err(phydev, "invalid qca,smarteee-tw-us-100m\n");
++			return -EINVAL;
++		}
++		priv->smarteee_lpi_tw_100m = tw;
++	}
++
++	ret = of_property_read_u32(node, "qca,clk-out-frequency", &freq);
++	if (!ret) {
++		switch (freq) {
++		case 25000000:
++			sel = AT803X_CLK_OUT_25MHZ_XTAL;
++			break;
++		case 50000000:
++			sel = AT803X_CLK_OUT_50MHZ_PLL;
++			break;
++		case 62500000:
++			sel = AT803X_CLK_OUT_62_5MHZ_PLL;
++			break;
++		case 125000000:
++			sel = AT803X_CLK_OUT_125MHZ_PLL;
++			break;
++		default:
++			phydev_err(phydev, "invalid qca,clk-out-frequency\n");
++			return -EINVAL;
++		}
++
++		priv->clk_25m_reg |= FIELD_PREP(AT803X_CLK_OUT_MASK, sel);
++		priv->clk_25m_mask |= AT803X_CLK_OUT_MASK;
++	}
++
++	ret = of_property_read_u32(node, "qca,clk-out-strength", &strength);
++	if (!ret) {
++		priv->clk_25m_mask |= AT803X_CLK_OUT_STRENGTH_MASK;
++		switch (strength) {
++		case AR803X_STRENGTH_FULL:
++			priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_FULL;
++			break;
++		case AR803X_STRENGTH_HALF:
++			priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_HALF;
++			break;
++		case AR803X_STRENGTH_QUARTER:
++			priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_QUARTER;
++			break;
++		default:
++			phydev_err(phydev, "invalid qca,clk-out-strength\n");
++			return -EINVAL;
++		}
++	}
++
++	return 0;
++}
++
++static int at803x_probe(struct phy_device *phydev)
++{
++	struct device *dev = &phydev->mdio.dev;
++	struct at803x_priv *priv;
++	int ret;
++
++	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++	if (!priv)
++		return -ENOMEM;
++
++	/* Init LED polarity mode to -1 */
++	priv->led_polarity_mode = -1;
++
++	phydev->priv = priv;
++
++	ret = at803x_parse_dt(phydev);
++	if (ret)
++		return ret;
++
++	return 0;
++}
++
++static int at803x_get_features(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++	int err;
++
++	err = genphy_read_abilities(phydev);
++	if (err)
++		return err;
++
++	if (phydev->drv->phy_id != ATH8031_PHY_ID)
++		return 0;
++
++	/* AR8031/AR8033 have different status registers
++	 * for copper and fiber operation. However, the
++	 * extended status register is the same for both
++	 * operation modes.
++	 *
++	 * As a result of that, ESTATUS_1000_XFULL is set
++	 * to 1 even when operating in copper TP mode.
++	 *
++	 * Remove this mode from the supported link modes
++	 * when not operating in 1000BaseX mode.
++	 */
++	if (!priv->is_1000basex)
++		linkmode_clear_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
++				   phydev->supported);
++
++	return 0;
++}
++
++static int at803x_smarteee_config(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++	u16 mask = 0, val = 0;
++	int ret;
++
++	if (priv->flags & AT803X_DISABLE_SMARTEEE)
++		return phy_modify_mmd(phydev, MDIO_MMD_PCS,
++				      AT803X_MMD3_SMARTEEE_CTL3,
++				      AT803X_MMD3_SMARTEEE_CTL3_LPI_EN, 0);
++
++	if (priv->smarteee_lpi_tw_1g) {
++		mask |= 0xff00;
++		val |= priv->smarteee_lpi_tw_1g << 8;
++	}
++	if (priv->smarteee_lpi_tw_100m) {
++		mask |= 0x00ff;
++		val |= priv->smarteee_lpi_tw_100m;
++	}
++	if (!mask)
++		return 0;
++
++	ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, AT803X_MMD3_SMARTEEE_CTL1,
++			     mask, val);
++	if (ret)
++		return ret;
++
++	return phy_modify_mmd(phydev, MDIO_MMD_PCS, AT803X_MMD3_SMARTEEE_CTL3,
++			      AT803X_MMD3_SMARTEEE_CTL3_LPI_EN,
++			      AT803X_MMD3_SMARTEEE_CTL3_LPI_EN);
++}
++
++static int at803x_clk_out_config(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++
++	if (!priv->clk_25m_mask)
++		return 0;
++
++	return phy_modify_mmd(phydev, MDIO_MMD_AN, AT803X_MMD7_CLK25M,
++			      priv->clk_25m_mask, priv->clk_25m_reg);
++}
++
++static int at8031_pll_config(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++
++	/* The default after hardware reset is PLL OFF. After a soft reset, the
++	 * values are retained.
++	 */
++	if (priv->flags & AT803X_KEEP_PLL_ENABLED)
++		return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
++					     0, AT803X_DEBUG_PLL_ON);
++	else
++		return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
++					     AT803X_DEBUG_PLL_ON, 0);
++}
++
++static int at803x_hibernation_mode_config(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++
++	/* The default after hardware reset is hibernation mode enabled. After
++	 * software reset, the value is retained.
++	 */
++	if (!(priv->flags & AT803X_DISABLE_HIBERNATION_MODE))
++		return 0;
++
++	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
++					 AT803X_DEBUG_HIB_CTRL_PS_HIB_EN, 0);
++}
++
++static int at803x_config_init(struct phy_device *phydev)
++{
++	int ret;
++
++	/* The RX and TX delay default is:
++	 *   after HW reset: RX delay enabled and TX delay disabled
++	 *   after SW reset: RX delay enabled, while TX delay retains the
++	 *   value before reset.
++	 */
++	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
++	    phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
++		ret = at803x_enable_rx_delay(phydev);
++	else
++		ret = at803x_disable_rx_delay(phydev);
++	if (ret < 0)
++		return ret;
++
++	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
++	    phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
++		ret = at803x_enable_tx_delay(phydev);
++	else
++		ret = at803x_disable_tx_delay(phydev);
++	if (ret < 0)
++		return ret;
++
++	ret = at803x_smarteee_config(phydev);
++	if (ret < 0)
++		return ret;
++
++	ret = at803x_clk_out_config(phydev);
++	if (ret < 0)
++		return ret;
++
++	ret = at803x_hibernation_mode_config(phydev);
++	if (ret < 0)
++		return ret;
++
++	/* Ar803x extended next page bit is enabled by default. Cisco
++	 * multigig switches read this bit and attempt to negotiate 10Gbps
++	 * rates even if the next page bit is disabled. This is incorrect
++	 * behaviour but we still need to accommodate it. XNP is only needed
++	 * for 10Gbps support, so disable XNP.
++	 */
++	return phy_modify(phydev, MII_ADVERTISE, MDIO_AN_CTRL1_XNP, 0);
++}
++
++static int at803x_ack_interrupt(struct phy_device *phydev)
++{
++	int err;
++
++	err = phy_read(phydev, AT803X_INTR_STATUS);
++
++	return (err < 0) ? err : 0;
++}
++
++static int at803x_config_intr(struct phy_device *phydev)
++{
++	int err;
++	int value;
++
++	value = phy_read(phydev, AT803X_INTR_ENABLE);
++
++	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
++		/* Clear any pending interrupts */
++		err = at803x_ack_interrupt(phydev);
++		if (err)
++			return err;
++
++		value |= AT803X_INTR_ENABLE_AUTONEG_ERR;
++		value |= AT803X_INTR_ENABLE_SPEED_CHANGED;
++		value |= AT803X_INTR_ENABLE_DUPLEX_CHANGED;
++		value |= AT803X_INTR_ENABLE_LINK_FAIL;
++		value |= AT803X_INTR_ENABLE_LINK_SUCCESS;
++
++		err = phy_write(phydev, AT803X_INTR_ENABLE, value);
++	} else {
++		err = phy_write(phydev, AT803X_INTR_ENABLE, 0);
++		if (err)
++			return err;
++
++		/* Clear any pending interrupts */
++		err = at803x_ack_interrupt(phydev);
++	}
++
++	return err;
++}
++
++static irqreturn_t at803x_handle_interrupt(struct phy_device *phydev)
++{
++	int irq_status, int_enabled;
++
++	irq_status = phy_read(phydev, AT803X_INTR_STATUS);
++	if (irq_status < 0) {
++		phy_error(phydev);
++		return IRQ_NONE;
++	}
++
++	/* Read the current enabled interrupts */
++	int_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
++	if (int_enabled < 0) {
++		phy_error(phydev);
++		return IRQ_NONE;
++	}
++
++	/* See if this was one of our enabled interrupts */
++	if (!(irq_status & int_enabled))
++		return IRQ_NONE;
++
++	phy_trigger_machine(phydev);
++
++	return IRQ_HANDLED;
++}
++
++static void at803x_link_change_notify(struct phy_device *phydev)
++{
++	/*
++	 * Conduct a hardware reset for AT8030 every time a link loss is
++	 * signalled. This is necessary to circumvent a hardware bug that
++	 * occurs when the cable is unplugged while TX packets are pending
++	 * in the FIFO. In such cases, the FIFO enters an error mode it
++	 * cannot recover from by software.
++	 */
++	if (phydev->state == PHY_NOLINK && phydev->mdio.reset_gpio) {
++		struct at803x_context context;
++
++		at803x_context_save(phydev, &context);
++
++		phy_device_reset(phydev, 1);
++		usleep_range(1000, 2000);
++		phy_device_reset(phydev, 0);
++		usleep_range(1000, 2000);
++
++		at803x_context_restore(phydev, &context);
++
++		phydev_dbg(phydev, "%s(): phy was reset\n", __func__);
++	}
++}
++
++static int at803x_read_specific_status(struct phy_device *phydev,
++				       struct at803x_ss_mask ss_mask)
++{
++	int ss;
++
++	/* Read the AT8035 PHY-Specific Status register, which indicates the
++	 * speed and duplex that the PHY is actually using, irrespective of
++	 * whether we are in autoneg mode or not.
++	 */
++	ss = phy_read(phydev, AT803X_SPECIFIC_STATUS);
++	if (ss < 0)
++		return ss;
++
++	if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) {
++		int sfc, speed;
++
++		sfc = phy_read(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL);
++		if (sfc < 0)
++			return sfc;
++
++		speed = ss & ss_mask.speed_mask;
++		speed >>= ss_mask.speed_shift;
++
++		switch (speed) {
++		case AT803X_SS_SPEED_10:
++			phydev->speed = SPEED_10;
++			break;
++		case AT803X_SS_SPEED_100:
++			phydev->speed = SPEED_100;
++			break;
++		case AT803X_SS_SPEED_1000:
++			phydev->speed = SPEED_1000;
++			break;
++		case QCA808X_SS_SPEED_2500:
++			phydev->speed = SPEED_2500;
++			break;
++		}
++		if (ss & AT803X_SS_DUPLEX)
++			phydev->duplex = DUPLEX_FULL;
++		else
++			phydev->duplex = DUPLEX_HALF;
++
++		if (ss & AT803X_SS_MDIX)
++			phydev->mdix = ETH_TP_MDI_X;
++		else
++			phydev->mdix = ETH_TP_MDI;
++
++		switch (FIELD_GET(AT803X_SFC_MDI_CROSSOVER_MODE_M, sfc)) {
++		case AT803X_SFC_MANUAL_MDI:
++			phydev->mdix_ctrl = ETH_TP_MDI;
++			break;
++		case AT803X_SFC_MANUAL_MDIX:
++			phydev->mdix_ctrl = ETH_TP_MDI_X;
++			break;
++		case AT803X_SFC_AUTOMATIC_CROSSOVER:
++			phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
++			break;
++		}
++	}
++
++	return 0;
++}
++
++static int at803x_read_status(struct phy_device *phydev)
++{
++	struct at803x_ss_mask ss_mask = { 0 };
++	int err, old_link = phydev->link;
++
++	/* Update the link, but return if there was an error */
++	err = genphy_update_link(phydev);
++	if (err)
++		return err;
++
++	/* why bother the PHY if nothing can have changed */
++	if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
++		return 0;
++
++	phydev->speed = SPEED_UNKNOWN;
++	phydev->duplex = DUPLEX_UNKNOWN;
++	phydev->pause = 0;
++	phydev->asym_pause = 0;
++
++	err = genphy_read_lpa(phydev);
++	if (err < 0)
++		return err;
++
++	ss_mask.speed_mask = AT803X_SS_SPEED_MASK;
++	ss_mask.speed_shift = __bf_shf(AT803X_SS_SPEED_MASK);
++	err = at803x_read_specific_status(phydev, ss_mask);
++	if (err < 0)
++		return err;
++
++	if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)
++		phy_resolve_aneg_pause(phydev);
++
++	return 0;
++}
++
++static int at803x_config_mdix(struct phy_device *phydev, u8 ctrl)
++{
++	u16 val;
++
++	switch (ctrl) {
++	case ETH_TP_MDI:
++		val = AT803X_SFC_MANUAL_MDI;
++		break;
++	case ETH_TP_MDI_X:
++		val = AT803X_SFC_MANUAL_MDIX;
++		break;
++	case ETH_TP_MDI_AUTO:
++		val = AT803X_SFC_AUTOMATIC_CROSSOVER;
++		break;
++	default:
++		return 0;
++	}
++
++	return phy_modify_changed(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL,
++			  AT803X_SFC_MDI_CROSSOVER_MODE_M,
++			  FIELD_PREP(AT803X_SFC_MDI_CROSSOVER_MODE_M, val));
++}
++
++static int at803x_prepare_config_aneg(struct phy_device *phydev)
++{
++	int ret;
++
++	ret = at803x_config_mdix(phydev, phydev->mdix_ctrl);
++	if (ret < 0)
++		return ret;
++
++	/* Changes of the midx bits are disruptive to the normal operation;
++	 * therefore any changes to these registers must be followed by a
++	 * software reset to take effect.
++	 */
++	if (ret == 1) {
++		ret = genphy_soft_reset(phydev);
++		if (ret < 0)
++			return ret;
++	}
++
++	return 0;
++}
++
++static int at803x_config_aneg(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++	int ret;
++
++	ret = at803x_prepare_config_aneg(phydev);
++	if (ret)
++		return ret;
++
++	if (priv->is_1000basex)
++		return genphy_c37_config_aneg(phydev);
++
++	return genphy_config_aneg(phydev);
++}
++
++static int at803x_get_downshift(struct phy_device *phydev, u8 *d)
++{
++	int val;
++
++	val = phy_read(phydev, AT803X_SMART_SPEED);
++	if (val < 0)
++		return val;
++
++	if (val & AT803X_SMART_SPEED_ENABLE)
++		*d = FIELD_GET(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, val) + 2;
++	else
++		*d = DOWNSHIFT_DEV_DISABLE;
++
++	return 0;
++}
++
++static int at803x_set_downshift(struct phy_device *phydev, u8 cnt)
++{
++	u16 mask, set;
++	int ret;
++
++	switch (cnt) {
++	case DOWNSHIFT_DEV_DEFAULT_COUNT:
++		cnt = AT803X_DEFAULT_DOWNSHIFT;
++		fallthrough;
++	case AT803X_MIN_DOWNSHIFT ... AT803X_MAX_DOWNSHIFT:
++		set = AT803X_SMART_SPEED_ENABLE |
++		      AT803X_SMART_SPEED_BYPASS_TIMER |
++		      FIELD_PREP(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, cnt - 2);
++		mask = AT803X_SMART_SPEED_RETRY_LIMIT_MASK;
++		break;
++	case DOWNSHIFT_DEV_DISABLE:
++		set = 0;
++		mask = AT803X_SMART_SPEED_ENABLE |
++		       AT803X_SMART_SPEED_BYPASS_TIMER;
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	ret = phy_modify_changed(phydev, AT803X_SMART_SPEED, mask, set);
++
++	/* After changing the smart speed settings, we need to perform a
++	 * software reset, use phy_init_hw() to make sure we set the
++	 * reapply any values which might got lost during software reset.
++	 */
++	if (ret == 1)
++		ret = phy_init_hw(phydev);
++
++	return ret;
++}
++
++static int at803x_get_tunable(struct phy_device *phydev,
++			      struct ethtool_tunable *tuna, void *data)
++{
++	switch (tuna->id) {
++	case ETHTOOL_PHY_DOWNSHIFT:
++		return at803x_get_downshift(phydev, data);
++	default:
++		return -EOPNOTSUPP;
++	}
++}
++
++static int at803x_set_tunable(struct phy_device *phydev,
++			      struct ethtool_tunable *tuna, const void *data)
++{
++	switch (tuna->id) {
++	case ETHTOOL_PHY_DOWNSHIFT:
++		return at803x_set_downshift(phydev, *(const u8 *)data);
++	default:
++		return -EOPNOTSUPP;
++	}
++}
++
++static int at803x_cable_test_result_trans(u16 status)
++{
++	switch (FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status)) {
++	case AT803X_CDT_STATUS_STAT_NORMAL:
++		return ETHTOOL_A_CABLE_RESULT_CODE_OK;
++	case AT803X_CDT_STATUS_STAT_SHORT:
++		return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
++	case AT803X_CDT_STATUS_STAT_OPEN:
++		return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
++	case AT803X_CDT_STATUS_STAT_FAIL:
++	default:
++		return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
++	}
++}
++
++static bool at803x_cdt_test_failed(u16 status)
++{
++	return FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status) ==
++		AT803X_CDT_STATUS_STAT_FAIL;
++}
++
++static bool at803x_cdt_fault_length_valid(u16 status)
++{
++	switch (FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status)) {
++	case AT803X_CDT_STATUS_STAT_OPEN:
++	case AT803X_CDT_STATUS_STAT_SHORT:
++		return true;
++	}
++	return false;
++}
++
++static int at803x_cdt_fault_length(int dt)
++{
++	/* According to the datasheet the distance to the fault is
++	 * DELTA_TIME * 0.824 meters.
++	 *
++	 * The author suspect the correct formula is:
++	 *
++	 *   fault_distance = DELTA_TIME * (c * VF) / 125MHz / 2
++	 *
++	 * where c is the speed of light, VF is the velocity factor of
++	 * the twisted pair cable, 125MHz the counter frequency and
++	 * we need to divide by 2 because the hardware will measure the
++	 * round trip time to the fault and back to the PHY.
++	 *
++	 * With a VF of 0.69 we get the factor 0.824 mentioned in the
++	 * datasheet.
++	 */
++	return (dt * 824) / 10;
++}
++
++static int at803x_cdt_start(struct phy_device *phydev,
++			    u32 cdt_start)
++{
++	return phy_write(phydev, AT803X_CDT, cdt_start);
++}
++
++static int at803x_cdt_wait_for_completion(struct phy_device *phydev,
++					  u32 cdt_en)
++{
++	int val, ret;
++
++	/* One test run takes about 25ms */
++	ret = phy_read_poll_timeout(phydev, AT803X_CDT, val,
++				    !(val & cdt_en),
++				    30000, 100000, true);
++
++	return ret < 0 ? ret : 0;
++}
++
++static int at803x_cable_test_one_pair(struct phy_device *phydev, int pair)
++{
++	static const int ethtool_pair[] = {
++		ETHTOOL_A_CABLE_PAIR_A,
++		ETHTOOL_A_CABLE_PAIR_B,
++		ETHTOOL_A_CABLE_PAIR_C,
++		ETHTOOL_A_CABLE_PAIR_D,
++	};
++	int ret, val;
++
++	val = FIELD_PREP(AT803X_CDT_MDI_PAIR_MASK, pair) |
++	      AT803X_CDT_ENABLE_TEST;
++	ret = at803x_cdt_start(phydev, val);
++	if (ret)
++		return ret;
++
++	ret = at803x_cdt_wait_for_completion(phydev, AT803X_CDT_ENABLE_TEST);
++	if (ret)
++		return ret;
++
++	val = phy_read(phydev, AT803X_CDT_STATUS);
++	if (val < 0)
++		return val;
++
++	if (at803x_cdt_test_failed(val))
++		return 0;
++
++	ethnl_cable_test_result(phydev, ethtool_pair[pair],
++				at803x_cable_test_result_trans(val));
++
++	if (at803x_cdt_fault_length_valid(val)) {
++		val = FIELD_GET(AT803X_CDT_STATUS_DELTA_TIME_MASK, val);
++		ethnl_cable_test_fault_length(phydev, ethtool_pair[pair],
++					      at803x_cdt_fault_length(val));
++	}
++
++	return 1;
++}
++
++static int at803x_cable_test_get_status(struct phy_device *phydev,
++					bool *finished, unsigned long pair_mask)
++{
++	int retries = 20;
++	int pair, ret;
++
++	*finished = false;
++
++	/* According to the datasheet the CDT can be performed when
++	 * there is no link partner or when the link partner is
++	 * auto-negotiating. Starting the test will restart the AN
++	 * automatically. It seems that doing this repeatedly we will
++	 * get a slot where our link partner won't disturb our
++	 * measurement.
++	 */
++	while (pair_mask && retries--) {
++		for_each_set_bit(pair, &pair_mask, 4) {
++			ret = at803x_cable_test_one_pair(phydev, pair);
++			if (ret < 0)
++				return ret;
++			if (ret)
++				clear_bit(pair, &pair_mask);
++		}
++		if (pair_mask)
++			msleep(250);
++	}
++
++	*finished = true;
++
++	return 0;
++}
++
++static void at803x_cable_test_autoneg(struct phy_device *phydev)
++{
++	/* Enable auto-negotiation, but advertise no capabilities, no link
++	 * will be established. A restart of the auto-negotiation is not
++	 * required, because the cable test will automatically break the link.
++	 */
++	phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
++	phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA);
++}
++
++static int at803x_cable_test_start(struct phy_device *phydev)
++{
++	at803x_cable_test_autoneg(phydev);
++	/* we do all the (time consuming) work later */
++	return 0;
++}
++
++static int at8031_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev,
++					    unsigned int selector)
++{
++	struct phy_device *phydev = rdev_get_drvdata(rdev);
++
++	if (selector)
++		return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
++					     0, AT803X_DEBUG_RGMII_1V8);
++	else
++		return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
++					     AT803X_DEBUG_RGMII_1V8, 0);
++}
++
++static int at8031_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev)
++{
++	struct phy_device *phydev = rdev_get_drvdata(rdev);
++	int val;
++
++	val = at803x_debug_reg_read(phydev, AT803X_DEBUG_REG_1F);
++	if (val < 0)
++		return val;
++
++	return (val & AT803X_DEBUG_RGMII_1V8) ? 1 : 0;
++}
++
++static const struct regulator_ops vddio_regulator_ops = {
++	.list_voltage = regulator_list_voltage_table,
++	.set_voltage_sel = at8031_rgmii_reg_set_voltage_sel,
++	.get_voltage_sel = at8031_rgmii_reg_get_voltage_sel,
++};
++
++static const unsigned int vddio_voltage_table[] = {
++	1500000,
++	1800000,
++};
++
++static const struct regulator_desc vddio_desc = {
++	.name = "vddio",
++	.of_match = of_match_ptr("vddio-regulator"),
++	.n_voltages = ARRAY_SIZE(vddio_voltage_table),
++	.volt_table = vddio_voltage_table,
++	.ops = &vddio_regulator_ops,
++	.type = REGULATOR_VOLTAGE,
++	.owner = THIS_MODULE,
++};
++
++static const struct regulator_ops vddh_regulator_ops = {
++};
++
++static const struct regulator_desc vddh_desc = {
++	.name = "vddh",
++	.of_match = of_match_ptr("vddh-regulator"),
++	.n_voltages = 1,
++	.fixed_uV = 2500000,
++	.ops = &vddh_regulator_ops,
++	.type = REGULATOR_VOLTAGE,
++	.owner = THIS_MODULE,
++};
++
++static int at8031_register_regulators(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++	struct device *dev = &phydev->mdio.dev;
++	struct regulator_config config = { };
++
++	config.dev = dev;
++	config.driver_data = phydev;
++
++	priv->vddio_rdev = devm_regulator_register(dev, &vddio_desc, &config);
++	if (IS_ERR(priv->vddio_rdev)) {
++		phydev_err(phydev, "failed to register VDDIO regulator\n");
++		return PTR_ERR(priv->vddio_rdev);
++	}
++
++	priv->vddh_rdev = devm_regulator_register(dev, &vddh_desc, &config);
++	if (IS_ERR(priv->vddh_rdev)) {
++		phydev_err(phydev, "failed to register VDDH regulator\n");
++		return PTR_ERR(priv->vddh_rdev);
++	}
++
++	return 0;
++}
++
++static int at8031_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
++{
++	struct phy_device *phydev = upstream;
++	__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_support);
++	__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
++	DECLARE_PHY_INTERFACE_MASK(interfaces);
++	phy_interface_t iface;
++
++	linkmode_zero(phy_support);
++	phylink_set(phy_support, 1000baseX_Full);
++	phylink_set(phy_support, 1000baseT_Full);
++	phylink_set(phy_support, Autoneg);
++	phylink_set(phy_support, Pause);
++	phylink_set(phy_support, Asym_Pause);
++
++	linkmode_zero(sfp_support);
++	sfp_parse_support(phydev->sfp_bus, id, sfp_support, interfaces);
++	/* Some modules support 10G modes as well as others we support.
++	 * Mask out non-supported modes so the correct interface is picked.
++	 */
++	linkmode_and(sfp_support, phy_support, sfp_support);
++
++	if (linkmode_empty(sfp_support)) {
++		dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
++		return -EINVAL;
++	}
++
++	iface = sfp_select_interface(phydev->sfp_bus, sfp_support);
++
++	/* Only 1000Base-X is supported by AR8031/8033 as the downstream SerDes
++	 * interface for use with SFP modules.
++	 * However, some copper modules detected as having a preferred SGMII
++	 * interface do default to and function in 1000Base-X mode, so just
++	 * print a warning and allow such modules, as they may have some chance
++	 * of working.
++	 */
++	if (iface == PHY_INTERFACE_MODE_SGMII)
++		dev_warn(&phydev->mdio.dev, "module may not function if 1000Base-X not supported\n");
++	else if (iface != PHY_INTERFACE_MODE_1000BASEX)
++		return -EINVAL;
++
++	return 0;
++}
++
++static const struct sfp_upstream_ops at8031_sfp_ops = {
++	.attach = phy_sfp_attach,
++	.detach = phy_sfp_detach,
++	.module_insert = at8031_sfp_insert,
++};
++
++static int at8031_parse_dt(struct phy_device *phydev)
++{
++	struct device_node *node = phydev->mdio.dev.of_node;
++	struct at803x_priv *priv = phydev->priv;
++	int ret;
++
++	if (of_property_read_bool(node, "qca,keep-pll-enabled"))
++		priv->flags |= AT803X_KEEP_PLL_ENABLED;
++
++	ret = at8031_register_regulators(phydev);
++	if (ret < 0)
++		return ret;
++
++	ret = devm_regulator_get_enable_optional(&phydev->mdio.dev,
++						 "vddio");
++	if (ret) {
++		phydev_err(phydev, "failed to get VDDIO regulator\n");
++		return ret;
++	}
++
++	/* Only AR8031/8033 support 1000Base-X for SFP modules */
++	return phy_sfp_probe(phydev, &at8031_sfp_ops);
++}
++
++static int at8031_probe(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++	int mode_cfg;
++	int ccr;
++	int ret;
++
++	ret = at803x_probe(phydev);
++	if (ret)
++		return ret;
++
++	/* Only supported on AR8031/AR8033, the AR8030/AR8035 use strapping
++	 * options.
++	 */
++	ret = at8031_parse_dt(phydev);
++	if (ret)
++		return ret;
++
++	ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
++	if (ccr < 0)
++		return ccr;
++	mode_cfg = ccr & AT803X_MODE_CFG_MASK;
++
++	switch (mode_cfg) {
++	case AT803X_MODE_CFG_BX1000_RGMII_50OHM:
++	case AT803X_MODE_CFG_BX1000_RGMII_75OHM:
++		priv->is_1000basex = true;
++		fallthrough;
++	case AT803X_MODE_CFG_FX100_RGMII_50OHM:
++	case AT803X_MODE_CFG_FX100_RGMII_75OHM:
++		priv->is_fiber = true;
++		break;
++	}
++
++	/* Disable WoL in 1588 register which is enabled
++	 * by default
++	 */
++	return phy_modify_mmd(phydev, MDIO_MMD_PCS,
++			      AT803X_PHY_MMD3_WOL_CTRL,
++			      AT803X_WOL_EN, 0);
++}
++
++static int at8031_config_init(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++	int ret;
++
++	/* Some bootloaders leave the fiber page selected.
++	 * Switch to the appropriate page (fiber or copper), as otherwise we
++	 * read the PHY capabilities from the wrong page.
++	 */
++	phy_lock_mdio_bus(phydev);
++	ret = at803x_write_page(phydev,
++				priv->is_fiber ? AT803X_PAGE_FIBER :
++						 AT803X_PAGE_COPPER);
++	phy_unlock_mdio_bus(phydev);
++	if (ret)
++		return ret;
++
++	ret = at8031_pll_config(phydev);
++	if (ret < 0)
++		return ret;
++
++	return at803x_config_init(phydev);
++}
++
++static int at8031_set_wol(struct phy_device *phydev,
++			  struct ethtool_wolinfo *wol)
++{
++	int ret;
++
++	/* First setup MAC address and enable WOL interrupt */
++	ret = at803x_set_wol(phydev, wol);
++	if (ret)
++		return ret;
++
++	if (wol->wolopts & WAKE_MAGIC)
++		/* Enable WOL function for 1588 */
++		ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
++				     AT803X_PHY_MMD3_WOL_CTRL,
++				     0, AT803X_WOL_EN);
++	else
++		/* Disable WoL function for 1588 */
++		ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
++				     AT803X_PHY_MMD3_WOL_CTRL,
++				     AT803X_WOL_EN, 0);
++
++	return ret;
++}
++
++static int at8031_config_intr(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++	int err, value = 0;
++
++	if (phydev->interrupts == PHY_INTERRUPT_ENABLED &&
++	    priv->is_fiber) {
++		/* Clear any pending interrupts */
++		err = at803x_ack_interrupt(phydev);
++		if (err)
++			return err;
++
++		value |= AT803X_INTR_ENABLE_LINK_FAIL_BX;
++		value |= AT803X_INTR_ENABLE_LINK_SUCCESS_BX;
++
++		err = phy_set_bits(phydev, AT803X_INTR_ENABLE, value);
++		if (err)
++			return err;
++	}
++
++	return at803x_config_intr(phydev);
++}
++
++/* AR8031 and AR8033 share the same read status logic */
++static int at8031_read_status(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++
++	if (priv->is_1000basex)
++		return genphy_c37_read_status(phydev);
++
++	return at803x_read_status(phydev);
++}
++
++/* AR8031 and AR8035 share the same cable test get status reg */
++static int at8031_cable_test_get_status(struct phy_device *phydev,
++					bool *finished)
++{
++	return at803x_cable_test_get_status(phydev, finished, 0xf);
++}
++
++/* AR8031 and AR8035 share the same cable test start logic */
++static int at8031_cable_test_start(struct phy_device *phydev)
++{
++	at803x_cable_test_autoneg(phydev);
++	phy_write(phydev, MII_CTRL1000, 0);
++	/* we do all the (time consuming) work later */
++	return 0;
++}
++
++/* AR8032, AR9331 and QCA9561 share the same cable test get status reg */
++static int at8032_cable_test_get_status(struct phy_device *phydev,
++					bool *finished)
++{
++	return at803x_cable_test_get_status(phydev, finished, 0x3);
++}
++
++static int at8035_parse_dt(struct phy_device *phydev)
++{
++	struct at803x_priv *priv = phydev->priv;
++
++	/* Mask is set by the generic at803x_parse_dt
++	 * if property is set. Assume property is set
++	 * with the mask not zero.
++	 */
++	if (priv->clk_25m_mask) {
++		/* Fixup for the AR8030/AR8035. This chip has another mask and
++		 * doesn't support the DSP reference. Eg. the lowest bit of the
++		 * mask. The upper two bits select the same frequencies. Mask
++		 * the lowest bit here.
++		 *
++		 * Warning:
++		 *   There was no datasheet for the AR8030 available so this is
++		 *   just a guess. But the AR8035 is listed as pin compatible
++		 *   to the AR8030 so there might be a good chance it works on
++		 *   the AR8030 too.
++		 */
++		priv->clk_25m_reg &= AT8035_CLK_OUT_MASK;
++		priv->clk_25m_mask &= AT8035_CLK_OUT_MASK;
++	}
++
++	return 0;
++}
++
++/* AR8030 and AR8035 shared the same special mask for clk_25m */
++static int at8035_probe(struct phy_device *phydev)
++{
++	int ret;
++
++	ret = at803x_probe(phydev);
++	if (ret)
++		return ret;
++
++	return at8035_parse_dt(phydev);
++}
++
++static int qca83xx_config_init(struct phy_device *phydev)
++{
++	u8 switch_revision;
++
++	switch_revision = phydev->dev_flags & QCA8K_DEVFLAGS_REVISION_MASK;
++
++	switch (switch_revision) {
++	case 1:
++		/* For 100M waveform */
++		at803x_debug_reg_write(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0x02ea);
++		/* Turn on Gigabit clock */
++		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x68a0);
++		break;
++
++	case 2:
++		phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0);
++		fallthrough;
++	case 4:
++		phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f);
++		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x6860);
++		at803x_debug_reg_write(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0x2c46);
++		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3C, 0x6000);
++		break;
++	}
++
++	/* Following original QCA sourcecode set port to prefer master */
++	phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
++
++	return 0;
++}
++
++static int qca8327_config_init(struct phy_device *phydev)
++{
++	/* QCA8327 require DAC amplitude adjustment for 100m set to +6%.
++	 * Disable on init and enable only with 100m speed following
++	 * qca original source code.
++	 */
++	at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++			      QCA8327_DEBUG_MANU_CTRL_EN, 0);
++
++	return qca83xx_config_init(phydev);
++}
++
++static void qca83xx_link_change_notify(struct phy_device *phydev)
++{
++	/* Set DAC Amplitude adjustment to +6% for 100m on link running */
++	if (phydev->state == PHY_RUNNING) {
++		if (phydev->speed == SPEED_100)
++			at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++					      QCA8327_DEBUG_MANU_CTRL_EN,
++					      QCA8327_DEBUG_MANU_CTRL_EN);
++	} else {
++		/* Reset DAC Amplitude adjustment */
++		at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
++	}
++}
++
++static int qca83xx_resume(struct phy_device *phydev)
++{
++	int ret, val;
++
++	/* Skip reset if not suspended */
++	if (!phydev->suspended)
++		return 0;
++
++	/* Reinit the port, reset values set by suspend */
++	qca83xx_config_init(phydev);
++
++	/* Reset the port on port resume */
++	phy_set_bits(phydev, MII_BMCR, BMCR_RESET | BMCR_ANENABLE);
++
++	/* On resume from suspend the switch execute a reset and
++	 * restart auto-negotiation. Wait for reset to complete.
++	 */
++	ret = phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET),
++				    50000, 600000, true);
++	if (ret)
++		return ret;
++
++	usleep_range(1000, 2000);
++
++	return 0;
++}
++
++static int qca83xx_suspend(struct phy_device *phydev)
++{
++	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_GREEN,
++			      AT803X_DEBUG_GATE_CLK_IN1000, 0);
++
++	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
++			      AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE |
++			      AT803X_DEBUG_HIB_CTRL_SEL_RST_80U, 0);
++
++	return 0;
++}
++
++static int qca8337_suspend(struct phy_device *phydev)
++{
++	/* Only QCA8337 support actual suspend. */
++	genphy_suspend(phydev);
++
++	return qca83xx_suspend(phydev);
++}
++
++static int qca8327_suspend(struct phy_device *phydev)
++{
++	u16 mask = 0;
++
++	/* QCA8327 cause port unreliability when phy suspend
++	 * is set.
++	 */
++	mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
++	phy_modify(phydev, MII_BMCR, mask, 0);
++
++	return qca83xx_suspend(phydev);
++}
++
++static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
++{
++	int ret;
++
++	/* Enable fast retrain */
++	ret = genphy_c45_fast_retrain(phydev, true);
++	if (ret)
++		return ret;
++
++	phy_write_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_TOP_OPTION1,
++		      QCA808X_TOP_OPTION1_DATA);
++	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB,
++		      QCA808X_MSE_THRESHOLD_20DB_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB,
++		      QCA808X_MSE_THRESHOLD_17DB_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB,
++		      QCA808X_MSE_THRESHOLD_27DB_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB,
++		      QCA808X_MSE_THRESHOLD_28DB_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_1,
++		      QCA808X_MMD3_DEBUG_1_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_4,
++		      QCA808X_MMD3_DEBUG_4_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_5,
++		      QCA808X_MMD3_DEBUG_5_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_3,
++		      QCA808X_MMD3_DEBUG_3_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_6,
++		      QCA808X_MMD3_DEBUG_6_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_2,
++		      QCA808X_MMD3_DEBUG_2_VALUE);
++
++	return 0;
++}
++
++static int qca808x_phy_ms_seed_enable(struct phy_device *phydev, bool enable)
++{
++	u16 seed_value;
++
++	if (!enable)
++		return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
++				QCA808X_MASTER_SLAVE_SEED_ENABLE, 0);
++
++	seed_value = prandom_u32_max(QCA808X_MASTER_SLAVE_SEED_RANGE);
++	return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
++			QCA808X_MASTER_SLAVE_SEED_CFG | QCA808X_MASTER_SLAVE_SEED_ENABLE,
++			FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value) |
++			QCA808X_MASTER_SLAVE_SEED_ENABLE);
++}
++
++static bool qca808x_is_prefer_master(struct phy_device *phydev)
++{
++	return (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_FORCE) ||
++		(phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
++}
++
++static bool qca808x_has_fast_retrain_or_slave_seed(struct phy_device *phydev)
++{
++	return linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
++}
++
++static int qca808x_config_init(struct phy_device *phydev)
++{
++	int ret;
++
++	/* Active adc&vga on 802.3az for the link 1000M and 100M */
++	ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_ADDR_CLD_CTRL7,
++			     QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
++	if (ret)
++		return ret;
++
++	/* Adjust the threshold on 802.3az for the link 1000M */
++	ret = phy_write_mmd(phydev, MDIO_MMD_PCS,
++			    QCA808X_PHY_MMD3_AZ_TRAINING_CTRL,
++			    QCA808X_MMD3_AZ_TRAINING_VAL);
++	if (ret)
++		return ret;
++
++	if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
++		/* Config the fast retrain for the link 2500M */
++		ret = qca808x_phy_fast_retrain_config(phydev);
++		if (ret)
++			return ret;
++
++		ret = genphy_read_master_slave(phydev);
++		if (ret < 0)
++			return ret;
++
++		if (!qca808x_is_prefer_master(phydev)) {
++			/* Enable seed and configure lower ramdom seed to make phy
++			 * linked as slave mode.
++			 */
++			ret = qca808x_phy_ms_seed_enable(phydev, true);
++			if (ret)
++				return ret;
++		}
++	}
++
++	/* Configure adc threshold as 100mv for the link 10M */
++	return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
++				     QCA808X_ADC_THRESHOLD_MASK,
++				     QCA808X_ADC_THRESHOLD_100MV);
++}
++
++static int qca808x_read_status(struct phy_device *phydev)
++{
++	struct at803x_ss_mask ss_mask = { 0 };
++	int ret;
++
++	ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
++	if (ret < 0)
++		return ret;
++
++	linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->lp_advertising,
++			 ret & MDIO_AN_10GBT_STAT_LP2_5G);
++
++	ret = genphy_read_status(phydev);
++	if (ret)
++		return ret;
++
++	/* qca8081 takes the different bits for speed value from at803x */
++	ss_mask.speed_mask = QCA808X_SS_SPEED_MASK;
++	ss_mask.speed_shift = __bf_shf(QCA808X_SS_SPEED_MASK);
++	ret = at803x_read_specific_status(phydev, ss_mask);
++	if (ret < 0)
++		return ret;
++
++	if (phydev->link) {
++		if (phydev->speed == SPEED_2500)
++			phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
++		else
++			phydev->interface = PHY_INTERFACE_MODE_SGMII;
++	} else {
++		/* generate seed as a lower random value to make PHY linked as SLAVE easily,
++		 * except for master/slave configuration fault detected or the master mode
++		 * preferred.
++		 *
++		 * the reason for not putting this code into the function link_change_notify is
++		 * the corner case where the link partner is also the qca8081 PHY and the seed
++		 * value is configured as the same value, the link can't be up and no link change
++		 * occurs.
++		 */
++		if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
++			if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
++			    qca808x_is_prefer_master(phydev)) {
++				qca808x_phy_ms_seed_enable(phydev, false);
++			} else {
++				qca808x_phy_ms_seed_enable(phydev, true);
++			}
++		}
++	}
++
++	return 0;
++}
++
++static int qca808x_soft_reset(struct phy_device *phydev)
++{
++	int ret;
++
++	ret = genphy_soft_reset(phydev);
++	if (ret < 0)
++		return ret;
++
++	if (qca808x_has_fast_retrain_or_slave_seed(phydev))
++		ret = qca808x_phy_ms_seed_enable(phydev, true);
++
++	return ret;
++}
++
++static bool qca808x_cdt_fault_length_valid(int cdt_code)
++{
++	switch (cdt_code) {
++	case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++		return true;
++	default:
++		return false;
++	}
++}
++
++static int qca808x_cable_test_result_trans(int cdt_code)
++{
++	switch (cdt_code) {
++	case QCA808X_CDT_STATUS_STAT_NORMAL:
++		return ETHTOOL_A_CABLE_RESULT_CODE_OK;
++	case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++		return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
++	case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++		return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++		return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
++	case QCA808X_CDT_STATUS_STAT_FAIL:
++	default:
++		return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
++	}
++}
++
++static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
++				    int result)
++{
++	int val;
++	u32 cdt_length_reg = 0;
++
++	switch (pair) {
++	case ETHTOOL_A_CABLE_PAIR_A:
++		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;
++		break;
++	case ETHTOOL_A_CABLE_PAIR_B:
++		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;
++		break;
++	case ETHTOOL_A_CABLE_PAIR_C:
++		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;
++		break;
++	case ETHTOOL_A_CABLE_PAIR_D:
++		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);
++	if (val < 0)
++		return val;
++
++	if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
++		val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
++	else
++		val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
++
++	return at803x_cdt_fault_length(val);
++}
++
++static int qca808x_cable_test_start(struct phy_device *phydev)
++{
++	int ret;
++
++	/* perform CDT with the following configs:
++	 * 1. disable hibernation.
++	 * 2. force PHY working in MDI mode.
++	 * 3. for PHY working in 1000BaseT.
++	 * 4. configure the threshold.
++	 */
++
++	ret = at803x_debug_reg_mask(phydev, QCA808X_DBG_AN_TEST, QCA808X_HIBERNATION_EN, 0);
++	if (ret < 0)
++		return ret;
++
++	ret = at803x_config_mdix(phydev, ETH_TP_MDI);
++	if (ret < 0)
++		return ret;
++
++	/* Force 1000base-T needs to configure PMA/PMD and MII_BMCR */
++	phydev->duplex = DUPLEX_FULL;
++	phydev->speed = SPEED_1000;
++	ret = genphy_c45_pma_setup_forced(phydev);
++	if (ret < 0)
++		return ret;
++
++	ret = genphy_setup_forced(phydev);
++	if (ret < 0)
++		return ret;
++
++	/* configure the thresholds for open, short, pair ok test */
++	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8074, 0xc040);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8076, 0xc040);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8077, 0xa060);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8078, 0xc050);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807a, 0xc060);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807e, 0xb060);
++
++	return 0;
++}
++
++static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
++					      u16 status)
++{
++	int length, result;
++	u16 pair_code;
++
++	switch (pair) {
++	case ETHTOOL_A_CABLE_PAIR_A:
++		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
++		break;
++	case ETHTOOL_A_CABLE_PAIR_B:
++		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
++		break;
++	case ETHTOOL_A_CABLE_PAIR_C:
++		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
++		break;
++	case ETHTOOL_A_CABLE_PAIR_D:
++		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	result = qca808x_cable_test_result_trans(pair_code);
++	ethnl_cable_test_result(phydev, pair, result);
++
++	if (qca808x_cdt_fault_length_valid(pair_code)) {
++		length = qca808x_cdt_fault_length(phydev, pair, result);
++		ethnl_cable_test_fault_length(phydev, pair, length);
++	}
++
++	return 0;
++}
++
++static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
++{
++	int ret, val;
++
++	*finished = false;
++
++	val = QCA808X_CDT_ENABLE_TEST |
++	      QCA808X_CDT_LENGTH_UNIT;
++	ret = at803x_cdt_start(phydev, val);
++	if (ret)
++		return ret;
++
++	ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
++	if (ret)
++		return ret;
++
++	val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);
++	if (val < 0)
++		return val;
++
++	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
++	if (ret)
++		return ret;
++
++	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
++	if (ret)
++		return ret;
++
++	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
++	if (ret)
++		return ret;
++
++	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
++	if (ret)
++		return ret;
++
++	*finished = true;
++
++	return 0;
++}
++
++static int qca808x_get_features(struct phy_device *phydev)
++{
++	int ret;
++
++	ret = genphy_c45_pma_read_abilities(phydev);
++	if (ret)
++		return ret;
++
++	/* The autoneg ability is not existed in bit3 of MMD7.1,
++	 * but it is supported by qca808x PHY, so we add it here
++	 * manually.
++	 */
++	linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
++
++	/* As for the qca8081 1G version chip, the 2500baseT ability is also
++	 * existed in the bit0 of MMD1.21, we need to remove it manually if
++	 * it is the qca8081 1G chip according to the bit0 of MMD7.0x901d.
++	 */
++	ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE);
++	if (ret < 0)
++		return ret;
++
++	if (QCA808X_PHY_CHIP_TYPE_1G & ret)
++		linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
++
++	return 0;
++}
++
++static int qca808x_config_aneg(struct phy_device *phydev)
++{
++	int phy_ctrl = 0;
++	int ret;
++
++	ret = at803x_prepare_config_aneg(phydev);
++	if (ret)
++		return ret;
++
++	/* The reg MII_BMCR also needs to be configured for force mode, the
++	 * genphy_config_aneg is also needed.
++	 */
++	if (phydev->autoneg == AUTONEG_DISABLE)
++		genphy_c45_pma_setup_forced(phydev);
++
++	if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising))
++		phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
++
++	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
++				     MDIO_AN_10GBT_CTRL_ADV2_5G, phy_ctrl);
++	if (ret < 0)
++		return ret;
++
++	return __genphy_config_aneg(phydev, ret);
++}
++
++static void qca808x_link_change_notify(struct phy_device *phydev)
++{
++	/* Assert interface sgmii fifo on link down, deassert it on link up,
++	 * the interface device address is always phy address added by 1.
++	 */
++	mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
++				   MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
++				   QCA8081_PHY_FIFO_RSTN,
++				   phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
++}
++
++static int qca808x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
++				    u16 *offload_trigger)
++{
++	/* Parsing specific to netdev trigger */
++	if (test_bit(TRIGGER_NETDEV_TX, &rules))
++		*offload_trigger |= QCA808X_LED_TX_BLINK;
++	if (test_bit(TRIGGER_NETDEV_RX, &rules))
++		*offload_trigger |= QCA808X_LED_RX_BLINK;
++	if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
++		*offload_trigger |= QCA808X_LED_SPEED10_ON;
++	if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++		*offload_trigger |= QCA808X_LED_SPEED100_ON;
++	if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++		*offload_trigger |= QCA808X_LED_SPEED1000_ON;
++	if (test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
++		*offload_trigger |= QCA808X_LED_SPEED2500_ON;
++	if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
++		*offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
++	if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
++		*offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
++
++	if (rules && !*offload_trigger)
++		return -EOPNOTSUPP;
++
++	/* Enable BLINK_CHECK_BYPASS by default to make the LED
++	 * blink even with duplex or speed mode not enabled.
++	 */
++	*offload_trigger |= QCA808X_LED_BLINK_CHECK_BYPASS;
++
++	return 0;
++}
++
++static int qca808x_led_hw_control_enable(struct phy_device *phydev, u8 index)
++{
++	u16 reg;
++
++	if (index > 2)
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++				  QCA808X_LED_FORCE_EN);
++}
++
++static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
++				       unsigned long rules)
++{
++	u16 offload_trigger = 0;
++
++	if (index > 2)
++		return -EINVAL;
++
++	return qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
++}
++
++static int qca808x_led_hw_control_set(struct phy_device *phydev, u8 index,
++				      unsigned long rules)
++{
++	u16 reg, offload_trigger = 0;
++	int ret;
++
++	if (index > 2)
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_CTRL(index);
++
++	ret = qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
++	if (ret)
++		return ret;
++
++	ret = qca808x_led_hw_control_enable(phydev, index);
++	if (ret)
++		return ret;
++
++	return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++			      QCA808X_LED_PATTERN_MASK,
++			      offload_trigger);
++}
++
++static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
++{
++	u16 reg;
++	int val;
++
++	if (index > 2)
++		return false;
++
++	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++	val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++
++	return !(val & QCA808X_LED_FORCE_EN);
++}
++
++static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
++				      unsigned long *rules)
++{
++	u16 reg;
++	int val;
++
++	if (index > 2)
++		return -EINVAL;
++
++	/* Check if we have hw control enabled */
++	if (qca808x_led_hw_control_status(phydev, index))
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_CTRL(index);
++
++	val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++	if (val & QCA808X_LED_TX_BLINK)
++		set_bit(TRIGGER_NETDEV_TX, rules);
++	if (val & QCA808X_LED_RX_BLINK)
++		set_bit(TRIGGER_NETDEV_RX, rules);
++	if (val & QCA808X_LED_SPEED10_ON)
++		set_bit(TRIGGER_NETDEV_LINK_10, rules);
++	if (val & QCA808X_LED_SPEED100_ON)
++		set_bit(TRIGGER_NETDEV_LINK_100, rules);
++	if (val & QCA808X_LED_SPEED1000_ON)
++		set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++	if (val & QCA808X_LED_SPEED2500_ON)
++		set_bit(TRIGGER_NETDEV_LINK_2500, rules);
++	if (val & QCA808X_LED_HALF_DUPLEX_ON)
++		set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
++	if (val & QCA808X_LED_FULL_DUPLEX_ON)
++		set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
++
++	return 0;
++}
++
++static int qca808x_led_hw_control_reset(struct phy_device *phydev, u8 index)
++{
++	u16 reg;
++
++	if (index > 2)
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_CTRL(index);
++
++	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++				  QCA808X_LED_PATTERN_MASK);
++}
++
++static int qca808x_led_brightness_set(struct phy_device *phydev,
++				      u8 index, enum led_brightness value)
++{
++	u16 reg;
++	int ret;
++
++	if (index > 2)
++		return -EINVAL;
++
++	if (!value) {
++		ret = qca808x_led_hw_control_reset(phydev, index);
++		if (ret)
++			return ret;
++	}
++
++	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++	return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++			      QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++			      QCA808X_LED_FORCE_EN | value ? QCA808X_LED_FORCE_ON :
++							     QCA808X_LED_FORCE_OFF);
++}
++
++static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
++				 unsigned long *delay_on,
++				 unsigned long *delay_off)
++{
++	int ret;
++	u16 reg;
++
++	if (index > 2)
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++	/* Set blink to 50% off, 50% on at 4Hz by default */
++	ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
++			     QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
++			     QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
++	if (ret)
++		return ret;
++
++	/* We use BLINK_1 for normal blinking */
++	ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++			     QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++			     QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
++	if (ret)
++		return ret;
++
++	/* We set blink to 4Hz, aka 250ms */
++	*delay_on = 250 / 2;
++	*delay_off = 250 / 2;
++
++	return 0;
++}
++
++static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
++				    unsigned long modes)
++{
++	struct at803x_priv *priv = phydev->priv;
++	bool active_low = false;
++	u32 mode;
++
++	for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
++		switch (mode) {
++		case PHY_LED_ACTIVE_LOW:
++			active_low = true;
++			break;
++		default:
++			return -EINVAL;
++		}
++	}
++
++	/* PHY polarity is global and can't be set per LED.
++	 * To detect this, check if last requested polarity mode
++	 * match the new one.
++	 */
++	if (priv->led_polarity_mode >= 0 &&
++	    priv->led_polarity_mode != active_low) {
++		phydev_err(phydev, "PHY polarity is global. Mismatched polarity on different LED\n");
++		return -EINVAL;
++	}
++
++	/* Save the last PHY polarity mode */
++	priv->led_polarity_mode = active_low;
++
++	return phy_modify_mmd(phydev, MDIO_MMD_AN,
++			      QCA808X_MMD7_LED_POLARITY_CTRL,
++			      QCA808X_LED_ACTIVE_HIGH,
++			      active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
++}
++
++static struct phy_driver at803x_driver[] = {
++{
++	/* Qualcomm Atheros AR8035 */
++	PHY_ID_MATCH_EXACT(ATH8035_PHY_ID),
++	.name			= "Qualcomm Atheros AR8035",
++	.flags			= PHY_POLL_CABLE_TEST,
++	.probe			= at8035_probe,
++	.config_aneg		= at803x_config_aneg,
++	.config_init		= at803x_config_init,
++	.soft_reset		= genphy_soft_reset,
++	.set_wol		= at803x_set_wol,
++	.get_wol		= at803x_get_wol,
++	.suspend		= at803x_suspend,
++	.resume			= at803x_resume,
++	/* PHY_GBIT_FEATURES */
++	.read_status		= at803x_read_status,
++	.config_intr		= at803x_config_intr,
++	.handle_interrupt	= at803x_handle_interrupt,
++	.get_tunable		= at803x_get_tunable,
++	.set_tunable		= at803x_set_tunable,
++	.cable_test_start	= at8031_cable_test_start,
++	.cable_test_get_status	= at8031_cable_test_get_status,
++}, {
++	/* Qualcomm Atheros AR8030 */
++	.phy_id			= ATH8030_PHY_ID,
++	.name			= "Qualcomm Atheros AR8030",
++	.phy_id_mask		= AT8030_PHY_ID_MASK,
++	.probe			= at8035_probe,
++	.config_init		= at803x_config_init,
++	.link_change_notify	= at803x_link_change_notify,
++	.set_wol		= at803x_set_wol,
++	.get_wol		= at803x_get_wol,
++	.suspend		= at803x_suspend,
++	.resume			= at803x_resume,
++	/* PHY_BASIC_FEATURES */
++	.config_intr		= at803x_config_intr,
++	.handle_interrupt	= at803x_handle_interrupt,
++}, {
++	/* Qualcomm Atheros AR8031/AR8033 */
++	PHY_ID_MATCH_EXACT(ATH8031_PHY_ID),
++	.name			= "Qualcomm Atheros AR8031/AR8033",
++	.flags			= PHY_POLL_CABLE_TEST,
++	.probe			= at8031_probe,
++	.config_init		= at8031_config_init,
++	.config_aneg		= at803x_config_aneg,
++	.soft_reset		= genphy_soft_reset,
++	.set_wol		= at8031_set_wol,
++	.get_wol		= at803x_get_wol,
++	.suspend		= at803x_suspend,
++	.resume			= at803x_resume,
++	.read_page		= at803x_read_page,
++	.write_page		= at803x_write_page,
++	.get_features		= at803x_get_features,
++	.read_status		= at8031_read_status,
++	.config_intr		= at8031_config_intr,
++	.handle_interrupt	= at803x_handle_interrupt,
++	.get_tunable		= at803x_get_tunable,
++	.set_tunable		= at803x_set_tunable,
++	.cable_test_start	= at8031_cable_test_start,
++	.cable_test_get_status	= at8031_cable_test_get_status,
++}, {
++	/* Qualcomm Atheros AR8032 */
++	PHY_ID_MATCH_EXACT(ATH8032_PHY_ID),
++	.name			= "Qualcomm Atheros AR8032",
++	.probe			= at803x_probe,
++	.flags			= PHY_POLL_CABLE_TEST,
++	.config_init		= at803x_config_init,
++	.link_change_notify	= at803x_link_change_notify,
++	.suspend		= at803x_suspend,
++	.resume			= at803x_resume,
++	/* PHY_BASIC_FEATURES */
++	.config_intr		= at803x_config_intr,
++	.handle_interrupt	= at803x_handle_interrupt,
++	.cable_test_start	= at803x_cable_test_start,
++	.cable_test_get_status	= at8032_cable_test_get_status,
++}, {
++	/* ATHEROS AR9331 */
++	PHY_ID_MATCH_EXACT(ATH9331_PHY_ID),
++	.name			= "Qualcomm Atheros AR9331 built-in PHY",
++	.probe			= at803x_probe,
++	.suspend		= at803x_suspend,
++	.resume			= at803x_resume,
++	.flags			= PHY_POLL_CABLE_TEST,
++	/* PHY_BASIC_FEATURES */
++	.config_intr		= at803x_config_intr,
++	.handle_interrupt	= at803x_handle_interrupt,
++	.cable_test_start	= at803x_cable_test_start,
++	.cable_test_get_status	= at8032_cable_test_get_status,
++	.read_status		= at803x_read_status,
++	.soft_reset		= genphy_soft_reset,
++	.config_aneg		= at803x_config_aneg,
++}, {
++	/* Qualcomm Atheros QCA9561 */
++	PHY_ID_MATCH_EXACT(QCA9561_PHY_ID),
++	.name			= "Qualcomm Atheros QCA9561 built-in PHY",
++	.probe			= at803x_probe,
++	.suspend		= at803x_suspend,
++	.resume			= at803x_resume,
++	.flags			= PHY_POLL_CABLE_TEST,
++	/* PHY_BASIC_FEATURES */
++	.config_intr		= at803x_config_intr,
++	.handle_interrupt	= at803x_handle_interrupt,
++	.cable_test_start	= at803x_cable_test_start,
++	.cable_test_get_status	= at8032_cable_test_get_status,
++	.read_status		= at803x_read_status,
++	.soft_reset		= genphy_soft_reset,
++	.config_aneg		= at803x_config_aneg,
++}, {
++	/* QCA8337 */
++	.phy_id			= QCA8337_PHY_ID,
++	.phy_id_mask		= QCA8K_PHY_ID_MASK,
++	.name			= "Qualcomm Atheros 8337 internal PHY",
++	/* PHY_GBIT_FEATURES */
++	.probe			= at803x_probe,
++	.flags			= PHY_IS_INTERNAL,
++	.config_init		= qca83xx_config_init,
++	.soft_reset		= genphy_soft_reset,
++	.get_sset_count		= qca83xx_get_sset_count,
++	.get_strings		= qca83xx_get_strings,
++	.get_stats		= qca83xx_get_stats,
++	.suspend		= qca8337_suspend,
++	.resume			= qca83xx_resume,
++}, {
++	/* QCA8327-A from switch QCA8327-AL1A */
++	.phy_id			= QCA8327_A_PHY_ID,
++	.phy_id_mask		= QCA8K_PHY_ID_MASK,
++	.name			= "Qualcomm Atheros 8327-A internal PHY",
++	/* PHY_GBIT_FEATURES */
++	.link_change_notify	= qca83xx_link_change_notify,
++	.probe			= at803x_probe,
++	.flags			= PHY_IS_INTERNAL,
++	.config_init		= qca8327_config_init,
++	.soft_reset		= genphy_soft_reset,
++	.get_sset_count		= qca83xx_get_sset_count,
++	.get_strings		= qca83xx_get_strings,
++	.get_stats		= qca83xx_get_stats,
++	.suspend		= qca8327_suspend,
++	.resume			= qca83xx_resume,
++}, {
++	/* QCA8327-B from switch QCA8327-BL1A */
++	.phy_id			= QCA8327_B_PHY_ID,
++	.phy_id_mask		= QCA8K_PHY_ID_MASK,
++	.name			= "Qualcomm Atheros 8327-B internal PHY",
++	/* PHY_GBIT_FEATURES */
++	.link_change_notify	= qca83xx_link_change_notify,
++	.probe			= at803x_probe,
++	.flags			= PHY_IS_INTERNAL,
++	.config_init		= qca8327_config_init,
++	.soft_reset		= genphy_soft_reset,
++	.get_sset_count		= qca83xx_get_sset_count,
++	.get_strings		= qca83xx_get_strings,
++	.get_stats		= qca83xx_get_stats,
++	.suspend		= qca8327_suspend,
++	.resume			= qca83xx_resume,
++}, {
++	/* Qualcomm QCA8081 */
++	PHY_ID_MATCH_EXACT(QCA8081_PHY_ID),
++	.name			= "Qualcomm QCA8081",
++	.flags			= PHY_POLL_CABLE_TEST,
++	.probe			= at803x_probe,
++	.config_intr		= at803x_config_intr,
++	.handle_interrupt	= at803x_handle_interrupt,
++	.get_tunable		= at803x_get_tunable,
++	.set_tunable		= at803x_set_tunable,
++	.set_wol		= at803x_set_wol,
++	.get_wol		= at803x_get_wol,
++	.get_features		= qca808x_get_features,
++	.config_aneg		= qca808x_config_aneg,
++	.suspend		= genphy_suspend,
++	.resume			= genphy_resume,
++	.read_status		= qca808x_read_status,
++	.config_init		= qca808x_config_init,
++	.soft_reset		= qca808x_soft_reset,
++	.cable_test_start	= qca808x_cable_test_start,
++	.cable_test_get_status	= qca808x_cable_test_get_status,
++	.link_change_notify	= qca808x_link_change_notify,
++	.led_brightness_set	= qca808x_led_brightness_set,
++	.led_blink_set		= qca808x_led_blink_set,
++	.led_hw_is_supported	= qca808x_led_hw_is_supported,
++	.led_hw_control_set	= qca808x_led_hw_control_set,
++	.led_hw_control_get	= qca808x_led_hw_control_get,
++	.led_polarity_set	= qca808x_led_polarity_set,
++}, };
++
++module_phy_driver(at803x_driver);
++
++static struct mdio_device_id __maybe_unused atheros_tbl[] = {
++	{ ATH8030_PHY_ID, AT8030_PHY_ID_MASK },
++	{ PHY_ID_MATCH_EXACT(ATH8031_PHY_ID) },
++	{ PHY_ID_MATCH_EXACT(ATH8032_PHY_ID) },
++	{ PHY_ID_MATCH_EXACT(ATH8035_PHY_ID) },
++	{ PHY_ID_MATCH_EXACT(ATH9331_PHY_ID) },
++	{ PHY_ID_MATCH_EXACT(QCA8337_PHY_ID) },
++	{ PHY_ID_MATCH_EXACT(QCA8327_A_PHY_ID) },
++	{ PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) },
++	{ PHY_ID_MATCH_EXACT(QCA9561_PHY_ID) },
++	{ PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) },
++	{ }
++};
++
++MODULE_DEVICE_TABLE(mdio, atheros_tbl);

+ 243 - 0
target/linux/generic/backport-6.6/713-v6.9-02-net-phy-qcom-create-and-move-functions-to-shared-lib.patch

@@ -0,0 +1,243 @@
+From 6fb760972c49490b03f3db2ad64cf30bdd28c54a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Mon, 29 Jan 2024 15:15:20 +0100
+Subject: [PATCH 2/5] net: phy: qcom: create and move functions to shared
+ library
+
+Create and move functions to shared library in preparation for qca83xx
+PHY Family to be detached from at803x driver.
+
+Only the shared defines are moved to the shared qcom.h header.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/qcom/Kconfig        |  4 ++
+ drivers/net/phy/qcom/Makefile       |  1 +
+ drivers/net/phy/qcom/at803x.c       | 69 +----------------------------
+ drivers/net/phy/qcom/qcom-phy-lib.c | 53 ++++++++++++++++++++++
+ drivers/net/phy/qcom/qcom.h         | 34 ++++++++++++++
+ 5 files changed, 94 insertions(+), 67 deletions(-)
+ create mode 100644 drivers/net/phy/qcom/qcom-phy-lib.c
+ create mode 100644 drivers/net/phy/qcom/qcom.h
+
+--- a/drivers/net/phy/qcom/Kconfig
++++ b/drivers/net/phy/qcom/Kconfig
+@@ -1,6 +1,10 @@
+ # SPDX-License-Identifier: GPL-2.0-only
++config QCOM_NET_PHYLIB
++	tristate
++
+ config AT803X_PHY
+ 	tristate "Qualcomm Atheros AR803X PHYs and QCA833x PHYs"
++	select QCOM_NET_PHYLIB
+ 	depends on REGULATOR
+ 	help
+ 	  Currently supports the AR8030, AR8031, AR8033, AR8035 and internal
+--- a/drivers/net/phy/qcom/Makefile
++++ b/drivers/net/phy/qcom/Makefile
+@@ -1,2 +1,3 @@
+ # SPDX-License-Identifier: GPL-2.0
++obj-$(CONFIG_QCOM_NET_PHYLIB)	+= qcom-phy-lib.o
+ obj-$(CONFIG_AT803X_PHY)	+= at803x.o
+--- a/drivers/net/phy/qcom/at803x.c
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -22,6 +22,8 @@
+ #include <linux/sfp.h>
+ #include <dt-bindings/net/qca-ar803x.h>
+ 
++#include "qcom.h"
++
+ #define AT803X_SPECIFIC_FUNCTION_CONTROL	0x10
+ #define AT803X_SFC_ASSERT_CRS			BIT(11)
+ #define AT803X_SFC_FORCE_LINK			BIT(10)
+@@ -84,9 +86,6 @@
+ #define AT803X_REG_CHIP_CONFIG			0x1f
+ #define AT803X_BT_BX_REG_SEL			0x8000
+ 
+-#define AT803X_DEBUG_ADDR			0x1D
+-#define AT803X_DEBUG_DATA			0x1E
+-
+ #define AT803X_MODE_CFG_MASK			0x0F
+ #define AT803X_MODE_CFG_BASET_RGMII		0x00
+ #define AT803X_MODE_CFG_BASET_SGMII		0x01
+@@ -103,19 +102,6 @@
+ #define AT803X_PSSR				0x11	/*PHY-Specific Status Register*/
+ #define AT803X_PSSR_MR_AN_COMPLETE		0x0200
+ 
+-#define AT803X_DEBUG_ANALOG_TEST_CTRL		0x00
+-#define QCA8327_DEBUG_MANU_CTRL_EN		BIT(2)
+-#define QCA8337_DEBUG_MANU_CTRL_EN		GENMASK(3, 2)
+-#define AT803X_DEBUG_RX_CLK_DLY_EN		BIT(15)
+-
+-#define AT803X_DEBUG_SYSTEM_CTRL_MODE		0x05
+-#define AT803X_DEBUG_TX_CLK_DLY_EN		BIT(8)
+-
+-#define AT803X_DEBUG_REG_HIB_CTRL		0x0b
+-#define   AT803X_DEBUG_HIB_CTRL_SEL_RST_80U	BIT(10)
+-#define   AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE	BIT(13)
+-#define   AT803X_DEBUG_HIB_CTRL_PS_HIB_EN	BIT(15)
+-
+ #define AT803X_DEBUG_REG_3C			0x3C
+ 
+ #define AT803X_DEBUG_REG_GREEN			0x3D
+@@ -393,18 +379,6 @@ MODULE_DESCRIPTION("Qualcomm Atheros AR8
+ MODULE_AUTHOR("Matus Ujhelyi");
+ MODULE_LICENSE("GPL");
+ 
+-enum stat_access_type {
+-	PHY,
+-	MMD
+-};
+-
+-struct at803x_hw_stat {
+-	const char *string;
+-	u8 reg;
+-	u32 mask;
+-	enum stat_access_type access_type;
+-};
+-
+ static struct at803x_hw_stat qca83xx_hw_stats[] = {
+ 	{ "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
+ 	{ "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
+@@ -439,45 +413,6 @@ struct at803x_context {
+ 	u16 led_control;
+ };
+ 
+-static int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data)
+-{
+-	int ret;
+-
+-	ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
+-	if (ret < 0)
+-		return ret;
+-
+-	return phy_write(phydev, AT803X_DEBUG_DATA, data);
+-}
+-
+-static int at803x_debug_reg_read(struct phy_device *phydev, u16 reg)
+-{
+-	int ret;
+-
+-	ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
+-	if (ret < 0)
+-		return ret;
+-
+-	return phy_read(phydev, AT803X_DEBUG_DATA);
+-}
+-
+-static int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,
+-				 u16 clear, u16 set)
+-{
+-	u16 val;
+-	int ret;
+-
+-	ret = at803x_debug_reg_read(phydev, reg);
+-	if (ret < 0)
+-		return ret;
+-
+-	val = ret & 0xffff;
+-	val &= ~clear;
+-	val |= set;
+-
+-	return phy_write(phydev, AT803X_DEBUG_DATA, val);
+-}
+-
+ static int at803x_write_page(struct phy_device *phydev, int page)
+ {
+ 	int mask;
+--- /dev/null
++++ b/drivers/net/phy/qcom/qcom-phy-lib.c
+@@ -0,0 +1,53 @@
++// SPDX-License-Identifier: GPL-2.0
++
++#include <linux/phy.h>
++#include <linux/module.h>
++
++#include "qcom.h"
++
++MODULE_DESCRIPTION("Qualcomm PHY driver Common Functions");
++MODULE_AUTHOR("Matus Ujhelyi");
++MODULE_AUTHOR("Christian Marangi <[email protected]>");
++MODULE_LICENSE("GPL");
++
++int at803x_debug_reg_read(struct phy_device *phydev, u16 reg)
++{
++	int ret;
++
++	ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
++	if (ret < 0)
++		return ret;
++
++	return phy_read(phydev, AT803X_DEBUG_DATA);
++}
++EXPORT_SYMBOL_GPL(at803x_debug_reg_read);
++
++int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,
++			  u16 clear, u16 set)
++{
++	u16 val;
++	int ret;
++
++	ret = at803x_debug_reg_read(phydev, reg);
++	if (ret < 0)
++		return ret;
++
++	val = ret & 0xffff;
++	val &= ~clear;
++	val |= set;
++
++	return phy_write(phydev, AT803X_DEBUG_DATA, val);
++}
++EXPORT_SYMBOL_GPL(at803x_debug_reg_mask);
++
++int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data)
++{
++	int ret;
++
++	ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
++	if (ret < 0)
++		return ret;
++
++	return phy_write(phydev, AT803X_DEBUG_DATA, data);
++}
++EXPORT_SYMBOL_GPL(at803x_debug_reg_write);
+--- /dev/null
++++ b/drivers/net/phy/qcom/qcom.h
+@@ -0,0 +1,34 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++
++#define AT803X_DEBUG_ADDR			0x1D
++#define AT803X_DEBUG_DATA			0x1E
++
++#define AT803X_DEBUG_ANALOG_TEST_CTRL		0x00
++#define QCA8327_DEBUG_MANU_CTRL_EN		BIT(2)
++#define QCA8337_DEBUG_MANU_CTRL_EN		GENMASK(3, 2)
++#define AT803X_DEBUG_RX_CLK_DLY_EN		BIT(15)
++
++#define AT803X_DEBUG_SYSTEM_CTRL_MODE		0x05
++#define AT803X_DEBUG_TX_CLK_DLY_EN		BIT(8)
++
++#define AT803X_DEBUG_REG_HIB_CTRL		0x0b
++#define   AT803X_DEBUG_HIB_CTRL_SEL_RST_80U	BIT(10)
++#define   AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE	BIT(13)
++#define   AT803X_DEBUG_HIB_CTRL_PS_HIB_EN	BIT(15)
++
++enum stat_access_type {
++	PHY,
++	MMD
++};
++
++struct at803x_hw_stat {
++	const char *string;
++	u8 reg;
++	u32 mask;
++	enum stat_access_type access_type;
++};
++
++int at803x_debug_reg_read(struct phy_device *phydev, u16 reg);
++int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,
++			  u16 clear, u16 set);
++int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data);

+ 638 - 0
target/linux/generic/backport-6.6/713-v6.9-03-net-phy-qcom-deatch-qca83xx-PHY-driver-from-at803x.patch

@@ -0,0 +1,638 @@
+From 2e45d404d99d43bb7127b74b5dea8818df64996c Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Mon, 29 Jan 2024 15:15:21 +0100
+Subject: [PATCH 3/5] net: phy: qcom: deatch qca83xx PHY driver from at803x
+
+Deatch qca83xx PHY driver from at803x.
+
+The QCA83xx PHYs implement specific function and doesn't use generic
+at803x so it can be detached from the driver and moved to a dedicated
+one.
+
+Probe function and priv struct is reimplemented to allocate and use
+only the qca83xx specific data. Unused data from at803x PHY driver
+are dropped from at803x priv struct.
+
+This is to make slimmer PHY drivers instead of including lots of bloat
+that would never be used in specific SoC.
+
+A new Kconfig flag QCA83XX_PHY is introduced to compile the new
+introduced PHY driver.
+
+As the Kconfig name starts with Qualcomm the same order is kept.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/qcom/Kconfig   |  11 +-
+ drivers/net/phy/qcom/Makefile  |   1 +
+ drivers/net/phy/qcom/at803x.c  | 235 ----------------------------
+ drivers/net/phy/qcom/qca83xx.c | 275 +++++++++++++++++++++++++++++++++
+ 4 files changed, 284 insertions(+), 238 deletions(-)
+ create mode 100644 drivers/net/phy/qcom/qca83xx.c
+
+--- a/drivers/net/phy/qcom/Kconfig
++++ b/drivers/net/phy/qcom/Kconfig
+@@ -3,9 +3,14 @@ config QCOM_NET_PHYLIB
+ 	tristate
+ 
+ config AT803X_PHY
+-	tristate "Qualcomm Atheros AR803X PHYs and QCA833x PHYs"
++	tristate "Qualcomm Atheros AR803X PHYs"
+ 	select QCOM_NET_PHYLIB
+ 	depends on REGULATOR
+ 	help
+-	  Currently supports the AR8030, AR8031, AR8033, AR8035 and internal
+-	  QCA8337(Internal qca8k PHY) model
++	  Currently supports the AR8030, AR8031, AR8033, AR8035 model
++
++config QCA83XX_PHY
++	tristate "Qualcomm Atheros QCA833x PHYs"
++	select QCOM_NET_PHYLIB
++	help
++	  Currently supports the internal QCA8337(Internal qca8k PHY) model
+--- a/drivers/net/phy/qcom/Makefile
++++ b/drivers/net/phy/qcom/Makefile
+@@ -1,3 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0
+ obj-$(CONFIG_QCOM_NET_PHYLIB)	+= qcom-phy-lib.o
+ obj-$(CONFIG_AT803X_PHY)	+= at803x.o
++obj-$(CONFIG_QCA83XX_PHY)	+= qca83xx.o
+--- a/drivers/net/phy/qcom/at803x.c
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -102,17 +102,10 @@
+ #define AT803X_PSSR				0x11	/*PHY-Specific Status Register*/
+ #define AT803X_PSSR_MR_AN_COMPLETE		0x0200
+ 
+-#define AT803X_DEBUG_REG_3C			0x3C
+-
+-#define AT803X_DEBUG_REG_GREEN			0x3D
+-#define   AT803X_DEBUG_GATE_CLK_IN1000		BIT(6)
+-
+ #define AT803X_DEBUG_REG_1F			0x1F
+ #define AT803X_DEBUG_PLL_ON			BIT(2)
+ #define AT803X_DEBUG_RGMII_1V8			BIT(3)
+ 
+-#define MDIO_AZ_DEBUG				0x800D
+-
+ /* AT803x supports either the XTAL input pad, an internal PLL or the
+  * DSP as clock reference for the clock output pad. The XTAL reference
+  * is only used for 25 MHz output, all other frequencies need the PLL.
+@@ -163,13 +156,7 @@
+ 
+ #define QCA8081_PHY_ID				0x004dd101
+ 
+-#define QCA8327_A_PHY_ID			0x004dd033
+-#define QCA8327_B_PHY_ID			0x004dd034
+-#define QCA8337_PHY_ID				0x004dd036
+ #define QCA9561_PHY_ID				0x004dd042
+-#define QCA8K_PHY_ID_MASK			0xffffffff
+-
+-#define QCA8K_DEVFLAGS_REVISION_MASK		GENMASK(2, 0)
+ 
+ #define AT803X_PAGE_FIBER			0
+ #define AT803X_PAGE_COPPER			1
+@@ -379,12 +366,6 @@ MODULE_DESCRIPTION("Qualcomm Atheros AR8
+ MODULE_AUTHOR("Matus Ujhelyi");
+ MODULE_LICENSE("GPL");
+ 
+-static struct at803x_hw_stat qca83xx_hw_stats[] = {
+-	{ "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
+-	{ "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
+-	{ "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
+-};
+-
+ struct at803x_ss_mask {
+ 	u16 speed_mask;
+ 	u8 speed_shift;
+@@ -400,7 +381,6 @@ struct at803x_priv {
+ 	bool is_1000basex;
+ 	struct regulator_dev *vddio_rdev;
+ 	struct regulator_dev *vddh_rdev;
+-	u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
+ 	int led_polarity_mode;
+ };
+ 
+@@ -564,53 +544,6 @@ static void at803x_get_wol(struct phy_de
+ 		wol->wolopts |= WAKE_MAGIC;
+ }
+ 
+-static int qca83xx_get_sset_count(struct phy_device *phydev)
+-{
+-	return ARRAY_SIZE(qca83xx_hw_stats);
+-}
+-
+-static void qca83xx_get_strings(struct phy_device *phydev, u8 *data)
+-{
+-	int i;
+-
+-	for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) {
+-		strscpy(data + i * ETH_GSTRING_LEN,
+-			qca83xx_hw_stats[i].string, ETH_GSTRING_LEN);
+-	}
+-}
+-
+-static u64 qca83xx_get_stat(struct phy_device *phydev, int i)
+-{
+-	struct at803x_hw_stat stat = qca83xx_hw_stats[i];
+-	struct at803x_priv *priv = phydev->priv;
+-	int val;
+-	u64 ret;
+-
+-	if (stat.access_type == MMD)
+-		val = phy_read_mmd(phydev, MDIO_MMD_PCS, stat.reg);
+-	else
+-		val = phy_read(phydev, stat.reg);
+-
+-	if (val < 0) {
+-		ret = U64_MAX;
+-	} else {
+-		val = val & stat.mask;
+-		priv->stats[i] += val;
+-		ret = priv->stats[i];
+-	}
+-
+-	return ret;
+-}
+-
+-static void qca83xx_get_stats(struct phy_device *phydev,
+-			      struct ethtool_stats *stats, u64 *data)
+-{
+-	int i;
+-
+-	for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++)
+-		data[i] = qca83xx_get_stat(phydev, i);
+-}
+-
+ static int at803x_suspend(struct phy_device *phydev)
+ {
+ 	int value;
+@@ -1707,124 +1640,6 @@ static int at8035_probe(struct phy_devic
+ 	return at8035_parse_dt(phydev);
+ }
+ 
+-static int qca83xx_config_init(struct phy_device *phydev)
+-{
+-	u8 switch_revision;
+-
+-	switch_revision = phydev->dev_flags & QCA8K_DEVFLAGS_REVISION_MASK;
+-
+-	switch (switch_revision) {
+-	case 1:
+-		/* For 100M waveform */
+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0x02ea);
+-		/* Turn on Gigabit clock */
+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x68a0);
+-		break;
+-
+-	case 2:
+-		phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0);
+-		fallthrough;
+-	case 4:
+-		phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f);
+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x6860);
+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0x2c46);
+-		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3C, 0x6000);
+-		break;
+-	}
+-
+-	/* Following original QCA sourcecode set port to prefer master */
+-	phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
+-
+-	return 0;
+-}
+-
+-static int qca8327_config_init(struct phy_device *phydev)
+-{
+-	/* QCA8327 require DAC amplitude adjustment for 100m set to +6%.
+-	 * Disable on init and enable only with 100m speed following
+-	 * qca original source code.
+-	 */
+-	at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+-			      QCA8327_DEBUG_MANU_CTRL_EN, 0);
+-
+-	return qca83xx_config_init(phydev);
+-}
+-
+-static void qca83xx_link_change_notify(struct phy_device *phydev)
+-{
+-	/* Set DAC Amplitude adjustment to +6% for 100m on link running */
+-	if (phydev->state == PHY_RUNNING) {
+-		if (phydev->speed == SPEED_100)
+-			at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+-					      QCA8327_DEBUG_MANU_CTRL_EN,
+-					      QCA8327_DEBUG_MANU_CTRL_EN);
+-	} else {
+-		/* Reset DAC Amplitude adjustment */
+-		at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+-				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
+-	}
+-}
+-
+-static int qca83xx_resume(struct phy_device *phydev)
+-{
+-	int ret, val;
+-
+-	/* Skip reset if not suspended */
+-	if (!phydev->suspended)
+-		return 0;
+-
+-	/* Reinit the port, reset values set by suspend */
+-	qca83xx_config_init(phydev);
+-
+-	/* Reset the port on port resume */
+-	phy_set_bits(phydev, MII_BMCR, BMCR_RESET | BMCR_ANENABLE);
+-
+-	/* On resume from suspend the switch execute a reset and
+-	 * restart auto-negotiation. Wait for reset to complete.
+-	 */
+-	ret = phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET),
+-				    50000, 600000, true);
+-	if (ret)
+-		return ret;
+-
+-	usleep_range(1000, 2000);
+-
+-	return 0;
+-}
+-
+-static int qca83xx_suspend(struct phy_device *phydev)
+-{
+-	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_GREEN,
+-			      AT803X_DEBUG_GATE_CLK_IN1000, 0);
+-
+-	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
+-			      AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE |
+-			      AT803X_DEBUG_HIB_CTRL_SEL_RST_80U, 0);
+-
+-	return 0;
+-}
+-
+-static int qca8337_suspend(struct phy_device *phydev)
+-{
+-	/* Only QCA8337 support actual suspend. */
+-	genphy_suspend(phydev);
+-
+-	return qca83xx_suspend(phydev);
+-}
+-
+-static int qca8327_suspend(struct phy_device *phydev)
+-{
+-	u16 mask = 0;
+-
+-	/* QCA8327 cause port unreliability when phy suspend
+-	 * is set.
+-	 */
+-	mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
+-	phy_modify(phydev, MII_BMCR, mask, 0);
+-
+-	return qca83xx_suspend(phydev);
+-}
+-
+ static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
+ {
+ 	int ret;
+@@ -2599,53 +2414,6 @@ static struct phy_driver at803x_driver[]
+ 	.soft_reset		= genphy_soft_reset,
+ 	.config_aneg		= at803x_config_aneg,
+ }, {
+-	/* QCA8337 */
+-	.phy_id			= QCA8337_PHY_ID,
+-	.phy_id_mask		= QCA8K_PHY_ID_MASK,
+-	.name			= "Qualcomm Atheros 8337 internal PHY",
+-	/* PHY_GBIT_FEATURES */
+-	.probe			= at803x_probe,
+-	.flags			= PHY_IS_INTERNAL,
+-	.config_init		= qca83xx_config_init,
+-	.soft_reset		= genphy_soft_reset,
+-	.get_sset_count		= qca83xx_get_sset_count,
+-	.get_strings		= qca83xx_get_strings,
+-	.get_stats		= qca83xx_get_stats,
+-	.suspend		= qca8337_suspend,
+-	.resume			= qca83xx_resume,
+-}, {
+-	/* QCA8327-A from switch QCA8327-AL1A */
+-	.phy_id			= QCA8327_A_PHY_ID,
+-	.phy_id_mask		= QCA8K_PHY_ID_MASK,
+-	.name			= "Qualcomm Atheros 8327-A internal PHY",
+-	/* PHY_GBIT_FEATURES */
+-	.link_change_notify	= qca83xx_link_change_notify,
+-	.probe			= at803x_probe,
+-	.flags			= PHY_IS_INTERNAL,
+-	.config_init		= qca8327_config_init,
+-	.soft_reset		= genphy_soft_reset,
+-	.get_sset_count		= qca83xx_get_sset_count,
+-	.get_strings		= qca83xx_get_strings,
+-	.get_stats		= qca83xx_get_stats,
+-	.suspend		= qca8327_suspend,
+-	.resume			= qca83xx_resume,
+-}, {
+-	/* QCA8327-B from switch QCA8327-BL1A */
+-	.phy_id			= QCA8327_B_PHY_ID,
+-	.phy_id_mask		= QCA8K_PHY_ID_MASK,
+-	.name			= "Qualcomm Atheros 8327-B internal PHY",
+-	/* PHY_GBIT_FEATURES */
+-	.link_change_notify	= qca83xx_link_change_notify,
+-	.probe			= at803x_probe,
+-	.flags			= PHY_IS_INTERNAL,
+-	.config_init		= qca8327_config_init,
+-	.soft_reset		= genphy_soft_reset,
+-	.get_sset_count		= qca83xx_get_sset_count,
+-	.get_strings		= qca83xx_get_strings,
+-	.get_stats		= qca83xx_get_stats,
+-	.suspend		= qca8327_suspend,
+-	.resume			= qca83xx_resume,
+-}, {
+ 	/* Qualcomm QCA8081 */
+ 	PHY_ID_MATCH_EXACT(QCA8081_PHY_ID),
+ 	.name			= "Qualcomm QCA8081",
+@@ -2683,9 +2451,6 @@ static struct mdio_device_id __maybe_unu
+ 	{ PHY_ID_MATCH_EXACT(ATH8032_PHY_ID) },
+ 	{ PHY_ID_MATCH_EXACT(ATH8035_PHY_ID) },
+ 	{ PHY_ID_MATCH_EXACT(ATH9331_PHY_ID) },
+-	{ PHY_ID_MATCH_EXACT(QCA8337_PHY_ID) },
+-	{ PHY_ID_MATCH_EXACT(QCA8327_A_PHY_ID) },
+-	{ PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) },
+ 	{ PHY_ID_MATCH_EXACT(QCA9561_PHY_ID) },
+ 	{ PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) },
+ 	{ }
+--- /dev/null
++++ b/drivers/net/phy/qcom/qca83xx.c
+@@ -0,0 +1,275 @@
++// SPDX-License-Identifier: GPL-2.0+
++
++#include <linux/phy.h>
++#include <linux/module.h>
++
++#include "qcom.h"
++
++#define AT803X_DEBUG_REG_3C			0x3C
++
++#define AT803X_DEBUG_REG_GREEN			0x3D
++#define   AT803X_DEBUG_GATE_CLK_IN1000		BIT(6)
++
++#define MDIO_AZ_DEBUG				0x800D
++
++#define QCA8327_A_PHY_ID			0x004dd033
++#define QCA8327_B_PHY_ID			0x004dd034
++#define QCA8337_PHY_ID				0x004dd036
++#define QCA8K_PHY_ID_MASK			0xffffffff
++
++#define QCA8K_DEVFLAGS_REVISION_MASK		GENMASK(2, 0)
++
++static struct at803x_hw_stat qca83xx_hw_stats[] = {
++	{ "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
++	{ "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
++	{ "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
++};
++
++struct qca83xx_priv {
++	u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
++};
++
++MODULE_DESCRIPTION("Qualcomm Atheros QCA83XX PHY driver");
++MODULE_AUTHOR("Matus Ujhelyi");
++MODULE_AUTHOR("Christian Marangi <[email protected]>");
++MODULE_LICENSE("GPL");
++
++static int qca83xx_get_sset_count(struct phy_device *phydev)
++{
++	return ARRAY_SIZE(qca83xx_hw_stats);
++}
++
++static void qca83xx_get_strings(struct phy_device *phydev, u8 *data)
++{
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) {
++		strscpy(data + i * ETH_GSTRING_LEN,
++			qca83xx_hw_stats[i].string, ETH_GSTRING_LEN);
++	}
++}
++
++static u64 qca83xx_get_stat(struct phy_device *phydev, int i)
++{
++	struct at803x_hw_stat stat = qca83xx_hw_stats[i];
++	struct qca83xx_priv *priv = phydev->priv;
++	int val;
++	u64 ret;
++
++	if (stat.access_type == MMD)
++		val = phy_read_mmd(phydev, MDIO_MMD_PCS, stat.reg);
++	else
++		val = phy_read(phydev, stat.reg);
++
++	if (val < 0) {
++		ret = U64_MAX;
++	} else {
++		val = val & stat.mask;
++		priv->stats[i] += val;
++		ret = priv->stats[i];
++	}
++
++	return ret;
++}
++
++static void qca83xx_get_stats(struct phy_device *phydev,
++			      struct ethtool_stats *stats, u64 *data)
++{
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++)
++		data[i] = qca83xx_get_stat(phydev, i);
++}
++
++static int qca83xx_probe(struct phy_device *phydev)
++{
++	struct device *dev = &phydev->mdio.dev;
++	struct qca83xx_priv *priv;
++
++	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++	if (!priv)
++		return -ENOMEM;
++
++	phydev->priv = priv;
++
++	return 0;
++}
++
++static int qca83xx_config_init(struct phy_device *phydev)
++{
++	u8 switch_revision;
++
++	switch_revision = phydev->dev_flags & QCA8K_DEVFLAGS_REVISION_MASK;
++
++	switch (switch_revision) {
++	case 1:
++		/* For 100M waveform */
++		at803x_debug_reg_write(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0x02ea);
++		/* Turn on Gigabit clock */
++		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x68a0);
++		break;
++
++	case 2:
++		phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0);
++		fallthrough;
++	case 4:
++		phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f);
++		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x6860);
++		at803x_debug_reg_write(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0x2c46);
++		at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3C, 0x6000);
++		break;
++	}
++
++	/* Following original QCA sourcecode set port to prefer master */
++	phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
++
++	return 0;
++}
++
++static int qca8327_config_init(struct phy_device *phydev)
++{
++	/* QCA8327 require DAC amplitude adjustment for 100m set to +6%.
++	 * Disable on init and enable only with 100m speed following
++	 * qca original source code.
++	 */
++	at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++			      QCA8327_DEBUG_MANU_CTRL_EN, 0);
++
++	return qca83xx_config_init(phydev);
++}
++
++static void qca83xx_link_change_notify(struct phy_device *phydev)
++{
++	/* Set DAC Amplitude adjustment to +6% for 100m on link running */
++	if (phydev->state == PHY_RUNNING) {
++		if (phydev->speed == SPEED_100)
++			at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++					      QCA8327_DEBUG_MANU_CTRL_EN,
++					      QCA8327_DEBUG_MANU_CTRL_EN);
++	} else {
++		/* Reset DAC Amplitude adjustment */
++		at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
++				      QCA8327_DEBUG_MANU_CTRL_EN, 0);
++	}
++}
++
++static int qca83xx_resume(struct phy_device *phydev)
++{
++	int ret, val;
++
++	/* Skip reset if not suspended */
++	if (!phydev->suspended)
++		return 0;
++
++	/* Reinit the port, reset values set by suspend */
++	qca83xx_config_init(phydev);
++
++	/* Reset the port on port resume */
++	phy_set_bits(phydev, MII_BMCR, BMCR_RESET | BMCR_ANENABLE);
++
++	/* On resume from suspend the switch execute a reset and
++	 * restart auto-negotiation. Wait for reset to complete.
++	 */
++	ret = phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET),
++				    50000, 600000, true);
++	if (ret)
++		return ret;
++
++	usleep_range(1000, 2000);
++
++	return 0;
++}
++
++static int qca83xx_suspend(struct phy_device *phydev)
++{
++	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_GREEN,
++			      AT803X_DEBUG_GATE_CLK_IN1000, 0);
++
++	at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
++			      AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE |
++			      AT803X_DEBUG_HIB_CTRL_SEL_RST_80U, 0);
++
++	return 0;
++}
++
++static int qca8337_suspend(struct phy_device *phydev)
++{
++	/* Only QCA8337 support actual suspend. */
++	genphy_suspend(phydev);
++
++	return qca83xx_suspend(phydev);
++}
++
++static int qca8327_suspend(struct phy_device *phydev)
++{
++	u16 mask = 0;
++
++	/* QCA8327 cause port unreliability when phy suspend
++	 * is set.
++	 */
++	mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
++	phy_modify(phydev, MII_BMCR, mask, 0);
++
++	return qca83xx_suspend(phydev);
++}
++
++static struct phy_driver qca83xx_driver[] = {
++{
++	/* QCA8337 */
++	.phy_id			= QCA8337_PHY_ID,
++	.phy_id_mask		= QCA8K_PHY_ID_MASK,
++	.name			= "Qualcomm Atheros 8337 internal PHY",
++	/* PHY_GBIT_FEATURES */
++	.probe			= qca83xx_probe,
++	.flags			= PHY_IS_INTERNAL,
++	.config_init		= qca83xx_config_init,
++	.soft_reset		= genphy_soft_reset,
++	.get_sset_count		= qca83xx_get_sset_count,
++	.get_strings		= qca83xx_get_strings,
++	.get_stats		= qca83xx_get_stats,
++	.suspend		= qca8337_suspend,
++	.resume			= qca83xx_resume,
++}, {
++	/* QCA8327-A from switch QCA8327-AL1A */
++	.phy_id			= QCA8327_A_PHY_ID,
++	.phy_id_mask		= QCA8K_PHY_ID_MASK,
++	.name			= "Qualcomm Atheros 8327-A internal PHY",
++	/* PHY_GBIT_FEATURES */
++	.link_change_notify	= qca83xx_link_change_notify,
++	.probe			= qca83xx_probe,
++	.flags			= PHY_IS_INTERNAL,
++	.config_init		= qca8327_config_init,
++	.soft_reset		= genphy_soft_reset,
++	.get_sset_count		= qca83xx_get_sset_count,
++	.get_strings		= qca83xx_get_strings,
++	.get_stats		= qca83xx_get_stats,
++	.suspend		= qca8327_suspend,
++	.resume			= qca83xx_resume,
++}, {
++	/* QCA8327-B from switch QCA8327-BL1A */
++	.phy_id			= QCA8327_B_PHY_ID,
++	.phy_id_mask		= QCA8K_PHY_ID_MASK,
++	.name			= "Qualcomm Atheros 8327-B internal PHY",
++	/* PHY_GBIT_FEATURES */
++	.link_change_notify	= qca83xx_link_change_notify,
++	.probe			= qca83xx_probe,
++	.flags			= PHY_IS_INTERNAL,
++	.config_init		= qca8327_config_init,
++	.soft_reset		= genphy_soft_reset,
++	.get_sset_count		= qca83xx_get_sset_count,
++	.get_strings		= qca83xx_get_strings,
++	.get_stats		= qca83xx_get_stats,
++	.suspend		= qca8327_suspend,
++	.resume			= qca83xx_resume,
++}, };
++
++module_phy_driver(qca83xx_driver);
++
++static struct mdio_device_id __maybe_unused qca83xx_tbl[] = {
++	{ PHY_ID_MATCH_EXACT(QCA8337_PHY_ID) },
++	{ PHY_ID_MATCH_EXACT(QCA8327_A_PHY_ID) },
++	{ PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) },
++	{ }
++};
++
++MODULE_DEVICE_TABLE(mdio, qca83xx_tbl);

+ 1014 - 0
target/linux/generic/backport-6.6/713-v6.9-04-net-phy-qcom-move-additional-functions-to-shared-lib.patch

@@ -0,0 +1,1014 @@
+From 249d2b80e4db0e38503ed0ec2af6c7401bc099b9 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Mon, 29 Jan 2024 15:15:22 +0100
+Subject: [PATCH 4/5] net: phy: qcom: move additional functions to shared
+ library
+
+Move additional functions to shared library in preparation for qca808x
+PHY Family to be detached from at803x driver.
+
+Only the shared defines are moved to the shared qcom.h header.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/qcom/at803x.c       | 426 +---------------------------
+ drivers/net/phy/qcom/qcom-phy-lib.c | 376 ++++++++++++++++++++++++
+ drivers/net/phy/qcom/qcom.h         |  86 ++++++
+ 3 files changed, 463 insertions(+), 425 deletions(-)
+
+--- a/drivers/net/phy/qcom/at803x.c
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -24,65 +24,11 @@
+ 
+ #include "qcom.h"
+ 
+-#define AT803X_SPECIFIC_FUNCTION_CONTROL	0x10
+-#define AT803X_SFC_ASSERT_CRS			BIT(11)
+-#define AT803X_SFC_FORCE_LINK			BIT(10)
+-#define AT803X_SFC_MDI_CROSSOVER_MODE_M		GENMASK(6, 5)
+-#define AT803X_SFC_AUTOMATIC_CROSSOVER		0x3
+-#define AT803X_SFC_MANUAL_MDIX			0x1
+-#define AT803X_SFC_MANUAL_MDI			0x0
+-#define AT803X_SFC_SQE_TEST			BIT(2)
+-#define AT803X_SFC_POLARITY_REVERSAL		BIT(1)
+-#define AT803X_SFC_DISABLE_JABBER		BIT(0)
+-
+-#define AT803X_SPECIFIC_STATUS			0x11
+-#define AT803X_SS_SPEED_MASK			GENMASK(15, 14)
+-#define AT803X_SS_SPEED_1000			2
+-#define AT803X_SS_SPEED_100			1
+-#define AT803X_SS_SPEED_10			0
+-#define AT803X_SS_DUPLEX			BIT(13)
+-#define AT803X_SS_SPEED_DUPLEX_RESOLVED		BIT(11)
+-#define AT803X_SS_MDIX				BIT(6)
+-
+-#define QCA808X_SS_SPEED_MASK			GENMASK(9, 7)
+-#define QCA808X_SS_SPEED_2500			4
+-
+-#define AT803X_INTR_ENABLE			0x12
+-#define AT803X_INTR_ENABLE_AUTONEG_ERR		BIT(15)
+-#define AT803X_INTR_ENABLE_SPEED_CHANGED	BIT(14)
+-#define AT803X_INTR_ENABLE_DUPLEX_CHANGED	BIT(13)
+-#define AT803X_INTR_ENABLE_PAGE_RECEIVED	BIT(12)
+-#define AT803X_INTR_ENABLE_LINK_FAIL		BIT(11)
+-#define AT803X_INTR_ENABLE_LINK_SUCCESS		BIT(10)
+-#define AT803X_INTR_ENABLE_LINK_FAIL_BX		BIT(8)
+-#define AT803X_INTR_ENABLE_LINK_SUCCESS_BX	BIT(7)
+-#define AT803X_INTR_ENABLE_WIRESPEED_DOWNGRADE	BIT(5)
+-#define AT803X_INTR_ENABLE_POLARITY_CHANGED	BIT(1)
+-#define AT803X_INTR_ENABLE_WOL			BIT(0)
+-
+-#define AT803X_INTR_STATUS			0x13
+-
+-#define AT803X_SMART_SPEED			0x14
+-#define AT803X_SMART_SPEED_ENABLE		BIT(5)
+-#define AT803X_SMART_SPEED_RETRY_LIMIT_MASK	GENMASK(4, 2)
+-#define AT803X_SMART_SPEED_BYPASS_TIMER		BIT(1)
+-#define AT803X_CDT				0x16
+-#define AT803X_CDT_MDI_PAIR_MASK		GENMASK(9, 8)
+-#define AT803X_CDT_ENABLE_TEST			BIT(0)
+-#define AT803X_CDT_STATUS			0x1c
+-#define AT803X_CDT_STATUS_STAT_NORMAL		0
+-#define AT803X_CDT_STATUS_STAT_SHORT		1
+-#define AT803X_CDT_STATUS_STAT_OPEN		2
+-#define AT803X_CDT_STATUS_STAT_FAIL		3
+-#define AT803X_CDT_STATUS_STAT_MASK		GENMASK(9, 8)
+-#define AT803X_CDT_STATUS_DELTA_TIME_MASK	GENMASK(7, 0)
+ #define AT803X_LED_CONTROL			0x18
+ 
+ #define AT803X_PHY_MMD3_WOL_CTRL		0x8012
+ #define AT803X_WOL_EN				BIT(5)
+-#define AT803X_LOC_MAC_ADDR_0_15_OFFSET		0x804C
+-#define AT803X_LOC_MAC_ADDR_16_31_OFFSET	0x804B
+-#define AT803X_LOC_MAC_ADDR_32_47_OFFSET	0x804A
++
+ #define AT803X_REG_CHIP_CONFIG			0x1f
+ #define AT803X_BT_BX_REG_SEL			0x8000
+ 
+@@ -138,10 +84,6 @@
+ #define AT803X_CLK_OUT_STRENGTH_HALF		1
+ #define AT803X_CLK_OUT_STRENGTH_QUARTER		2
+ 
+-#define AT803X_DEFAULT_DOWNSHIFT		5
+-#define AT803X_MIN_DOWNSHIFT			2
+-#define AT803X_MAX_DOWNSHIFT			9
+-
+ #define AT803X_MMD3_SMARTEEE_CTL1		0x805b
+ #define AT803X_MMD3_SMARTEEE_CTL2		0x805c
+ #define AT803X_MMD3_SMARTEEE_CTL3		0x805d
+@@ -366,11 +308,6 @@ MODULE_DESCRIPTION("Qualcomm Atheros AR8
+ MODULE_AUTHOR("Matus Ujhelyi");
+ MODULE_LICENSE("GPL");
+ 
+-struct at803x_ss_mask {
+-	u16 speed_mask;
+-	u8 speed_shift;
+-};
+-
+ struct at803x_priv {
+ 	int flags;
+ 	u16 clk_25m_reg;
+@@ -470,80 +407,6 @@ static void at803x_context_restore(struc
+ 	phy_write(phydev, AT803X_LED_CONTROL, context->led_control);
+ }
+ 
+-static int at803x_set_wol(struct phy_device *phydev,
+-			  struct ethtool_wolinfo *wol)
+-{
+-	int ret, irq_enabled;
+-
+-	if (wol->wolopts & WAKE_MAGIC) {
+-		struct net_device *ndev = phydev->attached_dev;
+-		const u8 *mac;
+-		unsigned int i;
+-		static const unsigned int offsets[] = {
+-			AT803X_LOC_MAC_ADDR_32_47_OFFSET,
+-			AT803X_LOC_MAC_ADDR_16_31_OFFSET,
+-			AT803X_LOC_MAC_ADDR_0_15_OFFSET,
+-		};
+-
+-		if (!ndev)
+-			return -ENODEV;
+-
+-		mac = (const u8 *)ndev->dev_addr;
+-
+-		if (!is_valid_ether_addr(mac))
+-			return -EINVAL;
+-
+-		for (i = 0; i < 3; i++)
+-			phy_write_mmd(phydev, MDIO_MMD_PCS, offsets[i],
+-				      mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
+-
+-		/* Enable WOL interrupt */
+-		ret = phy_modify(phydev, AT803X_INTR_ENABLE, 0, AT803X_INTR_ENABLE_WOL);
+-		if (ret)
+-			return ret;
+-	} else {
+-		/* Disable WOL interrupt */
+-		ret = phy_modify(phydev, AT803X_INTR_ENABLE, AT803X_INTR_ENABLE_WOL, 0);
+-		if (ret)
+-			return ret;
+-	}
+-
+-	/* Clear WOL status */
+-	ret = phy_read(phydev, AT803X_INTR_STATUS);
+-	if (ret < 0)
+-		return ret;
+-
+-	/* Check if there are other interrupts except for WOL triggered when PHY is
+-	 * in interrupt mode, only the interrupts enabled by AT803X_INTR_ENABLE can
+-	 * be passed up to the interrupt PIN.
+-	 */
+-	irq_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
+-	if (irq_enabled < 0)
+-		return irq_enabled;
+-
+-	irq_enabled &= ~AT803X_INTR_ENABLE_WOL;
+-	if (ret & irq_enabled && !phy_polling_mode(phydev))
+-		phy_trigger_machine(phydev);
+-
+-	return 0;
+-}
+-
+-static void at803x_get_wol(struct phy_device *phydev,
+-			   struct ethtool_wolinfo *wol)
+-{
+-	int value;
+-
+-	wol->supported = WAKE_MAGIC;
+-	wol->wolopts = 0;
+-
+-	value = phy_read(phydev, AT803X_INTR_ENABLE);
+-	if (value < 0)
+-		return;
+-
+-	if (value & AT803X_INTR_ENABLE_WOL)
+-		wol->wolopts |= WAKE_MAGIC;
+-}
+-
+ static int at803x_suspend(struct phy_device *phydev)
+ {
+ 	int value;
+@@ -816,73 +679,6 @@ static int at803x_config_init(struct phy
+ 	return phy_modify(phydev, MII_ADVERTISE, MDIO_AN_CTRL1_XNP, 0);
+ }
+ 
+-static int at803x_ack_interrupt(struct phy_device *phydev)
+-{
+-	int err;
+-
+-	err = phy_read(phydev, AT803X_INTR_STATUS);
+-
+-	return (err < 0) ? err : 0;
+-}
+-
+-static int at803x_config_intr(struct phy_device *phydev)
+-{
+-	int err;
+-	int value;
+-
+-	value = phy_read(phydev, AT803X_INTR_ENABLE);
+-
+-	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+-		/* Clear any pending interrupts */
+-		err = at803x_ack_interrupt(phydev);
+-		if (err)
+-			return err;
+-
+-		value |= AT803X_INTR_ENABLE_AUTONEG_ERR;
+-		value |= AT803X_INTR_ENABLE_SPEED_CHANGED;
+-		value |= AT803X_INTR_ENABLE_DUPLEX_CHANGED;
+-		value |= AT803X_INTR_ENABLE_LINK_FAIL;
+-		value |= AT803X_INTR_ENABLE_LINK_SUCCESS;
+-
+-		err = phy_write(phydev, AT803X_INTR_ENABLE, value);
+-	} else {
+-		err = phy_write(phydev, AT803X_INTR_ENABLE, 0);
+-		if (err)
+-			return err;
+-
+-		/* Clear any pending interrupts */
+-		err = at803x_ack_interrupt(phydev);
+-	}
+-
+-	return err;
+-}
+-
+-static irqreturn_t at803x_handle_interrupt(struct phy_device *phydev)
+-{
+-	int irq_status, int_enabled;
+-
+-	irq_status = phy_read(phydev, AT803X_INTR_STATUS);
+-	if (irq_status < 0) {
+-		phy_error(phydev);
+-		return IRQ_NONE;
+-	}
+-
+-	/* Read the current enabled interrupts */
+-	int_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
+-	if (int_enabled < 0) {
+-		phy_error(phydev);
+-		return IRQ_NONE;
+-	}
+-
+-	/* See if this was one of our enabled interrupts */
+-	if (!(irq_status & int_enabled))
+-		return IRQ_NONE;
+-
+-	phy_trigger_machine(phydev);
+-
+-	return IRQ_HANDLED;
+-}
+-
+ static void at803x_link_change_notify(struct phy_device *phydev)
+ {
+ 	/*
+@@ -908,69 +704,6 @@ static void at803x_link_change_notify(st
+ 	}
+ }
+ 
+-static int at803x_read_specific_status(struct phy_device *phydev,
+-				       struct at803x_ss_mask ss_mask)
+-{
+-	int ss;
+-
+-	/* Read the AT8035 PHY-Specific Status register, which indicates the
+-	 * speed and duplex that the PHY is actually using, irrespective of
+-	 * whether we are in autoneg mode or not.
+-	 */
+-	ss = phy_read(phydev, AT803X_SPECIFIC_STATUS);
+-	if (ss < 0)
+-		return ss;
+-
+-	if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) {
+-		int sfc, speed;
+-
+-		sfc = phy_read(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL);
+-		if (sfc < 0)
+-			return sfc;
+-
+-		speed = ss & ss_mask.speed_mask;
+-		speed >>= ss_mask.speed_shift;
+-
+-		switch (speed) {
+-		case AT803X_SS_SPEED_10:
+-			phydev->speed = SPEED_10;
+-			break;
+-		case AT803X_SS_SPEED_100:
+-			phydev->speed = SPEED_100;
+-			break;
+-		case AT803X_SS_SPEED_1000:
+-			phydev->speed = SPEED_1000;
+-			break;
+-		case QCA808X_SS_SPEED_2500:
+-			phydev->speed = SPEED_2500;
+-			break;
+-		}
+-		if (ss & AT803X_SS_DUPLEX)
+-			phydev->duplex = DUPLEX_FULL;
+-		else
+-			phydev->duplex = DUPLEX_HALF;
+-
+-		if (ss & AT803X_SS_MDIX)
+-			phydev->mdix = ETH_TP_MDI_X;
+-		else
+-			phydev->mdix = ETH_TP_MDI;
+-
+-		switch (FIELD_GET(AT803X_SFC_MDI_CROSSOVER_MODE_M, sfc)) {
+-		case AT803X_SFC_MANUAL_MDI:
+-			phydev->mdix_ctrl = ETH_TP_MDI;
+-			break;
+-		case AT803X_SFC_MANUAL_MDIX:
+-			phydev->mdix_ctrl = ETH_TP_MDI_X;
+-			break;
+-		case AT803X_SFC_AUTOMATIC_CROSSOVER:
+-			phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+-			break;
+-		}
+-	}
+-
+-	return 0;
+-}
+-
+ static int at803x_read_status(struct phy_device *phydev)
+ {
+ 	struct at803x_ss_mask ss_mask = { 0 };
+@@ -1006,50 +739,6 @@ static int at803x_read_status(struct phy
+ 	return 0;
+ }
+ 
+-static int at803x_config_mdix(struct phy_device *phydev, u8 ctrl)
+-{
+-	u16 val;
+-
+-	switch (ctrl) {
+-	case ETH_TP_MDI:
+-		val = AT803X_SFC_MANUAL_MDI;
+-		break;
+-	case ETH_TP_MDI_X:
+-		val = AT803X_SFC_MANUAL_MDIX;
+-		break;
+-	case ETH_TP_MDI_AUTO:
+-		val = AT803X_SFC_AUTOMATIC_CROSSOVER;
+-		break;
+-	default:
+-		return 0;
+-	}
+-
+-	return phy_modify_changed(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL,
+-			  AT803X_SFC_MDI_CROSSOVER_MODE_M,
+-			  FIELD_PREP(AT803X_SFC_MDI_CROSSOVER_MODE_M, val));
+-}
+-
+-static int at803x_prepare_config_aneg(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	ret = at803x_config_mdix(phydev, phydev->mdix_ctrl);
+-	if (ret < 0)
+-		return ret;
+-
+-	/* Changes of the midx bits are disruptive to the normal operation;
+-	 * therefore any changes to these registers must be followed by a
+-	 * software reset to take effect.
+-	 */
+-	if (ret == 1) {
+-		ret = genphy_soft_reset(phydev);
+-		if (ret < 0)
+-			return ret;
+-	}
+-
+-	return 0;
+-}
+-
+ static int at803x_config_aneg(struct phy_device *phydev)
+ {
+ 	struct at803x_priv *priv = phydev->priv;
+@@ -1065,80 +754,6 @@ static int at803x_config_aneg(struct phy
+ 	return genphy_config_aneg(phydev);
+ }
+ 
+-static int at803x_get_downshift(struct phy_device *phydev, u8 *d)
+-{
+-	int val;
+-
+-	val = phy_read(phydev, AT803X_SMART_SPEED);
+-	if (val < 0)
+-		return val;
+-
+-	if (val & AT803X_SMART_SPEED_ENABLE)
+-		*d = FIELD_GET(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, val) + 2;
+-	else
+-		*d = DOWNSHIFT_DEV_DISABLE;
+-
+-	return 0;
+-}
+-
+-static int at803x_set_downshift(struct phy_device *phydev, u8 cnt)
+-{
+-	u16 mask, set;
+-	int ret;
+-
+-	switch (cnt) {
+-	case DOWNSHIFT_DEV_DEFAULT_COUNT:
+-		cnt = AT803X_DEFAULT_DOWNSHIFT;
+-		fallthrough;
+-	case AT803X_MIN_DOWNSHIFT ... AT803X_MAX_DOWNSHIFT:
+-		set = AT803X_SMART_SPEED_ENABLE |
+-		      AT803X_SMART_SPEED_BYPASS_TIMER |
+-		      FIELD_PREP(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, cnt - 2);
+-		mask = AT803X_SMART_SPEED_RETRY_LIMIT_MASK;
+-		break;
+-	case DOWNSHIFT_DEV_DISABLE:
+-		set = 0;
+-		mask = AT803X_SMART_SPEED_ENABLE |
+-		       AT803X_SMART_SPEED_BYPASS_TIMER;
+-		break;
+-	default:
+-		return -EINVAL;
+-	}
+-
+-	ret = phy_modify_changed(phydev, AT803X_SMART_SPEED, mask, set);
+-
+-	/* After changing the smart speed settings, we need to perform a
+-	 * software reset, use phy_init_hw() to make sure we set the
+-	 * reapply any values which might got lost during software reset.
+-	 */
+-	if (ret == 1)
+-		ret = phy_init_hw(phydev);
+-
+-	return ret;
+-}
+-
+-static int at803x_get_tunable(struct phy_device *phydev,
+-			      struct ethtool_tunable *tuna, void *data)
+-{
+-	switch (tuna->id) {
+-	case ETHTOOL_PHY_DOWNSHIFT:
+-		return at803x_get_downshift(phydev, data);
+-	default:
+-		return -EOPNOTSUPP;
+-	}
+-}
+-
+-static int at803x_set_tunable(struct phy_device *phydev,
+-			      struct ethtool_tunable *tuna, const void *data)
+-{
+-	switch (tuna->id) {
+-	case ETHTOOL_PHY_DOWNSHIFT:
+-		return at803x_set_downshift(phydev, *(const u8 *)data);
+-	default:
+-		return -EOPNOTSUPP;
+-	}
+-}
+-
+ static int at803x_cable_test_result_trans(u16 status)
+ {
+ 	switch (FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status)) {
+@@ -1170,45 +785,6 @@ static bool at803x_cdt_fault_length_vali
+ 	return false;
+ }
+ 
+-static int at803x_cdt_fault_length(int dt)
+-{
+-	/* According to the datasheet the distance to the fault is
+-	 * DELTA_TIME * 0.824 meters.
+-	 *
+-	 * The author suspect the correct formula is:
+-	 *
+-	 *   fault_distance = DELTA_TIME * (c * VF) / 125MHz / 2
+-	 *
+-	 * where c is the speed of light, VF is the velocity factor of
+-	 * the twisted pair cable, 125MHz the counter frequency and
+-	 * we need to divide by 2 because the hardware will measure the
+-	 * round trip time to the fault and back to the PHY.
+-	 *
+-	 * With a VF of 0.69 we get the factor 0.824 mentioned in the
+-	 * datasheet.
+-	 */
+-	return (dt * 824) / 10;
+-}
+-
+-static int at803x_cdt_start(struct phy_device *phydev,
+-			    u32 cdt_start)
+-{
+-	return phy_write(phydev, AT803X_CDT, cdt_start);
+-}
+-
+-static int at803x_cdt_wait_for_completion(struct phy_device *phydev,
+-					  u32 cdt_en)
+-{
+-	int val, ret;
+-
+-	/* One test run takes about 25ms */
+-	ret = phy_read_poll_timeout(phydev, AT803X_CDT, val,
+-				    !(val & cdt_en),
+-				    30000, 100000, true);
+-
+-	return ret < 0 ? ret : 0;
+-}
+-
+ static int at803x_cable_test_one_pair(struct phy_device *phydev, int pair)
+ {
+ 	static const int ethtool_pair[] = {
+--- a/drivers/net/phy/qcom/qcom-phy-lib.c
++++ b/drivers/net/phy/qcom/qcom-phy-lib.c
+@@ -3,6 +3,9 @@
+ #include <linux/phy.h>
+ #include <linux/module.h>
+ 
++#include <linux/netdevice.h>
++#include <linux/etherdevice.h>
++
+ #include "qcom.h"
+ 
+ MODULE_DESCRIPTION("Qualcomm PHY driver Common Functions");
+@@ -51,3 +54,376 @@ int at803x_debug_reg_write(struct phy_de
+ 	return phy_write(phydev, AT803X_DEBUG_DATA, data);
+ }
+ EXPORT_SYMBOL_GPL(at803x_debug_reg_write);
++
++int at803x_set_wol(struct phy_device *phydev,
++		   struct ethtool_wolinfo *wol)
++{
++	int ret, irq_enabled;
++
++	if (wol->wolopts & WAKE_MAGIC) {
++		struct net_device *ndev = phydev->attached_dev;
++		const u8 *mac;
++		unsigned int i;
++		static const unsigned int offsets[] = {
++			AT803X_LOC_MAC_ADDR_32_47_OFFSET,
++			AT803X_LOC_MAC_ADDR_16_31_OFFSET,
++			AT803X_LOC_MAC_ADDR_0_15_OFFSET,
++		};
++
++		if (!ndev)
++			return -ENODEV;
++
++		mac = (const u8 *)ndev->dev_addr;
++
++		if (!is_valid_ether_addr(mac))
++			return -EINVAL;
++
++		for (i = 0; i < 3; i++)
++			phy_write_mmd(phydev, MDIO_MMD_PCS, offsets[i],
++				      mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
++
++		/* Enable WOL interrupt */
++		ret = phy_modify(phydev, AT803X_INTR_ENABLE, 0, AT803X_INTR_ENABLE_WOL);
++		if (ret)
++			return ret;
++	} else {
++		/* Disable WOL interrupt */
++		ret = phy_modify(phydev, AT803X_INTR_ENABLE, AT803X_INTR_ENABLE_WOL, 0);
++		if (ret)
++			return ret;
++	}
++
++	/* Clear WOL status */
++	ret = phy_read(phydev, AT803X_INTR_STATUS);
++	if (ret < 0)
++		return ret;
++
++	/* Check if there are other interrupts except for WOL triggered when PHY is
++	 * in interrupt mode, only the interrupts enabled by AT803X_INTR_ENABLE can
++	 * be passed up to the interrupt PIN.
++	 */
++	irq_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
++	if (irq_enabled < 0)
++		return irq_enabled;
++
++	irq_enabled &= ~AT803X_INTR_ENABLE_WOL;
++	if (ret & irq_enabled && !phy_polling_mode(phydev))
++		phy_trigger_machine(phydev);
++
++	return 0;
++}
++EXPORT_SYMBOL_GPL(at803x_set_wol);
++
++void at803x_get_wol(struct phy_device *phydev,
++		    struct ethtool_wolinfo *wol)
++{
++	int value;
++
++	wol->supported = WAKE_MAGIC;
++	wol->wolopts = 0;
++
++	value = phy_read(phydev, AT803X_INTR_ENABLE);
++	if (value < 0)
++		return;
++
++	if (value & AT803X_INTR_ENABLE_WOL)
++		wol->wolopts |= WAKE_MAGIC;
++}
++EXPORT_SYMBOL_GPL(at803x_get_wol);
++
++int at803x_ack_interrupt(struct phy_device *phydev)
++{
++	int err;
++
++	err = phy_read(phydev, AT803X_INTR_STATUS);
++
++	return (err < 0) ? err : 0;
++}
++EXPORT_SYMBOL_GPL(at803x_ack_interrupt);
++
++int at803x_config_intr(struct phy_device *phydev)
++{
++	int err;
++	int value;
++
++	value = phy_read(phydev, AT803X_INTR_ENABLE);
++
++	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
++		/* Clear any pending interrupts */
++		err = at803x_ack_interrupt(phydev);
++		if (err)
++			return err;
++
++		value |= AT803X_INTR_ENABLE_AUTONEG_ERR;
++		value |= AT803X_INTR_ENABLE_SPEED_CHANGED;
++		value |= AT803X_INTR_ENABLE_DUPLEX_CHANGED;
++		value |= AT803X_INTR_ENABLE_LINK_FAIL;
++		value |= AT803X_INTR_ENABLE_LINK_SUCCESS;
++
++		err = phy_write(phydev, AT803X_INTR_ENABLE, value);
++	} else {
++		err = phy_write(phydev, AT803X_INTR_ENABLE, 0);
++		if (err)
++			return err;
++
++		/* Clear any pending interrupts */
++		err = at803x_ack_interrupt(phydev);
++	}
++
++	return err;
++}
++EXPORT_SYMBOL_GPL(at803x_config_intr);
++
++irqreturn_t at803x_handle_interrupt(struct phy_device *phydev)
++{
++	int irq_status, int_enabled;
++
++	irq_status = phy_read(phydev, AT803X_INTR_STATUS);
++	if (irq_status < 0) {
++		phy_error(phydev);
++		return IRQ_NONE;
++	}
++
++	/* Read the current enabled interrupts */
++	int_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
++	if (int_enabled < 0) {
++		phy_error(phydev);
++		return IRQ_NONE;
++	}
++
++	/* See if this was one of our enabled interrupts */
++	if (!(irq_status & int_enabled))
++		return IRQ_NONE;
++
++	phy_trigger_machine(phydev);
++
++	return IRQ_HANDLED;
++}
++EXPORT_SYMBOL_GPL(at803x_handle_interrupt);
++
++int at803x_read_specific_status(struct phy_device *phydev,
++				struct at803x_ss_mask ss_mask)
++{
++	int ss;
++
++	/* Read the AT8035 PHY-Specific Status register, which indicates the
++	 * speed and duplex that the PHY is actually using, irrespective of
++	 * whether we are in autoneg mode or not.
++	 */
++	ss = phy_read(phydev, AT803X_SPECIFIC_STATUS);
++	if (ss < 0)
++		return ss;
++
++	if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) {
++		int sfc, speed;
++
++		sfc = phy_read(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL);
++		if (sfc < 0)
++			return sfc;
++
++		speed = ss & ss_mask.speed_mask;
++		speed >>= ss_mask.speed_shift;
++
++		switch (speed) {
++		case AT803X_SS_SPEED_10:
++			phydev->speed = SPEED_10;
++			break;
++		case AT803X_SS_SPEED_100:
++			phydev->speed = SPEED_100;
++			break;
++		case AT803X_SS_SPEED_1000:
++			phydev->speed = SPEED_1000;
++			break;
++		case QCA808X_SS_SPEED_2500:
++			phydev->speed = SPEED_2500;
++			break;
++		}
++		if (ss & AT803X_SS_DUPLEX)
++			phydev->duplex = DUPLEX_FULL;
++		else
++			phydev->duplex = DUPLEX_HALF;
++
++		if (ss & AT803X_SS_MDIX)
++			phydev->mdix = ETH_TP_MDI_X;
++		else
++			phydev->mdix = ETH_TP_MDI;
++
++		switch (FIELD_GET(AT803X_SFC_MDI_CROSSOVER_MODE_M, sfc)) {
++		case AT803X_SFC_MANUAL_MDI:
++			phydev->mdix_ctrl = ETH_TP_MDI;
++			break;
++		case AT803X_SFC_MANUAL_MDIX:
++			phydev->mdix_ctrl = ETH_TP_MDI_X;
++			break;
++		case AT803X_SFC_AUTOMATIC_CROSSOVER:
++			phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
++			break;
++		}
++	}
++
++	return 0;
++}
++EXPORT_SYMBOL_GPL(at803x_read_specific_status);
++
++int at803x_config_mdix(struct phy_device *phydev, u8 ctrl)
++{
++	u16 val;
++
++	switch (ctrl) {
++	case ETH_TP_MDI:
++		val = AT803X_SFC_MANUAL_MDI;
++		break;
++	case ETH_TP_MDI_X:
++		val = AT803X_SFC_MANUAL_MDIX;
++		break;
++	case ETH_TP_MDI_AUTO:
++		val = AT803X_SFC_AUTOMATIC_CROSSOVER;
++		break;
++	default:
++		return 0;
++	}
++
++	return phy_modify_changed(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL,
++			  AT803X_SFC_MDI_CROSSOVER_MODE_M,
++			  FIELD_PREP(AT803X_SFC_MDI_CROSSOVER_MODE_M, val));
++}
++EXPORT_SYMBOL_GPL(at803x_config_mdix);
++
++int at803x_prepare_config_aneg(struct phy_device *phydev)
++{
++	int ret;
++
++	ret = at803x_config_mdix(phydev, phydev->mdix_ctrl);
++	if (ret < 0)
++		return ret;
++
++	/* Changes of the midx bits are disruptive to the normal operation;
++	 * therefore any changes to these registers must be followed by a
++	 * software reset to take effect.
++	 */
++	if (ret == 1) {
++		ret = genphy_soft_reset(phydev);
++		if (ret < 0)
++			return ret;
++	}
++
++	return 0;
++}
++EXPORT_SYMBOL_GPL(at803x_prepare_config_aneg);
++
++static int at803x_get_downshift(struct phy_device *phydev, u8 *d)
++{
++	int val;
++
++	val = phy_read(phydev, AT803X_SMART_SPEED);
++	if (val < 0)
++		return val;
++
++	if (val & AT803X_SMART_SPEED_ENABLE)
++		*d = FIELD_GET(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, val) + 2;
++	else
++		*d = DOWNSHIFT_DEV_DISABLE;
++
++	return 0;
++}
++
++static int at803x_set_downshift(struct phy_device *phydev, u8 cnt)
++{
++	u16 mask, set;
++	int ret;
++
++	switch (cnt) {
++	case DOWNSHIFT_DEV_DEFAULT_COUNT:
++		cnt = AT803X_DEFAULT_DOWNSHIFT;
++		fallthrough;
++	case AT803X_MIN_DOWNSHIFT ... AT803X_MAX_DOWNSHIFT:
++		set = AT803X_SMART_SPEED_ENABLE |
++		      AT803X_SMART_SPEED_BYPASS_TIMER |
++		      FIELD_PREP(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, cnt - 2);
++		mask = AT803X_SMART_SPEED_RETRY_LIMIT_MASK;
++		break;
++	case DOWNSHIFT_DEV_DISABLE:
++		set = 0;
++		mask = AT803X_SMART_SPEED_ENABLE |
++		       AT803X_SMART_SPEED_BYPASS_TIMER;
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	ret = phy_modify_changed(phydev, AT803X_SMART_SPEED, mask, set);
++
++	/* After changing the smart speed settings, we need to perform a
++	 * software reset, use phy_init_hw() to make sure we set the
++	 * reapply any values which might got lost during software reset.
++	 */
++	if (ret == 1)
++		ret = phy_init_hw(phydev);
++
++	return ret;
++}
++
++int at803x_get_tunable(struct phy_device *phydev,
++		       struct ethtool_tunable *tuna, void *data)
++{
++	switch (tuna->id) {
++	case ETHTOOL_PHY_DOWNSHIFT:
++		return at803x_get_downshift(phydev, data);
++	default:
++		return -EOPNOTSUPP;
++	}
++}
++EXPORT_SYMBOL_GPL(at803x_get_tunable);
++
++int at803x_set_tunable(struct phy_device *phydev,
++		       struct ethtool_tunable *tuna, const void *data)
++{
++	switch (tuna->id) {
++	case ETHTOOL_PHY_DOWNSHIFT:
++		return at803x_set_downshift(phydev, *(const u8 *)data);
++	default:
++		return -EOPNOTSUPP;
++	}
++}
++EXPORT_SYMBOL_GPL(at803x_set_tunable);
++
++int at803x_cdt_fault_length(int dt)
++{
++	/* According to the datasheet the distance to the fault is
++	 * DELTA_TIME * 0.824 meters.
++	 *
++	 * The author suspect the correct formula is:
++	 *
++	 *   fault_distance = DELTA_TIME * (c * VF) / 125MHz / 2
++	 *
++	 * where c is the speed of light, VF is the velocity factor of
++	 * the twisted pair cable, 125MHz the counter frequency and
++	 * we need to divide by 2 because the hardware will measure the
++	 * round trip time to the fault and back to the PHY.
++	 *
++	 * With a VF of 0.69 we get the factor 0.824 mentioned in the
++	 * datasheet.
++	 */
++	return (dt * 824) / 10;
++}
++EXPORT_SYMBOL_GPL(at803x_cdt_fault_length);
++
++int at803x_cdt_start(struct phy_device *phydev, u32 cdt_start)
++{
++	return phy_write(phydev, AT803X_CDT, cdt_start);
++}
++EXPORT_SYMBOL_GPL(at803x_cdt_start);
++
++int at803x_cdt_wait_for_completion(struct phy_device *phydev,
++				   u32 cdt_en)
++{
++	int val, ret;
++
++	/* One test run takes about 25ms */
++	ret = phy_read_poll_timeout(phydev, AT803X_CDT, val,
++				    !(val & cdt_en),
++				    30000, 100000, true);
++
++	return ret < 0 ? ret : 0;
++}
++EXPORT_SYMBOL_GPL(at803x_cdt_wait_for_completion);
+--- a/drivers/net/phy/qcom/qcom.h
++++ b/drivers/net/phy/qcom/qcom.h
+@@ -1,5 +1,63 @@
+ /* SPDX-License-Identifier: GPL-2.0 */
+ 
++#define AT803X_SPECIFIC_FUNCTION_CONTROL	0x10
++#define AT803X_SFC_ASSERT_CRS			BIT(11)
++#define AT803X_SFC_FORCE_LINK			BIT(10)
++#define AT803X_SFC_MDI_CROSSOVER_MODE_M		GENMASK(6, 5)
++#define AT803X_SFC_AUTOMATIC_CROSSOVER		0x3
++#define AT803X_SFC_MANUAL_MDIX			0x1
++#define AT803X_SFC_MANUAL_MDI			0x0
++#define AT803X_SFC_SQE_TEST			BIT(2)
++#define AT803X_SFC_POLARITY_REVERSAL		BIT(1)
++#define AT803X_SFC_DISABLE_JABBER		BIT(0)
++
++#define AT803X_SPECIFIC_STATUS			0x11
++#define AT803X_SS_SPEED_MASK			GENMASK(15, 14)
++#define AT803X_SS_SPEED_1000			2
++#define AT803X_SS_SPEED_100			1
++#define AT803X_SS_SPEED_10			0
++#define AT803X_SS_DUPLEX			BIT(13)
++#define AT803X_SS_SPEED_DUPLEX_RESOLVED		BIT(11)
++#define AT803X_SS_MDIX				BIT(6)
++
++#define QCA808X_SS_SPEED_MASK			GENMASK(9, 7)
++#define QCA808X_SS_SPEED_2500			4
++
++#define AT803X_INTR_ENABLE			0x12
++#define AT803X_INTR_ENABLE_AUTONEG_ERR		BIT(15)
++#define AT803X_INTR_ENABLE_SPEED_CHANGED	BIT(14)
++#define AT803X_INTR_ENABLE_DUPLEX_CHANGED	BIT(13)
++#define AT803X_INTR_ENABLE_PAGE_RECEIVED	BIT(12)
++#define AT803X_INTR_ENABLE_LINK_FAIL		BIT(11)
++#define AT803X_INTR_ENABLE_LINK_SUCCESS		BIT(10)
++#define AT803X_INTR_ENABLE_LINK_FAIL_BX		BIT(8)
++#define AT803X_INTR_ENABLE_LINK_SUCCESS_BX	BIT(7)
++#define AT803X_INTR_ENABLE_WIRESPEED_DOWNGRADE	BIT(5)
++#define AT803X_INTR_ENABLE_POLARITY_CHANGED	BIT(1)
++#define AT803X_INTR_ENABLE_WOL			BIT(0)
++
++#define AT803X_INTR_STATUS			0x13
++
++#define AT803X_SMART_SPEED			0x14
++#define AT803X_SMART_SPEED_ENABLE		BIT(5)
++#define AT803X_SMART_SPEED_RETRY_LIMIT_MASK	GENMASK(4, 2)
++#define AT803X_SMART_SPEED_BYPASS_TIMER		BIT(1)
++
++#define AT803X_CDT				0x16
++#define AT803X_CDT_MDI_PAIR_MASK		GENMASK(9, 8)
++#define AT803X_CDT_ENABLE_TEST			BIT(0)
++#define AT803X_CDT_STATUS			0x1c
++#define AT803X_CDT_STATUS_STAT_NORMAL		0
++#define AT803X_CDT_STATUS_STAT_SHORT		1
++#define AT803X_CDT_STATUS_STAT_OPEN		2
++#define AT803X_CDT_STATUS_STAT_FAIL		3
++#define AT803X_CDT_STATUS_STAT_MASK		GENMASK(9, 8)
++#define AT803X_CDT_STATUS_DELTA_TIME_MASK	GENMASK(7, 0)
++
++#define AT803X_LOC_MAC_ADDR_0_15_OFFSET		0x804C
++#define AT803X_LOC_MAC_ADDR_16_31_OFFSET	0x804B
++#define AT803X_LOC_MAC_ADDR_32_47_OFFSET	0x804A
++
+ #define AT803X_DEBUG_ADDR			0x1D
+ #define AT803X_DEBUG_DATA			0x1E
+ 
+@@ -16,6 +74,10 @@
+ #define   AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE	BIT(13)
+ #define   AT803X_DEBUG_HIB_CTRL_PS_HIB_EN	BIT(15)
+ 
++#define AT803X_DEFAULT_DOWNSHIFT		5
++#define AT803X_MIN_DOWNSHIFT			2
++#define AT803X_MAX_DOWNSHIFT			9
++
+ enum stat_access_type {
+ 	PHY,
+ 	MMD
+@@ -28,7 +90,31 @@ struct at803x_hw_stat {
+ 	enum stat_access_type access_type;
+ };
+ 
++struct at803x_ss_mask {
++	u16 speed_mask;
++	u8 speed_shift;
++};
++
+ int at803x_debug_reg_read(struct phy_device *phydev, u16 reg);
+ int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,
+ 			  u16 clear, u16 set);
+ int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data);
++int at803x_set_wol(struct phy_device *phydev,
++		   struct ethtool_wolinfo *wol);
++void at803x_get_wol(struct phy_device *phydev,
++		    struct ethtool_wolinfo *wol);
++int at803x_ack_interrupt(struct phy_device *phydev);
++int at803x_config_intr(struct phy_device *phydev);
++irqreturn_t at803x_handle_interrupt(struct phy_device *phydev);
++int at803x_read_specific_status(struct phy_device *phydev,
++				struct at803x_ss_mask ss_mask);
++int at803x_config_mdix(struct phy_device *phydev, u8 ctrl);
++int at803x_prepare_config_aneg(struct phy_device *phydev);
++int at803x_get_tunable(struct phy_device *phydev,
++		       struct ethtool_tunable *tuna, void *data);
++int at803x_set_tunable(struct phy_device *phydev,
++		       struct ethtool_tunable *tuna, const void *data);
++int at803x_cdt_fault_length(int dt);
++int at803x_cdt_start(struct phy_device *phydev, u32 cdt_start);
++int at803x_cdt_wait_for_completion(struct phy_device *phydev,
++				   u32 cdt_en);

+ 1936 - 0
target/linux/generic/backport-6.6/713-v6.9-05-net-phy-qcom-detach-qca808x-PHY-driver-from-at803x.patch

@@ -0,0 +1,1936 @@
+From c89414adf2ec7cd9e7080c419aa5847f1db1009c Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Mon, 29 Jan 2024 15:15:23 +0100
+Subject: [PATCH 5/5] net: phy: qcom: detach qca808x PHY driver from at803x
+
+Almost all the QCA8081 PHY driver OPs are specific and only some of them
+use the generic at803x.
+
+To make the at803x code slimmer, move all the specific qca808x regs and
+functions to a dedicated PHY driver.
+
+Probe function and priv struct is reworked to allocate and use only the
+qca808x specific data. Unused data from at803x PHY driver are dropped
+from at803x priv struct.
+
+Also a new Kconfig is introduced QCA808X_PHY, to compile the newly
+introduced PHY driver for QCA8081 PHY.
+
+As the Kconfig name starts with Qualcomm the same order is kept.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/qcom/Kconfig   |   6 +
+ drivers/net/phy/qcom/Makefile  |   1 +
+ drivers/net/phy/qcom/at803x.c  | 897 +------------------------------
+ drivers/net/phy/qcom/qca808x.c | 934 +++++++++++++++++++++++++++++++++
+ 4 files changed, 942 insertions(+), 896 deletions(-)
+ create mode 100644 drivers/net/phy/qcom/qca808x.c
+
+--- a/drivers/net/phy/qcom/Kconfig
++++ b/drivers/net/phy/qcom/Kconfig
+@@ -14,3 +14,9 @@ config QCA83XX_PHY
+ 	select QCOM_NET_PHYLIB
+ 	help
+ 	  Currently supports the internal QCA8337(Internal qca8k PHY) model
++
++config QCA808X_PHY
++	tristate "Qualcomm QCA808x PHYs"
++	select QCOM_NET_PHYLIB
++	help
++	  Currently supports the QCA8081 model
+--- a/drivers/net/phy/qcom/Makefile
++++ b/drivers/net/phy/qcom/Makefile
+@@ -2,3 +2,4 @@
+ obj-$(CONFIG_QCOM_NET_PHYLIB)	+= qcom-phy-lib.o
+ obj-$(CONFIG_AT803X_PHY)	+= at803x.o
+ obj-$(CONFIG_QCA83XX_PHY)	+= qca83xx.o
++obj-$(CONFIG_QCA808X_PHY)	+= qca808x.o
+--- a/drivers/net/phy/qcom/at803x.c
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -96,8 +96,6 @@
+ #define ATH8035_PHY_ID				0x004dd072
+ #define AT8030_PHY_ID_MASK			0xffffffef
+ 
+-#define QCA8081_PHY_ID				0x004dd101
+-
+ #define QCA9561_PHY_ID				0x004dd042
+ 
+ #define AT803X_PAGE_FIBER			0
+@@ -110,201 +108,7 @@
+ /* disable hibernation mode */
+ #define AT803X_DISABLE_HIBERNATION_MODE		BIT(2)
+ 
+-/* ADC threshold */
+-#define QCA808X_PHY_DEBUG_ADC_THRESHOLD		0x2c80
+-#define QCA808X_ADC_THRESHOLD_MASK		GENMASK(7, 0)
+-#define QCA808X_ADC_THRESHOLD_80MV		0
+-#define QCA808X_ADC_THRESHOLD_100MV		0xf0
+-#define QCA808X_ADC_THRESHOLD_200MV		0x0f
+-#define QCA808X_ADC_THRESHOLD_300MV		0xff
+-
+-/* CLD control */
+-#define QCA808X_PHY_MMD3_ADDR_CLD_CTRL7		0x8007
+-#define QCA808X_8023AZ_AFE_CTRL_MASK		GENMASK(8, 4)
+-#define QCA808X_8023AZ_AFE_EN			0x90
+-
+-/* AZ control */
+-#define QCA808X_PHY_MMD3_AZ_TRAINING_CTRL	0x8008
+-#define QCA808X_MMD3_AZ_TRAINING_VAL		0x1c32
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB	0x8014
+-#define QCA808X_MSE_THRESHOLD_20DB_VALUE	0x529
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB	0x800E
+-#define QCA808X_MSE_THRESHOLD_17DB_VALUE	0x341
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB	0x801E
+-#define QCA808X_MSE_THRESHOLD_27DB_VALUE	0x419
+-
+-#define QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB	0x8020
+-#define QCA808X_MSE_THRESHOLD_28DB_VALUE	0x341
+-
+-#define QCA808X_PHY_MMD7_TOP_OPTION1		0x901c
+-#define QCA808X_TOP_OPTION1_DATA		0x0
+-
+-#define QCA808X_PHY_MMD3_DEBUG_1		0xa100
+-#define QCA808X_MMD3_DEBUG_1_VALUE		0x9203
+-#define QCA808X_PHY_MMD3_DEBUG_2		0xa101
+-#define QCA808X_MMD3_DEBUG_2_VALUE		0x48ad
+-#define QCA808X_PHY_MMD3_DEBUG_3		0xa103
+-#define QCA808X_MMD3_DEBUG_3_VALUE		0x1698
+-#define QCA808X_PHY_MMD3_DEBUG_4		0xa105
+-#define QCA808X_MMD3_DEBUG_4_VALUE		0x8001
+-#define QCA808X_PHY_MMD3_DEBUG_5		0xa106
+-#define QCA808X_MMD3_DEBUG_5_VALUE		0x1111
+-#define QCA808X_PHY_MMD3_DEBUG_6		0xa011
+-#define QCA808X_MMD3_DEBUG_6_VALUE		0x5f85
+-
+-/* master/slave seed config */
+-#define QCA808X_PHY_DEBUG_LOCAL_SEED		9
+-#define QCA808X_MASTER_SLAVE_SEED_ENABLE	BIT(1)
+-#define QCA808X_MASTER_SLAVE_SEED_CFG		GENMASK(12, 2)
+-#define QCA808X_MASTER_SLAVE_SEED_RANGE		0x32
+-
+-/* Hibernation yields lower power consumpiton in contrast with normal operation mode.
+- * when the copper cable is unplugged, the PHY enters into hibernation mode in about 10s.
+- */
+-#define QCA808X_DBG_AN_TEST			0xb
+-#define QCA808X_HIBERNATION_EN			BIT(15)
+-
+-#define QCA808X_CDT_ENABLE_TEST			BIT(15)
+-#define QCA808X_CDT_INTER_CHECK_DIS		BIT(13)
+-#define QCA808X_CDT_STATUS			BIT(11)
+-#define QCA808X_CDT_LENGTH_UNIT			BIT(10)
+-
+-#define QCA808X_MMD3_CDT_STATUS			0x8064
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_A		0x8065
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_B		0x8066
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_C		0x8067
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_D		0x8068
+-#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT	GENMASK(15, 8)
+-#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT	GENMASK(7, 0)
+-
+-#define QCA808X_CDT_CODE_PAIR_A			GENMASK(15, 12)
+-#define QCA808X_CDT_CODE_PAIR_B			GENMASK(11, 8)
+-#define QCA808X_CDT_CODE_PAIR_C			GENMASK(7, 4)
+-#define QCA808X_CDT_CODE_PAIR_D			GENMASK(3, 0)
+-
+-#define QCA808X_CDT_STATUS_STAT_TYPE		GENMASK(1, 0)
+-#define QCA808X_CDT_STATUS_STAT_FAIL		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
+-#define QCA808X_CDT_STATUS_STAT_NORMAL		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
+-#define QCA808X_CDT_STATUS_STAT_SAME_OPEN	FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
+-#define QCA808X_CDT_STATUS_STAT_SAME_SHORT	FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
+-
+-#define QCA808X_CDT_STATUS_STAT_MDI		GENMASK(3, 2)
+-#define QCA808X_CDT_STATUS_STAT_MDI1		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
+-#define QCA808X_CDT_STATUS_STAT_MDI2		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
+-#define QCA808X_CDT_STATUS_STAT_MDI3		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
+-
+-/* NORMAL are MDI with type set to 0 */
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI1
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+-									 QCA808X_CDT_STATUS_STAT_MDI1)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+-									 QCA808X_CDT_STATUS_STAT_MDI1)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI2
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+-									 QCA808X_CDT_STATUS_STAT_MDI2)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+-									 QCA808X_CDT_STATUS_STAT_MDI2)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI3
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+-									 QCA808X_CDT_STATUS_STAT_MDI3)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+-									 QCA808X_CDT_STATUS_STAT_MDI3)
+-
+-/* Added for reference of existence but should be handled by wait_for_completion already */
+-#define QCA808X_CDT_STATUS_STAT_BUSY		(BIT(1) | BIT(3))
+-
+-#define QCA808X_MMD7_LED_GLOBAL			0x8073
+-#define QCA808X_LED_BLINK_1			GENMASK(11, 6)
+-#define QCA808X_LED_BLINK_2			GENMASK(5, 0)
+-/* Values are the same for both BLINK_1 and BLINK_2 */
+-#define QCA808X_LED_BLINK_FREQ_MASK		GENMASK(5, 3)
+-#define QCA808X_LED_BLINK_FREQ_2HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
+-#define QCA808X_LED_BLINK_FREQ_4HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
+-#define QCA808X_LED_BLINK_FREQ_8HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
+-#define QCA808X_LED_BLINK_FREQ_16HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
+-#define QCA808X_LED_BLINK_FREQ_32HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
+-#define QCA808X_LED_BLINK_FREQ_64HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
+-#define QCA808X_LED_BLINK_FREQ_128HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
+-#define QCA808X_LED_BLINK_FREQ_256HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
+-#define QCA808X_LED_BLINK_DUTY_MASK		GENMASK(2, 0)
+-#define QCA808X_LED_BLINK_DUTY_50_50		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
+-#define QCA808X_LED_BLINK_DUTY_75_25		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
+-#define QCA808X_LED_BLINK_DUTY_25_75		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
+-#define QCA808X_LED_BLINK_DUTY_33_67		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
+-#define QCA808X_LED_BLINK_DUTY_67_33		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
+-#define QCA808X_LED_BLINK_DUTY_17_83		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
+-#define QCA808X_LED_BLINK_DUTY_83_17		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
+-#define QCA808X_LED_BLINK_DUTY_8_92		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
+-
+-#define QCA808X_MMD7_LED2_CTRL			0x8074
+-#define QCA808X_MMD7_LED2_FORCE_CTRL		0x8075
+-#define QCA808X_MMD7_LED1_CTRL			0x8076
+-#define QCA808X_MMD7_LED1_FORCE_CTRL		0x8077
+-#define QCA808X_MMD7_LED0_CTRL			0x8078
+-#define QCA808X_MMD7_LED_CTRL(x)		(0x8078 - ((x) * 2))
+-
+-/* LED hw control pattern is the same for every LED */
+-#define QCA808X_LED_PATTERN_MASK		GENMASK(15, 0)
+-#define QCA808X_LED_SPEED2500_ON		BIT(15)
+-#define QCA808X_LED_SPEED2500_BLINK		BIT(14)
+-/* Follow blink trigger even if duplex or speed condition doesn't match */
+-#define QCA808X_LED_BLINK_CHECK_BYPASS		BIT(13)
+-#define QCA808X_LED_FULL_DUPLEX_ON		BIT(12)
+-#define QCA808X_LED_HALF_DUPLEX_ON		BIT(11)
+-#define QCA808X_LED_TX_BLINK			BIT(10)
+-#define QCA808X_LED_RX_BLINK			BIT(9)
+-#define QCA808X_LED_TX_ON_10MS			BIT(8)
+-#define QCA808X_LED_RX_ON_10MS			BIT(7)
+-#define QCA808X_LED_SPEED1000_ON		BIT(6)
+-#define QCA808X_LED_SPEED100_ON			BIT(5)
+-#define QCA808X_LED_SPEED10_ON			BIT(4)
+-#define QCA808X_LED_COLLISION_BLINK		BIT(3)
+-#define QCA808X_LED_SPEED1000_BLINK		BIT(2)
+-#define QCA808X_LED_SPEED100_BLINK		BIT(1)
+-#define QCA808X_LED_SPEED10_BLINK		BIT(0)
+-
+-#define QCA808X_MMD7_LED0_FORCE_CTRL		0x8079
+-#define QCA808X_MMD7_LED_FORCE_CTRL(x)		(0x8079 - ((x) * 2))
+-
+-/* LED force ctrl is the same for every LED
+- * No documentation exist for this, not even internal one
+- * with NDA as QCOM gives only info about configuring
+- * hw control pattern rules and doesn't indicate any way
+- * to force the LED to specific mode.
+- * These define comes from reverse and testing and maybe
+- * lack of some info or some info are not entirely correct.
+- * For the basic LED control and hw control these finding
+- * are enough to support LED control in all the required APIs.
+- *
+- * On doing some comparison with implementation with qca807x,
+- * it was found that it's 1:1 equal to it and confirms all the
+- * reverse done. It was also found further specification with the
+- * force mode and the blink modes.
+- */
+-#define QCA808X_LED_FORCE_EN			BIT(15)
+-#define QCA808X_LED_FORCE_MODE_MASK		GENMASK(14, 13)
+-#define QCA808X_LED_FORCE_BLINK_1		FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
+-#define QCA808X_LED_FORCE_BLINK_2		FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
+-#define QCA808X_LED_FORCE_ON			FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
+-#define QCA808X_LED_FORCE_OFF			FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
+-
+-#define QCA808X_MMD7_LED_POLARITY_CTRL		0x901a
+-/* QSDK sets by default 0x46 to this reg that sets BIT 6 for
+- * LED to active high. It's not clear what BIT 3 and BIT 4 does.
+- */
+-#define QCA808X_LED_ACTIVE_HIGH			BIT(6)
+-
+-/* QCA808X 1G chip type */
+-#define QCA808X_PHY_MMD7_CHIP_TYPE		0x901d
+-#define QCA808X_PHY_CHIP_TYPE_1G		BIT(0)
+-
+-#define QCA8081_PHY_SERDES_MMD1_FIFO_CTRL	0x9072
+-#define QCA8081_PHY_FIFO_RSTN			BIT(11)
+-
+-MODULE_DESCRIPTION("Qualcomm Atheros AR803x and QCA808X PHY driver");
++MODULE_DESCRIPTION("Qualcomm Atheros AR803x PHY driver");
+ MODULE_AUTHOR("Matus Ujhelyi");
+ MODULE_LICENSE("GPL");
+ 
+@@ -318,7 +122,6 @@ struct at803x_priv {
+ 	bool is_1000basex;
+ 	struct regulator_dev *vddio_rdev;
+ 	struct regulator_dev *vddh_rdev;
+-	int led_polarity_mode;
+ };
+ 
+ struct at803x_context {
+@@ -519,9 +322,6 @@ static int at803x_probe(struct phy_devic
+ 	if (!priv)
+ 		return -ENOMEM;
+ 
+-	/* Init LED polarity mode to -1 */
+-	priv->led_polarity_mode = -1;
+-
+ 	phydev->priv = priv;
+ 
+ 	ret = at803x_parse_dt(phydev);
+@@ -1216,672 +1016,6 @@ static int at8035_probe(struct phy_devic
+ 	return at8035_parse_dt(phydev);
+ }
+ 
+-static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	/* Enable fast retrain */
+-	ret = genphy_c45_fast_retrain(phydev, true);
+-	if (ret)
+-		return ret;
+-
+-	phy_write_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_TOP_OPTION1,
+-		      QCA808X_TOP_OPTION1_DATA);
+-	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB,
+-		      QCA808X_MSE_THRESHOLD_20DB_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB,
+-		      QCA808X_MSE_THRESHOLD_17DB_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB,
+-		      QCA808X_MSE_THRESHOLD_27DB_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB,
+-		      QCA808X_MSE_THRESHOLD_28DB_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_1,
+-		      QCA808X_MMD3_DEBUG_1_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_4,
+-		      QCA808X_MMD3_DEBUG_4_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_5,
+-		      QCA808X_MMD3_DEBUG_5_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_3,
+-		      QCA808X_MMD3_DEBUG_3_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_6,
+-		      QCA808X_MMD3_DEBUG_6_VALUE);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_2,
+-		      QCA808X_MMD3_DEBUG_2_VALUE);
+-
+-	return 0;
+-}
+-
+-static int qca808x_phy_ms_seed_enable(struct phy_device *phydev, bool enable)
+-{
+-	u16 seed_value;
+-
+-	if (!enable)
+-		return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+-				QCA808X_MASTER_SLAVE_SEED_ENABLE, 0);
+-
+-	seed_value = prandom_u32_max(QCA808X_MASTER_SLAVE_SEED_RANGE);
+-	return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+-			QCA808X_MASTER_SLAVE_SEED_CFG | QCA808X_MASTER_SLAVE_SEED_ENABLE,
+-			FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value) |
+-			QCA808X_MASTER_SLAVE_SEED_ENABLE);
+-}
+-
+-static bool qca808x_is_prefer_master(struct phy_device *phydev)
+-{
+-	return (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_FORCE) ||
+-		(phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
+-}
+-
+-static bool qca808x_has_fast_retrain_or_slave_seed(struct phy_device *phydev)
+-{
+-	return linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
+-}
+-
+-static int qca808x_config_init(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	/* Active adc&vga on 802.3az for the link 1000M and 100M */
+-	ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_ADDR_CLD_CTRL7,
+-			     QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
+-	if (ret)
+-		return ret;
+-
+-	/* Adjust the threshold on 802.3az for the link 1000M */
+-	ret = phy_write_mmd(phydev, MDIO_MMD_PCS,
+-			    QCA808X_PHY_MMD3_AZ_TRAINING_CTRL,
+-			    QCA808X_MMD3_AZ_TRAINING_VAL);
+-	if (ret)
+-		return ret;
+-
+-	if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+-		/* Config the fast retrain for the link 2500M */
+-		ret = qca808x_phy_fast_retrain_config(phydev);
+-		if (ret)
+-			return ret;
+-
+-		ret = genphy_read_master_slave(phydev);
+-		if (ret < 0)
+-			return ret;
+-
+-		if (!qca808x_is_prefer_master(phydev)) {
+-			/* Enable seed and configure lower ramdom seed to make phy
+-			 * linked as slave mode.
+-			 */
+-			ret = qca808x_phy_ms_seed_enable(phydev, true);
+-			if (ret)
+-				return ret;
+-		}
+-	}
+-
+-	/* Configure adc threshold as 100mv for the link 10M */
+-	return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
+-				     QCA808X_ADC_THRESHOLD_MASK,
+-				     QCA808X_ADC_THRESHOLD_100MV);
+-}
+-
+-static int qca808x_read_status(struct phy_device *phydev)
+-{
+-	struct at803x_ss_mask ss_mask = { 0 };
+-	int ret;
+-
+-	ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+-	if (ret < 0)
+-		return ret;
+-
+-	linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->lp_advertising,
+-			 ret & MDIO_AN_10GBT_STAT_LP2_5G);
+-
+-	ret = genphy_read_status(phydev);
+-	if (ret)
+-		return ret;
+-
+-	/* qca8081 takes the different bits for speed value from at803x */
+-	ss_mask.speed_mask = QCA808X_SS_SPEED_MASK;
+-	ss_mask.speed_shift = __bf_shf(QCA808X_SS_SPEED_MASK);
+-	ret = at803x_read_specific_status(phydev, ss_mask);
+-	if (ret < 0)
+-		return ret;
+-
+-	if (phydev->link) {
+-		if (phydev->speed == SPEED_2500)
+-			phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+-		else
+-			phydev->interface = PHY_INTERFACE_MODE_SGMII;
+-	} else {
+-		/* generate seed as a lower random value to make PHY linked as SLAVE easily,
+-		 * except for master/slave configuration fault detected or the master mode
+-		 * preferred.
+-		 *
+-		 * the reason for not putting this code into the function link_change_notify is
+-		 * the corner case where the link partner is also the qca8081 PHY and the seed
+-		 * value is configured as the same value, the link can't be up and no link change
+-		 * occurs.
+-		 */
+-		if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+-			if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
+-			    qca808x_is_prefer_master(phydev)) {
+-				qca808x_phy_ms_seed_enable(phydev, false);
+-			} else {
+-				qca808x_phy_ms_seed_enable(phydev, true);
+-			}
+-		}
+-	}
+-
+-	return 0;
+-}
+-
+-static int qca808x_soft_reset(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	ret = genphy_soft_reset(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	if (qca808x_has_fast_retrain_or_slave_seed(phydev))
+-		ret = qca808x_phy_ms_seed_enable(phydev, true);
+-
+-	return ret;
+-}
+-
+-static bool qca808x_cdt_fault_length_valid(int cdt_code)
+-{
+-	switch (cdt_code) {
+-	case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+-	case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+-		return true;
+-	default:
+-		return false;
+-	}
+-}
+-
+-static int qca808x_cable_test_result_trans(int cdt_code)
+-{
+-	switch (cdt_code) {
+-	case QCA808X_CDT_STATUS_STAT_NORMAL:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+-	case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+-	case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
+-	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+-	case QCA808X_CDT_STATUS_STAT_FAIL:
+-	default:
+-		return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+-	}
+-}
+-
+-static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
+-				    int result)
+-{
+-	int val;
+-	u32 cdt_length_reg = 0;
+-
+-	switch (pair) {
+-	case ETHTOOL_A_CABLE_PAIR_A:
+-		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;
+-		break;
+-	case ETHTOOL_A_CABLE_PAIR_B:
+-		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;
+-		break;
+-	case ETHTOOL_A_CABLE_PAIR_C:
+-		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;
+-		break;
+-	case ETHTOOL_A_CABLE_PAIR_D:
+-		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;
+-		break;
+-	default:
+-		return -EINVAL;
+-	}
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);
+-	if (val < 0)
+-		return val;
+-
+-	if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
+-		val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
+-	else
+-		val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
+-
+-	return at803x_cdt_fault_length(val);
+-}
+-
+-static int qca808x_cable_test_start(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	/* perform CDT with the following configs:
+-	 * 1. disable hibernation.
+-	 * 2. force PHY working in MDI mode.
+-	 * 3. for PHY working in 1000BaseT.
+-	 * 4. configure the threshold.
+-	 */
+-
+-	ret = at803x_debug_reg_mask(phydev, QCA808X_DBG_AN_TEST, QCA808X_HIBERNATION_EN, 0);
+-	if (ret < 0)
+-		return ret;
+-
+-	ret = at803x_config_mdix(phydev, ETH_TP_MDI);
+-	if (ret < 0)
+-		return ret;
+-
+-	/* Force 1000base-T needs to configure PMA/PMD and MII_BMCR */
+-	phydev->duplex = DUPLEX_FULL;
+-	phydev->speed = SPEED_1000;
+-	ret = genphy_c45_pma_setup_forced(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	ret = genphy_setup_forced(phydev);
+-	if (ret < 0)
+-		return ret;
+-
+-	/* configure the thresholds for open, short, pair ok test */
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8074, 0xc040);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8076, 0xc040);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8077, 0xa060);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8078, 0xc050);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807a, 0xc060);
+-	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807e, 0xb060);
+-
+-	return 0;
+-}
+-
+-static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
+-					      u16 status)
+-{
+-	int length, result;
+-	u16 pair_code;
+-
+-	switch (pair) {
+-	case ETHTOOL_A_CABLE_PAIR_A:
+-		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
+-		break;
+-	case ETHTOOL_A_CABLE_PAIR_B:
+-		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
+-		break;
+-	case ETHTOOL_A_CABLE_PAIR_C:
+-		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
+-		break;
+-	case ETHTOOL_A_CABLE_PAIR_D:
+-		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
+-		break;
+-	default:
+-		return -EINVAL;
+-	}
+-
+-	result = qca808x_cable_test_result_trans(pair_code);
+-	ethnl_cable_test_result(phydev, pair, result);
+-
+-	if (qca808x_cdt_fault_length_valid(pair_code)) {
+-		length = qca808x_cdt_fault_length(phydev, pair, result);
+-		ethnl_cable_test_fault_length(phydev, pair, length);
+-	}
+-
+-	return 0;
+-}
+-
+-static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
+-{
+-	int ret, val;
+-
+-	*finished = false;
+-
+-	val = QCA808X_CDT_ENABLE_TEST |
+-	      QCA808X_CDT_LENGTH_UNIT;
+-	ret = at803x_cdt_start(phydev, val);
+-	if (ret)
+-		return ret;
+-
+-	ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
+-	if (ret)
+-		return ret;
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);
+-	if (val < 0)
+-		return val;
+-
+-	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
+-	if (ret)
+-		return ret;
+-
+-	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
+-	if (ret)
+-		return ret;
+-
+-	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
+-	if (ret)
+-		return ret;
+-
+-	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
+-	if (ret)
+-		return ret;
+-
+-	*finished = true;
+-
+-	return 0;
+-}
+-
+-static int qca808x_get_features(struct phy_device *phydev)
+-{
+-	int ret;
+-
+-	ret = genphy_c45_pma_read_abilities(phydev);
+-	if (ret)
+-		return ret;
+-
+-	/* The autoneg ability is not existed in bit3 of MMD7.1,
+-	 * but it is supported by qca808x PHY, so we add it here
+-	 * manually.
+-	 */
+-	linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+-
+-	/* As for the qca8081 1G version chip, the 2500baseT ability is also
+-	 * existed in the bit0 of MMD1.21, we need to remove it manually if
+-	 * it is the qca8081 1G chip according to the bit0 of MMD7.0x901d.
+-	 */
+-	ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE);
+-	if (ret < 0)
+-		return ret;
+-
+-	if (QCA808X_PHY_CHIP_TYPE_1G & ret)
+-		linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
+-
+-	return 0;
+-}
+-
+-static int qca808x_config_aneg(struct phy_device *phydev)
+-{
+-	int phy_ctrl = 0;
+-	int ret;
+-
+-	ret = at803x_prepare_config_aneg(phydev);
+-	if (ret)
+-		return ret;
+-
+-	/* The reg MII_BMCR also needs to be configured for force mode, the
+-	 * genphy_config_aneg is also needed.
+-	 */
+-	if (phydev->autoneg == AUTONEG_DISABLE)
+-		genphy_c45_pma_setup_forced(phydev);
+-
+-	if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising))
+-		phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
+-
+-	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+-				     MDIO_AN_10GBT_CTRL_ADV2_5G, phy_ctrl);
+-	if (ret < 0)
+-		return ret;
+-
+-	return __genphy_config_aneg(phydev, ret);
+-}
+-
+-static void qca808x_link_change_notify(struct phy_device *phydev)
+-{
+-	/* Assert interface sgmii fifo on link down, deassert it on link up,
+-	 * the interface device address is always phy address added by 1.
+-	 */
+-	mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
+-				   MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
+-				   QCA8081_PHY_FIFO_RSTN,
+-				   phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
+-}
+-
+-static int qca808x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
+-				    u16 *offload_trigger)
+-{
+-	/* Parsing specific to netdev trigger */
+-	if (test_bit(TRIGGER_NETDEV_TX, &rules))
+-		*offload_trigger |= QCA808X_LED_TX_BLINK;
+-	if (test_bit(TRIGGER_NETDEV_RX, &rules))
+-		*offload_trigger |= QCA808X_LED_RX_BLINK;
+-	if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
+-		*offload_trigger |= QCA808X_LED_SPEED10_ON;
+-	if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
+-		*offload_trigger |= QCA808X_LED_SPEED100_ON;
+-	if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
+-		*offload_trigger |= QCA808X_LED_SPEED1000_ON;
+-	if (test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
+-		*offload_trigger |= QCA808X_LED_SPEED2500_ON;
+-	if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
+-		*offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
+-	if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
+-		*offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
+-
+-	if (rules && !*offload_trigger)
+-		return -EOPNOTSUPP;
+-
+-	/* Enable BLINK_CHECK_BYPASS by default to make the LED
+-	 * blink even with duplex or speed mode not enabled.
+-	 */
+-	*offload_trigger |= QCA808X_LED_BLINK_CHECK_BYPASS;
+-
+-	return 0;
+-}
+-
+-static int qca808x_led_hw_control_enable(struct phy_device *phydev, u8 index)
+-{
+-	u16 reg;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+-	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
+-				  QCA808X_LED_FORCE_EN);
+-}
+-
+-static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
+-				       unsigned long rules)
+-{
+-	u16 offload_trigger = 0;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	return qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
+-}
+-
+-static int qca808x_led_hw_control_set(struct phy_device *phydev, u8 index,
+-				      unsigned long rules)
+-{
+-	u16 reg, offload_trigger = 0;
+-	int ret;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	reg = QCA808X_MMD7_LED_CTRL(index);
+-
+-	ret = qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
+-	if (ret)
+-		return ret;
+-
+-	ret = qca808x_led_hw_control_enable(phydev, index);
+-	if (ret)
+-		return ret;
+-
+-	return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+-			      QCA808X_LED_PATTERN_MASK,
+-			      offload_trigger);
+-}
+-
+-static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
+-{
+-	u16 reg;
+-	int val;
+-
+-	if (index > 2)
+-		return false;
+-
+-	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+-
+-	return !(val & QCA808X_LED_FORCE_EN);
+-}
+-
+-static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
+-				      unsigned long *rules)
+-{
+-	u16 reg;
+-	int val;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	/* Check if we have hw control enabled */
+-	if (qca808x_led_hw_control_status(phydev, index))
+-		return -EINVAL;
+-
+-	reg = QCA808X_MMD7_LED_CTRL(index);
+-
+-	val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+-	if (val & QCA808X_LED_TX_BLINK)
+-		set_bit(TRIGGER_NETDEV_TX, rules);
+-	if (val & QCA808X_LED_RX_BLINK)
+-		set_bit(TRIGGER_NETDEV_RX, rules);
+-	if (val & QCA808X_LED_SPEED10_ON)
+-		set_bit(TRIGGER_NETDEV_LINK_10, rules);
+-	if (val & QCA808X_LED_SPEED100_ON)
+-		set_bit(TRIGGER_NETDEV_LINK_100, rules);
+-	if (val & QCA808X_LED_SPEED1000_ON)
+-		set_bit(TRIGGER_NETDEV_LINK_1000, rules);
+-	if (val & QCA808X_LED_SPEED2500_ON)
+-		set_bit(TRIGGER_NETDEV_LINK_2500, rules);
+-	if (val & QCA808X_LED_HALF_DUPLEX_ON)
+-		set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
+-	if (val & QCA808X_LED_FULL_DUPLEX_ON)
+-		set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
+-
+-	return 0;
+-}
+-
+-static int qca808x_led_hw_control_reset(struct phy_device *phydev, u8 index)
+-{
+-	u16 reg;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	reg = QCA808X_MMD7_LED_CTRL(index);
+-
+-	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
+-				  QCA808X_LED_PATTERN_MASK);
+-}
+-
+-static int qca808x_led_brightness_set(struct phy_device *phydev,
+-				      u8 index, enum led_brightness value)
+-{
+-	u16 reg;
+-	int ret;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	if (!value) {
+-		ret = qca808x_led_hw_control_reset(phydev, index);
+-		if (ret)
+-			return ret;
+-	}
+-
+-	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+-	return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+-			      QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
+-			      QCA808X_LED_FORCE_EN | value ? QCA808X_LED_FORCE_ON :
+-							     QCA808X_LED_FORCE_OFF);
+-}
+-
+-static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
+-				 unsigned long *delay_on,
+-				 unsigned long *delay_off)
+-{
+-	int ret;
+-	u16 reg;
+-
+-	if (index > 2)
+-		return -EINVAL;
+-
+-	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+-	/* Set blink to 50% off, 50% on at 4Hz by default */
+-	ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
+-			     QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
+-			     QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
+-	if (ret)
+-		return ret;
+-
+-	/* We use BLINK_1 for normal blinking */
+-	ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+-			     QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
+-			     QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
+-	if (ret)
+-		return ret;
+-
+-	/* We set blink to 4Hz, aka 250ms */
+-	*delay_on = 250 / 2;
+-	*delay_off = 250 / 2;
+-
+-	return 0;
+-}
+-
+-static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
+-				    unsigned long modes)
+-{
+-	struct at803x_priv *priv = phydev->priv;
+-	bool active_low = false;
+-	u32 mode;
+-
+-	for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
+-		switch (mode) {
+-		case PHY_LED_ACTIVE_LOW:
+-			active_low = true;
+-			break;
+-		default:
+-			return -EINVAL;
+-		}
+-	}
+-
+-	/* PHY polarity is global and can't be set per LED.
+-	 * To detect this, check if last requested polarity mode
+-	 * match the new one.
+-	 */
+-	if (priv->led_polarity_mode >= 0 &&
+-	    priv->led_polarity_mode != active_low) {
+-		phydev_err(phydev, "PHY polarity is global. Mismatched polarity on different LED\n");
+-		return -EINVAL;
+-	}
+-
+-	/* Save the last PHY polarity mode */
+-	priv->led_polarity_mode = active_low;
+-
+-	return phy_modify_mmd(phydev, MDIO_MMD_AN,
+-			      QCA808X_MMD7_LED_POLARITY_CTRL,
+-			      QCA808X_LED_ACTIVE_HIGH,
+-			      active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
+-}
+-
+ static struct phy_driver at803x_driver[] = {
+ {
+ 	/* Qualcomm Atheros AR8035 */
+@@ -1989,34 +1123,6 @@ static struct phy_driver at803x_driver[]
+ 	.read_status		= at803x_read_status,
+ 	.soft_reset		= genphy_soft_reset,
+ 	.config_aneg		= at803x_config_aneg,
+-}, {
+-	/* Qualcomm QCA8081 */
+-	PHY_ID_MATCH_EXACT(QCA8081_PHY_ID),
+-	.name			= "Qualcomm QCA8081",
+-	.flags			= PHY_POLL_CABLE_TEST,
+-	.probe			= at803x_probe,
+-	.config_intr		= at803x_config_intr,
+-	.handle_interrupt	= at803x_handle_interrupt,
+-	.get_tunable		= at803x_get_tunable,
+-	.set_tunable		= at803x_set_tunable,
+-	.set_wol		= at803x_set_wol,
+-	.get_wol		= at803x_get_wol,
+-	.get_features		= qca808x_get_features,
+-	.config_aneg		= qca808x_config_aneg,
+-	.suspend		= genphy_suspend,
+-	.resume			= genphy_resume,
+-	.read_status		= qca808x_read_status,
+-	.config_init		= qca808x_config_init,
+-	.soft_reset		= qca808x_soft_reset,
+-	.cable_test_start	= qca808x_cable_test_start,
+-	.cable_test_get_status	= qca808x_cable_test_get_status,
+-	.link_change_notify	= qca808x_link_change_notify,
+-	.led_brightness_set	= qca808x_led_brightness_set,
+-	.led_blink_set		= qca808x_led_blink_set,
+-	.led_hw_is_supported	= qca808x_led_hw_is_supported,
+-	.led_hw_control_set	= qca808x_led_hw_control_set,
+-	.led_hw_control_get	= qca808x_led_hw_control_get,
+-	.led_polarity_set	= qca808x_led_polarity_set,
+ }, };
+ 
+ module_phy_driver(at803x_driver);
+@@ -2028,7 +1134,6 @@ static struct mdio_device_id __maybe_unu
+ 	{ PHY_ID_MATCH_EXACT(ATH8035_PHY_ID) },
+ 	{ PHY_ID_MATCH_EXACT(ATH9331_PHY_ID) },
+ 	{ PHY_ID_MATCH_EXACT(QCA9561_PHY_ID) },
+-	{ PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) },
+ 	{ }
+ };
+ 
+--- /dev/null
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -0,0 +1,934 @@
++// SPDX-License-Identifier: GPL-2.0+
++
++#include <linux/phy.h>
++#include <linux/module.h>
++#include <linux/ethtool_netlink.h>
++
++#include "qcom.h"
++
++/* ADC threshold */
++#define QCA808X_PHY_DEBUG_ADC_THRESHOLD		0x2c80
++#define QCA808X_ADC_THRESHOLD_MASK		GENMASK(7, 0)
++#define QCA808X_ADC_THRESHOLD_80MV		0
++#define QCA808X_ADC_THRESHOLD_100MV		0xf0
++#define QCA808X_ADC_THRESHOLD_200MV		0x0f
++#define QCA808X_ADC_THRESHOLD_300MV		0xff
++
++/* CLD control */
++#define QCA808X_PHY_MMD3_ADDR_CLD_CTRL7		0x8007
++#define QCA808X_8023AZ_AFE_CTRL_MASK		GENMASK(8, 4)
++#define QCA808X_8023AZ_AFE_EN			0x90
++
++/* AZ control */
++#define QCA808X_PHY_MMD3_AZ_TRAINING_CTRL	0x8008
++#define QCA808X_MMD3_AZ_TRAINING_VAL		0x1c32
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB	0x8014
++#define QCA808X_MSE_THRESHOLD_20DB_VALUE	0x529
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB	0x800E
++#define QCA808X_MSE_THRESHOLD_17DB_VALUE	0x341
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB	0x801E
++#define QCA808X_MSE_THRESHOLD_27DB_VALUE	0x419
++
++#define QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB	0x8020
++#define QCA808X_MSE_THRESHOLD_28DB_VALUE	0x341
++
++#define QCA808X_PHY_MMD7_TOP_OPTION1		0x901c
++#define QCA808X_TOP_OPTION1_DATA		0x0
++
++#define QCA808X_PHY_MMD3_DEBUG_1		0xa100
++#define QCA808X_MMD3_DEBUG_1_VALUE		0x9203
++#define QCA808X_PHY_MMD3_DEBUG_2		0xa101
++#define QCA808X_MMD3_DEBUG_2_VALUE		0x48ad
++#define QCA808X_PHY_MMD3_DEBUG_3		0xa103
++#define QCA808X_MMD3_DEBUG_3_VALUE		0x1698
++#define QCA808X_PHY_MMD3_DEBUG_4		0xa105
++#define QCA808X_MMD3_DEBUG_4_VALUE		0x8001
++#define QCA808X_PHY_MMD3_DEBUG_5		0xa106
++#define QCA808X_MMD3_DEBUG_5_VALUE		0x1111
++#define QCA808X_PHY_MMD3_DEBUG_6		0xa011
++#define QCA808X_MMD3_DEBUG_6_VALUE		0x5f85
++
++/* master/slave seed config */
++#define QCA808X_PHY_DEBUG_LOCAL_SEED		9
++#define QCA808X_MASTER_SLAVE_SEED_ENABLE	BIT(1)
++#define QCA808X_MASTER_SLAVE_SEED_CFG		GENMASK(12, 2)
++#define QCA808X_MASTER_SLAVE_SEED_RANGE		0x32
++
++/* Hibernation yields lower power consumpiton in contrast with normal operation mode.
++ * when the copper cable is unplugged, the PHY enters into hibernation mode in about 10s.
++ */
++#define QCA808X_DBG_AN_TEST			0xb
++#define QCA808X_HIBERNATION_EN			BIT(15)
++
++#define QCA808X_CDT_ENABLE_TEST			BIT(15)
++#define QCA808X_CDT_INTER_CHECK_DIS		BIT(13)
++#define QCA808X_CDT_STATUS			BIT(11)
++#define QCA808X_CDT_LENGTH_UNIT			BIT(10)
++
++#define QCA808X_MMD3_CDT_STATUS			0x8064
++#define QCA808X_MMD3_CDT_DIAG_PAIR_A		0x8065
++#define QCA808X_MMD3_CDT_DIAG_PAIR_B		0x8066
++#define QCA808X_MMD3_CDT_DIAG_PAIR_C		0x8067
++#define QCA808X_MMD3_CDT_DIAG_PAIR_D		0x8068
++#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT	GENMASK(15, 8)
++#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT	GENMASK(7, 0)
++
++#define QCA808X_CDT_CODE_PAIR_A			GENMASK(15, 12)
++#define QCA808X_CDT_CODE_PAIR_B			GENMASK(11, 8)
++#define QCA808X_CDT_CODE_PAIR_C			GENMASK(7, 4)
++#define QCA808X_CDT_CODE_PAIR_D			GENMASK(3, 0)
++
++#define QCA808X_CDT_STATUS_STAT_TYPE		GENMASK(1, 0)
++#define QCA808X_CDT_STATUS_STAT_FAIL		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
++#define QCA808X_CDT_STATUS_STAT_NORMAL		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
++#define QCA808X_CDT_STATUS_STAT_SAME_OPEN	FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
++#define QCA808X_CDT_STATUS_STAT_SAME_SHORT	FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
++
++#define QCA808X_CDT_STATUS_STAT_MDI		GENMASK(3, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI1		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
++#define QCA808X_CDT_STATUS_STAT_MDI2		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI3		FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
++
++/* NORMAL are MDI with type set to 0 */
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI1
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++									 QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++									 QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI2
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++									 QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++									 QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL	QCA808X_CDT_STATUS_STAT_MDI3
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN		(QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++									 QCA808X_CDT_STATUS_STAT_MDI3)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT	(QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++									 QCA808X_CDT_STATUS_STAT_MDI3)
++
++/* Added for reference of existence but should be handled by wait_for_completion already */
++#define QCA808X_CDT_STATUS_STAT_BUSY		(BIT(1) | BIT(3))
++
++#define QCA808X_MMD7_LED_GLOBAL			0x8073
++#define QCA808X_LED_BLINK_1			GENMASK(11, 6)
++#define QCA808X_LED_BLINK_2			GENMASK(5, 0)
++/* Values are the same for both BLINK_1 and BLINK_2 */
++#define QCA808X_LED_BLINK_FREQ_MASK		GENMASK(5, 3)
++#define QCA808X_LED_BLINK_FREQ_2HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
++#define QCA808X_LED_BLINK_FREQ_4HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
++#define QCA808X_LED_BLINK_FREQ_8HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
++#define QCA808X_LED_BLINK_FREQ_16HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
++#define QCA808X_LED_BLINK_FREQ_32HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
++#define QCA808X_LED_BLINK_FREQ_64HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
++#define QCA808X_LED_BLINK_FREQ_128HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
++#define QCA808X_LED_BLINK_FREQ_256HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
++#define QCA808X_LED_BLINK_DUTY_MASK		GENMASK(2, 0)
++#define QCA808X_LED_BLINK_DUTY_50_50		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
++#define QCA808X_LED_BLINK_DUTY_75_25		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
++#define QCA808X_LED_BLINK_DUTY_25_75		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
++#define QCA808X_LED_BLINK_DUTY_33_67		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
++#define QCA808X_LED_BLINK_DUTY_67_33		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
++#define QCA808X_LED_BLINK_DUTY_17_83		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
++#define QCA808X_LED_BLINK_DUTY_83_17		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
++#define QCA808X_LED_BLINK_DUTY_8_92		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
++
++#define QCA808X_MMD7_LED2_CTRL			0x8074
++#define QCA808X_MMD7_LED2_FORCE_CTRL		0x8075
++#define QCA808X_MMD7_LED1_CTRL			0x8076
++#define QCA808X_MMD7_LED1_FORCE_CTRL		0x8077
++#define QCA808X_MMD7_LED0_CTRL			0x8078
++#define QCA808X_MMD7_LED_CTRL(x)		(0x8078 - ((x) * 2))
++
++/* LED hw control pattern is the same for every LED */
++#define QCA808X_LED_PATTERN_MASK		GENMASK(15, 0)
++#define QCA808X_LED_SPEED2500_ON		BIT(15)
++#define QCA808X_LED_SPEED2500_BLINK		BIT(14)
++/* Follow blink trigger even if duplex or speed condition doesn't match */
++#define QCA808X_LED_BLINK_CHECK_BYPASS		BIT(13)
++#define QCA808X_LED_FULL_DUPLEX_ON		BIT(12)
++#define QCA808X_LED_HALF_DUPLEX_ON		BIT(11)
++#define QCA808X_LED_TX_BLINK			BIT(10)
++#define QCA808X_LED_RX_BLINK			BIT(9)
++#define QCA808X_LED_TX_ON_10MS			BIT(8)
++#define QCA808X_LED_RX_ON_10MS			BIT(7)
++#define QCA808X_LED_SPEED1000_ON		BIT(6)
++#define QCA808X_LED_SPEED100_ON			BIT(5)
++#define QCA808X_LED_SPEED10_ON			BIT(4)
++#define QCA808X_LED_COLLISION_BLINK		BIT(3)
++#define QCA808X_LED_SPEED1000_BLINK		BIT(2)
++#define QCA808X_LED_SPEED100_BLINK		BIT(1)
++#define QCA808X_LED_SPEED10_BLINK		BIT(0)
++
++#define QCA808X_MMD7_LED0_FORCE_CTRL		0x8079
++#define QCA808X_MMD7_LED_FORCE_CTRL(x)		(0x8079 - ((x) * 2))
++
++/* LED force ctrl is the same for every LED
++ * No documentation exist for this, not even internal one
++ * with NDA as QCOM gives only info about configuring
++ * hw control pattern rules and doesn't indicate any way
++ * to force the LED to specific mode.
++ * These define comes from reverse and testing and maybe
++ * lack of some info or some info are not entirely correct.
++ * For the basic LED control and hw control these finding
++ * are enough to support LED control in all the required APIs.
++ *
++ * On doing some comparison with implementation with qca807x,
++ * it was found that it's 1:1 equal to it and confirms all the
++ * reverse done. It was also found further specification with the
++ * force mode and the blink modes.
++ */
++#define QCA808X_LED_FORCE_EN			BIT(15)
++#define QCA808X_LED_FORCE_MODE_MASK		GENMASK(14, 13)
++#define QCA808X_LED_FORCE_BLINK_1		FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
++#define QCA808X_LED_FORCE_BLINK_2		FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
++#define QCA808X_LED_FORCE_ON			FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
++#define QCA808X_LED_FORCE_OFF			FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
++
++#define QCA808X_MMD7_LED_POLARITY_CTRL		0x901a
++/* QSDK sets by default 0x46 to this reg that sets BIT 6 for
++ * LED to active high. It's not clear what BIT 3 and BIT 4 does.
++ */
++#define QCA808X_LED_ACTIVE_HIGH			BIT(6)
++
++/* QCA808X 1G chip type */
++#define QCA808X_PHY_MMD7_CHIP_TYPE		0x901d
++#define QCA808X_PHY_CHIP_TYPE_1G		BIT(0)
++
++#define QCA8081_PHY_SERDES_MMD1_FIFO_CTRL	0x9072
++#define QCA8081_PHY_FIFO_RSTN			BIT(11)
++
++#define QCA8081_PHY_ID				0x004dd101
++
++MODULE_DESCRIPTION("Qualcomm Atheros QCA808X PHY driver");
++MODULE_AUTHOR("Matus Ujhelyi");
++MODULE_LICENSE("GPL");
++
++struct qca808x_priv {
++	int led_polarity_mode;
++};
++
++static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
++{
++	int ret;
++
++	/* Enable fast retrain */
++	ret = genphy_c45_fast_retrain(phydev, true);
++	if (ret)
++		return ret;
++
++	phy_write_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_TOP_OPTION1,
++		      QCA808X_TOP_OPTION1_DATA);
++	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB,
++		      QCA808X_MSE_THRESHOLD_20DB_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB,
++		      QCA808X_MSE_THRESHOLD_17DB_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB,
++		      QCA808X_MSE_THRESHOLD_27DB_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB,
++		      QCA808X_MSE_THRESHOLD_28DB_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_1,
++		      QCA808X_MMD3_DEBUG_1_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_4,
++		      QCA808X_MMD3_DEBUG_4_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_5,
++		      QCA808X_MMD3_DEBUG_5_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_3,
++		      QCA808X_MMD3_DEBUG_3_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_6,
++		      QCA808X_MMD3_DEBUG_6_VALUE);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_2,
++		      QCA808X_MMD3_DEBUG_2_VALUE);
++
++	return 0;
++}
++
++static int qca808x_phy_ms_seed_enable(struct phy_device *phydev, bool enable)
++{
++	u16 seed_value;
++
++	if (!enable)
++		return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
++				QCA808X_MASTER_SLAVE_SEED_ENABLE, 0);
++
++	seed_value = prandom_u32_max(QCA808X_MASTER_SLAVE_SEED_RANGE);
++	return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
++			QCA808X_MASTER_SLAVE_SEED_CFG | QCA808X_MASTER_SLAVE_SEED_ENABLE,
++			FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value) |
++			QCA808X_MASTER_SLAVE_SEED_ENABLE);
++}
++
++static bool qca808x_is_prefer_master(struct phy_device *phydev)
++{
++	return (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_FORCE) ||
++		(phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
++}
++
++static bool qca808x_has_fast_retrain_or_slave_seed(struct phy_device *phydev)
++{
++	return linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
++}
++
++static int qca808x_probe(struct phy_device *phydev)
++{
++	struct device *dev = &phydev->mdio.dev;
++	struct qca808x_priv *priv;
++
++	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++	if (!priv)
++		return -ENOMEM;
++
++	/* Init LED polarity mode to -1 */
++	priv->led_polarity_mode = -1;
++
++	phydev->priv = priv;
++
++	return 0;
++}
++
++static int qca808x_config_init(struct phy_device *phydev)
++{
++	int ret;
++
++	/* Active adc&vga on 802.3az for the link 1000M and 100M */
++	ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_ADDR_CLD_CTRL7,
++			     QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
++	if (ret)
++		return ret;
++
++	/* Adjust the threshold on 802.3az for the link 1000M */
++	ret = phy_write_mmd(phydev, MDIO_MMD_PCS,
++			    QCA808X_PHY_MMD3_AZ_TRAINING_CTRL,
++			    QCA808X_MMD3_AZ_TRAINING_VAL);
++	if (ret)
++		return ret;
++
++	if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
++		/* Config the fast retrain for the link 2500M */
++		ret = qca808x_phy_fast_retrain_config(phydev);
++		if (ret)
++			return ret;
++
++		ret = genphy_read_master_slave(phydev);
++		if (ret < 0)
++			return ret;
++
++		if (!qca808x_is_prefer_master(phydev)) {
++			/* Enable seed and configure lower ramdom seed to make phy
++			 * linked as slave mode.
++			 */
++			ret = qca808x_phy_ms_seed_enable(phydev, true);
++			if (ret)
++				return ret;
++		}
++	}
++
++	/* Configure adc threshold as 100mv for the link 10M */
++	return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
++				     QCA808X_ADC_THRESHOLD_MASK,
++				     QCA808X_ADC_THRESHOLD_100MV);
++}
++
++static int qca808x_read_status(struct phy_device *phydev)
++{
++	struct at803x_ss_mask ss_mask = { 0 };
++	int ret;
++
++	ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
++	if (ret < 0)
++		return ret;
++
++	linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->lp_advertising,
++			 ret & MDIO_AN_10GBT_STAT_LP2_5G);
++
++	ret = genphy_read_status(phydev);
++	if (ret)
++		return ret;
++
++	/* qca8081 takes the different bits for speed value from at803x */
++	ss_mask.speed_mask = QCA808X_SS_SPEED_MASK;
++	ss_mask.speed_shift = __bf_shf(QCA808X_SS_SPEED_MASK);
++	ret = at803x_read_specific_status(phydev, ss_mask);
++	if (ret < 0)
++		return ret;
++
++	if (phydev->link) {
++		if (phydev->speed == SPEED_2500)
++			phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
++		else
++			phydev->interface = PHY_INTERFACE_MODE_SGMII;
++	} else {
++		/* generate seed as a lower random value to make PHY linked as SLAVE easily,
++		 * except for master/slave configuration fault detected or the master mode
++		 * preferred.
++		 *
++		 * the reason for not putting this code into the function link_change_notify is
++		 * the corner case where the link partner is also the qca8081 PHY and the seed
++		 * value is configured as the same value, the link can't be up and no link change
++		 * occurs.
++		 */
++		if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
++			if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
++			    qca808x_is_prefer_master(phydev)) {
++				qca808x_phy_ms_seed_enable(phydev, false);
++			} else {
++				qca808x_phy_ms_seed_enable(phydev, true);
++			}
++		}
++	}
++
++	return 0;
++}
++
++static int qca808x_soft_reset(struct phy_device *phydev)
++{
++	int ret;
++
++	ret = genphy_soft_reset(phydev);
++	if (ret < 0)
++		return ret;
++
++	if (qca808x_has_fast_retrain_or_slave_seed(phydev))
++		ret = qca808x_phy_ms_seed_enable(phydev, true);
++
++	return ret;
++}
++
++static bool qca808x_cdt_fault_length_valid(int cdt_code)
++{
++	switch (cdt_code) {
++	case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++		return true;
++	default:
++		return false;
++	}
++}
++
++static int qca808x_cable_test_result_trans(int cdt_code)
++{
++	switch (cdt_code) {
++	case QCA808X_CDT_STATUS_STAT_NORMAL:
++		return ETHTOOL_A_CABLE_RESULT_CODE_OK;
++	case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++		return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
++	case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++		return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++	case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++		return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
++	case QCA808X_CDT_STATUS_STAT_FAIL:
++	default:
++		return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
++	}
++}
++
++static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
++				    int result)
++{
++	int val;
++	u32 cdt_length_reg = 0;
++
++	switch (pair) {
++	case ETHTOOL_A_CABLE_PAIR_A:
++		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;
++		break;
++	case ETHTOOL_A_CABLE_PAIR_B:
++		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;
++		break;
++	case ETHTOOL_A_CABLE_PAIR_C:
++		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;
++		break;
++	case ETHTOOL_A_CABLE_PAIR_D:
++		cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);
++	if (val < 0)
++		return val;
++
++	if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
++		val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
++	else
++		val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
++
++	return at803x_cdt_fault_length(val);
++}
++
++static int qca808x_cable_test_start(struct phy_device *phydev)
++{
++	int ret;
++
++	/* perform CDT with the following configs:
++	 * 1. disable hibernation.
++	 * 2. force PHY working in MDI mode.
++	 * 3. for PHY working in 1000BaseT.
++	 * 4. configure the threshold.
++	 */
++
++	ret = at803x_debug_reg_mask(phydev, QCA808X_DBG_AN_TEST, QCA808X_HIBERNATION_EN, 0);
++	if (ret < 0)
++		return ret;
++
++	ret = at803x_config_mdix(phydev, ETH_TP_MDI);
++	if (ret < 0)
++		return ret;
++
++	/* Force 1000base-T needs to configure PMA/PMD and MII_BMCR */
++	phydev->duplex = DUPLEX_FULL;
++	phydev->speed = SPEED_1000;
++	ret = genphy_c45_pma_setup_forced(phydev);
++	if (ret < 0)
++		return ret;
++
++	ret = genphy_setup_forced(phydev);
++	if (ret < 0)
++		return ret;
++
++	/* configure the thresholds for open, short, pair ok test */
++	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8074, 0xc040);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8076, 0xc040);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8077, 0xa060);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8078, 0xc050);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807a, 0xc060);
++	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807e, 0xb060);
++
++	return 0;
++}
++
++static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
++					      u16 status)
++{
++	int length, result;
++	u16 pair_code;
++
++	switch (pair) {
++	case ETHTOOL_A_CABLE_PAIR_A:
++		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
++		break;
++	case ETHTOOL_A_CABLE_PAIR_B:
++		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
++		break;
++	case ETHTOOL_A_CABLE_PAIR_C:
++		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
++		break;
++	case ETHTOOL_A_CABLE_PAIR_D:
++		pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	result = qca808x_cable_test_result_trans(pair_code);
++	ethnl_cable_test_result(phydev, pair, result);
++
++	if (qca808x_cdt_fault_length_valid(pair_code)) {
++		length = qca808x_cdt_fault_length(phydev, pair, result);
++		ethnl_cable_test_fault_length(phydev, pair, length);
++	}
++
++	return 0;
++}
++
++static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
++{
++	int ret, val;
++
++	*finished = false;
++
++	val = QCA808X_CDT_ENABLE_TEST |
++	      QCA808X_CDT_LENGTH_UNIT;
++	ret = at803x_cdt_start(phydev, val);
++	if (ret)
++		return ret;
++
++	ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
++	if (ret)
++		return ret;
++
++	val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);
++	if (val < 0)
++		return val;
++
++	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
++	if (ret)
++		return ret;
++
++	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
++	if (ret)
++		return ret;
++
++	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
++	if (ret)
++		return ret;
++
++	ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
++	if (ret)
++		return ret;
++
++	*finished = true;
++
++	return 0;
++}
++
++static int qca808x_get_features(struct phy_device *phydev)
++{
++	int ret;
++
++	ret = genphy_c45_pma_read_abilities(phydev);
++	if (ret)
++		return ret;
++
++	/* The autoneg ability is not existed in bit3 of MMD7.1,
++	 * but it is supported by qca808x PHY, so we add it here
++	 * manually.
++	 */
++	linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
++
++	/* As for the qca8081 1G version chip, the 2500baseT ability is also
++	 * existed in the bit0 of MMD1.21, we need to remove it manually if
++	 * it is the qca8081 1G chip according to the bit0 of MMD7.0x901d.
++	 */
++	ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE);
++	if (ret < 0)
++		return ret;
++
++	if (QCA808X_PHY_CHIP_TYPE_1G & ret)
++		linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
++
++	return 0;
++}
++
++static int qca808x_config_aneg(struct phy_device *phydev)
++{
++	int phy_ctrl = 0;
++	int ret;
++
++	ret = at803x_prepare_config_aneg(phydev);
++	if (ret)
++		return ret;
++
++	/* The reg MII_BMCR also needs to be configured for force mode, the
++	 * genphy_config_aneg is also needed.
++	 */
++	if (phydev->autoneg == AUTONEG_DISABLE)
++		genphy_c45_pma_setup_forced(phydev);
++
++	if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising))
++		phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
++
++	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
++				     MDIO_AN_10GBT_CTRL_ADV2_5G, phy_ctrl);
++	if (ret < 0)
++		return ret;
++
++	return __genphy_config_aneg(phydev, ret);
++}
++
++static void qca808x_link_change_notify(struct phy_device *phydev)
++{
++	/* Assert interface sgmii fifo on link down, deassert it on link up,
++	 * the interface device address is always phy address added by 1.
++	 */
++	mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
++				   MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
++				   QCA8081_PHY_FIFO_RSTN,
++				   phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
++}
++
++static int qca808x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
++				    u16 *offload_trigger)
++{
++	/* Parsing specific to netdev trigger */
++	if (test_bit(TRIGGER_NETDEV_TX, &rules))
++		*offload_trigger |= QCA808X_LED_TX_BLINK;
++	if (test_bit(TRIGGER_NETDEV_RX, &rules))
++		*offload_trigger |= QCA808X_LED_RX_BLINK;
++	if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
++		*offload_trigger |= QCA808X_LED_SPEED10_ON;
++	if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++		*offload_trigger |= QCA808X_LED_SPEED100_ON;
++	if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++		*offload_trigger |= QCA808X_LED_SPEED1000_ON;
++	if (test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
++		*offload_trigger |= QCA808X_LED_SPEED2500_ON;
++	if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
++		*offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
++	if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
++		*offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
++
++	if (rules && !*offload_trigger)
++		return -EOPNOTSUPP;
++
++	/* Enable BLINK_CHECK_BYPASS by default to make the LED
++	 * blink even with duplex or speed mode not enabled.
++	 */
++	*offload_trigger |= QCA808X_LED_BLINK_CHECK_BYPASS;
++
++	return 0;
++}
++
++static int qca808x_led_hw_control_enable(struct phy_device *phydev, u8 index)
++{
++	u16 reg;
++
++	if (index > 2)
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++				  QCA808X_LED_FORCE_EN);
++}
++
++static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
++				       unsigned long rules)
++{
++	u16 offload_trigger = 0;
++
++	if (index > 2)
++		return -EINVAL;
++
++	return qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
++}
++
++static int qca808x_led_hw_control_set(struct phy_device *phydev, u8 index,
++				      unsigned long rules)
++{
++	u16 reg, offload_trigger = 0;
++	int ret;
++
++	if (index > 2)
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_CTRL(index);
++
++	ret = qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
++	if (ret)
++		return ret;
++
++	ret = qca808x_led_hw_control_enable(phydev, index);
++	if (ret)
++		return ret;
++
++	return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++			      QCA808X_LED_PATTERN_MASK,
++			      offload_trigger);
++}
++
++static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
++{
++	u16 reg;
++	int val;
++
++	if (index > 2)
++		return false;
++
++	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++	val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++
++	return !(val & QCA808X_LED_FORCE_EN);
++}
++
++static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
++				      unsigned long *rules)
++{
++	u16 reg;
++	int val;
++
++	if (index > 2)
++		return -EINVAL;
++
++	/* Check if we have hw control enabled */
++	if (qca808x_led_hw_control_status(phydev, index))
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_CTRL(index);
++
++	val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++	if (val & QCA808X_LED_TX_BLINK)
++		set_bit(TRIGGER_NETDEV_TX, rules);
++	if (val & QCA808X_LED_RX_BLINK)
++		set_bit(TRIGGER_NETDEV_RX, rules);
++	if (val & QCA808X_LED_SPEED10_ON)
++		set_bit(TRIGGER_NETDEV_LINK_10, rules);
++	if (val & QCA808X_LED_SPEED100_ON)
++		set_bit(TRIGGER_NETDEV_LINK_100, rules);
++	if (val & QCA808X_LED_SPEED1000_ON)
++		set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++	if (val & QCA808X_LED_SPEED2500_ON)
++		set_bit(TRIGGER_NETDEV_LINK_2500, rules);
++	if (val & QCA808X_LED_HALF_DUPLEX_ON)
++		set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
++	if (val & QCA808X_LED_FULL_DUPLEX_ON)
++		set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
++
++	return 0;
++}
++
++static int qca808x_led_hw_control_reset(struct phy_device *phydev, u8 index)
++{
++	u16 reg;
++
++	if (index > 2)
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_CTRL(index);
++
++	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++				  QCA808X_LED_PATTERN_MASK);
++}
++
++static int qca808x_led_brightness_set(struct phy_device *phydev,
++				      u8 index, enum led_brightness value)
++{
++	u16 reg;
++	int ret;
++
++	if (index > 2)
++		return -EINVAL;
++
++	if (!value) {
++		ret = qca808x_led_hw_control_reset(phydev, index);
++		if (ret)
++			return ret;
++	}
++
++	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++	return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++			      QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++			      QCA808X_LED_FORCE_EN | value ? QCA808X_LED_FORCE_ON :
++							     QCA808X_LED_FORCE_OFF);
++}
++
++static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
++				 unsigned long *delay_on,
++				 unsigned long *delay_off)
++{
++	int ret;
++	u16 reg;
++
++	if (index > 2)
++		return -EINVAL;
++
++	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
++
++	/* Set blink to 50% off, 50% on at 4Hz by default */
++	ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
++			     QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
++			     QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
++	if (ret)
++		return ret;
++
++	/* We use BLINK_1 for normal blinking */
++	ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++			     QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++			     QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
++	if (ret)
++		return ret;
++
++	/* We set blink to 4Hz, aka 250ms */
++	*delay_on = 250 / 2;
++	*delay_off = 250 / 2;
++
++	return 0;
++}
++
++static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
++				    unsigned long modes)
++{
++	struct qca808x_priv *priv = phydev->priv;
++	bool active_low = false;
++	u32 mode;
++
++	for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
++		switch (mode) {
++		case PHY_LED_ACTIVE_LOW:
++			active_low = true;
++			break;
++		default:
++			return -EINVAL;
++		}
++	}
++
++	/* PHY polarity is global and can't be set per LED.
++	 * To detect this, check if last requested polarity mode
++	 * match the new one.
++	 */
++	if (priv->led_polarity_mode >= 0 &&
++	    priv->led_polarity_mode != active_low) {
++		phydev_err(phydev, "PHY polarity is global. Mismatched polarity on different LED\n");
++		return -EINVAL;
++	}
++
++	/* Save the last PHY polarity mode */
++	priv->led_polarity_mode = active_low;
++
++	return phy_modify_mmd(phydev, MDIO_MMD_AN,
++			      QCA808X_MMD7_LED_POLARITY_CTRL,
++			      QCA808X_LED_ACTIVE_HIGH,
++			      active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
++}
++
++static struct phy_driver qca808x_driver[] = {
++{
++	/* Qualcomm QCA8081 */
++	PHY_ID_MATCH_EXACT(QCA8081_PHY_ID),
++	.name			= "Qualcomm QCA8081",
++	.flags			= PHY_POLL_CABLE_TEST,
++	.probe			= qca808x_probe,
++	.config_intr		= at803x_config_intr,
++	.handle_interrupt	= at803x_handle_interrupt,
++	.get_tunable		= at803x_get_tunable,
++	.set_tunable		= at803x_set_tunable,
++	.set_wol		= at803x_set_wol,
++	.get_wol		= at803x_get_wol,
++	.get_features		= qca808x_get_features,
++	.config_aneg		= qca808x_config_aneg,
++	.suspend		= genphy_suspend,
++	.resume			= genphy_resume,
++	.read_status		= qca808x_read_status,
++	.config_init		= qca808x_config_init,
++	.soft_reset		= qca808x_soft_reset,
++	.cable_test_start	= qca808x_cable_test_start,
++	.cable_test_get_status	= qca808x_cable_test_get_status,
++	.link_change_notify	= qca808x_link_change_notify,
++	.led_brightness_set	= qca808x_led_brightness_set,
++	.led_blink_set		= qca808x_led_blink_set,
++	.led_hw_is_supported	= qca808x_led_hw_is_supported,
++	.led_hw_control_set	= qca808x_led_hw_control_set,
++	.led_hw_control_get	= qca808x_led_hw_control_get,
++	.led_polarity_set	= qca808x_led_polarity_set,
++}, };
++
++module_phy_driver(qca808x_driver);
++
++static struct mdio_device_id __maybe_unused qca808x_tbl[] = {
++	{ PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) },
++	{ }
++};
++
++MODULE_DEVICE_TABLE(mdio, qca808x_tbl);

+ 394 - 0
target/linux/generic/backport-6.6/714-net-pcs-add-driver-for-MediaTek-SGMII-PCS.patch

@@ -0,0 +1,394 @@
+From 4765a9722e09765866e131ec31f7b9cf4c1f4854 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <[email protected]>
+Date: Sun, 19 Mar 2023 12:57:50 +0000
+Subject: [PATCH] net: pcs: add driver for MediaTek SGMII PCS
+
+The SGMII core found in several MediaTek SoCs is identical to what can
+also be found in MediaTek's MT7531 Ethernet switch IC.
+As this has not always been clear, both drivers developed different
+implementations to deal with the PCS.
+Recently Alexander Couzens pointed out this fact which lead to the
+development of this shared driver.
+
+Add a dedicated driver, mostly by copying the code now found in the
+Ethernet driver. The now redundant code will be removed by a follow-up
+commit.
+
+Suggested-by: Alexander Couzens <[email protected]>
+Suggested-by: Russell King (Oracle) <[email protected]>
+Signed-off-by: Daniel Golle <[email protected]>
+Tested-by: Frank Wunderlich <[email protected]>
+Reviewed-by: Russell King (Oracle) <[email protected]>
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ MAINTAINERS                       |   8 +
+ drivers/net/pcs/Kconfig           |   7 +
+ drivers/net/pcs/Makefile          |   1 +
+ drivers/net/pcs/pcs-mtk-lynxi.c   | 305 ++++++++++++++++++++++++++++++
+ include/linux/pcs/pcs-mtk-lynxi.h |  13 ++
+ 5 files changed, 334 insertions(+)
+ create mode 100644 drivers/net/pcs/pcs-mtk-lynxi.c
+ create mode 100644 include/linux/pcs/pcs-mtk-lynxi.h
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -12928,6 +12928,14 @@ L:	[email protected]
+ S:	Maintained
+ F:	drivers/net/ethernet/mediatek/
+ 
++MEDIATEK ETHERNET PCS DRIVER
++M:	Alexander Couzens <[email protected]>
++M:	Daniel Golle <[email protected]>
++L:	[email protected]
++S:	Maintained
++F:	drivers/net/pcs/pcs-mtk-lynxi.c
++F:	include/linux/pcs/pcs-mtk-lynxi.h
++
+ MEDIATEK I2C CONTROLLER DRIVER
+ M:	Qii Wang <[email protected]>
+ L:	[email protected]
+--- a/drivers/net/pcs/Kconfig
++++ b/drivers/net/pcs/Kconfig
+@@ -32,4 +32,11 @@ config PCS_ALTERA_TSE
+ 	  This module provides helper functions for the Altera Triple Speed
+ 	  Ethernet SGMII PCS, that can be found on the Intel Socfpga family.
+ 
++config PCS_MTK_LYNXI
++	tristate
++	select REGMAP
++	help
++	  This module provides helpers to phylink for managing the LynxI PCS
++	  which is part of MediaTek's SoC and Ethernet switch ICs.
++
+ endmenu
+--- a/drivers/net/pcs/Makefile
++++ b/drivers/net/pcs/Makefile
+@@ -7,3 +7,4 @@ obj-$(CONFIG_PCS_XPCS)		+= pcs_xpcs.o
+ obj-$(CONFIG_PCS_LYNX)		+= pcs-lynx.o
+ obj-$(CONFIG_PCS_RZN1_MIIC)	+= pcs-rzn1-miic.o
+ obj-$(CONFIG_PCS_ALTERA_TSE)	+= pcs-altera-tse.o
++obj-$(CONFIG_PCS_MTK_LYNXI)	+= pcs-mtk-lynxi.o
+--- /dev/null
++++ b/drivers/net/pcs/pcs-mtk-lynxi.c
+@@ -0,0 +1,305 @@
++// SPDX-License-Identifier: GPL-2.0
++// Copyright (c) 2018-2019 MediaTek Inc.
++/* A library for MediaTek SGMII circuit
++ *
++ * Author: Sean Wang <[email protected]>
++ * Author: Alexander Couzens <[email protected]>
++ * Author: Daniel Golle <[email protected]>
++ *
++ */
++
++#include <linux/mdio.h>
++#include <linux/of.h>
++#include <linux/pcs/pcs-mtk-lynxi.h>
++#include <linux/phylink.h>
++#include <linux/regmap.h>
++
++/* SGMII subsystem config registers */
++/* BMCR (low 16) BMSR (high 16) */
++#define SGMSYS_PCS_CONTROL_1		0x0
++#define SGMII_BMCR			GENMASK(15, 0)
++#define SGMII_BMSR			GENMASK(31, 16)
++
++#define SGMSYS_PCS_DEVICE_ID		0x4
++#define SGMII_LYNXI_DEV_ID		0x4d544950
++
++#define SGMSYS_PCS_ADVERTISE		0x8
++#define SGMII_ADVERTISE			GENMASK(15, 0)
++#define SGMII_LPA			GENMASK(31, 16)
++
++#define SGMSYS_PCS_SCRATCH		0x14
++#define SGMII_DEV_VERSION		GENMASK(31, 16)
++
++/* Register to programmable link timer, the unit in 2 * 8ns */
++#define SGMSYS_PCS_LINK_TIMER		0x18
++#define SGMII_LINK_TIMER_MASK		GENMASK(19, 0)
++#define SGMII_LINK_TIMER_VAL(ns)	FIELD_PREP(SGMII_LINK_TIMER_MASK, \
++						   ((ns) / 2 / 8))
++
++/* Register to control remote fault */
++#define SGMSYS_SGMII_MODE		0x20
++#define SGMII_IF_MODE_SGMII		BIT(0)
++#define SGMII_SPEED_DUPLEX_AN		BIT(1)
++#define SGMII_SPEED_MASK		GENMASK(3, 2)
++#define SGMII_SPEED_10			FIELD_PREP(SGMII_SPEED_MASK, 0)
++#define SGMII_SPEED_100			FIELD_PREP(SGMII_SPEED_MASK, 1)
++#define SGMII_SPEED_1000		FIELD_PREP(SGMII_SPEED_MASK, 2)
++#define SGMII_DUPLEX_HALF		BIT(4)
++#define SGMII_REMOTE_FAULT_DIS		BIT(8)
++
++/* Register to reset SGMII design */
++#define SGMSYS_RESERVED_0		0x34
++#define SGMII_SW_RESET			BIT(0)
++
++/* Register to set SGMII speed, ANA RG_ Control Signals III */
++#define SGMII_PHY_SPEED_MASK		GENMASK(3, 2)
++#define SGMII_PHY_SPEED_1_25G		FIELD_PREP(SGMII_PHY_SPEED_MASK, 0)
++#define SGMII_PHY_SPEED_3_125G		FIELD_PREP(SGMII_PHY_SPEED_MASK, 1)
++
++/* Register to power up QPHY */
++#define SGMSYS_QPHY_PWR_STATE_CTRL	0xe8
++#define	SGMII_PHYA_PWD			BIT(4)
++
++/* Register to QPHY wrapper control */
++#define SGMSYS_QPHY_WRAP_CTRL		0xec
++#define SGMII_PN_SWAP_MASK		GENMASK(1, 0)
++#define SGMII_PN_SWAP_TX_RX		(BIT(0) | BIT(1))
++
++/* struct mtk_pcs_lynxi -  This structure holds each sgmii regmap andassociated
++ *                         data
++ * @regmap:                The register map pointing at the range used to setup
++ *                         SGMII modes
++ * @dev:                   Pointer to device owning the PCS
++ * @ana_rgc3:              The offset of register ANA_RGC3 relative to regmap
++ * @interface:             Currently configured interface mode
++ * @pcs:                   Phylink PCS structure
++ * @flags:                 Flags indicating hardware properties
++ */
++struct mtk_pcs_lynxi {
++	struct regmap		*regmap;
++	u32			ana_rgc3;
++	phy_interface_t		interface;
++	struct			phylink_pcs pcs;
++	u32			flags;
++};
++
++static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs)
++{
++	return container_of(pcs, struct mtk_pcs_lynxi, pcs);
++}
++
++static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs,
++				    struct phylink_link_state *state)
++{
++	struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
++	unsigned int bm, adv;
++
++	/* Read the BMSR and LPA */
++	regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm);
++	regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv);
++
++	phylink_mii_c22_pcs_decode_state(state, FIELD_GET(SGMII_BMSR, bm),
++					 FIELD_GET(SGMII_LPA, adv));
++}
++
++static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int mode,
++				phy_interface_t interface,
++				const unsigned long *advertising,
++				bool permit_pause_to_mac)
++{
++	struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
++	bool mode_changed = false, changed, use_an;
++	unsigned int rgc3, sgm_mode, bmcr;
++	int advertise, link_timer;
++
++	advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
++							     advertising);
++	if (advertise < 0)
++		return advertise;
++
++	/* Clearing IF_MODE_BIT0 switches the PCS to BASE-X mode, and
++	 * we assume that fixes it's speed at bitrate = line rate (in
++	 * other words, 1000Mbps or 2500Mbps).
++	 */
++	if (interface == PHY_INTERFACE_MODE_SGMII) {
++		sgm_mode = SGMII_IF_MODE_SGMII;
++		if (phylink_autoneg_inband(mode)) {
++			sgm_mode |= SGMII_REMOTE_FAULT_DIS |
++				    SGMII_SPEED_DUPLEX_AN;
++			use_an = true;
++		} else {
++			use_an = false;
++		}
++	} else if (phylink_autoneg_inband(mode)) {
++		/* 1000base-X or 2500base-X autoneg */
++		sgm_mode = SGMII_REMOTE_FAULT_DIS;
++		use_an = linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++					   advertising);
++	} else {
++		/* 1000base-X or 2500base-X without autoneg */
++		sgm_mode = 0;
++		use_an = false;
++	}
++
++	if (use_an)
++		bmcr = BMCR_ANENABLE;
++	else
++		bmcr = 0;
++
++	if (mpcs->interface != interface) {
++		link_timer = phylink_get_link_timer_ns(interface);
++		if (link_timer < 0)
++			return link_timer;
++
++		/* PHYA power down */
++		regmap_set_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
++				SGMII_PHYA_PWD);
++
++		/* Reset SGMII PCS state */
++		regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0,
++				SGMII_SW_RESET);
++
++		if (mpcs->flags & MTK_SGMII_FLAG_PN_SWAP)
++			regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL,
++					   SGMII_PN_SWAP_MASK,
++					   SGMII_PN_SWAP_TX_RX);
++
++		if (interface == PHY_INTERFACE_MODE_2500BASEX)
++			rgc3 = SGMII_PHY_SPEED_3_125G;
++		else
++			rgc3 = SGMII_PHY_SPEED_1_25G;
++
++		/* Configure the underlying interface speed */
++		regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
++				   SGMII_PHY_SPEED_MASK, rgc3);
++
++		/* Setup the link timer */
++		regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER,
++			     SGMII_LINK_TIMER_VAL(link_timer));
++
++		mpcs->interface = interface;
++		mode_changed = true;
++	}
++
++	/* Update the advertisement, noting whether it has changed */
++	regmap_update_bits_check(mpcs->regmap, SGMSYS_PCS_ADVERTISE,
++				 SGMII_ADVERTISE, advertise, &changed);
++
++	/* Update the sgmsys mode register */
++	regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
++			   SGMII_REMOTE_FAULT_DIS | SGMII_SPEED_DUPLEX_AN |
++			   SGMII_IF_MODE_SGMII, sgm_mode);
++
++	/* Update the BMCR */
++	regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
++			   BMCR_ANENABLE, bmcr);
++
++	/* Release PHYA power down state
++	 * Only removing bit SGMII_PHYA_PWD isn't enough.
++	 * There are cases when the SGMII_PHYA_PWD register contains 0x9 which
++	 * prevents SGMII from working. The SGMII still shows link but no traffic
++	 * can flow. Writing 0x0 to the PHYA_PWD register fix the issue. 0x0 was
++	 * taken from a good working state of the SGMII interface.
++	 * Unknown how much the QPHY needs but it is racy without a sleep.
++	 * Tested on mt7622 & mt7986.
++	 */
++	usleep_range(50, 100);
++	regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, 0);
++
++	return changed || mode_changed;
++}
++
++static void mtk_pcs_lynxi_restart_an(struct phylink_pcs *pcs)
++{
++	struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
++
++	regmap_set_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1, BMCR_ANRESTART);
++}
++
++static void mtk_pcs_lynxi_link_up(struct phylink_pcs *pcs, unsigned int mode,
++				  phy_interface_t interface, int speed,
++				  int duplex)
++{
++	struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
++	unsigned int sgm_mode;
++
++	if (!phylink_autoneg_inband(mode)) {
++		/* Force the speed and duplex setting */
++		if (speed == SPEED_10)
++			sgm_mode = SGMII_SPEED_10;
++		else if (speed == SPEED_100)
++			sgm_mode = SGMII_SPEED_100;
++		else
++			sgm_mode = SGMII_SPEED_1000;
++
++		if (duplex != DUPLEX_FULL)
++			sgm_mode |= SGMII_DUPLEX_HALF;
++
++		regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
++				   SGMII_DUPLEX_HALF | SGMII_SPEED_MASK,
++				   sgm_mode);
++	}
++}
++
++static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = {
++	.pcs_get_state = mtk_pcs_lynxi_get_state,
++	.pcs_config = mtk_pcs_lynxi_config,
++	.pcs_an_restart = mtk_pcs_lynxi_restart_an,
++	.pcs_link_up = mtk_pcs_lynxi_link_up,
++};
++
++struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
++					 struct regmap *regmap, u32 ana_rgc3,
++					 u32 flags)
++{
++	struct mtk_pcs_lynxi *mpcs;
++	u32 id, ver;
++	int ret;
++
++	ret = regmap_read(regmap, SGMSYS_PCS_DEVICE_ID, &id);
++	if (ret < 0)
++		return NULL;
++
++	if (id != SGMII_LYNXI_DEV_ID) {
++		dev_err(dev, "unknown PCS device id %08x\n", id);
++		return NULL;
++	}
++
++	ret = regmap_read(regmap, SGMSYS_PCS_SCRATCH, &ver);
++	if (ret < 0)
++		return NULL;
++
++	ver = FIELD_GET(SGMII_DEV_VERSION, ver);
++	if (ver != 0x1) {
++		dev_err(dev, "unknown PCS device version %04x\n", ver);
++		return NULL;
++	}
++
++	dev_dbg(dev, "MediaTek LynxI SGMII PCS (id 0x%08x, ver 0x%04x)\n", id,
++		ver);
++
++	mpcs = kzalloc(sizeof(*mpcs), GFP_KERNEL);
++	if (!mpcs)
++		return NULL;
++
++	mpcs->ana_rgc3 = ana_rgc3;
++	mpcs->regmap = regmap;
++	mpcs->flags = flags;
++	mpcs->pcs.ops = &mtk_pcs_lynxi_ops;
++	mpcs->pcs.poll = true;
++	mpcs->interface = PHY_INTERFACE_MODE_NA;
++
++	return &mpcs->pcs;
++}
++EXPORT_SYMBOL(mtk_pcs_lynxi_create);
++
++void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs)
++{
++	if (!pcs)
++		return;
++
++	kfree(pcs_to_mtk_pcs_lynxi(pcs));
++}
++EXPORT_SYMBOL(mtk_pcs_lynxi_destroy);
++
++MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/include/linux/pcs/pcs-mtk-lynxi.h
+@@ -0,0 +1,13 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++#ifndef __LINUX_PCS_MTK_LYNXI_H
++#define __LINUX_PCS_MTK_LYNXI_H
++
++#include <linux/phylink.h>
++#include <linux/regmap.h>
++
++#define MTK_SGMII_FLAG_PN_SWAP BIT(0)
++struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
++					 struct regmap *regmap,
++					 u32 ana_rgc3, u32 flags);
++void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs);
++#endif

+ 28 - 0
target/linux/generic/backport-6.6/714-v6.8-01-net-phy-make-addr-type-u8-in-phy_package_shared-stru.patch

@@ -0,0 +1,28 @@
+From ebb30ccbbdbd6fae5177b676da4f4ac92bb4f635 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 15 Dec 2023 14:15:31 +0100
+Subject: [PATCH 1/4] net: phy: make addr type u8 in phy_package_shared struct
+
+Switch addr type in phy_package_shared struct to u8.
+
+The value is already checked to be non negative and to be less than
+PHY_MAX_ADDR, hence u8 is better suited than using int.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Russell King (Oracle) <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ include/linux/phy.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -330,7 +330,7 @@ struct mdio_bus_stats {
+  * phy_package_leave().
+  */
+ struct phy_package_shared {
+-	int addr;
++	u8 addr;
+ 	refcount_t refcnt;
+ 	unsigned long flags;
+ 	size_t priv_size;

+ 341 - 0
target/linux/generic/backport-6.6/714-v6.8-02-net-phy-extend-PHY-package-API-to-support-multiple-g.patch

@@ -0,0 +1,341 @@
+From 9eea577eb1155fe4a183bc5e7bf269b0b2e7a6ba Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 15 Dec 2023 14:15:32 +0100
+Subject: [PATCH 2/4] net: phy: extend PHY package API to support multiple
+ global address
+
+Current API for PHY package are limited to single address to configure
+global settings for the PHY package.
+
+It was found that some PHY package (for example the qca807x, a PHY
+package that is shipped with a bundle of 5 PHY) requires multiple PHY
+address to configure global settings. An example scenario is a PHY that
+have a dedicated PHY for PSGMII/serdes calibrarion and have a specific
+PHY in the package where the global PHY mode is set and affects every
+other PHY in the package.
+
+Change the API in the following way:
+- Change phy_package_join() to take the base addr of the PHY package
+  instead of the global PHY addr.
+- Make __/phy_package_write/read() require an additional arg that
+  select what global PHY address to use by passing the offset from the
+  base addr passed on phy_package_join().
+
+Each user of this API is updated to follow this new implementation
+following a pattern where an enum is defined to declare the offset of the
+addr.
+
+We also drop the check if shared is defined as any user of the
+phy_package_read/write is expected to use phy_package_join first. Misuse
+of this will correctly trigger a kernel panic for NULL pointer
+exception.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/bcm54140.c       | 16 ++++++--
+ drivers/net/phy/mscc/mscc.h      |  5 +++
+ drivers/net/phy/mscc/mscc_main.c |  4 +-
+ drivers/net/phy/phy_device.c     | 35 +++++++++--------
+ include/linux/phy.h              | 64 +++++++++++++++++++++-----------
+ 5 files changed, 80 insertions(+), 44 deletions(-)
+
+--- a/drivers/net/phy/bcm54140.c
++++ b/drivers/net/phy/bcm54140.c
+@@ -128,6 +128,10 @@
+ #define BCM54140_DEFAULT_DOWNSHIFT 5
+ #define BCM54140_MAX_DOWNSHIFT 9
+ 
++enum bcm54140_global_phy {
++	BCM54140_BASE_ADDR = 0,
++};
++
+ struct bcm54140_priv {
+ 	int port;
+ 	int base_addr;
+@@ -429,11 +433,13 @@ static int bcm54140_base_read_rdb(struct
+ 	int ret;
+ 
+ 	phy_lock_mdio_bus(phydev);
+-	ret = __phy_package_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
++	ret = __phy_package_write(phydev, BCM54140_BASE_ADDR,
++				  MII_BCM54XX_RDB_ADDR, rdb);
+ 	if (ret < 0)
+ 		goto out;
+ 
+-	ret = __phy_package_read(phydev, MII_BCM54XX_RDB_DATA);
++	ret = __phy_package_read(phydev, BCM54140_BASE_ADDR,
++				 MII_BCM54XX_RDB_DATA);
+ 
+ out:
+ 	phy_unlock_mdio_bus(phydev);
+@@ -446,11 +452,13 @@ static int bcm54140_base_write_rdb(struc
+ 	int ret;
+ 
+ 	phy_lock_mdio_bus(phydev);
+-	ret = __phy_package_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
++	ret = __phy_package_write(phydev, BCM54140_BASE_ADDR,
++				  MII_BCM54XX_RDB_ADDR, rdb);
+ 	if (ret < 0)
+ 		goto out;
+ 
+-	ret = __phy_package_write(phydev, MII_BCM54XX_RDB_DATA, val);
++	ret = __phy_package_write(phydev, BCM54140_BASE_ADDR,
++				  MII_BCM54XX_RDB_DATA, val);
+ 
+ out:
+ 	phy_unlock_mdio_bus(phydev);
+--- a/drivers/net/phy/mscc/mscc.h
++++ b/drivers/net/phy/mscc/mscc.h
+@@ -414,6 +414,11 @@ struct vsc8531_private {
+  * gpio_lock: used for PHC operations. Common for all PHYs as the load/save GPIO
+  * is shared.
+  */
++
++enum vsc85xx_global_phy {
++	VSC88XX_BASE_ADDR = 0,
++};
++
+ struct vsc85xx_shared_private {
+ 	struct mutex gpio_lock;
+ };
+--- a/drivers/net/phy/mscc/mscc_main.c
++++ b/drivers/net/phy/mscc/mscc_main.c
+@@ -700,7 +700,7 @@ int phy_base_write(struct phy_device *ph
+ 		dump_stack();
+ 	}
+ 
+-	return __phy_package_write(phydev, regnum, val);
++	return __phy_package_write(phydev, VSC88XX_BASE_ADDR, regnum, val);
+ }
+ 
+ /* phydev->bus->mdio_lock should be locked when using this function */
+@@ -711,7 +711,7 @@ int phy_base_read(struct phy_device *phy
+ 		dump_stack();
+ 	}
+ 
+-	return __phy_package_read(phydev, regnum);
++	return __phy_package_read(phydev, VSC88XX_BASE_ADDR, regnum);
+ }
+ 
+ u32 vsc85xx_csr_read(struct phy_device *phydev,
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -1602,20 +1602,22 @@ EXPORT_SYMBOL_GPL(phy_driver_is_genphy_1
+ /**
+  * phy_package_join - join a common PHY group
+  * @phydev: target phy_device struct
+- * @addr: cookie and PHY address for global register access
++ * @base_addr: cookie and base PHY address of PHY package for offset
++ *   calculation of global register access
+  * @priv_size: if non-zero allocate this amount of bytes for private data
+  *
+  * This joins a PHY group and provides a shared storage for all phydevs in
+  * this group. This is intended to be used for packages which contain
+  * more than one PHY, for example a quad PHY transceiver.
+  *
+- * The addr parameter serves as a cookie which has to have the same value
+- * for all members of one group and as a PHY address to access generic
+- * registers of a PHY package. Usually, one of the PHY addresses of the
+- * different PHYs in the package provides access to these global registers.
++ * The base_addr parameter serves as cookie which has to have the same values
++ * for all members of one group and as the base PHY address of the PHY package
++ * for offset calculation to access generic registers of a PHY package.
++ * Usually, one of the PHY addresses of the different PHYs in the package
++ * provides access to these global registers.
+  * The address which is given here, will be used in the phy_package_read()
+- * and phy_package_write() convenience functions. If your PHY doesn't have
+- * global registers you can just pick any of the PHY addresses.
++ * and phy_package_write() convenience functions as base and added to the
++ * passed offset in those functions.
+  *
+  * This will set the shared pointer of the phydev to the shared storage.
+  * If this is the first call for a this cookie the shared storage will be
+@@ -1625,17 +1627,17 @@ EXPORT_SYMBOL_GPL(phy_driver_is_genphy_1
+  * Returns < 1 on error, 0 on success. Esp. calling phy_package_join()
+  * with the same cookie but a different priv_size is an error.
+  */
+-int phy_package_join(struct phy_device *phydev, int addr, size_t priv_size)
++int phy_package_join(struct phy_device *phydev, int base_addr, size_t priv_size)
+ {
+ 	struct mii_bus *bus = phydev->mdio.bus;
+ 	struct phy_package_shared *shared;
+ 	int ret;
+ 
+-	if (addr < 0 || addr >= PHY_MAX_ADDR)
++	if (base_addr < 0 || base_addr >= PHY_MAX_ADDR)
+ 		return -EINVAL;
+ 
+ 	mutex_lock(&bus->shared_lock);
+-	shared = bus->shared[addr];
++	shared = bus->shared[base_addr];
+ 	if (!shared) {
+ 		ret = -ENOMEM;
+ 		shared = kzalloc(sizeof(*shared), GFP_KERNEL);
+@@ -1647,9 +1649,9 @@ int phy_package_join(struct phy_device *
+ 				goto err_free;
+ 			shared->priv_size = priv_size;
+ 		}
+-		shared->addr = addr;
++		shared->base_addr = base_addr;
+ 		refcount_set(&shared->refcnt, 1);
+-		bus->shared[addr] = shared;
++		bus->shared[base_addr] = shared;
+ 	} else {
+ 		ret = -EINVAL;
+ 		if (priv_size && priv_size != shared->priv_size)
+@@ -1687,7 +1689,7 @@ void phy_package_leave(struct phy_device
+ 		return;
+ 
+ 	if (refcount_dec_and_mutex_lock(&shared->refcnt, &bus->shared_lock)) {
+-		bus->shared[shared->addr] = NULL;
++		bus->shared[shared->base_addr] = NULL;
+ 		mutex_unlock(&bus->shared_lock);
+ 		kfree(shared->priv);
+ 		kfree(shared);
+@@ -1706,7 +1708,8 @@ static void devm_phy_package_leave(struc
+  * devm_phy_package_join - resource managed phy_package_join()
+  * @dev: device that is registering this PHY package
+  * @phydev: target phy_device struct
+- * @addr: cookie and PHY address for global register access
++ * @base_addr: cookie and base PHY address of PHY package for offset
++ *   calculation of global register access
+  * @priv_size: if non-zero allocate this amount of bytes for private data
+  *
+  * Managed phy_package_join(). Shared storage fetched by this function,
+@@ -1714,7 +1717,7 @@ static void devm_phy_package_leave(struc
+  * phy_package_join() for more information.
+  */
+ int devm_phy_package_join(struct device *dev, struct phy_device *phydev,
+-			  int addr, size_t priv_size)
++			  int base_addr, size_t priv_size)
+ {
+ 	struct phy_device **ptr;
+ 	int ret;
+@@ -1724,7 +1727,7 @@ int devm_phy_package_join(struct device
+ 	if (!ptr)
+ 		return -ENOMEM;
+ 
+-	ret = phy_package_join(phydev, addr, priv_size);
++	ret = phy_package_join(phydev, base_addr, priv_size);
+ 
+ 	if (!ret) {
+ 		*ptr = phydev;
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -319,7 +319,8 @@ struct mdio_bus_stats {
+ 
+ /**
+  * struct phy_package_shared - Shared information in PHY packages
+- * @addr: Common PHY address used to combine PHYs in one package
++ * @base_addr: Base PHY address of PHY package used to combine PHYs
++ *   in one package and for offset calculation of phy_package_read/write
+  * @refcnt: Number of PHYs connected to this shared data
+  * @flags: Initialization of PHY package
+  * @priv_size: Size of the shared private data @priv
+@@ -330,7 +331,7 @@ struct mdio_bus_stats {
+  * phy_package_leave().
+  */
+ struct phy_package_shared {
+-	u8 addr;
++	u8 base_addr;
+ 	refcount_t refcnt;
+ 	unsigned long flags;
+ 	size_t priv_size;
+@@ -1763,10 +1764,10 @@ int phy_ethtool_get_link_ksettings(struc
+ int phy_ethtool_set_link_ksettings(struct net_device *ndev,
+ 				   const struct ethtool_link_ksettings *cmd);
+ int phy_ethtool_nway_reset(struct net_device *ndev);
+-int phy_package_join(struct phy_device *phydev, int addr, size_t priv_size);
++int phy_package_join(struct phy_device *phydev, int base_addr, size_t priv_size);
+ void phy_package_leave(struct phy_device *phydev);
+ int devm_phy_package_join(struct device *dev, struct phy_device *phydev,
+-			  int addr, size_t priv_size);
++			  int base_addr, size_t priv_size);
+ 
+ #if IS_ENABLED(CONFIG_PHYLIB)
+ int __init mdio_bus_init(void);
+@@ -1778,46 +1779,65 @@ int phy_ethtool_get_sset_count(struct ph
+ int phy_ethtool_get_stats(struct phy_device *phydev,
+ 			  struct ethtool_stats *stats, u64 *data);
+ 
+-static inline int phy_package_read(struct phy_device *phydev, u32 regnum)
++static inline int phy_package_address(struct phy_device *phydev,
++				      unsigned int addr_offset)
+ {
+ 	struct phy_package_shared *shared = phydev->shared;
++	u8 base_addr = shared->base_addr;
+ 
+-	if (!shared)
++	if (addr_offset >= PHY_MAX_ADDR - base_addr)
+ 		return -EIO;
+ 
+-	return mdiobus_read(phydev->mdio.bus, shared->addr, regnum);
++	/* we know that addr will be in the range 0..31 and thus the
++	 * implicit cast to a signed int is not a problem.
++	 */
++	return base_addr + addr_offset;
+ }
+ 
+-static inline int __phy_package_read(struct phy_device *phydev, u32 regnum)
++static inline int phy_package_read(struct phy_device *phydev,
++				   unsigned int addr_offset, u32 regnum)
+ {
+-	struct phy_package_shared *shared = phydev->shared;
++	int addr = phy_package_address(phydev, addr_offset);
+ 
+-	if (!shared)
+-		return -EIO;
++	if (addr < 0)
++		return addr;
++
++	return mdiobus_read(phydev->mdio.bus, addr, regnum);
++}
++
++static inline int __phy_package_read(struct phy_device *phydev,
++				     unsigned int addr_offset, u32 regnum)
++{
++	int addr = phy_package_address(phydev, addr_offset);
++
++	if (addr < 0)
++		return addr;
+ 
+-	return __mdiobus_read(phydev->mdio.bus, shared->addr, regnum);
++	return __mdiobus_read(phydev->mdio.bus, addr, regnum);
+ }
+ 
+ static inline int phy_package_write(struct phy_device *phydev,
+-				    u32 regnum, u16 val)
++				    unsigned int addr_offset, u32 regnum,
++				    u16 val)
+ {
+-	struct phy_package_shared *shared = phydev->shared;
++	int addr = phy_package_address(phydev, addr_offset);
+ 
+-	if (!shared)
+-		return -EIO;
++	if (addr < 0)
++		return addr;
+ 
+-	return mdiobus_write(phydev->mdio.bus, shared->addr, regnum, val);
++	return mdiobus_write(phydev->mdio.bus, addr, regnum, val);
+ }
+ 
+ static inline int __phy_package_write(struct phy_device *phydev,
+-				      u32 regnum, u16 val)
++				      unsigned int addr_offset, u32 regnum,
++				      u16 val)
+ {
+-	struct phy_package_shared *shared = phydev->shared;
++	int addr = phy_package_address(phydev, addr_offset);
+ 
+-	if (!shared)
+-		return -EIO;
++	if (addr < 0)
++		return addr;
+ 
+-	return __mdiobus_write(phydev->mdio.bus, shared->addr, regnum, val);
++	return __mdiobus_write(phydev->mdio.bus, addr, regnum, val);
+ }
+ 
+ static inline bool __phy_package_set_once(struct phy_device *phydev,

+ 116 - 0
target/linux/generic/backport-6.6/714-v6.8-03-net-phy-restructure-__phy_write-read_mmd-to-helper-a.patch

@@ -0,0 +1,116 @@
+From 028672bd1d73cf65249a420c1de75e8d2acd2f6a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 15 Dec 2023 14:15:33 +0100
+Subject: [PATCH 3/4] net: phy: restructure __phy_write/read_mmd to helper and
+ phydev user
+
+Restructure phy_write_mmd and phy_read_mmd to implement generic helper
+for direct mdiobus access for mmd and use these helper for phydev user.
+
+This is needed in preparation of PHY package API that requires generic
+access to the mdiobus and are deatched from phydev struct but instead
+access them based on PHY package base_addr and offsets.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Reviewed-by: Russell King (Oracle) <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/phy-core.c | 64 ++++++++++++++++++--------------------
+ 1 file changed, 30 insertions(+), 34 deletions(-)
+
+--- a/drivers/net/phy/phy-core.c
++++ b/drivers/net/phy/phy-core.c
+@@ -528,6 +528,28 @@ static void mmd_phy_indirect(struct mii_
+ 			devad | MII_MMD_CTRL_NOINCR);
+ }
+ 
++static int mmd_phy_read(struct mii_bus *bus, int phy_addr, bool is_c45,
++			int devad, u32 regnum)
++{
++	if (is_c45)
++		return __mdiobus_c45_read(bus, phy_addr, devad, regnum);
++
++	mmd_phy_indirect(bus, phy_addr, devad, regnum);
++	/* Read the content of the MMD's selected register */
++	return __mdiobus_read(bus, phy_addr, MII_MMD_DATA);
++}
++
++static int mmd_phy_write(struct mii_bus *bus, int phy_addr, bool is_c45,
++			 int devad, u32 regnum, u16 val)
++{
++	if (is_c45)
++		return __mdiobus_c45_write(bus, phy_addr, devad, regnum, val);
++
++	mmd_phy_indirect(bus, phy_addr, devad, regnum);
++	/* Write the data into MMD's selected register */
++	return __mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);
++}
++
+ /**
+  * __phy_read_mmd - Convenience function for reading a register
+  * from an MMD on a given PHY.
+@@ -539,26 +561,14 @@ static void mmd_phy_indirect(struct mii_
+  */
+ int __phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum)
+ {
+-	int val;
+-
+ 	if (regnum > (u16)~0 || devad > 32)
+ 		return -EINVAL;
+ 
+-	if (phydev->drv && phydev->drv->read_mmd) {
+-		val = phydev->drv->read_mmd(phydev, devad, regnum);
+-	} else if (phydev->is_c45) {
+-		val = __mdiobus_c45_read(phydev->mdio.bus, phydev->mdio.addr,
+-					 devad, regnum);
+-	} else {
+-		struct mii_bus *bus = phydev->mdio.bus;
+-		int phy_addr = phydev->mdio.addr;
+-
+-		mmd_phy_indirect(bus, phy_addr, devad, regnum);
+-
+-		/* Read the content of the MMD's selected register */
+-		val = __mdiobus_read(bus, phy_addr, MII_MMD_DATA);
+-	}
+-	return val;
++	if (phydev->drv && phydev->drv->read_mmd)
++		return phydev->drv->read_mmd(phydev, devad, regnum);
++
++	return mmd_phy_read(phydev->mdio.bus, phydev->mdio.addr,
++			    phydev->is_c45, devad, regnum);
+ }
+ EXPORT_SYMBOL(__phy_read_mmd);
+ 
+@@ -595,28 +605,14 @@ EXPORT_SYMBOL(phy_read_mmd);
+  */
+ int __phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val)
+ {
+-	int ret;
+-
+ 	if (regnum > (u16)~0 || devad > 32)
+ 		return -EINVAL;
+ 
+-	if (phydev->drv && phydev->drv->write_mmd) {
+-		ret = phydev->drv->write_mmd(phydev, devad, regnum, val);
+-	} else if (phydev->is_c45) {
+-		ret = __mdiobus_c45_write(phydev->mdio.bus, phydev->mdio.addr,
+-					  devad, regnum, val);
+-	} else {
+-		struct mii_bus *bus = phydev->mdio.bus;
+-		int phy_addr = phydev->mdio.addr;
++	if (phydev->drv && phydev->drv->write_mmd)
++		return phydev->drv->write_mmd(phydev, devad, regnum, val);
+ 
+-		mmd_phy_indirect(bus, phy_addr, devad, regnum);
+-
+-		/* Write the data into MMD's selected register */
+-		__mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);
+-
+-		ret = 0;
+-	}
+-	return ret;
++	return mmd_phy_write(phydev->mdio.bus, phydev->mdio.addr,
++			     phydev->is_c45, devad, regnum, val);
+ }
+ EXPORT_SYMBOL(__phy_write_mmd);
+ 

+ 196 - 0
target/linux/generic/backport-6.6/714-v6.8-04-net-phy-add-support-for-PHY-package-MMD-read-write.patch

@@ -0,0 +1,196 @@
+From d63710fc0f1a501fd75a7025e3070a96ffa1645f Mon Sep 17 00:00:00 2001
+From: Christian Marangi <[email protected]>
+Date: Fri, 15 Dec 2023 14:15:34 +0100
+Subject: [PATCH 4/4] net: phy: add support for PHY package MMD read/write
+
+Some PHY in PHY package may require to read/write MMD regs to correctly
+configure the PHY package.
+
+Add support for these additional required function in both lock and no
+lock variant.
+
+It's assumed that the entire PHY package is either C22 or C45. We use
+C22 or C45 way of writing/reading to mmd regs based on the passed phydev
+whether it's C22 or C45.
+
+Signed-off-by: Christian Marangi <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/phy-core.c | 140 +++++++++++++++++++++++++++++++++++++
+ include/linux/phy.h        |  16 +++++
+ 2 files changed, 156 insertions(+)
+
+--- a/drivers/net/phy/phy-core.c
++++ b/drivers/net/phy/phy-core.c
+@@ -639,6 +639,146 @@ int phy_write_mmd(struct phy_device *phy
+ EXPORT_SYMBOL(phy_write_mmd);
+ 
+ /**
++ * __phy_package_read_mmd - read MMD reg relative to PHY package base addr
++ * @phydev: The phy_device struct
++ * @addr_offset: The offset to be added to PHY package base_addr
++ * @devad: The MMD to read from
++ * @regnum: The register on the MMD to read
++ *
++ * Convenience helper for reading a register of an MMD on a given PHY
++ * using the PHY package base address. The base address is added to
++ * the addr_offset value.
++ *
++ * Same calling rules as for __phy_read();
++ *
++ * NOTE: It's assumed that the entire PHY package is either C22 or C45.
++ */
++int __phy_package_read_mmd(struct phy_device *phydev,
++			   unsigned int addr_offset, int devad,
++			   u32 regnum)
++{
++	int addr = phy_package_address(phydev, addr_offset);
++
++	if (addr < 0)
++		return addr;
++
++	if (regnum > (u16)~0 || devad > 32)
++		return -EINVAL;
++
++	return mmd_phy_read(phydev->mdio.bus, addr, phydev->is_c45, devad,
++			    regnum);
++}
++EXPORT_SYMBOL(__phy_package_read_mmd);
++
++/**
++ * phy_package_read_mmd - read MMD reg relative to PHY package base addr
++ * @phydev: The phy_device struct
++ * @addr_offset: The offset to be added to PHY package base_addr
++ * @devad: The MMD to read from
++ * @regnum: The register on the MMD to read
++ *
++ * Convenience helper for reading a register of an MMD on a given PHY
++ * using the PHY package base address. The base address is added to
++ * the addr_offset value.
++ *
++ * Same calling rules as for phy_read();
++ *
++ * NOTE: It's assumed that the entire PHY package is either C22 or C45.
++ */
++int phy_package_read_mmd(struct phy_device *phydev,
++			 unsigned int addr_offset, int devad,
++			 u32 regnum)
++{
++	int addr = phy_package_address(phydev, addr_offset);
++	int val;
++
++	if (addr < 0)
++		return addr;
++
++	if (regnum > (u16)~0 || devad > 32)
++		return -EINVAL;
++
++	phy_lock_mdio_bus(phydev);
++	val = mmd_phy_read(phydev->mdio.bus, addr, phydev->is_c45, devad,
++			   regnum);
++	phy_unlock_mdio_bus(phydev);
++
++	return val;
++}
++EXPORT_SYMBOL(phy_package_read_mmd);
++
++/**
++ * __phy_package_write_mmd - write MMD reg relative to PHY package base addr
++ * @phydev: The phy_device struct
++ * @addr_offset: The offset to be added to PHY package base_addr
++ * @devad: The MMD to write to
++ * @regnum: The register on the MMD to write
++ * @val: value to write to @regnum
++ *
++ * Convenience helper for writing a register of an MMD on a given PHY
++ * using the PHY package base address. The base address is added to
++ * the addr_offset value.
++ *
++ * Same calling rules as for __phy_write();
++ *
++ * NOTE: It's assumed that the entire PHY package is either C22 or C45.
++ */
++int __phy_package_write_mmd(struct phy_device *phydev,
++			    unsigned int addr_offset, int devad,
++			    u32 regnum, u16 val)
++{
++	int addr = phy_package_address(phydev, addr_offset);
++
++	if (addr < 0)
++		return addr;
++
++	if (regnum > (u16)~0 || devad > 32)
++		return -EINVAL;
++
++	return mmd_phy_write(phydev->mdio.bus, addr, phydev->is_c45, devad,
++			     regnum, val);
++}
++EXPORT_SYMBOL(__phy_package_write_mmd);
++
++/**
++ * phy_package_write_mmd - write MMD reg relative to PHY package base addr
++ * @phydev: The phy_device struct
++ * @addr_offset: The offset to be added to PHY package base_addr
++ * @devad: The MMD to write to
++ * @regnum: The register on the MMD to write
++ * @val: value to write to @regnum
++ *
++ * Convenience helper for writing a register of an MMD on a given PHY
++ * using the PHY package base address. The base address is added to
++ * the addr_offset value.
++ *
++ * Same calling rules as for phy_write();
++ *
++ * NOTE: It's assumed that the entire PHY package is either C22 or C45.
++ */
++int phy_package_write_mmd(struct phy_device *phydev,
++			  unsigned int addr_offset, int devad,
++			  u32 regnum, u16 val)
++{
++	int addr = phy_package_address(phydev, addr_offset);
++	int ret;
++
++	if (addr < 0)
++		return addr;
++
++	if (regnum > (u16)~0 || devad > 32)
++		return -EINVAL;
++
++	phy_lock_mdio_bus(phydev);
++	ret = mmd_phy_write(phydev->mdio.bus, addr, phydev->is_c45, devad,
++			    regnum, val);
++	phy_unlock_mdio_bus(phydev);
++
++	return ret;
++}
++EXPORT_SYMBOL(phy_package_write_mmd);
++
++/**
+  * phy_modify_changed - Function for modifying a PHY register
+  * @phydev: the phy_device struct
+  * @regnum: register number to modify
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1840,6 +1840,22 @@ static inline int __phy_package_write(st
+ 	return __mdiobus_write(phydev->mdio.bus, addr, regnum, val);
+ }
+ 
++int __phy_package_read_mmd(struct phy_device *phydev,
++			   unsigned int addr_offset, int devad,
++			   u32 regnum);
++
++int phy_package_read_mmd(struct phy_device *phydev,
++			 unsigned int addr_offset, int devad,
++			 u32 regnum);
++
++int __phy_package_write_mmd(struct phy_device *phydev,
++			    unsigned int addr_offset, int devad,
++			    u32 regnum, u16 val);
++
++int phy_package_write_mmd(struct phy_device *phydev,
++			  unsigned int addr_offset, int devad,
++			  u32 regnum, u16 val);
++
+ static inline bool __phy_package_set_once(struct phy_device *phydev,
+ 					  unsigned int b)
+ {

+ 103 - 0
target/linux/generic/backport-6.6/715-01-v6.2-net-fman-memac-Add-serdes-support.patch

@@ -0,0 +1,103 @@
+From affa013f494486079c3c5ad2d00cebc41a3d7445 Mon Sep 17 00:00:00 2001
+From: Sean Anderson <[email protected]>
+Date: Mon, 17 Oct 2022 16:22:36 -0400
+Subject: [PATCH 01/21] net: fman: memac: Add serdes support
+
+This adds support for using a serdes which has to be configured. This is
+primarly in preparation for phylink conversion, which will then change the
+serdes mode dynamically.
+
+Signed-off-by: Sean Anderson <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ .../net/ethernet/freescale/fman/fman_memac.c  | 49 ++++++++++++++++++-
+ 1 file changed, 47 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/freescale/fman/fman_memac.c
++++ b/drivers/net/ethernet/freescale/fman/fman_memac.c
+@@ -13,6 +13,7 @@
+ #include <linux/io.h>
+ #include <linux/phy.h>
+ #include <linux/phy_fixed.h>
++#include <linux/phy/phy.h>
+ #include <linux/of_mdio.h>
+ 
+ /* PCS registers */
+@@ -324,6 +325,7 @@ struct fman_mac {
+ 	void *fm;
+ 	struct fman_rev_info fm_rev_info;
+ 	bool basex_if;
++	struct phy *serdes;
+ 	struct phy_device *pcsphy;
+ 	bool allmulti_enabled;
+ };
+@@ -1203,17 +1205,56 @@ int memac_initialization(struct mac_devi
+ 		}
+ 	}
+ 
++	memac->serdes = devm_of_phy_get(mac_dev->dev, mac_node, "serdes");
++	err = PTR_ERR(memac->serdes);
++	if (err == -ENODEV || err == -ENOSYS) {
++		dev_dbg(mac_dev->dev, "could not get (optional) serdes\n");
++		memac->serdes = NULL;
++	} else if (IS_ERR(memac->serdes)) {
++		dev_err_probe(mac_dev->dev, err, "could not get serdes\n");
++		goto _return_fm_mac_free;
++	} else {
++		err = phy_init(memac->serdes);
++		if (err) {
++			dev_err_probe(mac_dev->dev, err,
++				      "could not initialize serdes\n");
++			goto _return_fm_mac_free;
++		}
++
++		err = phy_power_on(memac->serdes);
++		if (err) {
++			dev_err_probe(mac_dev->dev, err,
++				      "could not power on serdes\n");
++			goto _return_phy_exit;
++		}
++
++		if (memac->phy_if == PHY_INTERFACE_MODE_SGMII ||
++		    memac->phy_if == PHY_INTERFACE_MODE_1000BASEX ||
++		    memac->phy_if == PHY_INTERFACE_MODE_2500BASEX ||
++		    memac->phy_if == PHY_INTERFACE_MODE_QSGMII ||
++		    memac->phy_if == PHY_INTERFACE_MODE_XGMII) {
++			err = phy_set_mode_ext(memac->serdes, PHY_MODE_ETHERNET,
++					       memac->phy_if);
++			if (err) {
++				dev_err_probe(mac_dev->dev, err,
++					      "could not set serdes mode to %s\n",
++					      phy_modes(memac->phy_if));
++				goto _return_phy_power_off;
++			}
++		}
++	}
++
+ 	if (!mac_dev->phy_node && of_phy_is_fixed_link(mac_node)) {
+ 		struct phy_device *phy;
+ 
+ 		err = of_phy_register_fixed_link(mac_node);
+ 		if (err)
+-			goto _return_fm_mac_free;
++			goto _return_phy_power_off;
+ 
+ 		fixed_link = kzalloc(sizeof(*fixed_link), GFP_KERNEL);
+ 		if (!fixed_link) {
+ 			err = -ENOMEM;
+-			goto _return_fm_mac_free;
++			goto _return_phy_power_off;
+ 		}
+ 
+ 		mac_dev->phy_node = of_node_get(mac_node);
+@@ -1242,6 +1283,10 @@ int memac_initialization(struct mac_devi
+ 
+ 	goto _return;
+ 
++_return_phy_power_off:
++	phy_power_off(memac->serdes);
++_return_phy_exit:
++	phy_exit(memac->serdes);
+ _return_fixed_link_free:
+ 	kfree(fixed_link);
+ _return_fm_mac_free:

+ 384 - 0
target/linux/generic/backport-6.6/715-02-v6.2-net-fman-memac-Use-lynx-pcs-driver.patch

@@ -0,0 +1,384 @@
+From fe60e7154d3a35af975c5e6570d6ec31aab9a731 Mon Sep 17 00:00:00 2001
+From: Sean Anderson <[email protected]>
+Date: Mon, 17 Oct 2022 16:22:37 -0400
+Subject: [PATCH 02/21] net: fman: memac: Use lynx pcs driver
+
+Although not stated in the datasheet, as far as I can tell PCS for mEMACs
+is a "Lynx." By reusing the existing driver, we can remove the PCS
+management code from the memac driver. This requires calling some PCS
+functions manually which phylink would usually do for us, but we will let
+it do that soon.
+
+One problem is that we don't actually have a PCS for QSGMII. We pretend
+that each mEMAC's MDIO bus has four QSGMII PCSs, but this is not the case.
+Only the "base" mEMAC's MDIO bus has the four QSGMII PCSs. This is not an
+issue yet, because we never get the PCS state. However, it will be once the
+conversion to phylink is complete, since the links will appear to never
+come up. To get around this, we allow specifying multiple PCSs in pcsphy.
+This breaks backwards compatibility with old device trees, but only for
+QSGMII. IMO this is the only reasonable way to figure out what the actual
+QSGMII PCS is.
+
+Additionally, we now also support a separate XFI PCS. This can allow the
+SerDes driver to set different addresses for the SGMII and XFI PCSs so they
+can be accessed at the same time.
+
+Signed-off-by: Sean Anderson <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/ethernet/freescale/fman/Kconfig   |   3 +
+ .../net/ethernet/freescale/fman/fman_memac.c  | 258 +++++++-----------
+ 2 files changed, 105 insertions(+), 156 deletions(-)
+
+--- a/drivers/net/ethernet/freescale/fman/Kconfig
++++ b/drivers/net/ethernet/freescale/fman/Kconfig
+@@ -4,6 +4,9 @@ config FSL_FMAN
+ 	depends on FSL_SOC || ARCH_LAYERSCAPE || COMPILE_TEST
+ 	select GENERIC_ALLOCATOR
+ 	select PHYLIB
++	select PHYLINK
++	select PCS
++	select PCS_LYNX
+ 	select CRC32
+ 	default n
+ 	help
+--- a/drivers/net/ethernet/freescale/fman/fman_memac.c
++++ b/drivers/net/ethernet/freescale/fman/fman_memac.c
+@@ -11,43 +11,12 @@
+ 
+ #include <linux/slab.h>
+ #include <linux/io.h>
++#include <linux/pcs-lynx.h>
+ #include <linux/phy.h>
+ #include <linux/phy_fixed.h>
+ #include <linux/phy/phy.h>
+ #include <linux/of_mdio.h>
+ 
+-/* PCS registers */
+-#define MDIO_SGMII_CR			0x00
+-#define MDIO_SGMII_DEV_ABIL_SGMII	0x04
+-#define MDIO_SGMII_LINK_TMR_L		0x12
+-#define MDIO_SGMII_LINK_TMR_H		0x13
+-#define MDIO_SGMII_IF_MODE		0x14
+-
+-/* SGMII Control defines */
+-#define SGMII_CR_AN_EN			0x1000
+-#define SGMII_CR_RESTART_AN		0x0200
+-#define SGMII_CR_FD			0x0100
+-#define SGMII_CR_SPEED_SEL1_1G		0x0040
+-#define SGMII_CR_DEF_VAL		(SGMII_CR_AN_EN | SGMII_CR_FD | \
+-					 SGMII_CR_SPEED_SEL1_1G)
+-
+-/* SGMII Device Ability for SGMII defines */
+-#define MDIO_SGMII_DEV_ABIL_SGMII_MODE	0x4001
+-#define MDIO_SGMII_DEV_ABIL_BASEX_MODE	0x01A0
+-
+-/* Link timer define */
+-#define LINK_TMR_L			0xa120
+-#define LINK_TMR_H			0x0007
+-#define LINK_TMR_L_BASEX		0xaf08
+-#define LINK_TMR_H_BASEX		0x002f
+-
+-/* SGMII IF Mode defines */
+-#define IF_MODE_USE_SGMII_AN		0x0002
+-#define IF_MODE_SGMII_EN		0x0001
+-#define IF_MODE_SGMII_SPEED_100M	0x0004
+-#define IF_MODE_SGMII_SPEED_1G		0x0008
+-#define IF_MODE_SGMII_DUPLEX_HALF	0x0010
+-
+ /* Num of additional exact match MAC adr regs */
+ #define MEMAC_NUM_OF_PADDRS 7
+ 
+@@ -326,7 +295,9 @@ struct fman_mac {
+ 	struct fman_rev_info fm_rev_info;
+ 	bool basex_if;
+ 	struct phy *serdes;
+-	struct phy_device *pcsphy;
++	struct phylink_pcs *sgmii_pcs;
++	struct phylink_pcs *qsgmii_pcs;
++	struct phylink_pcs *xfi_pcs;
+ 	bool allmulti_enabled;
+ };
+ 
+@@ -487,91 +458,22 @@ static u32 get_mac_addr_hash_code(u64 et
+ 	return xor_val;
+ }
+ 
+-static void setup_sgmii_internal_phy(struct fman_mac *memac,
+-				     struct fixed_phy_status *fixed_link)
+-{
+-	u16 tmp_reg16;
+-
+-	if (WARN_ON(!memac->pcsphy))
+-		return;
+-
+-	/* SGMII mode */
+-	tmp_reg16 = IF_MODE_SGMII_EN;
+-	if (!fixed_link)
+-		/* AN enable */
+-		tmp_reg16 |= IF_MODE_USE_SGMII_AN;
+-	else {
+-		switch (fixed_link->speed) {
+-		case 10:
+-			/* For 10M: IF_MODE[SPEED_10M] = 0 */
+-		break;
+-		case 100:
+-			tmp_reg16 |= IF_MODE_SGMII_SPEED_100M;
+-		break;
+-		case 1000:
+-		default:
+-			tmp_reg16 |= IF_MODE_SGMII_SPEED_1G;
+-		break;
+-		}
+-		if (!fixed_link->duplex)
+-			tmp_reg16 |= IF_MODE_SGMII_DUPLEX_HALF;
+-	}
+-	phy_write(memac->pcsphy, MDIO_SGMII_IF_MODE, tmp_reg16);
+-
+-	/* Device ability according to SGMII specification */
+-	tmp_reg16 = MDIO_SGMII_DEV_ABIL_SGMII_MODE;
+-	phy_write(memac->pcsphy, MDIO_SGMII_DEV_ABIL_SGMII, tmp_reg16);
+-
+-	/* Adjust link timer for SGMII  -
+-	 * According to Cisco SGMII specification the timer should be 1.6 ms.
+-	 * The link_timer register is configured in units of the clock.
+-	 * - When running as 1G SGMII, Serdes clock is 125 MHz, so
+-	 * unit = 1 / (125*10^6 Hz) = 8 ns.
+-	 * 1.6 ms in units of 8 ns = 1.6ms / 8ns = 2*10^5 = 0x30d40
+-	 * - When running as 2.5G SGMII, Serdes clock is 312.5 MHz, so
+-	 * unit = 1 / (312.5*10^6 Hz) = 3.2 ns.
+-	 * 1.6 ms in units of 3.2 ns = 1.6ms / 3.2ns = 5*10^5 = 0x7a120.
+-	 * Since link_timer value of 1G SGMII will be too short for 2.5 SGMII,
+-	 * we always set up here a value of 2.5 SGMII.
+-	 */
+-	phy_write(memac->pcsphy, MDIO_SGMII_LINK_TMR_H, LINK_TMR_H);
+-	phy_write(memac->pcsphy, MDIO_SGMII_LINK_TMR_L, LINK_TMR_L);
+-
+-	if (!fixed_link)
+-		/* Restart AN */
+-		tmp_reg16 = SGMII_CR_DEF_VAL | SGMII_CR_RESTART_AN;
++static void setup_sgmii_internal(struct fman_mac *memac,
++				 struct phylink_pcs *pcs,
++				 struct fixed_phy_status *fixed_link)
++{
++	__ETHTOOL_DECLARE_LINK_MODE_MASK(advertising);
++	phy_interface_t iface = memac->basex_if ? PHY_INTERFACE_MODE_1000BASEX :
++				PHY_INTERFACE_MODE_SGMII;
++	unsigned int mode = fixed_link ? MLO_AN_FIXED : MLO_AN_INBAND;
++
++	linkmode_set_pause(advertising, true, true);
++	pcs->ops->pcs_config(pcs, mode, iface, advertising, true);
++	if (fixed_link)
++		pcs->ops->pcs_link_up(pcs, mode, iface, fixed_link->speed,
++				      fixed_link->duplex);
+ 	else
+-		/* AN disabled */
+-		tmp_reg16 = SGMII_CR_DEF_VAL & ~SGMII_CR_AN_EN;
+-	phy_write(memac->pcsphy, 0x0, tmp_reg16);
+-}
+-
+-static void setup_sgmii_internal_phy_base_x(struct fman_mac *memac)
+-{
+-	u16 tmp_reg16;
+-
+-	/* AN Device capability  */
+-	tmp_reg16 = MDIO_SGMII_DEV_ABIL_BASEX_MODE;
+-	phy_write(memac->pcsphy, MDIO_SGMII_DEV_ABIL_SGMII, tmp_reg16);
+-
+-	/* Adjust link timer for SGMII  -
+-	 * For Serdes 1000BaseX auto-negotiation the timer should be 10 ms.
+-	 * The link_timer register is configured in units of the clock.
+-	 * - When running as 1G SGMII, Serdes clock is 125 MHz, so
+-	 * unit = 1 / (125*10^6 Hz) = 8 ns.
+-	 * 10 ms in units of 8 ns = 10ms / 8ns = 1250000 = 0x1312d0
+-	 * - When running as 2.5G SGMII, Serdes clock is 312.5 MHz, so
+-	 * unit = 1 / (312.5*10^6 Hz) = 3.2 ns.
+-	 * 10 ms in units of 3.2 ns = 10ms / 3.2ns = 3125000 = 0x2faf08.
+-	 * Since link_timer value of 1G SGMII will be too short for 2.5 SGMII,
+-	 * we always set up here a value of 2.5 SGMII.
+-	 */
+-	phy_write(memac->pcsphy, MDIO_SGMII_LINK_TMR_H, LINK_TMR_H_BASEX);
+-	phy_write(memac->pcsphy, MDIO_SGMII_LINK_TMR_L, LINK_TMR_L_BASEX);
+-
+-	/* Restart AN */
+-	tmp_reg16 = SGMII_CR_DEF_VAL | SGMII_CR_RESTART_AN;
+-	phy_write(memac->pcsphy, 0x0, tmp_reg16);
++		pcs->ops->pcs_an_restart(pcs);
+ }
+ 
+ static int check_init_parameters(struct fman_mac *memac)
+@@ -983,7 +885,6 @@ static int memac_set_exception(struct fm
+ static int memac_init(struct fman_mac *memac)
+ {
+ 	struct memac_cfg *memac_drv_param;
+-	u8 i;
+ 	enet_addr_t eth_addr;
+ 	bool slow_10g_if = false;
+ 	struct fixed_phy_status *fixed_link = NULL;
+@@ -1036,32 +937,10 @@ static int memac_init(struct fman_mac *m
+ 		iowrite32be(reg32, &memac->regs->command_config);
+ 	}
+ 
+-	if (memac->phy_if == PHY_INTERFACE_MODE_SGMII) {
+-		/* Configure internal SGMII PHY */
+-		if (memac->basex_if)
+-			setup_sgmii_internal_phy_base_x(memac);
+-		else
+-			setup_sgmii_internal_phy(memac, fixed_link);
+-	} else if (memac->phy_if == PHY_INTERFACE_MODE_QSGMII) {
+-		/* Configure 4 internal SGMII PHYs */
+-		for (i = 0; i < 4; i++) {
+-			u8 qsmgii_phy_addr, phy_addr;
+-			/* QSGMII PHY address occupies 3 upper bits of 5-bit
+-			 * phy_address; the lower 2 bits are used to extend
+-			 * register address space and access each one of 4
+-			 * ports inside QSGMII.
+-			 */
+-			phy_addr = memac->pcsphy->mdio.addr;
+-			qsmgii_phy_addr = (u8)((phy_addr << 2) | i);
+-			memac->pcsphy->mdio.addr = qsmgii_phy_addr;
+-			if (memac->basex_if)
+-				setup_sgmii_internal_phy_base_x(memac);
+-			else
+-				setup_sgmii_internal_phy(memac, fixed_link);
+-
+-			memac->pcsphy->mdio.addr = phy_addr;
+-		}
+-	}
++	if (memac->phy_if == PHY_INTERFACE_MODE_SGMII)
++		setup_sgmii_internal(memac, memac->sgmii_pcs, fixed_link);
++	else if (memac->phy_if == PHY_INTERFACE_MODE_QSGMII)
++		setup_sgmii_internal(memac, memac->qsgmii_pcs, fixed_link);
+ 
+ 	/* Max Frame Length */
+ 	err = fman_set_mac_max_frame(memac->fm, memac->mac_id,
+@@ -1097,12 +976,25 @@ static int memac_init(struct fman_mac *m
+ 	return 0;
+ }
+ 
++static void pcs_put(struct phylink_pcs *pcs)
++{
++	struct mdio_device *mdiodev;
++
++	if (IS_ERR_OR_NULL(pcs))
++		return;
++
++	mdiodev = lynx_get_mdio_device(pcs);
++	lynx_pcs_destroy(pcs);
++	mdio_device_free(mdiodev);
++}
++
+ static int memac_free(struct fman_mac *memac)
+ {
+ 	free_init_resources(memac);
+ 
+-	if (memac->pcsphy)
+-		put_device(&memac->pcsphy->mdio.dev);
++	pcs_put(memac->sgmii_pcs);
++	pcs_put(memac->qsgmii_pcs);
++	pcs_put(memac->xfi_pcs);
+ 
+ 	kfree(memac->memac_drv_param);
+ 	kfree(memac);
+@@ -1153,12 +1045,31 @@ static struct fman_mac *memac_config(str
+ 	return memac;
+ }
+ 
++static struct phylink_pcs *memac_pcs_create(struct device_node *mac_node,
++					    int index)
++{
++	struct device_node *node;
++	struct mdio_device *mdiodev = NULL;
++	struct phylink_pcs *pcs;
++
++	node = of_parse_phandle(mac_node, "pcsphy-handle", index);
++	if (node && of_device_is_available(node))
++		mdiodev = of_mdio_find_device(node);
++	of_node_put(node);
++
++	if (!mdiodev)
++		return ERR_PTR(-EPROBE_DEFER);
++
++	pcs = lynx_pcs_create(mdiodev);
++	return pcs;
++}
++
+ int memac_initialization(struct mac_device *mac_dev,
+ 			 struct device_node *mac_node,
+ 			 struct fman_mac_params *params)
+ {
+ 	int			 err;
+-	struct device_node	*phy_node;
++	struct phylink_pcs	*pcs;
+ 	struct fixed_phy_status *fixed_link;
+ 	struct fman_mac		*memac;
+ 
+@@ -1188,23 +1099,58 @@ int memac_initialization(struct mac_devi
+ 	memac = mac_dev->fman_mac;
+ 	memac->memac_drv_param->max_frame_length = fman_get_max_frm();
+ 	memac->memac_drv_param->reset_on_init = true;
+-	if (memac->phy_if == PHY_INTERFACE_MODE_SGMII ||
+-	    memac->phy_if == PHY_INTERFACE_MODE_QSGMII) {
+-		phy_node = of_parse_phandle(mac_node, "pcsphy-handle", 0);
+-		if (!phy_node) {
+-			pr_err("PCS PHY node is not available\n");
+-			err = -EINVAL;
++
++	err = of_property_match_string(mac_node, "pcs-handle-names", "xfi");
++	if (err >= 0) {
++		memac->xfi_pcs = memac_pcs_create(mac_node, err);
++		if (IS_ERR(memac->xfi_pcs)) {
++			err = PTR_ERR(memac->xfi_pcs);
++			dev_err_probe(mac_dev->dev, err, "missing xfi pcs\n");
+ 			goto _return_fm_mac_free;
+ 		}
++	} else if (err != -EINVAL && err != -ENODATA) {
++		goto _return_fm_mac_free;
++	}
+ 
+-		memac->pcsphy = of_phy_find_device(phy_node);
+-		if (!memac->pcsphy) {
+-			pr_err("of_phy_find_device (PCS PHY) failed\n");
+-			err = -EINVAL;
++	err = of_property_match_string(mac_node, "pcs-handle-names", "qsgmii");
++	if (err >= 0) {
++		memac->qsgmii_pcs = memac_pcs_create(mac_node, err);
++		if (IS_ERR(memac->qsgmii_pcs)) {
++			err = PTR_ERR(memac->qsgmii_pcs);
++			dev_err_probe(mac_dev->dev, err,
++				      "missing qsgmii pcs\n");
+ 			goto _return_fm_mac_free;
+ 		}
++	} else if (err != -EINVAL && err != -ENODATA) {
++		goto _return_fm_mac_free;
++	}
++
++	/* For compatibility, if pcs-handle-names is missing, we assume this
++	 * phy is the first one in pcsphy-handle
++	 */
++	err = of_property_match_string(mac_node, "pcs-handle-names", "sgmii");
++	if (err == -EINVAL || err == -ENODATA)
++		pcs = memac_pcs_create(mac_node, 0);
++	else if (err < 0)
++		goto _return_fm_mac_free;
++	else
++		pcs = memac_pcs_create(mac_node, err);
++
++	if (!pcs) {
++		dev_err(mac_dev->dev, "missing pcs\n");
++		err = -ENOENT;
++		goto _return_fm_mac_free;
+ 	}
+ 
++	/* If err is set here, it means that pcs-handle-names was missing above
++	 * (and therefore that xfi_pcs cannot be set). If we are defaulting to
++	 * XGMII, assume this is for XFI. Otherwise, assume it is for SGMII.
++	 */
++	if (err && mac_dev->phy_if == PHY_INTERFACE_MODE_XGMII)
++		memac->xfi_pcs = pcs;
++	else
++		memac->sgmii_pcs = pcs;
++
+ 	memac->serdes = devm_of_phy_get(mac_dev->dev, mac_node, "serdes");
+ 	err = PTR_ERR(memac->serdes);
+ 	if (err == -ENODEV || err == -ENOSYS) {

+ 2451 - 0
target/linux/generic/backport-6.6/715-03-v6.2-net-dpaa-Convert-to-phylink.patch

@@ -0,0 +1,2451 @@
+From 38e50fc3d43882a43115b4f1ca3eb88255163c5b Mon Sep 17 00:00:00 2001
+From: Sean Anderson <[email protected]>
+Date: Mon, 17 Oct 2022 16:22:38 -0400
+Subject: [PATCH 03/21] net: dpaa: Convert to phylink
+
+This converts DPAA to phylink. All macs are converted. This should work
+with no device tree modifications (including those made in this series),
+except for QSGMII (as noted previously).
+
+The mEMAC configuration is one of the tricker areas. I have tried to
+capture all the restrictions across the various models. Most of the time,
+we assume that if the serdes supports a mode or the phy-interface-mode
+specifies it, then we support it. The only place we can't do this is
+(RG)MII, since there's no serdes. In that case, we rely on a (new)
+devicetree property. There are also several cases where half-duplex is
+broken. Unfortunately, only a single compatible is used for the MAC, so we
+have to use the board compatible instead.
+
+The 10GEC conversion is very straightforward, since it only supports XAUI.
+There is generally nothing to configure.
+
+The dTSEC conversion is broadly similar to mEMAC, but is simpler because we
+don't support configuring the SerDes (though this can be easily added) and
+we don't have multiple PCSs. From what I can tell, there's nothing
+different in the driver or documentation between SGMII and 1000BASE-X
+except for the advertising. Similarly, I couldn't find anything about
+2500BASE-X. In both cases, I treat them like SGMII. These modes aren't used
+by any in-tree boards. Similarly, despite being mentioned in the driver, I
+couldn't find any documented SoCs which supported QSGMII.  I have left it
+unimplemented for now.
+
+Signed-off-by: Sean Anderson <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/ethernet/freescale/dpaa/Kconfig   |   4 +-
+ .../net/ethernet/freescale/dpaa/dpaa_eth.c    |  89 +--
+ .../ethernet/freescale/dpaa/dpaa_ethtool.c    |  90 +--
+ drivers/net/ethernet/freescale/fman/Kconfig   |   1 -
+ .../net/ethernet/freescale/fman/fman_dtsec.c  | 458 +++++++--------
+ .../net/ethernet/freescale/fman/fman_mac.h    |  10 -
+ .../net/ethernet/freescale/fman/fman_memac.c  | 547 +++++++++---------
+ .../net/ethernet/freescale/fman/fman_tgec.c   | 131 ++---
+ drivers/net/ethernet/freescale/fman/mac.c     | 168 +-----
+ drivers/net/ethernet/freescale/fman/mac.h     |  23 +-
+ 10 files changed, 612 insertions(+), 909 deletions(-)
+
+--- a/drivers/net/ethernet/freescale/dpaa/Kconfig
++++ b/drivers/net/ethernet/freescale/dpaa/Kconfig
+@@ -2,8 +2,8 @@
+ menuconfig FSL_DPAA_ETH
+ 	tristate "DPAA Ethernet"
+ 	depends on FSL_DPAA && FSL_FMAN
+-	select PHYLIB
+-	select FIXED_PHY
++	select PHYLINK
++	select PCS_LYNX
+ 	help
+ 	  Data Path Acceleration Architecture Ethernet driver,
+ 	  supporting the Freescale QorIQ chips.
+--- a/drivers/net/ethernet/freescale/dpaa/dpaa_eth.c
++++ b/drivers/net/ethernet/freescale/dpaa/dpaa_eth.c
+@@ -264,8 +264,19 @@ static int dpaa_netdev_init(struct net_d
+ 	net_dev->needed_headroom = priv->tx_headroom;
+ 	net_dev->watchdog_timeo = msecs_to_jiffies(tx_timeout);
+ 
+-	mac_dev->net_dev = net_dev;
++	/* The rest of the config is filled in by the mac device already */
++	mac_dev->phylink_config.dev = &net_dev->dev;
++	mac_dev->phylink_config.type = PHYLINK_NETDEV;
+ 	mac_dev->update_speed = dpaa_eth_cgr_set_speed;
++	mac_dev->phylink = phylink_create(&mac_dev->phylink_config,
++					  dev_fwnode(mac_dev->dev),
++					  mac_dev->phy_if,
++					  mac_dev->phylink_ops);
++	if (IS_ERR(mac_dev->phylink)) {
++		err = PTR_ERR(mac_dev->phylink);
++		dev_err_probe(dev, err, "Could not create phylink\n");
++		return err;
++	}
+ 
+ 	/* start without the RUNNING flag, phylib controls it later */
+ 	netif_carrier_off(net_dev);
+@@ -273,6 +284,7 @@ static int dpaa_netdev_init(struct net_d
+ 	err = register_netdev(net_dev);
+ 	if (err < 0) {
+ 		dev_err(dev, "register_netdev() = %d\n", err);
++		phylink_destroy(mac_dev->phylink);
+ 		return err;
+ 	}
+ 
+@@ -295,8 +307,7 @@ static int dpaa_stop(struct net_device *
+ 	 */
+ 	msleep(200);
+ 
+-	if (mac_dev->phy_dev)
+-		phy_stop(mac_dev->phy_dev);
++	phylink_stop(mac_dev->phylink);
+ 	mac_dev->disable(mac_dev->fman_mac);
+ 
+ 	for (i = 0; i < ARRAY_SIZE(mac_dev->port); i++) {
+@@ -305,8 +316,7 @@ static int dpaa_stop(struct net_device *
+ 			err = error;
+ 	}
+ 
+-	if (net_dev->phydev)
+-		phy_disconnect(net_dev->phydev);
++	phylink_disconnect_phy(mac_dev->phylink);
+ 	net_dev->phydev = NULL;
+ 
+ 	msleep(200);
+@@ -834,10 +844,10 @@ static int dpaa_eth_cgr_init(struct dpaa
+ 
+ 	/* Set different thresholds based on the configured MAC speed.
+ 	 * This may turn suboptimal if the MAC is reconfigured at another
+-	 * speed, so MACs must call dpaa_eth_cgr_set_speed in their adjust_link
++	 * speed, so MACs must call dpaa_eth_cgr_set_speed in their link_up
+ 	 * callback.
+ 	 */
+-	if (priv->mac_dev->if_support & SUPPORTED_10000baseT_Full)
++	if (priv->mac_dev->phylink_config.mac_capabilities & MAC_10000FD)
+ 		cs_th = DPAA_CS_THRESHOLD_10G;
+ 	else
+ 		cs_th = DPAA_CS_THRESHOLD_1G;
+@@ -866,7 +876,7 @@ out_error:
+ 
+ static void dpaa_eth_cgr_set_speed(struct mac_device *mac_dev, int speed)
+ {
+-	struct net_device *net_dev = mac_dev->net_dev;
++	struct net_device *net_dev = to_net_dev(mac_dev->phylink_config.dev);
+ 	struct dpaa_priv *priv = netdev_priv(net_dev);
+ 	struct qm_mcc_initcgr opts = { };
+ 	u32 cs_th;
+@@ -2905,58 +2915,6 @@ static void dpaa_eth_napi_disable(struct
+ 	}
+ }
+ 
+-static void dpaa_adjust_link(struct net_device *net_dev)
+-{
+-	struct mac_device *mac_dev;
+-	struct dpaa_priv *priv;
+-
+-	priv = netdev_priv(net_dev);
+-	mac_dev = priv->mac_dev;
+-	mac_dev->adjust_link(mac_dev);
+-}
+-
+-/* The Aquantia PHYs are capable of performing rate adaptation */
+-#define PHY_VEND_AQUANTIA	0x03a1b400
+-#define PHY_VEND_AQUANTIA2	0x31c31c00
+-
+-static int dpaa_phy_init(struct net_device *net_dev)
+-{
+-	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
+-	struct mac_device *mac_dev;
+-	struct phy_device *phy_dev;
+-	struct dpaa_priv *priv;
+-	u32 phy_vendor;
+-
+-	priv = netdev_priv(net_dev);
+-	mac_dev = priv->mac_dev;
+-
+-	phy_dev = of_phy_connect(net_dev, mac_dev->phy_node,
+-				 &dpaa_adjust_link, 0,
+-				 mac_dev->phy_if);
+-	if (!phy_dev) {
+-		netif_err(priv, ifup, net_dev, "init_phy() failed\n");
+-		return -ENODEV;
+-	}
+-
+-	phy_vendor = phy_dev->drv->phy_id & GENMASK(31, 10);
+-	/* Unless the PHY is capable of rate adaptation */
+-	if (mac_dev->phy_if != PHY_INTERFACE_MODE_XGMII ||
+-	    (phy_vendor != PHY_VEND_AQUANTIA &&
+-	     phy_vendor != PHY_VEND_AQUANTIA2)) {
+-		/* remove any features not supported by the controller */
+-		ethtool_convert_legacy_u32_to_link_mode(mask,
+-							mac_dev->if_support);
+-		linkmode_and(phy_dev->supported, phy_dev->supported, mask);
+-	}
+-
+-	phy_support_asym_pause(phy_dev);
+-
+-	mac_dev->phy_dev = phy_dev;
+-	net_dev->phydev = phy_dev;
+-
+-	return 0;
+-}
+-
+ static int dpaa_open(struct net_device *net_dev)
+ {
+ 	struct mac_device *mac_dev;
+@@ -2967,7 +2925,8 @@ static int dpaa_open(struct net_device *
+ 	mac_dev = priv->mac_dev;
+ 	dpaa_eth_napi_enable(priv);
+ 
+-	err = dpaa_phy_init(net_dev);
++	err = phylink_of_phy_connect(mac_dev->phylink,
++				     mac_dev->dev->of_node, 0);
+ 	if (err)
+ 		goto phy_init_failed;
+ 
+@@ -2982,7 +2941,7 @@ static int dpaa_open(struct net_device *
+ 		netif_err(priv, ifup, net_dev, "mac_dev->enable() = %d\n", err);
+ 		goto mac_start_failed;
+ 	}
+-	phy_start(priv->mac_dev->phy_dev);
++	phylink_start(mac_dev->phylink);
+ 
+ 	netif_tx_start_all_queues(net_dev);
+ 
+@@ -2991,6 +2950,7 @@ static int dpaa_open(struct net_device *
+ mac_start_failed:
+ 	for (i = 0; i < ARRAY_SIZE(mac_dev->port); i++)
+ 		fman_port_disable(mac_dev->port[i]);
++	phylink_disconnect_phy(mac_dev->phylink);
+ 
+ phy_init_failed:
+ 	dpaa_eth_napi_disable(priv);
+@@ -3146,10 +3106,12 @@ static int dpaa_ts_ioctl(struct net_devi
+ static int dpaa_ioctl(struct net_device *net_dev, struct ifreq *rq, int cmd)
+ {
+ 	int ret = -EINVAL;
++	struct dpaa_priv *priv = netdev_priv(net_dev);
+ 
+ 	if (cmd == SIOCGMIIREG) {
+ 		if (net_dev->phydev)
+-			return phy_mii_ioctl(net_dev->phydev, rq, cmd);
++			return phylink_mii_ioctl(priv->mac_dev->phylink, rq,
++						 cmd);
+ 	}
+ 
+ 	if (cmd == SIOCSHWTSTAMP)
+@@ -3552,6 +3514,7 @@ static int dpaa_remove(struct platform_d
+ 
+ 	dev_set_drvdata(dev, NULL);
+ 	unregister_netdev(net_dev);
++	phylink_destroy(priv->mac_dev->phylink);
+ 
+ 	err = dpaa_fq_free(dev, &priv->dpaa_fq_list);
+ 
+--- a/drivers/net/ethernet/freescale/dpaa/dpaa_ethtool.c
++++ b/drivers/net/ethernet/freescale/dpaa/dpaa_ethtool.c
+@@ -54,27 +54,19 @@ static char dpaa_stats_global[][ETH_GSTR
+ static int dpaa_get_link_ksettings(struct net_device *net_dev,
+ 				   struct ethtool_link_ksettings *cmd)
+ {
+-	if (!net_dev->phydev)
+-		return 0;
++	struct dpaa_priv *priv = netdev_priv(net_dev);
++	struct mac_device *mac_dev = priv->mac_dev;
+ 
+-	phy_ethtool_ksettings_get(net_dev->phydev, cmd);
+-
+-	return 0;
++	return phylink_ethtool_ksettings_get(mac_dev->phylink, cmd);
+ }
+ 
+ static int dpaa_set_link_ksettings(struct net_device *net_dev,
+ 				   const struct ethtool_link_ksettings *cmd)
+ {
+-	int err;
+-
+-	if (!net_dev->phydev)
+-		return -ENODEV;
++	struct dpaa_priv *priv = netdev_priv(net_dev);
++	struct mac_device *mac_dev = priv->mac_dev;
+ 
+-	err = phy_ethtool_ksettings_set(net_dev->phydev, cmd);
+-	if (err < 0)
+-		netdev_err(net_dev, "phy_ethtool_ksettings_set() = %d\n", err);
+-
+-	return err;
++	return phylink_ethtool_ksettings_set(mac_dev->phylink, cmd);
+ }
+ 
+ static void dpaa_get_drvinfo(struct net_device *net_dev,
+@@ -99,80 +91,28 @@ static void dpaa_set_msglevel(struct net
+ 
+ static int dpaa_nway_reset(struct net_device *net_dev)
+ {
+-	int err;
+-
+-	if (!net_dev->phydev)
+-		return -ENODEV;
++	struct dpaa_priv *priv = netdev_priv(net_dev);
++	struct mac_device *mac_dev = priv->mac_dev;
+ 
+-	err = 0;
+-	if (net_dev->phydev->autoneg) {
+-		err = phy_start_aneg(net_dev->phydev);
+-		if (err < 0)
+-			netdev_err(net_dev, "phy_start_aneg() = %d\n",
+-				   err);
+-	}
+-
+-	return err;
++	return phylink_ethtool_nway_reset(mac_dev->phylink);
+ }
+ 
+ static void dpaa_get_pauseparam(struct net_device *net_dev,
+ 				struct ethtool_pauseparam *epause)
+ {
+-	struct mac_device *mac_dev;
+-	struct dpaa_priv *priv;
+-
+-	priv = netdev_priv(net_dev);
+-	mac_dev = priv->mac_dev;
+-
+-	if (!net_dev->phydev)
+-		return;
++	struct dpaa_priv *priv = netdev_priv(net_dev);
++	struct mac_device *mac_dev = priv->mac_dev;
+ 
+-	epause->autoneg = mac_dev->autoneg_pause;
+-	epause->rx_pause = mac_dev->rx_pause_active;
+-	epause->tx_pause = mac_dev->tx_pause_active;
++	phylink_ethtool_get_pauseparam(mac_dev->phylink, epause);
+ }
+ 
+ static int dpaa_set_pauseparam(struct net_device *net_dev,
+ 			       struct ethtool_pauseparam *epause)
+ {
+-	struct mac_device *mac_dev;
+-	struct phy_device *phydev;
+-	bool rx_pause, tx_pause;
+-	struct dpaa_priv *priv;
+-	int err;
+-
+-	priv = netdev_priv(net_dev);
+-	mac_dev = priv->mac_dev;
+-
+-	phydev = net_dev->phydev;
+-	if (!phydev) {
+-		netdev_err(net_dev, "phy device not initialized\n");
+-		return -ENODEV;
+-	}
+-
+-	if (!phy_validate_pause(phydev, epause))
+-		return -EINVAL;
+-
+-	/* The MAC should know how to handle PAUSE frame autonegotiation before
+-	 * adjust_link is triggered by a forced renegotiation of sym/asym PAUSE
+-	 * settings.
+-	 */
+-	mac_dev->autoneg_pause = !!epause->autoneg;
+-	mac_dev->rx_pause_req = !!epause->rx_pause;
+-	mac_dev->tx_pause_req = !!epause->tx_pause;
+-
+-	/* Determine the sym/asym advertised PAUSE capabilities from the desired
+-	 * rx/tx pause settings.
+-	 */
+-
+-	phy_set_asym_pause(phydev, epause->rx_pause, epause->tx_pause);
+-
+-	fman_get_pause_cfg(mac_dev, &rx_pause, &tx_pause);
+-	err = fman_set_mac_active_pause(mac_dev, rx_pause, tx_pause);
+-	if (err < 0)
+-		netdev_err(net_dev, "set_mac_active_pause() = %d\n", err);
++	struct dpaa_priv *priv = netdev_priv(net_dev);
++	struct mac_device *mac_dev = priv->mac_dev;
+ 
+-	return err;
++	return phylink_ethtool_set_pauseparam(mac_dev->phylink, epause);
+ }
+ 
+ static int dpaa_get_sset_count(struct net_device *net_dev, int type)
+--- a/drivers/net/ethernet/freescale/fman/Kconfig
++++ b/drivers/net/ethernet/freescale/fman/Kconfig
+@@ -3,7 +3,6 @@ config FSL_FMAN
+ 	tristate "FMan support"
+ 	depends on FSL_SOC || ARCH_LAYERSCAPE || COMPILE_TEST
+ 	select GENERIC_ALLOCATOR
+-	select PHYLIB
+ 	select PHYLINK
+ 	select PCS
+ 	select PCS_LYNX
+--- a/drivers/net/ethernet/freescale/fman/fman_dtsec.c
++++ b/drivers/net/ethernet/freescale/fman/fman_dtsec.c
+@@ -17,6 +17,7 @@
+ #include <linux/crc32.h>
+ #include <linux/of_mdio.h>
+ #include <linux/mii.h>
++#include <linux/netdevice.h>
+ 
+ /* TBI register addresses */
+ #define MII_TBICON		0x11
+@@ -29,9 +30,6 @@
+ #define TBICON_CLK_SELECT	0x0020	/* Clock select */
+ #define TBICON_MI_MODE		0x0010	/* GMII mode (TBI if not set) */
+ 
+-#define TBIANA_SGMII		0x4001
+-#define TBIANA_1000X		0x01a0
+-
+ /* Interrupt Mask Register (IMASK) */
+ #define DTSEC_IMASK_BREN	0x80000000
+ #define DTSEC_IMASK_RXCEN	0x40000000
+@@ -92,9 +90,10 @@
+ 
+ #define DTSEC_ECNTRL_GMIIM		0x00000040
+ #define DTSEC_ECNTRL_TBIM		0x00000020
+-#define DTSEC_ECNTRL_SGMIIM		0x00000002
+ #define DTSEC_ECNTRL_RPM		0x00000010
+ #define DTSEC_ECNTRL_R100M		0x00000008
++#define DTSEC_ECNTRL_RMM		0x00000004
++#define DTSEC_ECNTRL_SGMIIM		0x00000002
+ #define DTSEC_ECNTRL_QSGMIIM		0x00000001
+ 
+ #define TCTRL_TTSE			0x00000040
+@@ -318,7 +317,8 @@ struct fman_mac {
+ 	void *fm;
+ 	struct fman_rev_info fm_rev_info;
+ 	bool basex_if;
+-	struct phy_device *tbiphy;
++	struct mdio_device *tbidev;
++	struct phylink_pcs pcs;
+ };
+ 
+ static void set_dflts(struct dtsec_cfg *cfg)
+@@ -356,56 +356,14 @@ static int init(struct dtsec_regs __iome
+ 		phy_interface_t iface, u16 iface_speed, u64 addr,
+ 		u32 exception_mask, u8 tbi_addr)
+ {
+-	bool is_rgmii, is_sgmii, is_qsgmii;
+ 	enet_addr_t eth_addr;
+-	u32 tmp;
++	u32 tmp = 0;
+ 	int i;
+ 
+ 	/* Soft reset */
+ 	iowrite32be(MACCFG1_SOFT_RESET, &regs->maccfg1);
+ 	iowrite32be(0, &regs->maccfg1);
+ 
+-	/* dtsec_id2 */
+-	tmp = ioread32be(&regs->tsec_id2);
+-
+-	/* check RGMII support */
+-	if (iface == PHY_INTERFACE_MODE_RGMII ||
+-	    iface == PHY_INTERFACE_MODE_RGMII_ID ||
+-	    iface == PHY_INTERFACE_MODE_RGMII_RXID ||
+-	    iface == PHY_INTERFACE_MODE_RGMII_TXID ||
+-	    iface == PHY_INTERFACE_MODE_RMII)
+-		if (tmp & DTSEC_ID2_INT_REDUCED_OFF)
+-			return -EINVAL;
+-
+-	if (iface == PHY_INTERFACE_MODE_SGMII ||
+-	    iface == PHY_INTERFACE_MODE_MII)
+-		if (tmp & DTSEC_ID2_INT_REDUCED_OFF)
+-			return -EINVAL;
+-
+-	is_rgmii = iface == PHY_INTERFACE_MODE_RGMII ||
+-		   iface == PHY_INTERFACE_MODE_RGMII_ID ||
+-		   iface == PHY_INTERFACE_MODE_RGMII_RXID ||
+-		   iface == PHY_INTERFACE_MODE_RGMII_TXID;
+-	is_sgmii = iface == PHY_INTERFACE_MODE_SGMII;
+-	is_qsgmii = iface == PHY_INTERFACE_MODE_QSGMII;
+-
+-	tmp = 0;
+-	if (is_rgmii || iface == PHY_INTERFACE_MODE_GMII)
+-		tmp |= DTSEC_ECNTRL_GMIIM;
+-	if (is_sgmii)
+-		tmp |= (DTSEC_ECNTRL_SGMIIM | DTSEC_ECNTRL_TBIM);
+-	if (is_qsgmii)
+-		tmp |= (DTSEC_ECNTRL_SGMIIM | DTSEC_ECNTRL_TBIM |
+-			DTSEC_ECNTRL_QSGMIIM);
+-	if (is_rgmii)
+-		tmp |= DTSEC_ECNTRL_RPM;
+-	if (iface_speed == SPEED_100)
+-		tmp |= DTSEC_ECNTRL_R100M;
+-
+-	iowrite32be(tmp, &regs->ecntrl);
+-
+-	tmp = 0;
+-
+ 	if (cfg->tx_pause_time)
+ 		tmp |= cfg->tx_pause_time;
+ 	if (cfg->tx_pause_time_extd)
+@@ -446,17 +404,10 @@ static int init(struct dtsec_regs __iome
+ 
+ 	tmp = 0;
+ 
+-	if (iface_speed < SPEED_1000)
+-		tmp |= MACCFG2_NIBBLE_MODE;
+-	else if (iface_speed == SPEED_1000)
+-		tmp |= MACCFG2_BYTE_MODE;
+-
+ 	tmp |= (cfg->preamble_len << MACCFG2_PREAMBLE_LENGTH_SHIFT) &
+ 		MACCFG2_PREAMBLE_LENGTH_MASK;
+ 	if (cfg->tx_pad_crc)
+ 		tmp |= MACCFG2_PAD_CRC_EN;
+-	/* Full Duplex */
+-	tmp |= MACCFG2_FULL_DUPLEX;
+ 	iowrite32be(tmp, &regs->maccfg2);
+ 
+ 	tmp = (((cfg->non_back_to_back_ipg1 <<
+@@ -525,10 +476,6 @@ static void set_bucket(struct dtsec_regs
+ 
+ static int check_init_parameters(struct fman_mac *dtsec)
+ {
+-	if (dtsec->max_speed >= SPEED_10000) {
+-		pr_err("1G MAC driver supports 1G or lower speeds\n");
+-		return -EINVAL;
+-	}
+ 	if ((dtsec->dtsec_drv_param)->rx_prepend >
+ 	    MAX_PACKET_ALIGNMENT) {
+ 		pr_err("packetAlignmentPadding can't be > than %d\n",
+@@ -630,22 +577,10 @@ static int get_exception_flag(enum fman_
+ 	return bit_mask;
+ }
+ 
+-static bool is_init_done(struct dtsec_cfg *dtsec_drv_params)
+-{
+-	/* Checks if dTSEC driver parameters were initialized */
+-	if (!dtsec_drv_params)
+-		return true;
+-
+-	return false;
+-}
+-
+ static u16 dtsec_get_max_frame_length(struct fman_mac *dtsec)
+ {
+ 	struct dtsec_regs __iomem *regs = dtsec->regs;
+ 
+-	if (is_init_done(dtsec->dtsec_drv_param))
+-		return 0;
+-
+ 	return (u16)ioread32be(&regs->maxfrm);
+ }
+ 
+@@ -682,6 +617,7 @@ static void dtsec_isr(void *handle)
+ 		dtsec->exception_cb(dtsec->dev_id, FM_MAC_EX_1G_COL_RET_LMT);
+ 	if (event & DTSEC_IMASK_XFUNEN) {
+ 		/* FM_TX_LOCKUP_ERRATA_DTSEC6 Errata workaround */
++		/* FIXME: This races with the rest of the driver! */
+ 		if (dtsec->fm_rev_info.major == 2) {
+ 			u32 tpkt1, tmp_reg1, tpkt2, tmp_reg2, i;
+ 			/* a. Write 0x00E0_0C00 to DTSEC_ID
+@@ -814,6 +750,43 @@ static void free_init_resources(struct f
+ 	dtsec->unicast_addr_hash = NULL;
+ }
+ 
++static struct fman_mac *pcs_to_dtsec(struct phylink_pcs *pcs)
++{
++	return container_of(pcs, struct fman_mac, pcs);
++}
++
++static void dtsec_pcs_get_state(struct phylink_pcs *pcs,
++				struct phylink_link_state *state)
++{
++	struct fman_mac *dtsec = pcs_to_dtsec(pcs);
++
++	phylink_mii_c22_pcs_get_state(dtsec->tbidev, state);
++}
++
++static int dtsec_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
++			    phy_interface_t interface,
++			    const unsigned long *advertising,
++			    bool permit_pause_to_mac)
++{
++	struct fman_mac *dtsec = pcs_to_dtsec(pcs);
++
++	return phylink_mii_c22_pcs_config(dtsec->tbidev, mode, interface,
++					  advertising);
++}
++
++static void dtsec_pcs_an_restart(struct phylink_pcs *pcs)
++{
++	struct fman_mac *dtsec = pcs_to_dtsec(pcs);
++
++	phylink_mii_c22_pcs_an_restart(dtsec->tbidev);
++}
++
++static const struct phylink_pcs_ops dtsec_pcs_ops = {
++	.pcs_get_state = dtsec_pcs_get_state,
++	.pcs_config = dtsec_pcs_config,
++	.pcs_an_restart = dtsec_pcs_an_restart,
++};
++
+ static void graceful_start(struct fman_mac *dtsec)
+ {
+ 	struct dtsec_regs __iomem *regs = dtsec->regs;
+@@ -854,36 +827,11 @@ static void graceful_stop(struct fman_ma
+ 
+ static int dtsec_enable(struct fman_mac *dtsec)
+ {
+-	struct dtsec_regs __iomem *regs = dtsec->regs;
+-	u32 tmp;
+-
+-	if (!is_init_done(dtsec->dtsec_drv_param))
+-		return -EINVAL;
+-
+-	/* Enable */
+-	tmp = ioread32be(&regs->maccfg1);
+-	tmp |= MACCFG1_RX_EN | MACCFG1_TX_EN;
+-	iowrite32be(tmp, &regs->maccfg1);
+-
+-	/* Graceful start - clear the graceful Rx/Tx stop bit */
+-	graceful_start(dtsec);
+-
+ 	return 0;
+ }
+ 
+ static void dtsec_disable(struct fman_mac *dtsec)
+ {
+-	struct dtsec_regs __iomem *regs = dtsec->regs;
+-	u32 tmp;
+-
+-	WARN_ON_ONCE(!is_init_done(dtsec->dtsec_drv_param));
+-
+-	/* Graceful stop - Assert the graceful Rx/Tx stop bit */
+-	graceful_stop(dtsec);
+-
+-	tmp = ioread32be(&regs->maccfg1);
+-	tmp &= ~(MACCFG1_RX_EN | MACCFG1_TX_EN);
+-	iowrite32be(tmp, &regs->maccfg1);
+ }
+ 
+ static int dtsec_set_tx_pause_frames(struct fman_mac *dtsec,
+@@ -894,11 +842,6 @@ static int dtsec_set_tx_pause_frames(str
+ 	struct dtsec_regs __iomem *regs = dtsec->regs;
+ 	u32 ptv = 0;
+ 
+-	if (!is_init_done(dtsec->dtsec_drv_param))
+-		return -EINVAL;
+-
+-	graceful_stop(dtsec);
+-
+ 	if (pause_time) {
+ 		/* FM_BAD_TX_TS_IN_B_2_B_ERRATA_DTSEC_A003 Errata workaround */
+ 		if (dtsec->fm_rev_info.major == 2 && pause_time <= 320) {
+@@ -919,8 +862,6 @@ static int dtsec_set_tx_pause_frames(str
+ 		iowrite32be(ioread32be(&regs->maccfg1) & ~MACCFG1_TX_FLOW,
+ 			    &regs->maccfg1);
+ 
+-	graceful_start(dtsec);
+-
+ 	return 0;
+ }
+ 
+@@ -929,11 +870,6 @@ static int dtsec_accept_rx_pause_frames(
+ 	struct dtsec_regs __iomem *regs = dtsec->regs;
+ 	u32 tmp;
+ 
+-	if (!is_init_done(dtsec->dtsec_drv_param))
+-		return -EINVAL;
+-
+-	graceful_stop(dtsec);
+-
+ 	tmp = ioread32be(&regs->maccfg1);
+ 	if (en)
+ 		tmp |= MACCFG1_RX_FLOW;
+@@ -941,17 +877,125 @@ static int dtsec_accept_rx_pause_frames(
+ 		tmp &= ~MACCFG1_RX_FLOW;
+ 	iowrite32be(tmp, &regs->maccfg1);
+ 
++	return 0;
++}
++
++static struct phylink_pcs *dtsec_select_pcs(struct phylink_config *config,
++					    phy_interface_t iface)
++{
++	struct fman_mac *dtsec = fman_config_to_mac(config)->fman_mac;
++
++	switch (iface) {
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_1000BASEX:
++	case PHY_INTERFACE_MODE_2500BASEX:
++		return &dtsec->pcs;
++	default:
++		return NULL;
++	}
++}
++
++static void dtsec_mac_config(struct phylink_config *config, unsigned int mode,
++			     const struct phylink_link_state *state)
++{
++	struct mac_device *mac_dev = fman_config_to_mac(config);
++	struct dtsec_regs __iomem *regs = mac_dev->fman_mac->regs;
++	u32 tmp;
++
++	switch (state->interface) {
++	case PHY_INTERFACE_MODE_RMII:
++		tmp = DTSEC_ECNTRL_RMM;
++		break;
++	case PHY_INTERFACE_MODE_RGMII:
++	case PHY_INTERFACE_MODE_RGMII_ID:
++	case PHY_INTERFACE_MODE_RGMII_RXID:
++	case PHY_INTERFACE_MODE_RGMII_TXID:
++		tmp = DTSEC_ECNTRL_GMIIM | DTSEC_ECNTRL_RPM;
++		break;
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_1000BASEX:
++	case PHY_INTERFACE_MODE_2500BASEX:
++		tmp = DTSEC_ECNTRL_TBIM | DTSEC_ECNTRL_SGMIIM;
++		break;
++	default:
++		dev_warn(mac_dev->dev, "cannot configure dTSEC for %s\n",
++			 phy_modes(state->interface));
++		return;
++	}
++
++	iowrite32be(tmp, &regs->ecntrl);
++}
++
++static void dtsec_link_up(struct phylink_config *config, struct phy_device *phy,
++			  unsigned int mode, phy_interface_t interface,
++			  int speed, int duplex, bool tx_pause, bool rx_pause)
++{
++	struct mac_device *mac_dev = fman_config_to_mac(config);
++	struct fman_mac *dtsec = mac_dev->fman_mac;
++	struct dtsec_regs __iomem *regs = dtsec->regs;
++	u16 pause_time = tx_pause ? FSL_FM_PAUSE_TIME_ENABLE :
++			 FSL_FM_PAUSE_TIME_DISABLE;
++	u32 tmp;
++
++	dtsec_set_tx_pause_frames(dtsec, 0, pause_time, 0);
++	dtsec_accept_rx_pause_frames(dtsec, rx_pause);
++
++	tmp = ioread32be(&regs->ecntrl);
++	if (speed == SPEED_100)
++		tmp |= DTSEC_ECNTRL_R100M;
++	else
++		tmp &= ~DTSEC_ECNTRL_R100M;
++	iowrite32be(tmp, &regs->ecntrl);
++
++	tmp = ioread32be(&regs->maccfg2);
++	tmp &= ~(MACCFG2_NIBBLE_MODE | MACCFG2_BYTE_MODE | MACCFG2_FULL_DUPLEX);
++	if (speed >= SPEED_1000)
++		tmp |= MACCFG2_BYTE_MODE;
++	else
++		tmp |= MACCFG2_NIBBLE_MODE;
++
++	if (duplex == DUPLEX_FULL)
++		tmp |= MACCFG2_FULL_DUPLEX;
++
++	iowrite32be(tmp, &regs->maccfg2);
++
++	mac_dev->update_speed(mac_dev, speed);
++
++	/* Enable */
++	tmp = ioread32be(&regs->maccfg1);
++	tmp |= MACCFG1_RX_EN | MACCFG1_TX_EN;
++	iowrite32be(tmp, &regs->maccfg1);
++
++	/* Graceful start - clear the graceful Rx/Tx stop bit */
+ 	graceful_start(dtsec);
++}
+ 
+-	return 0;
++static void dtsec_link_down(struct phylink_config *config, unsigned int mode,
++			    phy_interface_t interface)
++{
++	struct fman_mac *dtsec = fman_config_to_mac(config)->fman_mac;
++	struct dtsec_regs __iomem *regs = dtsec->regs;
++	u32 tmp;
++
++	/* Graceful stop - Assert the graceful Rx/Tx stop bit */
++	graceful_stop(dtsec);
++
++	tmp = ioread32be(&regs->maccfg1);
++	tmp &= ~(MACCFG1_RX_EN | MACCFG1_TX_EN);
++	iowrite32be(tmp, &regs->maccfg1);
+ }
+ 
++static const struct phylink_mac_ops dtsec_mac_ops = {
++	.validate = phylink_generic_validate,
++	.mac_select_pcs = dtsec_select_pcs,
++	.mac_config = dtsec_mac_config,
++	.mac_link_up = dtsec_link_up,
++	.mac_link_down = dtsec_link_down,
++};
++
+ static int dtsec_modify_mac_address(struct fman_mac *dtsec,
+ 				    const enet_addr_t *enet_addr)
+ {
+-	if (!is_init_done(dtsec->dtsec_drv_param))
+-		return -EINVAL;
+-
+ 	graceful_stop(dtsec);
+ 
+ 	/* Initialize MAC Station Address registers (1 & 2)
+@@ -975,9 +1019,6 @@ static int dtsec_add_hash_mac_address(st
+ 	u32 crc = 0xFFFFFFFF;
+ 	bool mcast, ghtx;
+ 
+-	if (!is_init_done(dtsec->dtsec_drv_param))
+-		return -EINVAL;
+-
+ 	addr = ENET_ADDR_TO_UINT64(*eth_addr);
+ 
+ 	ghtx = (bool)((ioread32be(&regs->rctrl) & RCTRL_GHTX) ? true : false);
+@@ -1037,9 +1078,6 @@ static int dtsec_set_allmulti(struct fma
+ 	u32 tmp;
+ 	struct dtsec_regs __iomem *regs = dtsec->regs;
+ 
+-	if (!is_init_done(dtsec->dtsec_drv_param))
+-		return -EINVAL;
+-
+ 	tmp = ioread32be(&regs->rctrl);
+ 	if (enable)
+ 		tmp |= RCTRL_MPROM;
+@@ -1056,9 +1094,6 @@ static int dtsec_set_tstamp(struct fman_
+ 	struct dtsec_regs __iomem *regs = dtsec->regs;
+ 	u32 rctrl, tctrl;
+ 
+-	if (!is_init_done(dtsec->dtsec_drv_param))
+-		return -EINVAL;
+-
+ 	rctrl = ioread32be(&regs->rctrl);
+ 	tctrl = ioread32be(&regs->tctrl);
+ 
+@@ -1087,9 +1122,6 @@ static int dtsec_del_hash_mac_address(st
+ 	u32 crc = 0xFFFFFFFF;
+ 	bool mcast, ghtx;
+ 
+-	if (!is_init_done(dtsec->dtsec_drv_param))
+-		return -EINVAL;
+-
+ 	addr = ENET_ADDR_TO_UINT64(*eth_addr);
+ 
+ 	ghtx = (bool)((ioread32be(&regs->rctrl) & RCTRL_GHTX) ? true : false);
+@@ -1153,9 +1185,6 @@ static int dtsec_set_promiscuous(struct
+ 	struct dtsec_regs __iomem *regs = dtsec->regs;
+ 	u32 tmp;
+ 
+-	if (!is_init_done(dtsec->dtsec_drv_param))
+-		return -EINVAL;
+-
+ 	/* Set unicast promiscuous */
+ 	tmp = ioread32be(&regs->rctrl);
+ 	if (new_val)
+@@ -1177,90 +1206,12 @@ static int dtsec_set_promiscuous(struct
+ 	return 0;
+ }
+ 
+-static int dtsec_adjust_link(struct fman_mac *dtsec, u16 speed)
+-{
+-	struct dtsec_regs __iomem *regs = dtsec->regs;
+-	u32 tmp;
+-
+-	if (!is_init_done(dtsec->dtsec_drv_param))
+-		return -EINVAL;
+-
+-	graceful_stop(dtsec);
+-
+-	tmp = ioread32be(&regs->maccfg2);
+-
+-	/* Full Duplex */
+-	tmp |= MACCFG2_FULL_DUPLEX;
+-
+-	tmp &= ~(MACCFG2_NIBBLE_MODE | MACCFG2_BYTE_MODE);
+-	if (speed < SPEED_1000)
+-		tmp |= MACCFG2_NIBBLE_MODE;
+-	else if (speed == SPEED_1000)
+-		tmp |= MACCFG2_BYTE_MODE;
+-	iowrite32be(tmp, &regs->maccfg2);
+-
+-	tmp = ioread32be(&regs->ecntrl);
+-	if (speed == SPEED_100)
+-		tmp |= DTSEC_ECNTRL_R100M;
+-	else
+-		tmp &= ~DTSEC_ECNTRL_R100M;
+-	iowrite32be(tmp, &regs->ecntrl);
+-
+-	graceful_start(dtsec);
+-
+-	return 0;
+-}
+-
+-static int dtsec_restart_autoneg(struct fman_mac *dtsec)
+-{
+-	u16 tmp_reg16;
+-
+-	if (!is_init_done(dtsec->dtsec_drv_param))
+-		return -EINVAL;
+-
+-	tmp_reg16 = phy_read(dtsec->tbiphy, MII_BMCR);
+-
+-	tmp_reg16 &= ~(BMCR_SPEED100 | BMCR_SPEED1000);
+-	tmp_reg16 |= (BMCR_ANENABLE | BMCR_ANRESTART |
+-		      BMCR_FULLDPLX | BMCR_SPEED1000);
+-
+-	phy_write(dtsec->tbiphy, MII_BMCR, tmp_reg16);
+-
+-	return 0;
+-}
+-
+-static void adjust_link_dtsec(struct mac_device *mac_dev)
+-{
+-	struct phy_device *phy_dev = mac_dev->phy_dev;
+-	struct fman_mac *fman_mac;
+-	bool rx_pause, tx_pause;
+-	int err;
+-
+-	fman_mac = mac_dev->fman_mac;
+-	if (!phy_dev->link) {
+-		dtsec_restart_autoneg(fman_mac);
+-
+-		return;
+-	}
+-
+-	dtsec_adjust_link(fman_mac, phy_dev->speed);
+-	mac_dev->update_speed(mac_dev, phy_dev->speed);
+-	fman_get_pause_cfg(mac_dev, &rx_pause, &tx_pause);
+-	err = fman_set_mac_active_pause(mac_dev, rx_pause, tx_pause);
+-	if (err < 0)
+-		dev_err(mac_dev->dev, "fman_set_mac_active_pause() = %d\n",
+-			err);
+-}
+-
+ static int dtsec_set_exception(struct fman_mac *dtsec,
+ 			       enum fman_mac_exceptions exception, bool enable)
+ {
+ 	struct dtsec_regs __iomem *regs = dtsec->regs;
+ 	u32 bit_mask = 0;
+ 
+-	if (!is_init_done(dtsec->dtsec_drv_param))
+-		return -EINVAL;
+-
+ 	if (exception != FM_MAC_EX_1G_1588_TS_RX_ERR) {
+ 		bit_mask = get_exception_flag(exception);
+ 		if (bit_mask) {
+@@ -1310,12 +1261,9 @@ static int dtsec_init(struct fman_mac *d
+ {
+ 	struct dtsec_regs __iomem *regs = dtsec->regs;
+ 	struct dtsec_cfg *dtsec_drv_param;
+-	u16 max_frm_ln;
++	u16 max_frm_ln, tbicon;
+ 	int err;
+ 
+-	if (is_init_done(dtsec->dtsec_drv_param))
+-		return -EINVAL;
+-
+ 	if (DEFAULT_RESET_ON_INIT &&
+ 	    (fman_reset_mac(dtsec->fm, dtsec->mac_id) != 0)) {
+ 		pr_err("Can't reset MAC!\n");
+@@ -1330,38 +1278,19 @@ static int dtsec_init(struct fman_mac *d
+ 
+ 	err = init(dtsec->regs, dtsec_drv_param, dtsec->phy_if,
+ 		   dtsec->max_speed, dtsec->addr, dtsec->exceptions,
+-		   dtsec->tbiphy->mdio.addr);
++		   dtsec->tbidev->addr);
+ 	if (err) {
+ 		free_init_resources(dtsec);
+ 		pr_err("DTSEC version doesn't support this i/f mode\n");
+ 		return err;
+ 	}
+ 
+-	if (dtsec->phy_if == PHY_INTERFACE_MODE_SGMII) {
+-		u16 tmp_reg16;
+-
+-		/* Configure the TBI PHY Control Register */
+-		tmp_reg16 = TBICON_CLK_SELECT | TBICON_SOFT_RESET;
+-		phy_write(dtsec->tbiphy, MII_TBICON, tmp_reg16);
+-
+-		tmp_reg16 = TBICON_CLK_SELECT;
+-		phy_write(dtsec->tbiphy, MII_TBICON, tmp_reg16);
+-
+-		tmp_reg16 = (BMCR_RESET | BMCR_ANENABLE |
+-			     BMCR_FULLDPLX | BMCR_SPEED1000);
+-		phy_write(dtsec->tbiphy, MII_BMCR, tmp_reg16);
+-
+-		if (dtsec->basex_if)
+-			tmp_reg16 = TBIANA_1000X;
+-		else
+-			tmp_reg16 = TBIANA_SGMII;
+-		phy_write(dtsec->tbiphy, MII_ADVERTISE, tmp_reg16);
++	/* Configure the TBI PHY Control Register */
++	tbicon = TBICON_CLK_SELECT | TBICON_SOFT_RESET;
++	mdiodev_write(dtsec->tbidev, MII_TBICON, tbicon);
+ 
+-		tmp_reg16 = (BMCR_ANENABLE | BMCR_ANRESTART |
+-			     BMCR_FULLDPLX | BMCR_SPEED1000);
+-
+-		phy_write(dtsec->tbiphy, MII_BMCR, tmp_reg16);
+-	}
++	tbicon = TBICON_CLK_SELECT;
++	mdiodev_write(dtsec->tbidev, MII_TBICON, tbicon);
+ 
+ 	/* Max Frame Length */
+ 	max_frm_ln = (u16)ioread32be(&regs->maxfrm);
+@@ -1406,6 +1335,8 @@ static int dtsec_free(struct fman_mac *d
+ 
+ 	kfree(dtsec->dtsec_drv_param);
+ 	dtsec->dtsec_drv_param = NULL;
++	if (!IS_ERR_OR_NULL(dtsec->tbidev))
++		put_device(&dtsec->tbidev->dev);
+ 	kfree(dtsec);
+ 
+ 	return 0;
+@@ -1434,7 +1365,6 @@ static struct fman_mac *dtsec_config(str
+ 
+ 	dtsec->regs = mac_dev->vaddr;
+ 	dtsec->addr = ENET_ADDR_TO_UINT64(mac_dev->addr);
+-	dtsec->max_speed = params->max_speed;
+ 	dtsec->phy_if = mac_dev->phy_if;
+ 	dtsec->mac_id = params->mac_id;
+ 	dtsec->exceptions = (DTSEC_IMASK_BREN	|
+@@ -1457,7 +1387,6 @@ static struct fman_mac *dtsec_config(str
+ 	dtsec->en_tsu_err_exception = dtsec->dtsec_drv_param->ptp_exception_en;
+ 
+ 	dtsec->fm = params->fm;
+-	dtsec->basex_if = params->basex_if;
+ 
+ 	/* Save FMan revision */
+ 	fman_get_revision(dtsec->fm, &dtsec->fm_rev_info);
+@@ -1476,18 +1405,18 @@ int dtsec_initialization(struct mac_devi
+ 	int			err;
+ 	struct fman_mac		*dtsec;
+ 	struct device_node	*phy_node;
++	unsigned long		 capabilities;
++	unsigned long		*supported;
+ 
++	mac_dev->phylink_ops		= &dtsec_mac_ops;
+ 	mac_dev->set_promisc		= dtsec_set_promiscuous;
+ 	mac_dev->change_addr		= dtsec_modify_mac_address;
+ 	mac_dev->add_hash_mac_addr	= dtsec_add_hash_mac_address;
+ 	mac_dev->remove_hash_mac_addr	= dtsec_del_hash_mac_address;
+-	mac_dev->set_tx_pause		= dtsec_set_tx_pause_frames;
+-	mac_dev->set_rx_pause		= dtsec_accept_rx_pause_frames;
+ 	mac_dev->set_exception		= dtsec_set_exception;
+ 	mac_dev->set_allmulti		= dtsec_set_allmulti;
+ 	mac_dev->set_tstamp		= dtsec_set_tstamp;
+ 	mac_dev->set_multi		= fman_set_multi;
+-	mac_dev->adjust_link            = adjust_link_dtsec;
+ 	mac_dev->enable			= dtsec_enable;
+ 	mac_dev->disable		= dtsec_disable;
+ 
+@@ -1502,19 +1431,56 @@ int dtsec_initialization(struct mac_devi
+ 	dtsec->dtsec_drv_param->tx_pad_crc = true;
+ 
+ 	phy_node = of_parse_phandle(mac_node, "tbi-handle", 0);
+-	if (!phy_node) {
+-		pr_err("TBI PHY node is not available\n");
++	if (!phy_node || of_device_is_available(phy_node)) {
++		of_node_put(phy_node);
+ 		err = -EINVAL;
++		dev_err_probe(mac_dev->dev, err,
++			      "TBI PCS node is not available\n");
+ 		goto _return_fm_mac_free;
+ 	}
+ 
+-	dtsec->tbiphy = of_phy_find_device(phy_node);
+-	if (!dtsec->tbiphy) {
+-		pr_err("of_phy_find_device (TBI PHY) failed\n");
+-		err = -EINVAL;
++	dtsec->tbidev = of_mdio_find_device(phy_node);
++	of_node_put(phy_node);
++	if (!dtsec->tbidev) {
++		err = -EPROBE_DEFER;
++		dev_err_probe(mac_dev->dev, err,
++			      "could not find mdiodev for PCS\n");
+ 		goto _return_fm_mac_free;
+ 	}
+-	put_device(&dtsec->tbiphy->mdio.dev);
++	dtsec->pcs.ops = &dtsec_pcs_ops;
++	dtsec->pcs.poll = true;
++
++	supported = mac_dev->phylink_config.supported_interfaces;
++
++	/* FIXME: Can we use DTSEC_ID2_INT_FULL_OFF to determine if these are
++	 * supported? If not, we can determine support via the phy if SerDes
++	 * support is added.
++	 */
++	if (mac_dev->phy_if == PHY_INTERFACE_MODE_SGMII ||
++	    mac_dev->phy_if == PHY_INTERFACE_MODE_1000BASEX) {
++		__set_bit(PHY_INTERFACE_MODE_SGMII, supported);
++		__set_bit(PHY_INTERFACE_MODE_1000BASEX, supported);
++	} else if (mac_dev->phy_if == PHY_INTERFACE_MODE_2500BASEX) {
++		__set_bit(PHY_INTERFACE_MODE_2500BASEX, supported);
++	}
++
++	if (!(ioread32be(&dtsec->regs->tsec_id2) & DTSEC_ID2_INT_REDUCED_OFF)) {
++		phy_interface_set_rgmii(supported);
++
++		/* DTSEC_ID2_INT_REDUCED_OFF indicates that the dTSEC supports
++		 * RMII and RGMII. However, the only SoCs which support RMII
++		 * are the P1017 and P1023. Avoid advertising this mode on
++		 * other SoCs. This is a bit of a moot point, since there's no
++		 * in-tree support for ethernet on these platforms...
++		 */
++		if (of_machine_is_compatible("fsl,P1023") ||
++		    of_machine_is_compatible("fsl,P1023RDB"))
++			__set_bit(PHY_INTERFACE_MODE_RMII, supported);
++	}
++
++	capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE;
++	capabilities |= MAC_10 | MAC_100 | MAC_1000FD | MAC_2500FD;
++	mac_dev->phylink_config.mac_capabilities = capabilities;
+ 
+ 	err = dtsec_init(dtsec);
+ 	if (err < 0)
+--- a/drivers/net/ethernet/freescale/fman/fman_mac.h
++++ b/drivers/net/ethernet/freescale/fman/fman_mac.h
+@@ -170,20 +170,10 @@ struct fman_mac_params {
+ 	 * 0 - FM_MAX_NUM_OF_10G_MACS
+ 	 */
+ 	u8 mac_id;
+-	/* Note that the speed should indicate the maximum rate that
+-	 * this MAC should support rather than the actual speed;
+-	 */
+-	u16 max_speed;
+ 	/* A handle to the FM object this port related to */
+ 	void *fm;
+ 	fman_mac_exception_cb *event_cb;    /* MDIO Events Callback Routine */
+ 	fman_mac_exception_cb *exception_cb;/* Exception Callback Routine */
+-	/* SGMII/QSGII interface with 1000BaseX auto-negotiation between MAC
+-	 * and phy or backplane; Note: 1000BaseX auto-negotiation relates only
+-	 * to interface between MAC and phy/backplane, SGMII phy can still
+-	 * synchronize with far-end phy at 10Mbps, 100Mbps or 1000Mbps
+-	*/
+-	bool basex_if;
+ };
+ 
+ struct eth_hash_t {
+--- a/drivers/net/ethernet/freescale/fman/fman_memac.c
++++ b/drivers/net/ethernet/freescale/fman/fman_memac.c
+@@ -278,9 +278,6 @@ struct fman_mac {
+ 	struct memac_regs __iomem *regs;
+ 	/* MAC address of device */
+ 	u64 addr;
+-	/* Ethernet physical interface */
+-	phy_interface_t phy_if;
+-	u16 max_speed;
+ 	struct mac_device *dev_id; /* device cookie used by the exception cbs */
+ 	fman_mac_exception_cb *exception_cb;
+ 	fman_mac_exception_cb *event_cb;
+@@ -293,12 +290,12 @@ struct fman_mac {
+ 	struct memac_cfg *memac_drv_param;
+ 	void *fm;
+ 	struct fman_rev_info fm_rev_info;
+-	bool basex_if;
+ 	struct phy *serdes;
+ 	struct phylink_pcs *sgmii_pcs;
+ 	struct phylink_pcs *qsgmii_pcs;
+ 	struct phylink_pcs *xfi_pcs;
+ 	bool allmulti_enabled;
++	bool rgmii_no_half_duplex;
+ };
+ 
+ static void add_addr_in_paddr(struct memac_regs __iomem *regs, const u8 *adr,
+@@ -356,7 +353,6 @@ static void set_exception(struct memac_r
+ }
+ 
+ static int init(struct memac_regs __iomem *regs, struct memac_cfg *cfg,
+-		phy_interface_t phy_if, u16 speed, bool slow_10g_if,
+ 		u32 exceptions)
+ {
+ 	u32 tmp;
+@@ -384,41 +380,6 @@ static int init(struct memac_regs __iome
+ 	iowrite32be((u32)cfg->pause_quanta, &regs->pause_quanta[0]);
+ 	iowrite32be((u32)0, &regs->pause_thresh[0]);
+ 
+-	/* IF_MODE */
+-	tmp = 0;
+-	switch (phy_if) {
+-	case PHY_INTERFACE_MODE_XGMII:
+-		tmp |= IF_MODE_10G;
+-		break;
+-	case PHY_INTERFACE_MODE_MII:
+-		tmp |= IF_MODE_MII;
+-		break;
+-	default:
+-		tmp |= IF_MODE_GMII;
+-		if (phy_if == PHY_INTERFACE_MODE_RGMII ||
+-		    phy_if == PHY_INTERFACE_MODE_RGMII_ID ||
+-		    phy_if == PHY_INTERFACE_MODE_RGMII_RXID ||
+-		    phy_if == PHY_INTERFACE_MODE_RGMII_TXID)
+-			tmp |= IF_MODE_RGMII | IF_MODE_RGMII_AUTO;
+-	}
+-	iowrite32be(tmp, &regs->if_mode);
+-
+-	/* TX_FIFO_SECTIONS */
+-	tmp = 0;
+-	if (phy_if == PHY_INTERFACE_MODE_XGMII) {
+-		if (slow_10g_if) {
+-			tmp |= (TX_FIFO_SECTIONS_TX_AVAIL_SLOW_10G |
+-				TX_FIFO_SECTIONS_TX_EMPTY_DEFAULT_10G);
+-		} else {
+-			tmp |= (TX_FIFO_SECTIONS_TX_AVAIL_10G |
+-				TX_FIFO_SECTIONS_TX_EMPTY_DEFAULT_10G);
+-		}
+-	} else {
+-		tmp |= (TX_FIFO_SECTIONS_TX_AVAIL_1G |
+-			TX_FIFO_SECTIONS_TX_EMPTY_DEFAULT_1G);
+-	}
+-	iowrite32be(tmp, &regs->tx_fifo_sections);
+-
+ 	/* clear all pending events and set-up interrupts */
+ 	iowrite32be(0xffffffff, &regs->ievent);
+ 	set_exception(regs, exceptions, true);
+@@ -458,24 +419,6 @@ static u32 get_mac_addr_hash_code(u64 et
+ 	return xor_val;
+ }
+ 
+-static void setup_sgmii_internal(struct fman_mac *memac,
+-				 struct phylink_pcs *pcs,
+-				 struct fixed_phy_status *fixed_link)
+-{
+-	__ETHTOOL_DECLARE_LINK_MODE_MASK(advertising);
+-	phy_interface_t iface = memac->basex_if ? PHY_INTERFACE_MODE_1000BASEX :
+-				PHY_INTERFACE_MODE_SGMII;
+-	unsigned int mode = fixed_link ? MLO_AN_FIXED : MLO_AN_INBAND;
+-
+-	linkmode_set_pause(advertising, true, true);
+-	pcs->ops->pcs_config(pcs, mode, iface, advertising, true);
+-	if (fixed_link)
+-		pcs->ops->pcs_link_up(pcs, mode, iface, fixed_link->speed,
+-				      fixed_link->duplex);
+-	else
+-		pcs->ops->pcs_an_restart(pcs);
+-}
+-
+ static int check_init_parameters(struct fman_mac *memac)
+ {
+ 	if (!memac->exception_cb) {
+@@ -581,41 +524,31 @@ static void free_init_resources(struct f
+ 	memac->unicast_addr_hash = NULL;
+ }
+ 
+-static bool is_init_done(struct memac_cfg *memac_drv_params)
+-{
+-	/* Checks if mEMAC driver parameters were initialized */
+-	if (!memac_drv_params)
+-		return true;
+-
+-	return false;
+-}
+-
+ static int memac_enable(struct fman_mac *memac)
+ {
+-	struct memac_regs __iomem *regs = memac->regs;
+-	u32 tmp;
++	int ret;
+ 
+-	if (!is_init_done(memac->memac_drv_param))
+-		return -EINVAL;
++	ret = phy_init(memac->serdes);
++	if (ret) {
++		dev_err(memac->dev_id->dev,
++			"could not initialize serdes: %pe\n", ERR_PTR(ret));
++		return ret;
++	}
+ 
+-	tmp = ioread32be(&regs->command_config);
+-	tmp |= CMD_CFG_RX_EN | CMD_CFG_TX_EN;
+-	iowrite32be(tmp, &regs->command_config);
++	ret = phy_power_on(memac->serdes);
++	if (ret) {
++		dev_err(memac->dev_id->dev,
++			"could not power on serdes: %pe\n", ERR_PTR(ret));
++		phy_exit(memac->serdes);
++	}
+ 
+-	return 0;
++	return ret;
+ }
+ 
+ static void memac_disable(struct fman_mac *memac)
+-
+ {
+-	struct memac_regs __iomem *regs = memac->regs;
+-	u32 tmp;
+-
+-	WARN_ON_ONCE(!is_init_done(memac->memac_drv_param));
+-
+-	tmp = ioread32be(&regs->command_config);
+-	tmp &= ~(CMD_CFG_RX_EN | CMD_CFG_TX_EN);
+-	iowrite32be(tmp, &regs->command_config);
++	phy_power_off(memac->serdes);
++	phy_exit(memac->serdes);
+ }
+ 
+ static int memac_set_promiscuous(struct fman_mac *memac, bool new_val)
+@@ -623,9 +556,6 @@ static int memac_set_promiscuous(struct
+ 	struct memac_regs __iomem *regs = memac->regs;
+ 	u32 tmp;
+ 
+-	if (!is_init_done(memac->memac_drv_param))
+-		return -EINVAL;
+-
+ 	tmp = ioread32be(&regs->command_config);
+ 	if (new_val)
+ 		tmp |= CMD_CFG_PROMIS_EN;
+@@ -637,73 +567,12 @@ static int memac_set_promiscuous(struct
+ 	return 0;
+ }
+ 
+-static int memac_adjust_link(struct fman_mac *memac, u16 speed)
+-{
+-	struct memac_regs __iomem *regs = memac->regs;
+-	u32 tmp;
+-
+-	if (!is_init_done(memac->memac_drv_param))
+-		return -EINVAL;
+-
+-	tmp = ioread32be(&regs->if_mode);
+-
+-	/* Set full duplex */
+-	tmp &= ~IF_MODE_HD;
+-
+-	if (phy_interface_mode_is_rgmii(memac->phy_if)) {
+-		/* Configure RGMII in manual mode */
+-		tmp &= ~IF_MODE_RGMII_AUTO;
+-		tmp &= ~IF_MODE_RGMII_SP_MASK;
+-		/* Full duplex */
+-		tmp |= IF_MODE_RGMII_FD;
+-
+-		switch (speed) {
+-		case SPEED_1000:
+-			tmp |= IF_MODE_RGMII_1000;
+-			break;
+-		case SPEED_100:
+-			tmp |= IF_MODE_RGMII_100;
+-			break;
+-		case SPEED_10:
+-			tmp |= IF_MODE_RGMII_10;
+-			break;
+-		default:
+-			break;
+-		}
+-	}
+-
+-	iowrite32be(tmp, &regs->if_mode);
+-
+-	return 0;
+-}
+-
+-static void adjust_link_memac(struct mac_device *mac_dev)
+-{
+-	struct phy_device *phy_dev = mac_dev->phy_dev;
+-	struct fman_mac *fman_mac;
+-	bool rx_pause, tx_pause;
+-	int err;
+-
+-	fman_mac = mac_dev->fman_mac;
+-	memac_adjust_link(fman_mac, phy_dev->speed);
+-	mac_dev->update_speed(mac_dev, phy_dev->speed);
+-
+-	fman_get_pause_cfg(mac_dev, &rx_pause, &tx_pause);
+-	err = fman_set_mac_active_pause(mac_dev, rx_pause, tx_pause);
+-	if (err < 0)
+-		dev_err(mac_dev->dev, "fman_set_mac_active_pause() = %d\n",
+-			err);
+-}
+-
+ static int memac_set_tx_pause_frames(struct fman_mac *memac, u8 priority,
+ 				     u16 pause_time, u16 thresh_time)
+ {
+ 	struct memac_regs __iomem *regs = memac->regs;
+ 	u32 tmp;
+ 
+-	if (!is_init_done(memac->memac_drv_param))
+-		return -EINVAL;
+-
+ 	tmp = ioread32be(&regs->tx_fifo_sections);
+ 
+ 	GET_TX_EMPTY_DEFAULT_VALUE(tmp);
+@@ -738,9 +607,6 @@ static int memac_accept_rx_pause_frames(
+ 	struct memac_regs __iomem *regs = memac->regs;
+ 	u32 tmp;
+ 
+-	if (!is_init_done(memac->memac_drv_param))
+-		return -EINVAL;
+-
+ 	tmp = ioread32be(&regs->command_config);
+ 	if (en)
+ 		tmp &= ~CMD_CFG_PAUSE_IGNORE;
+@@ -752,12 +618,175 @@ static int memac_accept_rx_pause_frames(
+ 	return 0;
+ }
+ 
++static void memac_validate(struct phylink_config *config,
++			   unsigned long *supported,
++			   struct phylink_link_state *state)
++{
++	struct fman_mac *memac = fman_config_to_mac(config)->fman_mac;
++	unsigned long caps = config->mac_capabilities;
++
++	if (phy_interface_mode_is_rgmii(state->interface) &&
++	    memac->rgmii_no_half_duplex)
++		caps &= ~(MAC_10HD | MAC_100HD);
++
++	phylink_validate_mask_caps(supported, state, caps);
++}
++
++/**
++ * memac_if_mode() - Convert an interface mode into an IF_MODE config
++ * @interface: A phy interface mode
++ *
++ * Return: A configuration word, suitable for programming into the lower bits
++ *         of %IF_MODE.
++ */
++static u32 memac_if_mode(phy_interface_t interface)
++{
++	switch (interface) {
++	case PHY_INTERFACE_MODE_MII:
++		return IF_MODE_MII;
++	case PHY_INTERFACE_MODE_RGMII:
++	case PHY_INTERFACE_MODE_RGMII_ID:
++	case PHY_INTERFACE_MODE_RGMII_RXID:
++	case PHY_INTERFACE_MODE_RGMII_TXID:
++		return IF_MODE_GMII | IF_MODE_RGMII;
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_1000BASEX:
++	case PHY_INTERFACE_MODE_QSGMII:
++		return IF_MODE_GMII;
++	case PHY_INTERFACE_MODE_10GBASER:
++		return IF_MODE_10G;
++	default:
++		WARN_ON_ONCE(1);
++		return 0;
++	}
++}
++
++static struct phylink_pcs *memac_select_pcs(struct phylink_config *config,
++					    phy_interface_t iface)
++{
++	struct fman_mac *memac = fman_config_to_mac(config)->fman_mac;
++
++	switch (iface) {
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_1000BASEX:
++		return memac->sgmii_pcs;
++	case PHY_INTERFACE_MODE_QSGMII:
++		return memac->qsgmii_pcs;
++	case PHY_INTERFACE_MODE_10GBASER:
++		return memac->xfi_pcs;
++	default:
++		return NULL;
++	}
++}
++
++static int memac_prepare(struct phylink_config *config, unsigned int mode,
++			 phy_interface_t iface)
++{
++	struct fman_mac *memac = fman_config_to_mac(config)->fman_mac;
++
++	switch (iface) {
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_1000BASEX:
++	case PHY_INTERFACE_MODE_QSGMII:
++	case PHY_INTERFACE_MODE_10GBASER:
++		return phy_set_mode_ext(memac->serdes, PHY_MODE_ETHERNET,
++					iface);
++	default:
++		return 0;
++	}
++}
++
++static void memac_mac_config(struct phylink_config *config, unsigned int mode,
++			     const struct phylink_link_state *state)
++{
++	struct mac_device *mac_dev = fman_config_to_mac(config);
++	struct memac_regs __iomem *regs = mac_dev->fman_mac->regs;
++	u32 tmp = ioread32be(&regs->if_mode);
++
++	tmp &= ~(IF_MODE_MASK | IF_MODE_RGMII);
++	tmp |= memac_if_mode(state->interface);
++	if (phylink_autoneg_inband(mode))
++		tmp |= IF_MODE_RGMII_AUTO;
++	iowrite32be(tmp, &regs->if_mode);
++}
++
++static void memac_link_up(struct phylink_config *config, struct phy_device *phy,
++			  unsigned int mode, phy_interface_t interface,
++			  int speed, int duplex, bool tx_pause, bool rx_pause)
++{
++	struct mac_device *mac_dev = fman_config_to_mac(config);
++	struct fman_mac *memac = mac_dev->fman_mac;
++	struct memac_regs __iomem *regs = memac->regs;
++	u32 tmp = memac_if_mode(interface);
++	u16 pause_time = tx_pause ? FSL_FM_PAUSE_TIME_ENABLE :
++			 FSL_FM_PAUSE_TIME_DISABLE;
++
++	memac_set_tx_pause_frames(memac, 0, pause_time, 0);
++	memac_accept_rx_pause_frames(memac, rx_pause);
++
++	if (duplex == DUPLEX_HALF)
++		tmp |= IF_MODE_HD;
++
++	switch (speed) {
++	case SPEED_1000:
++		tmp |= IF_MODE_RGMII_1000;
++		break;
++	case SPEED_100:
++		tmp |= IF_MODE_RGMII_100;
++		break;
++	case SPEED_10:
++		tmp |= IF_MODE_RGMII_10;
++		break;
++	}
++	iowrite32be(tmp, &regs->if_mode);
++
++	/* TODO: EEE? */
++
++	if (speed == SPEED_10000) {
++		if (memac->fm_rev_info.major == 6 &&
++		    memac->fm_rev_info.minor == 4)
++			tmp = TX_FIFO_SECTIONS_TX_AVAIL_SLOW_10G;
++		else
++			tmp = TX_FIFO_SECTIONS_TX_AVAIL_10G;
++		tmp |= TX_FIFO_SECTIONS_TX_EMPTY_DEFAULT_10G;
++	} else {
++		tmp = TX_FIFO_SECTIONS_TX_AVAIL_1G |
++		      TX_FIFO_SECTIONS_TX_EMPTY_DEFAULT_1G;
++	}
++	iowrite32be(tmp, &regs->tx_fifo_sections);
++
++	mac_dev->update_speed(mac_dev, speed);
++
++	tmp = ioread32be(&regs->command_config);
++	tmp |= CMD_CFG_RX_EN | CMD_CFG_TX_EN;
++	iowrite32be(tmp, &regs->command_config);
++}
++
++static void memac_link_down(struct phylink_config *config, unsigned int mode,
++			    phy_interface_t interface)
++{
++	struct fman_mac *memac = fman_config_to_mac(config)->fman_mac;
++	struct memac_regs __iomem *regs = memac->regs;
++	u32 tmp;
++
++	/* TODO: graceful */
++	tmp = ioread32be(&regs->command_config);
++	tmp &= ~(CMD_CFG_RX_EN | CMD_CFG_TX_EN);
++	iowrite32be(tmp, &regs->command_config);
++}
++
++static const struct phylink_mac_ops memac_mac_ops = {
++	.validate = memac_validate,
++	.mac_select_pcs = memac_select_pcs,
++	.mac_prepare = memac_prepare,
++	.mac_config = memac_mac_config,
++	.mac_link_up = memac_link_up,
++	.mac_link_down = memac_link_down,
++};
++
+ static int memac_modify_mac_address(struct fman_mac *memac,
+ 				    const enet_addr_t *enet_addr)
+ {
+-	if (!is_init_done(memac->memac_drv_param))
+-		return -EINVAL;
+-
+ 	add_addr_in_paddr(memac->regs, (const u8 *)(*enet_addr), 0);
+ 
+ 	return 0;
+@@ -771,9 +800,6 @@ static int memac_add_hash_mac_address(st
+ 	u32 hash;
+ 	u64 addr;
+ 
+-	if (!is_init_done(memac->memac_drv_param))
+-		return -EINVAL;
+-
+ 	addr = ENET_ADDR_TO_UINT64(*eth_addr);
+ 
+ 	if (!(addr & GROUP_ADDRESS)) {
+@@ -802,9 +828,6 @@ static int memac_set_allmulti(struct fma
+ 	u32 entry;
+ 	struct memac_regs __iomem *regs = memac->regs;
+ 
+-	if (!is_init_done(memac->memac_drv_param))
+-		return -EINVAL;
+-
+ 	if (enable) {
+ 		for (entry = 0; entry < HASH_TABLE_SIZE; entry++)
+ 			iowrite32be(entry | HASH_CTRL_MCAST_EN,
+@@ -834,9 +857,6 @@ static int memac_del_hash_mac_address(st
+ 	u32 hash;
+ 	u64 addr;
+ 
+-	if (!is_init_done(memac->memac_drv_param))
+-		return -EINVAL;
+-
+ 	addr = ENET_ADDR_TO_UINT64(*eth_addr);
+ 
+ 	hash = get_mac_addr_hash_code(addr) & HASH_CTRL_ADDR_MASK;
+@@ -864,9 +884,6 @@ static int memac_set_exception(struct fm
+ {
+ 	u32 bit_mask = 0;
+ 
+-	if (!is_init_done(memac->memac_drv_param))
+-		return -EINVAL;
+-
+ 	bit_mask = get_exception_flag(exception);
+ 	if (bit_mask) {
+ 		if (enable)
+@@ -886,23 +903,15 @@ static int memac_init(struct fman_mac *m
+ {
+ 	struct memac_cfg *memac_drv_param;
+ 	enet_addr_t eth_addr;
+-	bool slow_10g_if = false;
+-	struct fixed_phy_status *fixed_link = NULL;
+ 	int err;
+ 	u32 reg32 = 0;
+ 
+-	if (is_init_done(memac->memac_drv_param))
+-		return -EINVAL;
+-
+ 	err = check_init_parameters(memac);
+ 	if (err)
+ 		return err;
+ 
+ 	memac_drv_param = memac->memac_drv_param;
+ 
+-	if (memac->fm_rev_info.major == 6 && memac->fm_rev_info.minor == 4)
+-		slow_10g_if = true;
+-
+ 	/* First, reset the MAC if desired. */
+ 	if (memac_drv_param->reset_on_init) {
+ 		err = reset(memac->regs);
+@@ -918,10 +927,7 @@ static int memac_init(struct fman_mac *m
+ 		add_addr_in_paddr(memac->regs, (const u8 *)eth_addr, 0);
+ 	}
+ 
+-	fixed_link = memac_drv_param->fixed_link;
+-
+-	init(memac->regs, memac->memac_drv_param, memac->phy_if,
+-	     memac->max_speed, slow_10g_if, memac->exceptions);
++	init(memac->regs, memac->memac_drv_param, memac->exceptions);
+ 
+ 	/* FM_RX_FIFO_CORRUPT_ERRATA_10GMAC_A006320 errata workaround
+ 	 * Exists only in FMan 6.0 and 6.3.
+@@ -937,11 +943,6 @@ static int memac_init(struct fman_mac *m
+ 		iowrite32be(reg32, &memac->regs->command_config);
+ 	}
+ 
+-	if (memac->phy_if == PHY_INTERFACE_MODE_SGMII)
+-		setup_sgmii_internal(memac, memac->sgmii_pcs, fixed_link);
+-	else if (memac->phy_if == PHY_INTERFACE_MODE_QSGMII)
+-		setup_sgmii_internal(memac, memac->qsgmii_pcs, fixed_link);
+-
+ 	/* Max Frame Length */
+ 	err = fman_set_mac_max_frame(memac->fm, memac->mac_id,
+ 				     memac_drv_param->max_frame_length);
+@@ -970,9 +971,6 @@ static int memac_init(struct fman_mac *m
+ 	fman_register_intr(memac->fm, FMAN_MOD_MAC, memac->mac_id,
+ 			   FMAN_INTR_TYPE_NORMAL, memac_exception, memac);
+ 
+-	kfree(memac_drv_param);
+-	memac->memac_drv_param = NULL;
+-
+ 	return 0;
+ }
+ 
+@@ -995,7 +993,6 @@ static int memac_free(struct fman_mac *m
+ 	pcs_put(memac->sgmii_pcs);
+ 	pcs_put(memac->qsgmii_pcs);
+ 	pcs_put(memac->xfi_pcs);
+-
+ 	kfree(memac->memac_drv_param);
+ 	kfree(memac);
+ 
+@@ -1028,8 +1025,6 @@ static struct fman_mac *memac_config(str
+ 	memac->addr = ENET_ADDR_TO_UINT64(mac_dev->addr);
+ 
+ 	memac->regs = mac_dev->vaddr;
+-	memac->max_speed = params->max_speed;
+-	memac->phy_if = mac_dev->phy_if;
+ 	memac->mac_id = params->mac_id;
+ 	memac->exceptions = (MEMAC_IMASK_TSECC_ER | MEMAC_IMASK_TECC_ER |
+ 			     MEMAC_IMASK_RECC_ER | MEMAC_IMASK_MGI);
+@@ -1037,7 +1032,6 @@ static struct fman_mac *memac_config(str
+ 	memac->event_cb = params->event_cb;
+ 	memac->dev_id = mac_dev;
+ 	memac->fm = params->fm;
+-	memac->basex_if = params->basex_if;
+ 
+ 	/* Save FMan revision */
+ 	fman_get_revision(memac->fm, &memac->fm_rev_info);
+@@ -1064,37 +1058,44 @@ static struct phylink_pcs *memac_pcs_cre
+ 	return pcs;
+ }
+ 
++static bool memac_supports(struct mac_device *mac_dev, phy_interface_t iface)
++{
++	/* If there's no serdes device, assume that it's been configured for
++	 * whatever the default interface mode is.
++	 */
++	if (!mac_dev->fman_mac->serdes)
++		return mac_dev->phy_if == iface;
++	/* Otherwise, ask the serdes */
++	return !phy_validate(mac_dev->fman_mac->serdes, PHY_MODE_ETHERNET,
++			     iface, NULL);
++}
++
+ int memac_initialization(struct mac_device *mac_dev,
+ 			 struct device_node *mac_node,
+ 			 struct fman_mac_params *params)
+ {
+ 	int			 err;
++	struct device_node      *fixed;
+ 	struct phylink_pcs	*pcs;
+-	struct fixed_phy_status *fixed_link;
+ 	struct fman_mac		*memac;
++	unsigned long		 capabilities;
++	unsigned long		*supported;
+ 
++	mac_dev->phylink_ops		= &memac_mac_ops;
+ 	mac_dev->set_promisc		= memac_set_promiscuous;
+ 	mac_dev->change_addr		= memac_modify_mac_address;
+ 	mac_dev->add_hash_mac_addr	= memac_add_hash_mac_address;
+ 	mac_dev->remove_hash_mac_addr	= memac_del_hash_mac_address;
+-	mac_dev->set_tx_pause		= memac_set_tx_pause_frames;
+-	mac_dev->set_rx_pause		= memac_accept_rx_pause_frames;
+ 	mac_dev->set_exception		= memac_set_exception;
+ 	mac_dev->set_allmulti		= memac_set_allmulti;
+ 	mac_dev->set_tstamp		= memac_set_tstamp;
+ 	mac_dev->set_multi		= fman_set_multi;
+-	mac_dev->adjust_link            = adjust_link_memac;
+ 	mac_dev->enable			= memac_enable;
+ 	mac_dev->disable		= memac_disable;
+ 
+-	if (params->max_speed == SPEED_10000)
+-		mac_dev->phy_if = PHY_INTERFACE_MODE_XGMII;
+-
+ 	mac_dev->fman_mac = memac_config(mac_dev, params);
+-	if (!mac_dev->fman_mac) {
+-		err = -EINVAL;
+-		goto _return;
+-	}
++	if (!mac_dev->fman_mac)
++		return -EINVAL;
+ 
+ 	memac = mac_dev->fman_mac;
+ 	memac->memac_drv_param->max_frame_length = fman_get_max_frm();
+@@ -1136,9 +1137,9 @@ int memac_initialization(struct mac_devi
+ 	else
+ 		pcs = memac_pcs_create(mac_node, err);
+ 
+-	if (!pcs) {
+-		dev_err(mac_dev->dev, "missing pcs\n");
+-		err = -ENOENT;
++	if (IS_ERR(pcs)) {
++		err = PTR_ERR(pcs);
++		dev_err_probe(mac_dev->dev, err, "missing pcs\n");
+ 		goto _return_fm_mac_free;
+ 	}
+ 
+@@ -1159,84 +1160,100 @@ int memac_initialization(struct mac_devi
+ 	} else if (IS_ERR(memac->serdes)) {
+ 		dev_err_probe(mac_dev->dev, err, "could not get serdes\n");
+ 		goto _return_fm_mac_free;
+-	} else {
+-		err = phy_init(memac->serdes);
+-		if (err) {
+-			dev_err_probe(mac_dev->dev, err,
+-				      "could not initialize serdes\n");
+-			goto _return_fm_mac_free;
+-		}
+-
+-		err = phy_power_on(memac->serdes);
+-		if (err) {
+-			dev_err_probe(mac_dev->dev, err,
+-				      "could not power on serdes\n");
+-			goto _return_phy_exit;
+-		}
+-
+-		if (memac->phy_if == PHY_INTERFACE_MODE_SGMII ||
+-		    memac->phy_if == PHY_INTERFACE_MODE_1000BASEX ||
+-		    memac->phy_if == PHY_INTERFACE_MODE_2500BASEX ||
+-		    memac->phy_if == PHY_INTERFACE_MODE_QSGMII ||
+-		    memac->phy_if == PHY_INTERFACE_MODE_XGMII) {
+-			err = phy_set_mode_ext(memac->serdes, PHY_MODE_ETHERNET,
+-					       memac->phy_if);
+-			if (err) {
+-				dev_err_probe(mac_dev->dev, err,
+-					      "could not set serdes mode to %s\n",
+-					      phy_modes(memac->phy_if));
+-				goto _return_phy_power_off;
+-			}
+-		}
+ 	}
+ 
+-	if (!mac_dev->phy_node && of_phy_is_fixed_link(mac_node)) {
+-		struct phy_device *phy;
+-
+-		err = of_phy_register_fixed_link(mac_node);
+-		if (err)
+-			goto _return_phy_power_off;
+-
+-		fixed_link = kzalloc(sizeof(*fixed_link), GFP_KERNEL);
+-		if (!fixed_link) {
+-			err = -ENOMEM;
+-			goto _return_phy_power_off;
+-		}
++	/* The internal connection to the serdes is XGMII, but this isn't
++	 * really correct for the phy mode (which is the external connection).
++	 * However, this is how all older device trees say that they want
++	 * 10GBASE-R (aka XFI), so just convert it for them.
++	 */
++	if (mac_dev->phy_if == PHY_INTERFACE_MODE_XGMII)
++		mac_dev->phy_if = PHY_INTERFACE_MODE_10GBASER;
+ 
+-		mac_dev->phy_node = of_node_get(mac_node);
+-		phy = of_phy_find_device(mac_dev->phy_node);
+-		if (!phy) {
+-			err = -EINVAL;
+-			of_node_put(mac_dev->phy_node);
+-			goto _return_fixed_link_free;
+-		}
++	/* TODO: The following interface modes are supported by (some) hardware
++	 * but not by this driver:
++	 * - 1000BASE-KX
++	 * - 10GBASE-KR
++	 * - XAUI/HiGig
++	 */
++	supported = mac_dev->phylink_config.supported_interfaces;
+ 
+-		fixed_link->link = phy->link;
+-		fixed_link->speed = phy->speed;
+-		fixed_link->duplex = phy->duplex;
+-		fixed_link->pause = phy->pause;
+-		fixed_link->asym_pause = phy->asym_pause;
++	/* Note that half duplex is only supported on 10/100M interfaces. */
+ 
+-		put_device(&phy->mdio.dev);
+-		memac->memac_drv_param->fixed_link = fixed_link;
++	if (memac->sgmii_pcs &&
++	    (memac_supports(mac_dev, PHY_INTERFACE_MODE_SGMII) ||
++	     memac_supports(mac_dev, PHY_INTERFACE_MODE_1000BASEX))) {
++		__set_bit(PHY_INTERFACE_MODE_SGMII, supported);
++		__set_bit(PHY_INTERFACE_MODE_1000BASEX, supported);
++	}
++
++	if (memac->sgmii_pcs &&
++	    memac_supports(mac_dev, PHY_INTERFACE_MODE_2500BASEX))
++		__set_bit(PHY_INTERFACE_MODE_2500BASEX, supported);
++
++	if (memac->qsgmii_pcs &&
++	    memac_supports(mac_dev, PHY_INTERFACE_MODE_QSGMII))
++		__set_bit(PHY_INTERFACE_MODE_QSGMII, supported);
++	else if (mac_dev->phy_if == PHY_INTERFACE_MODE_QSGMII)
++		dev_warn(mac_dev->dev, "no QSGMII pcs specified\n");
++
++	if (memac->xfi_pcs &&
++	    memac_supports(mac_dev, PHY_INTERFACE_MODE_10GBASER)) {
++		__set_bit(PHY_INTERFACE_MODE_10GBASER, supported);
++	} else {
++		/* From what I can tell, no 10g macs support RGMII. */
++		phy_interface_set_rgmii(supported);
++		__set_bit(PHY_INTERFACE_MODE_MII, supported);
+ 	}
+ 
++	capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | MAC_10 | MAC_100;
++	capabilities |= MAC_1000FD | MAC_2500FD | MAC_10000FD;
++
++	/* These SoCs don't support half duplex at all; there's no different
++	 * FMan version or compatible, so we just have to check the machine
++	 * compatible instead
++	 */
++	if (of_machine_is_compatible("fsl,ls1043a") ||
++	    of_machine_is_compatible("fsl,ls1046a") ||
++	    of_machine_is_compatible("fsl,B4QDS"))
++		capabilities &= ~(MAC_10HD | MAC_100HD);
++
++	mac_dev->phylink_config.mac_capabilities = capabilities;
++
++	/* The T2080 and T4240 don't support half duplex RGMII. There is no
++	 * other way to identify these SoCs, so just use the machine
++	 * compatible.
++	 */
++	if (of_machine_is_compatible("fsl,T2080QDS") ||
++	    of_machine_is_compatible("fsl,T2080RDB") ||
++	    of_machine_is_compatible("fsl,T2081QDS") ||
++	    of_machine_is_compatible("fsl,T4240QDS") ||
++	    of_machine_is_compatible("fsl,T4240RDB"))
++		memac->rgmii_no_half_duplex = true;
++
++	/* Most boards should use MLO_AN_INBAND, but existing boards don't have
++	 * a managed property. Default to MLO_AN_INBAND if nothing else is
++	 * specified. We need to be careful and not enable this if we have a
++	 * fixed link or if we are using MII or RGMII, since those
++	 * configurations modes don't use in-band autonegotiation.
++	 */
++	fixed = of_get_child_by_name(mac_node, "fixed-link");
++	if (!fixed && !of_property_read_bool(mac_node, "fixed-link") &&
++	    !of_property_read_bool(mac_node, "managed") &&
++	    mac_dev->phy_if != PHY_INTERFACE_MODE_MII &&
++	    !phy_interface_mode_is_rgmii(mac_dev->phy_if))
++		mac_dev->phylink_config.ovr_an_inband = true;
++	of_node_put(fixed);
++
+ 	err = memac_init(mac_dev->fman_mac);
+ 	if (err < 0)
+-		goto _return_fixed_link_free;
++		goto _return_fm_mac_free;
+ 
+ 	dev_info(mac_dev->dev, "FMan MEMAC\n");
+ 
+-	goto _return;
++	return 0;
+ 
+-_return_phy_power_off:
+-	phy_power_off(memac->serdes);
+-_return_phy_exit:
+-	phy_exit(memac->serdes);
+-_return_fixed_link_free:
+-	kfree(fixed_link);
+ _return_fm_mac_free:
+ 	memac_free(mac_dev->fman_mac);
+-_return:
+ 	return err;
+ }
+--- a/drivers/net/ethernet/freescale/fman/fman_tgec.c
++++ b/drivers/net/ethernet/freescale/fman/fman_tgec.c
+@@ -13,6 +13,7 @@
+ #include <linux/bitrev.h>
+ #include <linux/io.h>
+ #include <linux/crc32.h>
++#include <linux/netdevice.h>
+ 
+ /* Transmit Inter-Packet Gap Length Register (TX_IPG_LENGTH) */
+ #define TGEC_TX_IPG_LENGTH_MASK	0x000003ff
+@@ -243,10 +244,6 @@ static int init(struct tgec_regs __iomem
+ 
+ static int check_init_parameters(struct fman_mac *tgec)
+ {
+-	if (tgec->max_speed < SPEED_10000) {
+-		pr_err("10G MAC driver only support 10G speed\n");
+-		return -EINVAL;
+-	}
+ 	if (!tgec->exception_cb) {
+ 		pr_err("uninitialized exception_cb\n");
+ 		return -EINVAL;
+@@ -384,40 +381,13 @@ static void free_init_resources(struct f
+ 	tgec->unicast_addr_hash = NULL;
+ }
+ 
+-static bool is_init_done(struct tgec_cfg *cfg)
+-{
+-	/* Checks if tGEC driver parameters were initialized */
+-	if (!cfg)
+-		return true;
+-
+-	return false;
+-}
+-
+ static int tgec_enable(struct fman_mac *tgec)
+ {
+-	struct tgec_regs __iomem *regs = tgec->regs;
+-	u32 tmp;
+-
+-	if (!is_init_done(tgec->cfg))
+-		return -EINVAL;
+-
+-	tmp = ioread32be(&regs->command_config);
+-	tmp |= CMD_CFG_RX_EN | CMD_CFG_TX_EN;
+-	iowrite32be(tmp, &regs->command_config);
+-
+ 	return 0;
+ }
+ 
+ static void tgec_disable(struct fman_mac *tgec)
+ {
+-	struct tgec_regs __iomem *regs = tgec->regs;
+-	u32 tmp;
+-
+-	WARN_ON_ONCE(!is_init_done(tgec->cfg));
+-
+-	tmp = ioread32be(&regs->command_config);
+-	tmp &= ~(CMD_CFG_RX_EN | CMD_CFG_TX_EN);
+-	iowrite32be(tmp, &regs->command_config);
+ }
+ 
+ static int tgec_set_promiscuous(struct fman_mac *tgec, bool new_val)
+@@ -425,9 +395,6 @@ static int tgec_set_promiscuous(struct f
+ 	struct tgec_regs __iomem *regs = tgec->regs;
+ 	u32 tmp;
+ 
+-	if (!is_init_done(tgec->cfg))
+-		return -EINVAL;
+-
+ 	tmp = ioread32be(&regs->command_config);
+ 	if (new_val)
+ 		tmp |= CMD_CFG_PROMIS_EN;
+@@ -444,9 +411,6 @@ static int tgec_set_tx_pause_frames(stru
+ {
+ 	struct tgec_regs __iomem *regs = tgec->regs;
+ 
+-	if (!is_init_done(tgec->cfg))
+-		return -EINVAL;
+-
+ 	iowrite32be((u32)pause_time, &regs->pause_quant);
+ 
+ 	return 0;
+@@ -457,9 +421,6 @@ static int tgec_accept_rx_pause_frames(s
+ 	struct tgec_regs __iomem *regs = tgec->regs;
+ 	u32 tmp;
+ 
+-	if (!is_init_done(tgec->cfg))
+-		return -EINVAL;
+-
+ 	tmp = ioread32be(&regs->command_config);
+ 	if (!en)
+ 		tmp |= CMD_CFG_PAUSE_IGNORE;
+@@ -470,12 +431,53 @@ static int tgec_accept_rx_pause_frames(s
+ 	return 0;
+ }
+ 
++static void tgec_mac_config(struct phylink_config *config, unsigned int mode,
++			    const struct phylink_link_state *state)
++{
++}
++
++static void tgec_link_up(struct phylink_config *config, struct phy_device *phy,
++			 unsigned int mode, phy_interface_t interface,
++			 int speed, int duplex, bool tx_pause, bool rx_pause)
++{
++	struct mac_device *mac_dev = fman_config_to_mac(config);
++	struct fman_mac *tgec = mac_dev->fman_mac;
++	struct tgec_regs __iomem *regs = tgec->regs;
++	u16 pause_time = tx_pause ? FSL_FM_PAUSE_TIME_ENABLE :
++			 FSL_FM_PAUSE_TIME_DISABLE;
++	u32 tmp;
++
++	tgec_set_tx_pause_frames(tgec, 0, pause_time, 0);
++	tgec_accept_rx_pause_frames(tgec, rx_pause);
++	mac_dev->update_speed(mac_dev, speed);
++
++	tmp = ioread32be(&regs->command_config);
++	tmp |= CMD_CFG_RX_EN | CMD_CFG_TX_EN;
++	iowrite32be(tmp, &regs->command_config);
++}
++
++static void tgec_link_down(struct phylink_config *config, unsigned int mode,
++			   phy_interface_t interface)
++{
++	struct fman_mac *tgec = fman_config_to_mac(config)->fman_mac;
++	struct tgec_regs __iomem *regs = tgec->regs;
++	u32 tmp;
++
++	tmp = ioread32be(&regs->command_config);
++	tmp &= ~(CMD_CFG_RX_EN | CMD_CFG_TX_EN);
++	iowrite32be(tmp, &regs->command_config);
++}
++
++static const struct phylink_mac_ops tgec_mac_ops = {
++	.validate = phylink_generic_validate,
++	.mac_config = tgec_mac_config,
++	.mac_link_up = tgec_link_up,
++	.mac_link_down = tgec_link_down,
++};
++
+ static int tgec_modify_mac_address(struct fman_mac *tgec,
+ 				   const enet_addr_t *p_enet_addr)
+ {
+-	if (!is_init_done(tgec->cfg))
+-		return -EINVAL;
+-
+ 	tgec->addr = ENET_ADDR_TO_UINT64(*p_enet_addr);
+ 	set_mac_address(tgec->regs, (const u8 *)(*p_enet_addr));
+ 
+@@ -490,9 +492,6 @@ static int tgec_add_hash_mac_address(str
+ 	u32 crc = 0xFFFFFFFF, hash;
+ 	u64 addr;
+ 
+-	if (!is_init_done(tgec->cfg))
+-		return -EINVAL;
+-
+ 	addr = ENET_ADDR_TO_UINT64(*eth_addr);
+ 
+ 	if (!(addr & GROUP_ADDRESS)) {
+@@ -525,9 +524,6 @@ static int tgec_set_allmulti(struct fman
+ 	u32 entry;
+ 	struct tgec_regs __iomem *regs = tgec->regs;
+ 
+-	if (!is_init_done(tgec->cfg))
+-		return -EINVAL;
+-
+ 	if (enable) {
+ 		for (entry = 0; entry < TGEC_HASH_TABLE_SIZE; entry++)
+ 			iowrite32be(entry | TGEC_HASH_MCAST_EN,
+@@ -548,9 +544,6 @@ static int tgec_set_tstamp(struct fman_m
+ 	struct tgec_regs __iomem *regs = tgec->regs;
+ 	u32 tmp;
+ 
+-	if (!is_init_done(tgec->cfg))
+-		return -EINVAL;
+-
+ 	tmp = ioread32be(&regs->command_config);
+ 
+ 	if (enable)
+@@ -572,9 +565,6 @@ static int tgec_del_hash_mac_address(str
+ 	u32 crc = 0xFFFFFFFF, hash;
+ 	u64 addr;
+ 
+-	if (!is_init_done(tgec->cfg))
+-		return -EINVAL;
+-
+ 	addr = ((*(u64 *)eth_addr) >> 16);
+ 
+ 	/* CRC calculation */
+@@ -601,22 +591,12 @@ static int tgec_del_hash_mac_address(str
+ 	return 0;
+ }
+ 
+-static void tgec_adjust_link(struct mac_device *mac_dev)
+-{
+-	struct phy_device *phy_dev = mac_dev->phy_dev;
+-
+-	mac_dev->update_speed(mac_dev, phy_dev->speed);
+-}
+-
+ static int tgec_set_exception(struct fman_mac *tgec,
+ 			      enum fman_mac_exceptions exception, bool enable)
+ {
+ 	struct tgec_regs __iomem *regs = tgec->regs;
+ 	u32 bit_mask = 0;
+ 
+-	if (!is_init_done(tgec->cfg))
+-		return -EINVAL;
+-
+ 	bit_mask = get_exception_flag(exception);
+ 	if (bit_mask) {
+ 		if (enable)
+@@ -641,9 +621,6 @@ static int tgec_init(struct fman_mac *tg
+ 	enet_addr_t eth_addr;
+ 	int err;
+ 
+-	if (is_init_done(tgec->cfg))
+-		return -EINVAL;
+-
+ 	if (DEFAULT_RESET_ON_INIT &&
+ 	    (fman_reset_mac(tgec->fm, tgec->mac_id) != 0)) {
+ 		pr_err("Can't reset MAC!\n");
+@@ -753,7 +730,6 @@ static struct fman_mac *tgec_config(stru
+ 
+ 	tgec->regs = mac_dev->vaddr;
+ 	tgec->addr = ENET_ADDR_TO_UINT64(mac_dev->addr);
+-	tgec->max_speed = params->max_speed;
+ 	tgec->mac_id = params->mac_id;
+ 	tgec->exceptions = (TGEC_IMASK_MDIO_SCAN_EVENT	|
+ 			    TGEC_IMASK_REM_FAULT	|
+@@ -788,17 +764,15 @@ int tgec_initialization(struct mac_devic
+ 	int err;
+ 	struct fman_mac		*tgec;
+ 
++	mac_dev->phylink_ops		= &tgec_mac_ops;
+ 	mac_dev->set_promisc		= tgec_set_promiscuous;
+ 	mac_dev->change_addr		= tgec_modify_mac_address;
+ 	mac_dev->add_hash_mac_addr	= tgec_add_hash_mac_address;
+ 	mac_dev->remove_hash_mac_addr	= tgec_del_hash_mac_address;
+-	mac_dev->set_tx_pause		= tgec_set_tx_pause_frames;
+-	mac_dev->set_rx_pause		= tgec_accept_rx_pause_frames;
+ 	mac_dev->set_exception		= tgec_set_exception;
+ 	mac_dev->set_allmulti		= tgec_set_allmulti;
+ 	mac_dev->set_tstamp		= tgec_set_tstamp;
+ 	mac_dev->set_multi		= fman_set_multi;
+-	mac_dev->adjust_link            = tgec_adjust_link;
+ 	mac_dev->enable			= tgec_enable;
+ 	mac_dev->disable		= tgec_disable;
+ 
+@@ -808,6 +782,19 @@ int tgec_initialization(struct mac_devic
+ 		goto _return;
+ 	}
+ 
++	/* The internal connection to the serdes is XGMII, but this isn't
++	 * really correct for the phy mode (which is the external connection).
++	 * However, this is how all older device trees say that they want
++	 * XAUI, so just convert it for them.
++	 */
++	if (mac_dev->phy_if == PHY_INTERFACE_MODE_XGMII)
++		mac_dev->phy_if = PHY_INTERFACE_MODE_XAUI;
++
++	__set_bit(PHY_INTERFACE_MODE_XAUI,
++		  mac_dev->phylink_config.supported_interfaces);
++	mac_dev->phylink_config.mac_capabilities =
++		MAC_SYM_PAUSE | MAC_ASYM_PAUSE | MAC_10000FD;
++
+ 	tgec = mac_dev->fman_mac;
+ 	tgec->cfg->max_frame_length = fman_get_max_frm();
+ 	err = tgec_init(tgec);
+--- a/drivers/net/ethernet/freescale/fman/mac.c
++++ b/drivers/net/ethernet/freescale/fman/mac.c
+@@ -15,6 +15,7 @@
+ #include <linux/phy.h>
+ #include <linux/netdevice.h>
+ #include <linux/phy_fixed.h>
++#include <linux/phylink.h>
+ #include <linux/etherdevice.h>
+ #include <linux/libfdt_env.h>
+ 
+@@ -93,130 +94,8 @@ int fman_set_multi(struct net_device *ne
+ 	return 0;
+ }
+ 
+-/**
+- * fman_set_mac_active_pause
+- * @mac_dev:	A pointer to the MAC device
+- * @rx:		Pause frame setting for RX
+- * @tx:		Pause frame setting for TX
+- *
+- * Set the MAC RX/TX PAUSE frames settings
+- *
+- * Avoid redundant calls to FMD, if the MAC driver already contains the desired
+- * active PAUSE settings. Otherwise, the new active settings should be reflected
+- * in FMan.
+- *
+- * Return: 0 on success; Error code otherwise.
+- */
+-int fman_set_mac_active_pause(struct mac_device *mac_dev, bool rx, bool tx)
+-{
+-	struct fman_mac *fman_mac = mac_dev->fman_mac;
+-	int err = 0;
+-
+-	if (rx != mac_dev->rx_pause_active) {
+-		err = mac_dev->set_rx_pause(fman_mac, rx);
+-		if (likely(err == 0))
+-			mac_dev->rx_pause_active = rx;
+-	}
+-
+-	if (tx != mac_dev->tx_pause_active) {
+-		u16 pause_time = (tx ? FSL_FM_PAUSE_TIME_ENABLE :
+-					 FSL_FM_PAUSE_TIME_DISABLE);
+-
+-		err = mac_dev->set_tx_pause(fman_mac, 0, pause_time, 0);
+-
+-		if (likely(err == 0))
+-			mac_dev->tx_pause_active = tx;
+-	}
+-
+-	return err;
+-}
+-EXPORT_SYMBOL(fman_set_mac_active_pause);
+-
+-/**
+- * fman_get_pause_cfg
+- * @mac_dev:	A pointer to the MAC device
+- * @rx_pause:	Return value for RX setting
+- * @tx_pause:	Return value for TX setting
+- *
+- * Determine the MAC RX/TX PAUSE frames settings based on PHY
+- * autonegotiation or values set by eththool.
+- *
+- * Return: Pointer to FMan device.
+- */
+-void fman_get_pause_cfg(struct mac_device *mac_dev, bool *rx_pause,
+-			bool *tx_pause)
+-{
+-	struct phy_device *phy_dev = mac_dev->phy_dev;
+-	u16 lcl_adv, rmt_adv;
+-	u8 flowctrl;
+-
+-	*rx_pause = *tx_pause = false;
+-
+-	if (!phy_dev->duplex)
+-		return;
+-
+-	/* If PAUSE autonegotiation is disabled, the TX/RX PAUSE settings
+-	 * are those set by ethtool.
+-	 */
+-	if (!mac_dev->autoneg_pause) {
+-		*rx_pause = mac_dev->rx_pause_req;
+-		*tx_pause = mac_dev->tx_pause_req;
+-		return;
+-	}
+-
+-	/* Else if PAUSE autonegotiation is enabled, the TX/RX PAUSE
+-	 * settings depend on the result of the link negotiation.
+-	 */
+-
+-	/* get local capabilities */
+-	lcl_adv = linkmode_adv_to_lcl_adv_t(phy_dev->advertising);
+-
+-	/* get link partner capabilities */
+-	rmt_adv = 0;
+-	if (phy_dev->pause)
+-		rmt_adv |= LPA_PAUSE_CAP;
+-	if (phy_dev->asym_pause)
+-		rmt_adv |= LPA_PAUSE_ASYM;
+-
+-	/* Calculate TX/RX settings based on local and peer advertised
+-	 * symmetric/asymmetric PAUSE capabilities.
+-	 */
+-	flowctrl = mii_resolve_flowctrl_fdx(lcl_adv, rmt_adv);
+-	if (flowctrl & FLOW_CTRL_RX)
+-		*rx_pause = true;
+-	if (flowctrl & FLOW_CTRL_TX)
+-		*tx_pause = true;
+-}
+-EXPORT_SYMBOL(fman_get_pause_cfg);
+-
+-#define DTSEC_SUPPORTED \
+-	(SUPPORTED_10baseT_Half \
+-	| SUPPORTED_10baseT_Full \
+-	| SUPPORTED_100baseT_Half \
+-	| SUPPORTED_100baseT_Full \
+-	| SUPPORTED_Autoneg \
+-	| SUPPORTED_Pause \
+-	| SUPPORTED_Asym_Pause \
+-	| SUPPORTED_FIBRE \
+-	| SUPPORTED_MII)
+-
+ static DEFINE_MUTEX(eth_lock);
+ 
+-static const u16 phy2speed[] = {
+-	[PHY_INTERFACE_MODE_MII]		= SPEED_100,
+-	[PHY_INTERFACE_MODE_GMII]		= SPEED_1000,
+-	[PHY_INTERFACE_MODE_SGMII]		= SPEED_1000,
+-	[PHY_INTERFACE_MODE_TBI]		= SPEED_1000,
+-	[PHY_INTERFACE_MODE_RMII]		= SPEED_100,
+-	[PHY_INTERFACE_MODE_RGMII]		= SPEED_1000,
+-	[PHY_INTERFACE_MODE_RGMII_ID]		= SPEED_1000,
+-	[PHY_INTERFACE_MODE_RGMII_RXID]	= SPEED_1000,
+-	[PHY_INTERFACE_MODE_RGMII_TXID]	= SPEED_1000,
+-	[PHY_INTERFACE_MODE_RTBI]		= SPEED_1000,
+-	[PHY_INTERFACE_MODE_QSGMII]		= SPEED_1000,
+-	[PHY_INTERFACE_MODE_XGMII]		= SPEED_10000
+-};
+-
+ static struct platform_device *dpaa_eth_add_device(int fman_id,
+ 						   struct mac_device *mac_dev)
+ {
+@@ -263,8 +142,8 @@ no_mem:
+ }
+ 
+ static const struct of_device_id mac_match[] = {
+-	{ .compatible	= "fsl,fman-dtsec", .data = dtsec_initialization },
+-	{ .compatible	= "fsl,fman-xgec", .data = tgec_initialization },
++	{ .compatible   = "fsl,fman-dtsec", .data = dtsec_initialization },
++	{ .compatible   = "fsl,fman-xgec", .data = tgec_initialization },
+ 	{ .compatible	= "fsl,fman-memac", .data = memac_initialization },
+ 	{}
+ };
+@@ -295,6 +174,7 @@ static int mac_probe(struct platform_dev
+ 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ 	if (!priv)
+ 		return -ENOMEM;
++	platform_set_drvdata(_of_dev, mac_dev);
+ 
+ 	/* Save private information */
+ 	mac_dev->priv = priv;
+@@ -424,57 +304,21 @@ static int mac_probe(struct platform_dev
+ 	}
+ 	mac_dev->phy_if = phy_if;
+ 
+-	priv->speed		= phy2speed[mac_dev->phy_if];
+-	params.max_speed	= priv->speed;
+-	mac_dev->if_support	= DTSEC_SUPPORTED;
+-	/* We don't support half-duplex in SGMII mode */
+-	if (mac_dev->phy_if == PHY_INTERFACE_MODE_SGMII)
+-		mac_dev->if_support &= ~(SUPPORTED_10baseT_Half |
+-					SUPPORTED_100baseT_Half);
+-
+-	/* Gigabit support (no half-duplex) */
+-	if (params.max_speed == 1000)
+-		mac_dev->if_support |= SUPPORTED_1000baseT_Full;
+-
+-	/* The 10G interface only supports one mode */
+-	if (mac_dev->phy_if == PHY_INTERFACE_MODE_XGMII)
+-		mac_dev->if_support = SUPPORTED_10000baseT_Full;
+-
+-	/* Get the rest of the PHY information */
+-	mac_dev->phy_node = of_parse_phandle(mac_node, "phy-handle", 0);
+-
+-	params.basex_if		= false;
+ 	params.mac_id		= priv->cell_index;
+ 	params.fm		= (void *)priv->fman;
+ 	params.exception_cb	= mac_exception;
+ 	params.event_cb		= mac_exception;
+ 
+ 	err = init(mac_dev, mac_node, &params);
+-	if (err < 0) {
+-		dev_err(dev, "mac_dev->init() = %d\n", err);
+-		of_node_put(mac_dev->phy_node);
+-		return err;
+-	}
+-
+-	/* pause frame autonegotiation enabled */
+-	mac_dev->autoneg_pause = true;
+-
+-	/* By intializing the values to false, force FMD to enable PAUSE frames
+-	 * on RX and TX
+-	 */
+-	mac_dev->rx_pause_req = true;
+-	mac_dev->tx_pause_req = true;
+-	mac_dev->rx_pause_active = false;
+-	mac_dev->tx_pause_active = false;
+-	err = fman_set_mac_active_pause(mac_dev, true, true);
+ 	if (err < 0)
+-		dev_err(dev, "fman_set_mac_active_pause() = %d\n", err);
++		return err;
+ 
+ 	if (!is_zero_ether_addr(mac_dev->addr))
+ 		dev_info(dev, "FMan MAC address: %pM\n", mac_dev->addr);
+ 
+ 	priv->eth_dev = dpaa_eth_add_device(fman_id, mac_dev);
+ 	if (IS_ERR(priv->eth_dev)) {
++		err = PTR_ERR(priv->eth_dev);
+ 		dev_err(dev, "failed to add Ethernet platform device for MAC %d\n",
+ 			priv->cell_index);
+ 		priv->eth_dev = NULL;
+--- a/drivers/net/ethernet/freescale/fman/mac.h
++++ b/drivers/net/ethernet/freescale/fman/mac.h
+@@ -9,6 +9,7 @@
+ #include <linux/device.h>
+ #include <linux/if_ether.h>
+ #include <linux/phy.h>
++#include <linux/phylink.h>
+ #include <linux/list.h>
+ 
+ #include "fman_port.h"
+@@ -24,32 +25,22 @@ struct mac_device {
+ 	struct resource		*res;
+ 	u8			 addr[ETH_ALEN];
+ 	struct fman_port	*port[2];
+-	u32			 if_support;
+-	struct phy_device	*phy_dev;
++	struct phylink		*phylink;
++	struct phylink_config	phylink_config;
+ 	phy_interface_t		phy_if;
+-	struct device_node	*phy_node;
+-	struct net_device	*net_dev;
+ 
+-	bool autoneg_pause;
+-	bool rx_pause_req;
+-	bool tx_pause_req;
+-	bool rx_pause_active;
+-	bool tx_pause_active;
+ 	bool promisc;
+ 	bool allmulti;
+ 
++	const struct phylink_mac_ops *phylink_ops;
+ 	int (*enable)(struct fman_mac *mac_dev);
+ 	void (*disable)(struct fman_mac *mac_dev);
+-	void (*adjust_link)(struct mac_device *mac_dev);
+ 	int (*set_promisc)(struct fman_mac *mac_dev, bool enable);
+ 	int (*change_addr)(struct fman_mac *mac_dev, const enet_addr_t *enet_addr);
+ 	int (*set_allmulti)(struct fman_mac *mac_dev, bool enable);
+ 	int (*set_tstamp)(struct fman_mac *mac_dev, bool enable);
+ 	int (*set_multi)(struct net_device *net_dev,
+ 			 struct mac_device *mac_dev);
+-	int (*set_rx_pause)(struct fman_mac *mac_dev, bool en);
+-	int (*set_tx_pause)(struct fman_mac *mac_dev, u8 priority,
+-			    u16 pause_time, u16 thresh_time);
+ 	int (*set_exception)(struct fman_mac *mac_dev,
+ 			     enum fman_mac_exceptions exception, bool enable);
+ 	int (*add_hash_mac_addr)(struct fman_mac *mac_dev,
+@@ -63,6 +54,12 @@ struct mac_device {
+ 	struct mac_priv_s	*priv;
+ };
+ 
++static inline struct mac_device
++*fman_config_to_mac(struct phylink_config *config)
++{
++	return container_of(config, struct mac_device, phylink_config);
++}
++
+ struct dpaa_eth_data {
+ 	struct mac_device *mac_dev;
+ 	int mac_hw_id;

+ 93 - 0
target/linux/generic/backport-6.6/715-04-v6.2-net-phylink-provide-phylink_validate_mask_caps-helpe.patch

@@ -0,0 +1,93 @@
+From bf4de031052fe7c5309e8956c342d4e5ce79038e Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Mon, 17 Oct 2022 16:22:35 -0400
+Subject: [PATCH 04/21] net: phylink: provide phylink_validate_mask_caps()
+ helper
+
+Provide a helper that restricts the link modes according to the
+phylink capabilities.
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+[rebased on net-next/master and added documentation]
+Signed-off-by: Sean Anderson <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/phylink.c | 41 +++++++++++++++++++++++++++------------
+ include/linux/phylink.h   |  3 +++
+ 2 files changed, 32 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -564,31 +564,48 @@ unsigned long phylink_get_capabilities(p
+ EXPORT_SYMBOL_GPL(phylink_get_capabilities);
+ 
+ /**
+- * phylink_generic_validate() - generic validate() callback implementation
+- * @config: a pointer to a &struct phylink_config.
++ * phylink_validate_mask_caps() - Restrict link modes based on caps
+  * @supported: ethtool bitmask for supported link modes.
+- * @state: a pointer to a &struct phylink_link_state.
++ * @state: an (optional) pointer to a &struct phylink_link_state.
++ * @mac_capabilities: bitmask of MAC capabilities
+  *
+- * Generic implementation of the validate() callback that MAC drivers can
+- * use when they pass the range of supported interfaces and MAC capabilities.
+- * This makes use of phylink_get_linkmodes().
++ * Calculate the supported link modes based on @mac_capabilities, and restrict
++ * @supported and @state based on that. Use this function if your capabiliies
++ * aren't constant, such as if they vary depending on the interface.
+  */
+-void phylink_generic_validate(struct phylink_config *config,
+-			      unsigned long *supported,
+-			      struct phylink_link_state *state)
++void phylink_validate_mask_caps(unsigned long *supported,
++				struct phylink_link_state *state,
++				unsigned long mac_capabilities)
+ {
+ 	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
+ 	unsigned long caps;
+ 
+ 	phylink_set_port_modes(mask);
+ 	phylink_set(mask, Autoneg);
+-	caps = phylink_get_capabilities(state->interface,
+-					config->mac_capabilities,
++	caps = phylink_get_capabilities(state->interface, mac_capabilities,
+ 					state->rate_matching);
+ 	phylink_caps_to_linkmodes(mask, caps);
+ 
+ 	linkmode_and(supported, supported, mask);
+-	linkmode_and(state->advertising, state->advertising, mask);
++	if (state)
++		linkmode_and(state->advertising, state->advertising, mask);
++}
++EXPORT_SYMBOL_GPL(phylink_validate_mask_caps);
++
++/**
++ * phylink_generic_validate() - generic validate() callback implementation
++ * @config: a pointer to a &struct phylink_config.
++ * @supported: ethtool bitmask for supported link modes.
++ * @state: a pointer to a &struct phylink_link_state.
++ *
++ * Generic implementation of the validate() callback that MAC drivers can
++ * use when they pass the range of supported interfaces and MAC capabilities.
++ */
++void phylink_generic_validate(struct phylink_config *config,
++			      unsigned long *supported,
++			      struct phylink_link_state *state)
++{
++	phylink_validate_mask_caps(supported, state, config->mac_capabilities);
+ }
+ EXPORT_SYMBOL_GPL(phylink_generic_validate);
+ 
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -558,6 +558,9 @@ void phylink_caps_to_linkmodes(unsigned
+ unsigned long phylink_get_capabilities(phy_interface_t interface,
+ 				       unsigned long mac_capabilities,
+ 				       int rate_matching);
++void phylink_validate_mask_caps(unsigned long *supported,
++				struct phylink_link_state *state,
++				unsigned long caps);
+ void phylink_generic_validate(struct phylink_config *config,
+ 			      unsigned long *supported,
+ 			      struct phylink_link_state *state);

+ 39 - 0
target/linux/generic/backport-6.6/715-05-v6.2-phylink-require-valid-state-argument-to-phylink_vali.patch

@@ -0,0 +1,39 @@
+From 2bf7e4a68c42eed909f3c29582e1fb85cb157e35 Mon Sep 17 00:00:00 2001
+From: Jakub Kicinski <[email protected]>
+Date: Tue, 25 Oct 2022 11:51:26 -0700
+Subject: [PATCH 05/21] phylink: require valid state argument to
+ phylink_validate_mask_caps()
+
+state is deferenced earlier in the function, the NULL check
+is pointless. Since we don't have any crash reports presumably
+it's safe to assume state is not NULL.
+
+Fixes: f392a1846489 ("net: phylink: provide phylink_validate_mask_caps() helper")
+Reviewed-by: Sean Anderson <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/phylink.c | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -566,7 +566,7 @@ EXPORT_SYMBOL_GPL(phylink_get_capabiliti
+ /**
+  * phylink_validate_mask_caps() - Restrict link modes based on caps
+  * @supported: ethtool bitmask for supported link modes.
+- * @state: an (optional) pointer to a &struct phylink_link_state.
++ * @state: pointer to a &struct phylink_link_state.
+  * @mac_capabilities: bitmask of MAC capabilities
+  *
+  * Calculate the supported link modes based on @mac_capabilities, and restrict
+@@ -587,8 +587,7 @@ void phylink_validate_mask_caps(unsigned
+ 	phylink_caps_to_linkmodes(mask, caps);
+ 
+ 	linkmode_and(supported, supported, mask);
+-	if (state)
+-		linkmode_and(state->advertising, state->advertising, mask);
++	linkmode_and(state->advertising, state->advertising, mask);
+ }
+ EXPORT_SYMBOL_GPL(phylink_validate_mask_caps);
+ 

+ 48 - 0
target/linux/generic/backport-6.6/715-06-v6.2-net-phylink-add-phylink_get_link_timer_ns-helper.patch

@@ -0,0 +1,48 @@
+From f8fc363bf0c023e4736a0328174b4a24b44ab23a Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Thu, 27 Oct 2022 14:10:37 +0100
+Subject: [PATCH 06/21] net: phylink: add phylink_get_link_timer_ns() helper
+
+Add a helper to convert the PHY interface mode to the required link
+timer setting as stated by the appropriate standard. Inappropriate
+interface modes return an error.
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ include/linux/phylink.h | 24 ++++++++++++++++++++++++
+ 1 file changed, 24 insertions(+)
+
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -617,6 +617,30 @@ int phylink_speed_up(struct phylink *pl)
+ 
+ void phylink_set_port_modes(unsigned long *bits);
+ 
++/**
++ * phylink_get_link_timer_ns - return the PCS link timer value
++ * @interface: link &typedef phy_interface_t mode
++ *
++ * Return the PCS link timer setting in nanoseconds for the PHY @interface
++ * mode, or -EINVAL if not appropriate.
++ */
++static inline int phylink_get_link_timer_ns(phy_interface_t interface)
++{
++	switch (interface) {
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_QSGMII:
++	case PHY_INTERFACE_MODE_USXGMII:
++		return 1600000;
++
++	case PHY_INTERFACE_MODE_1000BASEX:
++	case PHY_INTERFACE_MODE_2500BASEX:
++		return 10000000;
++
++	default:
++		return -EINVAL;
++	}
++}
++
+ void phylink_mii_c22_pcs_decode_state(struct phylink_link_state *state,
+ 				      u16 bmsr, u16 lpa);
+ void phylink_mii_c22_pcs_get_state(struct mdio_device *pcs,

+ 250 - 0
target/linux/generic/backport-6.6/715-07-v6.2-net-remove-explicit-phylink_generic_validate-referen.patch

@@ -0,0 +1,250 @@
+From b45b773a96b0e9e8d51e5d005485f4e376d6ce9a Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Fri, 4 Nov 2022 17:13:01 +0000
+Subject: [PATCH 07/21] net: remove explicit phylink_generic_validate()
+ references
+
+Virtually all conventional network drivers are now converted to use
+phylink_generic_validate() - only DSA drivers and fman_memac remain,
+so lets remove the necessity for network drivers to explicitly set
+this member, and default to phylink_generic_validate() when unset.
+This is possible as .validate must currently be set.
+
+Any remaining instances that have not been addressed by this patch can
+be fixed up later.
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Reviewed-by: Vladimir Oltean <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/ethernet/altera/altera_tse_main.c            | 1 -
+ drivers/net/ethernet/atheros/ag71xx.c                    | 1 -
+ drivers/net/ethernet/cadence/macb_main.c                 | 1 -
+ drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c         | 1 -
+ drivers/net/ethernet/freescale/enetc/enetc_pf.c          | 1 -
+ drivers/net/ethernet/freescale/fman/fman_dtsec.c         | 1 -
+ drivers/net/ethernet/freescale/fman/fman_tgec.c          | 1 -
+ drivers/net/ethernet/marvell/mvneta.c                    | 1 -
+ drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c          | 1 -
+ drivers/net/ethernet/marvell/prestera/prestera_main.c    | 1 -
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c              | 1 -
+ drivers/net/ethernet/microchip/lan966x/lan966x_phylink.c | 1 -
+ drivers/net/ethernet/microchip/sparx5/sparx5_phylink.c   | 1 -
+ drivers/net/ethernet/mscc/ocelot_net.c                   | 1 -
+ drivers/net/ethernet/stmicro/stmmac/stmmac_main.c        | 1 -
+ drivers/net/ethernet/ti/am65-cpsw-nuss.c                 | 1 -
+ drivers/net/ethernet/xilinx/xilinx_axienet_main.c        | 1 -
+ drivers/net/phy/phylink.c                                | 5 ++++-
+ drivers/net/usb/asix_devices.c                           | 1 -
+ include/linux/phylink.h                                  | 5 +++++
+ 20 files changed, 9 insertions(+), 19 deletions(-)
+
+--- a/drivers/net/ethernet/altera/altera_tse_main.c
++++ b/drivers/net/ethernet/altera/altera_tse_main.c
+@@ -1096,7 +1096,6 @@ static struct phylink_pcs *alt_tse_selec
+ }
+ 
+ static const struct phylink_mac_ops alt_tse_phylink_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_an_restart = alt_tse_mac_an_restart,
+ 	.mac_config = alt_tse_mac_config,
+ 	.mac_link_down = alt_tse_mac_link_down,
+--- a/drivers/net/ethernet/atheros/ag71xx.c
++++ b/drivers/net/ethernet/atheros/ag71xx.c
+@@ -1086,7 +1086,6 @@ static void ag71xx_mac_link_up(struct ph
+ }
+ 
+ static const struct phylink_mac_ops ag71xx_phylink_mac_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_config = ag71xx_mac_config,
+ 	.mac_link_down = ag71xx_mac_link_down,
+ 	.mac_link_up = ag71xx_mac_link_up,
+--- a/drivers/net/ethernet/cadence/macb_main.c
++++ b/drivers/net/ethernet/cadence/macb_main.c
+@@ -752,7 +752,6 @@ static struct phylink_pcs *macb_mac_sele
+ }
+ 
+ static const struct phylink_mac_ops macb_phylink_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_select_pcs = macb_mac_select_pcs,
+ 	.mac_config = macb_mac_config,
+ 	.mac_link_down = macb_mac_link_down,
+--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
+@@ -235,7 +235,6 @@ static void dpaa2_mac_link_down(struct p
+ }
+ 
+ static const struct phylink_mac_ops dpaa2_mac_phylink_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_select_pcs = dpaa2_mac_select_pcs,
+ 	.mac_config = dpaa2_mac_config,
+ 	.mac_link_up = dpaa2_mac_link_up,
+--- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c
++++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
+@@ -1111,7 +1111,6 @@ static void enetc_pl_mac_link_down(struc
+ }
+ 
+ static const struct phylink_mac_ops enetc_mac_phylink_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_select_pcs = enetc_pl_mac_select_pcs,
+ 	.mac_config = enetc_pl_mac_config,
+ 	.mac_link_up = enetc_pl_mac_link_up,
+--- a/drivers/net/ethernet/freescale/fman/fman_dtsec.c
++++ b/drivers/net/ethernet/freescale/fman/fman_dtsec.c
+@@ -986,7 +986,6 @@ static void dtsec_link_down(struct phyli
+ }
+ 
+ static const struct phylink_mac_ops dtsec_mac_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_select_pcs = dtsec_select_pcs,
+ 	.mac_config = dtsec_mac_config,
+ 	.mac_link_up = dtsec_link_up,
+--- a/drivers/net/ethernet/freescale/fman/fman_tgec.c
++++ b/drivers/net/ethernet/freescale/fman/fman_tgec.c
+@@ -469,7 +469,6 @@ static void tgec_link_down(struct phylin
+ }
+ 
+ static const struct phylink_mac_ops tgec_mac_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_config = tgec_mac_config,
+ 	.mac_link_up = tgec_link_up,
+ 	.mac_link_down = tgec_link_down,
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -4228,7 +4228,6 @@ static void mvneta_mac_link_up(struct ph
+ }
+ 
+ static const struct phylink_mac_ops mvneta_phylink_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_select_pcs = mvneta_mac_select_pcs,
+ 	.mac_prepare = mvneta_mac_prepare,
+ 	.mac_config = mvneta_mac_config,
+--- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
++++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
+@@ -6633,7 +6633,6 @@ static void mvpp2_mac_link_down(struct p
+ }
+ 
+ static const struct phylink_mac_ops mvpp2_phylink_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_select_pcs = mvpp2_select_pcs,
+ 	.mac_prepare = mvpp2_mac_prepare,
+ 	.mac_config = mvpp2_mac_config,
+--- a/drivers/net/ethernet/marvell/prestera/prestera_main.c
++++ b/drivers/net/ethernet/marvell/prestera/prestera_main.c
+@@ -360,7 +360,6 @@ static void prestera_pcs_an_restart(stru
+ }
+ 
+ static const struct phylink_mac_ops prestera_mac_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_select_pcs = prestera_mac_select_pcs,
+ 	.mac_config = prestera_mac_config,
+ 	.mac_link_down = prestera_mac_link_down,
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -654,7 +654,6 @@ static void mtk_mac_link_up(struct phyli
+ }
+ 
+ static const struct phylink_mac_ops mtk_phylink_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_select_pcs = mtk_mac_select_pcs,
+ 	.mac_pcs_get_state = mtk_mac_pcs_get_state,
+ 	.mac_config = mtk_mac_config,
+--- a/drivers/net/ethernet/microchip/lan966x/lan966x_phylink.c
++++ b/drivers/net/ethernet/microchip/lan966x/lan966x_phylink.c
+@@ -125,7 +125,6 @@ static void lan966x_pcs_aneg_restart(str
+ }
+ 
+ const struct phylink_mac_ops lan966x_phylink_mac_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_select_pcs = lan966x_phylink_mac_select,
+ 	.mac_config = lan966x_phylink_mac_config,
+ 	.mac_prepare = lan966x_phylink_mac_prepare,
+--- a/drivers/net/ethernet/microchip/sparx5/sparx5_phylink.c
++++ b/drivers/net/ethernet/microchip/sparx5/sparx5_phylink.c
+@@ -138,7 +138,6 @@ const struct phylink_pcs_ops sparx5_phyl
+ };
+ 
+ const struct phylink_mac_ops sparx5_phylink_mac_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_select_pcs = sparx5_phylink_mac_select_pcs,
+ 	.mac_config = sparx5_phylink_mac_config,
+ 	.mac_link_down = sparx5_phylink_mac_link_down,
+--- a/drivers/net/ethernet/mscc/ocelot_net.c
++++ b/drivers/net/ethernet/mscc/ocelot_net.c
+@@ -1737,7 +1737,6 @@ static void vsc7514_phylink_mac_link_up(
+ }
+ 
+ static const struct phylink_mac_ops ocelot_phylink_ops = {
+-	.validate		= phylink_generic_validate,
+ 	.mac_config		= vsc7514_phylink_mac_config,
+ 	.mac_link_down		= vsc7514_phylink_mac_link_down,
+ 	.mac_link_up		= vsc7514_phylink_mac_link_up,
+--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+@@ -1090,7 +1090,6 @@ static void stmmac_mac_link_up(struct ph
+ }
+ 
+ static const struct phylink_mac_ops stmmac_phylink_mac_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_select_pcs = stmmac_mac_select_pcs,
+ 	.mac_config = stmmac_mac_config,
+ 	.mac_link_down = stmmac_mac_link_down,
+--- a/drivers/net/ethernet/ti/am65-cpsw-nuss.c
++++ b/drivers/net/ethernet/ti/am65-cpsw-nuss.c
+@@ -1493,7 +1493,6 @@ static void am65_cpsw_nuss_mac_link_up(s
+ }
+ 
+ static const struct phylink_mac_ops am65_cpsw_phylink_mac_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_config = am65_cpsw_nuss_mac_config,
+ 	.mac_link_down = am65_cpsw_nuss_mac_link_down,
+ 	.mac_link_up = am65_cpsw_nuss_mac_link_up,
+--- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
++++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
+@@ -1736,7 +1736,6 @@ static void axienet_mac_link_up(struct p
+ }
+ 
+ static const struct phylink_mac_ops axienet_phylink_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_select_pcs = axienet_mac_select_pcs,
+ 	.mac_config = axienet_mac_config,
+ 	.mac_link_down = axienet_mac_link_down,
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -651,7 +651,10 @@ static int phylink_validate_mac_and_pcs(
+ 	}
+ 
+ 	/* Then validate the link parameters with the MAC */
+-	pl->mac_ops->validate(pl->config, supported, state);
++	if (pl->mac_ops->validate)
++		pl->mac_ops->validate(pl->config, supported, state);
++	else
++		phylink_generic_validate(pl->config, supported, state);
+ 
+ 	return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
+ }
+--- a/drivers/net/usb/asix_devices.c
++++ b/drivers/net/usb/asix_devices.c
+@@ -787,7 +787,6 @@ static void ax88772_mac_link_up(struct p
+ }
+ 
+ static const struct phylink_mac_ops ax88772_phylink_mac_ops = {
+-	.validate = phylink_generic_validate,
+ 	.mac_config = ax88772_mac_config,
+ 	.mac_link_down = ax88772_mac_link_down,
+ 	.mac_link_up = ax88772_mac_link_up,
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -207,6 +207,11 @@ struct phylink_mac_ops {
+  *
+  * If the @state->interface mode is not supported, then the @supported
+  * mask must be cleared.
++ *
++ * This member is optional; if not set, the generic validator will be
++ * used making use of @config->mac_capabilities and
++ * @config->supported_interfaces to determine which link modes are
++ * supported.
+  */
+ void validate(struct phylink_config *config, unsigned long *supported,
+ 	      struct phylink_link_state *state);

+ 48 - 0
target/linux/generic/backport-6.6/715-08-net-sfp-make-sfp_bus_find_fwnode-take-a-const-fwnode.patch

@@ -0,0 +1,48 @@
+From a90ac762d345890b40d88a1385a34a2449c2d75e Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Fri, 24 Mar 2023 09:23:42 +0000
+Subject: [PATCH] net: sfp: make sfp_bus_find_fwnode() take a const fwnode
+
+sfp_bus_find_fwnode() does not write to the fwnode, so let's make it
+const.
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Reviewed-by: Simon Horman <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/sfp-bus.c | 2 +-
+ include/linux/sfp.h       | 5 +++--
+ 2 files changed, 4 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/sfp-bus.c
++++ b/drivers/net/phy/sfp-bus.c
+@@ -603,7 +603,7 @@ static void sfp_upstream_clear(struct sf
+  *	- %-ENOMEM if we failed to allocate the bus.
+  *	- an error from the upstream's connect_phy() method.
+  */
+-struct sfp_bus *sfp_bus_find_fwnode(struct fwnode_handle *fwnode)
++struct sfp_bus *sfp_bus_find_fwnode(const struct fwnode_handle *fwnode)
+ {
+ 	struct fwnode_reference_args ref;
+ 	struct sfp_bus *bus;
+--- a/include/linux/sfp.h
++++ b/include/linux/sfp.h
+@@ -548,7 +548,7 @@ int sfp_get_module_eeprom_by_page(struct
+ void sfp_upstream_start(struct sfp_bus *bus);
+ void sfp_upstream_stop(struct sfp_bus *bus);
+ void sfp_bus_put(struct sfp_bus *bus);
+-struct sfp_bus *sfp_bus_find_fwnode(struct fwnode_handle *fwnode);
++struct sfp_bus *sfp_bus_find_fwnode(const struct fwnode_handle *fwnode);
+ int sfp_bus_add_upstream(struct sfp_bus *bus, void *upstream,
+ 			 const struct sfp_upstream_ops *ops);
+ void sfp_bus_del_upstream(struct sfp_bus *bus);
+@@ -610,7 +610,8 @@ static inline void sfp_bus_put(struct sf
+ {
+ }
+ 
+-static inline struct sfp_bus *sfp_bus_find_fwnode(struct fwnode_handle *fwnode)
++static inline struct sfp_bus *
++sfp_bus_find_fwnode(const struct fwnode_handle *fwnode)
+ {
+ 	return NULL;
+ }

+ 31 - 0
target/linux/generic/backport-6.6/715-09-v6.4-net-pcs-lynx-don-t-print-an_enabled-in-pcs_get_state.patch

@@ -0,0 +1,31 @@
+From ecec0ebbc6381a5a375f1cf10c4858f24e91e2ef Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Wed, 15 Mar 2023 14:46:49 +0000
+Subject: [PATCH] net: pcs: lynx: don't print an_enabled in pcs_get_state()
+
+an_enabled will be going away, and in any case, pcs_get_state() should
+not be updating this member. Remove the print.
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Reviewed-by: Steen Hegelund <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/pcs/pcs-lynx.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/pcs/pcs-lynx.c
++++ b/drivers/net/pcs/pcs-lynx.c
+@@ -115,11 +115,11 @@ static void lynx_pcs_get_state(struct ph
+ 	}
+ 
+ 	dev_dbg(&lynx->mdio->dev,
+-		"mode=%s/%s/%s link=%u an_enabled=%u an_complete=%u\n",
++		"mode=%s/%s/%s link=%u an_complete=%u\n",
+ 		phy_modes(state->interface),
+ 		phy_speed_to_str(state->speed),
+ 		phy_duplex_to_str(state->duplex),
+-		state->link, state->an_enabled, state->an_complete);
++		state->link, state->an_complete);
+ }
+ 
+ static int lynx_pcs_config_giga(struct mdio_device *pcs, unsigned int mode,

+ 32 - 0
target/linux/generic/backport-6.6/715-10-v6.4-net-dpaa2-mac-use-Autoneg-bit-rather-than-an_enabled.patch

@@ -0,0 +1,32 @@
+From 99d0f3a1095f4c938b1665025c29411edafe8a01 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Tue, 21 Mar 2023 15:58:44 +0000
+Subject: [PATCH] net: dpaa2-mac: use Autoneg bit rather than an_enabled
+
+The Autoneg bit in the advertising bitmap and state->an_enabled are
+always identical. Thus, we will be removing state->an_enabled.
+
+Use the Autoneg bit in the advertising bitmap to indicate whether
+autonegotiation should be used, rather than using the an_enabled
+member which will be going away. This means we use the same condition
+as phylink_mii_c22_pcs_config().
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Reviewed-by: Simon Horman <[email protected]>
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
+@@ -158,7 +158,8 @@ static void dpaa2_mac_config(struct phyl
+ 	struct dpmac_link_state *dpmac_state = &mac->state;
+ 	int err;
+ 
+-	if (state->an_enabled)
++	if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++			      state->advertising))
+ 		dpmac_state->options |= DPMAC_LINK_OPT_AUTONEG;
+ 	else
+ 		dpmac_state->options &= ~DPMAC_LINK_OPT_AUTONEG;

+ 64 - 0
target/linux/generic/backport-6.6/715-11-v6.4-net-phylink-support-validated-pause-and-autoneg-in-f.patch

@@ -0,0 +1,64 @@
+From 471c40bde606ec0b1ce8c616f7998739c7a783a6 Mon Sep 17 00:00:00 2001
+From: Ivan Bornyakov <[email protected]>
+Date: Fri, 10 Feb 2023 18:46:27 +0300
+Subject: [PATCH 10/21] net: phylink: support validated pause and autoneg in
+ fixed-link
+
+In fixed-link setup phylink_parse_fixedlink() unconditionally sets
+Pause, Asym_Pause and Autoneg bits to "supported" bitmap, while MAC may
+not support these.
+
+This leads to ethtool reporting:
+
+ > Supported pause frame use: Symmetric Receive-only
+ > Supports auto-negotiation: Yes
+
+regardless of what is actually supported.
+
+Instead of unconditionally set Pause, Asym_Pause and Autoneg it is
+sensible to set them according to validated "supported" bitmap, i.e. the
+result of phylink_validate().
+
+Signed-off-by: Ivan Bornyakov <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/phylink.c | 17 ++++++++++++++---
+ 1 file changed, 14 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -709,6 +709,7 @@ static int phylink_parse_fixedlink(struc
+ 				   struct fwnode_handle *fwnode)
+ {
+ 	struct fwnode_handle *fixed_node;
++	bool pause, asym_pause, autoneg;
+ 	const struct phy_setting *s;
+ 	struct gpio_desc *desc;
+ 	u32 speed;
+@@ -781,13 +782,23 @@ static int phylink_parse_fixedlink(struc
+ 	linkmode_copy(pl->link_config.advertising, pl->supported);
+ 	phylink_validate(pl, pl->supported, &pl->link_config);
+ 
++	pause = phylink_test(pl->supported, Pause);
++	asym_pause = phylink_test(pl->supported, Asym_Pause);
++	autoneg = phylink_test(pl->supported, Autoneg);
+ 	s = phy_lookup_setting(pl->link_config.speed, pl->link_config.duplex,
+ 			       pl->supported, true);
+ 	linkmode_zero(pl->supported);
+ 	phylink_set(pl->supported, MII);
+-	phylink_set(pl->supported, Pause);
+-	phylink_set(pl->supported, Asym_Pause);
+-	phylink_set(pl->supported, Autoneg);
++
++	if (pause)
++		phylink_set(pl->supported, Pause);
++
++	if (asym_pause)
++		phylink_set(pl->supported, Asym_Pause);
++
++	if (autoneg)
++		phylink_set(pl->supported, Autoneg);
++
+ 	if (s) {
+ 		__set_bit(s->bit, pl->supported);
+ 		__set_bit(s->bit, pl->link_config.lp_advertising);

+ 177 - 0
target/linux/generic/backport-6.6/715-12-v6.4-net-phylink-remove-an_enabled.patch

@@ -0,0 +1,177 @@
+From 7211ffd70941933a7825a56cf480f07ee81c321c Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Tue, 21 Mar 2023 15:58:54 +0000
+Subject: [PATCH 11/21] net: phylink: remove an_enabled
+
+The Autoneg bit in the advertising bitmap and state->an_enabled are
+always identical. state->an_enabled is now no longer used by any
+drivers, so lets kill this duplication.
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Reviewed-by: Simon Horman <[email protected]>
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/phylink.c | 37 +++++++++++++++++--------------------
+ include/linux/phylink.h   |  2 --
+ 2 files changed, 17 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -841,7 +841,6 @@ static int phylink_parse_mode(struct phy
+ 		phylink_set(pl->supported, Autoneg);
+ 		phylink_set(pl->supported, Asym_Pause);
+ 		phylink_set(pl->supported, Pause);
+-		pl->link_config.an_enabled = true;
+ 		pl->cfg_link_an_mode = MLO_AN_INBAND;
+ 
+ 		switch (pl->link_config.interface) {
+@@ -944,9 +943,6 @@ static int phylink_parse_mode(struct phy
+ 				    "failed to validate link configuration for in-band status\n");
+ 			return -EINVAL;
+ 		}
+-
+-		/* Check if MAC/PCS also supports Autoneg. */
+-		pl->link_config.an_enabled = phylink_test(pl->supported, Autoneg);
+ 	}
+ 
+ 	return 0;
+@@ -956,7 +952,8 @@ static void phylink_apply_manual_flow(st
+ 				      struct phylink_link_state *state)
+ {
+ 	/* If autoneg is disabled, pause AN is also disabled */
+-	if (!state->an_enabled)
++	if (!linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++			       state->advertising))
+ 		state->pause &= ~MLO_PAUSE_AN;
+ 
+ 	/* Manual configuration of pause modes */
+@@ -996,21 +993,22 @@ static void phylink_mac_config(struct ph
+ 			       const struct phylink_link_state *state)
+ {
+ 	phylink_dbg(pl,
+-		    "%s: mode=%s/%s/%s/%s/%s adv=%*pb pause=%02x link=%u an=%u\n",
++		    "%s: mode=%s/%s/%s/%s/%s adv=%*pb pause=%02x link=%u\n",
+ 		    __func__, phylink_an_mode_str(pl->cur_link_an_mode),
+ 		    phy_modes(state->interface),
+ 		    phy_speed_to_str(state->speed),
+ 		    phy_duplex_to_str(state->duplex),
+ 		    phy_rate_matching_to_str(state->rate_matching),
+ 		    __ETHTOOL_LINK_MODE_MASK_NBITS, state->advertising,
+-		    state->pause, state->link, state->an_enabled);
++		    state->pause, state->link);
+ 
+ 	pl->mac_ops->mac_config(pl->config, pl->cur_link_an_mode, state);
+ }
+ 
+ static void phylink_mac_pcs_an_restart(struct phylink *pl)
+ {
+-	if (pl->link_config.an_enabled &&
++	if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++			      pl->link_config.advertising) &&
+ 	    phy_interface_mode_is_8023z(pl->link_config.interface) &&
+ 	    phylink_autoneg_inband(pl->cur_link_an_mode)) {
+ 		if (pl->pcs)
+@@ -1137,9 +1135,9 @@ static void phylink_mac_pcs_get_state(st
+ 	linkmode_copy(state->advertising, pl->link_config.advertising);
+ 	linkmode_zero(state->lp_advertising);
+ 	state->interface = pl->link_config.interface;
+-	state->an_enabled = pl->link_config.an_enabled;
+ 	state->rate_matching = pl->link_config.rate_matching;
+-	if (state->an_enabled) {
++	if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++			      state->advertising)) {
+ 		state->speed = SPEED_UNKNOWN;
+ 		state->duplex = DUPLEX_UNKNOWN;
+ 		state->pause = MLO_PAUSE_NONE;
+@@ -1531,7 +1529,6 @@ struct phylink *phylink_create(struct ph
+ 	pl->link_config.pause = MLO_PAUSE_AN;
+ 	pl->link_config.speed = SPEED_UNKNOWN;
+ 	pl->link_config.duplex = DUPLEX_UNKNOWN;
+-	pl->link_config.an_enabled = true;
+ 	pl->mac_ops = mac_ops;
+ 	__set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
+ 	timer_setup(&pl->link_poll, phylink_fixed_poll, 0);
+@@ -2155,8 +2152,9 @@ static void phylink_get_ksettings(const
+ 		kset->base.speed = state->speed;
+ 		kset->base.duplex = state->duplex;
+ 	}
+-	kset->base.autoneg = state->an_enabled ? AUTONEG_ENABLE :
+-				AUTONEG_DISABLE;
++	kset->base.autoneg = linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++					       state->advertising) ?
++				AUTONEG_ENABLE : AUTONEG_DISABLE;
+ }
+ 
+ /**
+@@ -2303,9 +2301,8 @@ int phylink_ethtool_ksettings_set(struct
+ 	/* We have ruled out the case with a PHY attached, and the
+ 	 * fixed-link cases.  All that is left are in-band links.
+ 	 */
+-	config.an_enabled = kset->base.autoneg == AUTONEG_ENABLE;
+ 	linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, config.advertising,
+-			 config.an_enabled);
++			 kset->base.autoneg == AUTONEG_ENABLE);
+ 
+ 	/* If this link is with an SFP, ensure that changes to advertised modes
+ 	 * also cause the associated interface to be selected such that the
+@@ -2339,13 +2336,14 @@ int phylink_ethtool_ksettings_set(struct
+ 	}
+ 
+ 	/* If autonegotiation is enabled, we must have an advertisement */
+-	if (config.an_enabled && phylink_is_empty_linkmode(config.advertising))
++	if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++			      config.advertising) &&
++	    phylink_is_empty_linkmode(config.advertising))
+ 		return -EINVAL;
+ 
+ 	mutex_lock(&pl->state_mutex);
+ 	pl->link_config.speed = config.speed;
+ 	pl->link_config.duplex = config.duplex;
+-	pl->link_config.an_enabled = config.an_enabled;
+ 
+ 	if (pl->link_config.interface != config.interface) {
+ 		/* The interface changed, e.g. 1000base-X <-> 2500base-X */
+@@ -2951,7 +2949,6 @@ static int phylink_sfp_config_phy(struct
+ 	config.speed = SPEED_UNKNOWN;
+ 	config.duplex = DUPLEX_UNKNOWN;
+ 	config.pause = MLO_PAUSE_AN;
+-	config.an_enabled = pl->link_config.an_enabled;
+ 
+ 	/* Ignore errors if we're expecting a PHY to attach later */
+ 	ret = phylink_validate(pl, support, &config);
+@@ -3020,7 +3017,6 @@ static int phylink_sfp_config_optical(st
+ 	config.speed = SPEED_UNKNOWN;
+ 	config.duplex = DUPLEX_UNKNOWN;
+ 	config.pause = MLO_PAUSE_AN;
+-	config.an_enabled = true;
+ 
+ 	/* For all the interfaces that are supported, reduce the sfp_support
+ 	 * mask to only those link modes that can be supported.
+@@ -3354,7 +3350,8 @@ void phylink_mii_c22_pcs_decode_state(st
+ 	/* If there is no link or autonegotiation is disabled, the LP advertisement
+ 	 * data is not meaningful, so don't go any further.
+ 	 */
+-	if (!state->link || !state->an_enabled)
++	if (!state->link || !linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++					       state->advertising))
+ 		return;
+ 
+ 	switch (state->interface) {
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -93,7 +93,6 @@ static inline bool phylink_autoneg_inban
+  *   the medium link mode (@speed and @duplex) and the speed/duplex of the phy
+  *   interface mode (@interface) are different.
+  * @link: true if the link is up.
+- * @an_enabled: true if autonegotiation is enabled/desired.
+  * @an_complete: true if autonegotiation has completed.
+  */
+ struct phylink_link_state {
+@@ -105,7 +104,6 @@ struct phylink_link_state {
+ 	int pause;
+ 	int rate_matching;
+ 	unsigned int link:1;
+-	unsigned int an_enabled:1;
+ 	unsigned int an_complete:1;
+ };
+ 

+ 88 - 0
target/linux/generic/backport-6.6/715-13-v6.5-net-phylink-constify-fwnode-arguments.patch

@@ -0,0 +1,88 @@
+From a3555d1f5c208f0a63eafee77381f68d304a0512 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Fri, 12 May 2023 17:58:37 +0100
+Subject: [PATCH 12/21] net: phylink: constify fwnode arguments
+
+Both phylink_create() and phylink_fwnode_phy_connect() do not modify
+the fwnode argument that they are passed, so lets constify these.
+
+Reviewed-by: Simon Horman <[email protected]>
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/phylink.c | 11 ++++++-----
+ include/linux/phylink.h   |  9 +++++----
+ 2 files changed, 11 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -706,7 +706,7 @@ static int phylink_validate(struct phyli
+ }
+ 
+ static int phylink_parse_fixedlink(struct phylink *pl,
+-				   struct fwnode_handle *fwnode)
++				   const struct fwnode_handle *fwnode)
+ {
+ 	struct fwnode_handle *fixed_node;
+ 	bool pause, asym_pause, autoneg;
+@@ -817,7 +817,8 @@ static int phylink_parse_fixedlink(struc
+ 	return 0;
+ }
+ 
+-static int phylink_parse_mode(struct phylink *pl, struct fwnode_handle *fwnode)
++static int phylink_parse_mode(struct phylink *pl,
++			      const struct fwnode_handle *fwnode)
+ {
+ 	struct fwnode_handle *dn;
+ 	const char *managed;
+@@ -1440,7 +1441,7 @@ static void phylink_fixed_poll(struct ti
+ static const struct sfp_upstream_ops sfp_phylink_ops;
+ 
+ static int phylink_register_sfp(struct phylink *pl,
+-				struct fwnode_handle *fwnode)
++				const struct fwnode_handle *fwnode)
+ {
+ 	struct sfp_bus *bus;
+ 	int ret;
+@@ -1479,7 +1480,7 @@ static int phylink_register_sfp(struct p
+  * must use IS_ERR() to check for errors from this function.
+  */
+ struct phylink *phylink_create(struct phylink_config *config,
+-			       struct fwnode_handle *fwnode,
++			       const struct fwnode_handle *fwnode,
+ 			       phy_interface_t iface,
+ 			       const struct phylink_mac_ops *mac_ops)
+ {
+@@ -1809,7 +1810,7 @@ EXPORT_SYMBOL_GPL(phylink_of_phy_connect
+  * Returns 0 on success or a negative errno.
+  */
+ int phylink_fwnode_phy_connect(struct phylink *pl,
+-			       struct fwnode_handle *fwnode,
++			       const struct fwnode_handle *fwnode,
+ 			       u32 flags)
+ {
+ 	struct fwnode_handle *phy_fwnode;
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -568,16 +568,17 @@ void phylink_generic_validate(struct phy
+ 			      unsigned long *supported,
+ 			      struct phylink_link_state *state);
+ 
+-struct phylink *phylink_create(struct phylink_config *, struct fwnode_handle *,
+-			       phy_interface_t iface,
+-			       const struct phylink_mac_ops *mac_ops);
++struct phylink *phylink_create(struct phylink_config *,
++			       const struct fwnode_handle *,
++			       phy_interface_t,
++			       const struct phylink_mac_ops *);
+ void phylink_destroy(struct phylink *);
+ bool phylink_expects_phy(struct phylink *pl);
+ 
+ int phylink_connect_phy(struct phylink *, struct phy_device *);
+ int phylink_of_phy_connect(struct phylink *, struct device_node *, u32 flags);
+ int phylink_fwnode_phy_connect(struct phylink *pl,
+-			       struct fwnode_handle *fwnode,
++			       const struct fwnode_handle *fwnode,
+ 			       u32 flags);
+ void phylink_disconnect_phy(struct phylink *);
+ 

+ 38 - 0
target/linux/generic/backport-6.6/715-14-v6.3-net-phy-constify-fwnode_get_phy_node-fwnode-argument.patch

@@ -0,0 +1,38 @@
+From 4a0faa02d419a6728abef0f1d8a32d8c35ef95e6 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Fri, 24 Mar 2023 09:23:53 +0000
+Subject: [PATCH] net: phy: constify fwnode_get_phy_node() fwnode argument
+
+fwnode_get_phy_node() does not motify the fwnode structure, so make
+the argument const,
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Reviewed-by: Simon Horman <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/phy_device.c | 2 +-
+ include/linux/phy.h          | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -3003,7 +3003,7 @@ EXPORT_SYMBOL_GPL(device_phy_find_device
+  * and "phy-device" are not supported in ACPI. DT supports all the three
+  * named references to the phy node.
+  */
+-struct fwnode_handle *fwnode_get_phy_node(struct fwnode_handle *fwnode)
++struct fwnode_handle *fwnode_get_phy_node(const struct fwnode_handle *fwnode)
+ {
+ 	struct fwnode_handle *phy_node;
+ 
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1473,7 +1473,7 @@ int fwnode_get_phy_id(struct fwnode_hand
+ struct mdio_device *fwnode_mdio_find_device(struct fwnode_handle *fwnode);
+ struct phy_device *fwnode_phy_find_device(struct fwnode_handle *phy_fwnode);
+ struct phy_device *device_phy_find_device(struct device *dev);
+-struct fwnode_handle *fwnode_get_phy_node(struct fwnode_handle *fwnode);
++struct fwnode_handle *fwnode_get_phy_node(const struct fwnode_handle *fwnode);
+ struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45);
+ int phy_device_register(struct phy_device *phy);
+ void phy_device_free(struct phy_device *phydev);

+ 44 - 0
target/linux/generic/backport-6.6/715-15-v6.4-net-phylink-fix-ksettings_set-ethtool-call.patch

@@ -0,0 +1,44 @@
+From cc73de0411f7d3cdd157564a78f7a39058420ff8 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Sat, 13 May 2023 22:03:45 +0100
+Subject: [PATCH 13/21] net: phylink: fix ksettings_set() ethtool call
+
+While testing a Fiberstore SFP-10G-T module (which uses 10GBASE-R with
+rate adaption) in a Clearfog platform (which can't do that) it was
+found that the PHYs advertisement was not limited according to the
+hosts capabilities when using ethtool to change it.
+
+Fix this by ensuring that we mask the advertisement with the computed
+support mask as the very first thing we do.
+
+Fixes: cbc1bb1e4689 ("net: phylink: simplify phy case for ksettings_set method")
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/phylink.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -2226,6 +2226,10 @@ int phylink_ethtool_ksettings_set(struct
+ 
+ 	ASSERT_RTNL();
+ 
++	/* Mask out unsupported advertisements */
++	linkmode_and(config.advertising, kset->link_modes.advertising,
++		     pl->supported);
++
+ 	if (pl->phydev) {
+ 		/* We can rely on phylib for this update; we also do not need
+ 		 * to update the pl->link_config settings:
+@@ -2250,10 +2254,6 @@ int phylink_ethtool_ksettings_set(struct
+ 
+ 	config = pl->link_config;
+ 
+-	/* Mask out unsupported advertisements */
+-	linkmode_and(config.advertising, kset->link_modes.advertising,
+-		     pl->supported);
+-
+ 	/* FIXME: should we reject autoneg if phy/mac does not support it? */
+ 	switch (kset->base.autoneg) {
+ 	case AUTONEG_DISABLE:

+ 149 - 0
target/linux/generic/backport-6.6/715-16-v6.5-net-sfp-add-support-for-setting-signalling-rate.patch

@@ -0,0 +1,149 @@
+From 0100d1c5789018ba77bf2f4fab3bd91ecece7b3b Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Wed, 17 May 2023 11:38:12 +0100
+Subject: [PATCH 14/21] net: sfp: add support for setting signalling rate
+
+Add support to the SFP layer to allow phylink to set the signalling
+rate for a SFP module. The rate given will be in units of kilo-baud
+(1000 baud).
+
+Reviewed-by: Simon Horman <[email protected]>
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/phylink.c | 24 ++++++++++++++++++++++++
+ drivers/net/phy/sfp-bus.c | 20 ++++++++++++++++++++
+ drivers/net/phy/sfp.c     |  5 +++++
+ drivers/net/phy/sfp.h     |  1 +
+ include/linux/sfp.h       |  6 ++++++
+ 5 files changed, 56 insertions(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -156,6 +156,23 @@ static const char *phylink_an_mode_str(u
+ 	return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
+ }
+ 
++static unsigned int phylink_interface_signal_rate(phy_interface_t interface)
++{
++	switch (interface) {
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_1000BASEX: /* 1.25Mbd */
++		return 1250;
++	case PHY_INTERFACE_MODE_2500BASEX: /* 3.125Mbd */
++		return 3125;
++	case PHY_INTERFACE_MODE_5GBASER: /* 5.15625Mbd */
++		return 5156;
++	case PHY_INTERFACE_MODE_10GBASER: /* 10.3125Mbd */
++		return 10313;
++	default:
++		return 0;
++	}
++}
++
+ /**
+  * phylink_interface_max_speed() - get the maximum speed of a phy interface
+  * @interface: phy interface mode defined by &typedef phy_interface_t
+@@ -1024,6 +1041,7 @@ static void phylink_major_config(struct
+ {
+ 	struct phylink_pcs *pcs = NULL;
+ 	bool pcs_changed = false;
++	unsigned int rate_kbd;
+ 	int err;
+ 
+ 	phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
+@@ -1083,6 +1101,12 @@ static void phylink_major_config(struct
+ 				    ERR_PTR(err));
+ 	}
+ 
++	if (pl->sfp_bus) {
++		rate_kbd = phylink_interface_signal_rate(state->interface);
++		if (rate_kbd)
++			sfp_upstream_set_signal_rate(pl->sfp_bus, rate_kbd);
++	}
++
+ 	phylink_pcs_poll_start(pl);
+ }
+ 
+--- a/drivers/net/phy/sfp-bus.c
++++ b/drivers/net/phy/sfp-bus.c
+@@ -586,6 +586,26 @@ static void sfp_upstream_clear(struct sf
+ }
+ 
+ /**
++ * sfp_upstream_set_signal_rate() - set data signalling rate
++ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
++ * @rate_kbd: signalling rate in units of 1000 baud
++ *
++ * Configure the rate select settings on the SFP module for the signalling
++ * rate (not the same as the data rate).
++ *
++ * Locks that may be held:
++ *  Phylink's state_mutex
++ *  rtnl lock
++ *  SFP's sm_mutex
++ */
++void sfp_upstream_set_signal_rate(struct sfp_bus *bus, unsigned int rate_kbd)
++{
++	if (bus->registered)
++		bus->socket_ops->set_signal_rate(bus->sfp, rate_kbd);
++}
++EXPORT_SYMBOL_GPL(sfp_upstream_set_signal_rate);
++
++/**
+  * sfp_bus_find_fwnode() - parse and locate the SFP bus from fwnode
+  * @fwnode: firmware node for the parent device (MAC or PHY)
+  *
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -2474,6 +2474,10 @@ static void sfp_stop(struct sfp *sfp)
+ 	sfp_sm_event(sfp, SFP_E_DEV_DOWN);
+ }
+ 
++static void sfp_set_signal_rate(struct sfp *sfp, unsigned int rate_kbd)
++{
++}
++
+ static int sfp_module_info(struct sfp *sfp, struct ethtool_modinfo *modinfo)
+ {
+ 	/* locking... and check module is present */
+@@ -2552,6 +2556,7 @@ static const struct sfp_socket_ops sfp_m
+ 	.detach = sfp_detach,
+ 	.start = sfp_start,
+ 	.stop = sfp_stop,
++	.set_signal_rate = sfp_set_signal_rate,
+ 	.module_info = sfp_module_info,
+ 	.module_eeprom = sfp_module_eeprom,
+ 	.module_eeprom_by_page = sfp_module_eeprom_by_page,
+--- a/drivers/net/phy/sfp.h
++++ b/drivers/net/phy/sfp.h
+@@ -19,6 +19,7 @@ struct sfp_socket_ops {
+ 	void (*detach)(struct sfp *sfp);
+ 	void (*start)(struct sfp *sfp);
+ 	void (*stop)(struct sfp *sfp);
++	void (*set_signal_rate)(struct sfp *sfp, unsigned int rate_kbd);
+ 	int (*module_info)(struct sfp *sfp, struct ethtool_modinfo *modinfo);
+ 	int (*module_eeprom)(struct sfp *sfp, struct ethtool_eeprom *ee,
+ 			     u8 *data);
+--- a/include/linux/sfp.h
++++ b/include/linux/sfp.h
+@@ -547,6 +547,7 @@ int sfp_get_module_eeprom_by_page(struct
+ 				  struct netlink_ext_ack *extack);
+ void sfp_upstream_start(struct sfp_bus *bus);
+ void sfp_upstream_stop(struct sfp_bus *bus);
++void sfp_upstream_set_signal_rate(struct sfp_bus *bus, unsigned int rate_kbd);
+ void sfp_bus_put(struct sfp_bus *bus);
+ struct sfp_bus *sfp_bus_find_fwnode(const struct fwnode_handle *fwnode);
+ int sfp_bus_add_upstream(struct sfp_bus *bus, void *upstream,
+@@ -606,6 +607,11 @@ static inline void sfp_upstream_stop(str
+ {
+ }
+ 
++static inline void sfp_upstream_set_signal_rate(struct sfp_bus *bus,
++						unsigned int rate_kbd)
++{
++}
++
+ static inline void sfp_bus_put(struct sfp_bus *bus)
+ {
+ }

+ 147 - 0
target/linux/generic/backport-6.6/715-17-v6.5-net-phy-add-helpers-for-comparing-phy-IDs.patch

@@ -0,0 +1,147 @@
+From b84acdb07222a701bfc6403b374249c86f97d18d Mon Sep 17 00:00:00 2001
+From: Russell King <[email protected]>
+Date: Fri, 19 May 2023 14:03:59 +0100
+Subject: [PATCH 15/21] net: phy: add helpers for comparing phy IDs
+
+There are several places which open code comparing PHY IDs. Provide a
+couple of helpers to assist with this, using a slightly simpler test
+than the original:
+
+- phy_id_compare() compares two arbitary PHY IDs and a mask of the
+  significant bits in the ID.
+- phydev_id_compare() compares the bound phydev with the specified
+  PHY ID, using the bound driver's mask.
+
+Signed-off-by: Russell King <[email protected]>
+Reviewed-by: Simon Horman <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: David S. Miller <[email protected]>
+---
+ drivers/net/phy/micrel.c     |  6 +++---
+ drivers/net/phy/phy_device.c | 16 +++++++---------
+ drivers/net/phy/phylink.c    |  4 ++--
+ include/linux/phy.h          | 28 ++++++++++++++++++++++++++++
+ 4 files changed, 40 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/phy/micrel.c
++++ b/drivers/net/phy/micrel.c
+@@ -620,7 +620,7 @@ static int ksz8051_ksz8795_match_phy_dev
+ {
+ 	int ret;
+ 
+-	if ((phydev->phy_id & MICREL_PHY_ID_MASK) != PHY_ID_KSZ8051)
++	if (!phy_id_compare(phydev->phy_id, PHY_ID_KSZ8051, MICREL_PHY_ID_MASK))
+ 		return 0;
+ 
+ 	ret = phy_read(phydev, MII_BMSR);
+@@ -1455,7 +1455,7 @@ static int ksz9x31_cable_test_fault_leng
+ 	 *
+ 	 * distance to fault = (VCT_DATA - 22) * 4 / cable propagation velocity
+ 	 */
+-	if ((phydev->phy_id & MICREL_PHY_ID_MASK) == PHY_ID_KSZ9131)
++	if (phydev_id_compare(phydev, PHY_ID_KSZ9131))
+ 		dt = clamp(dt - 22, 0, 255);
+ 
+ 	return (dt * 400) / 10;
+@@ -1887,7 +1887,7 @@ static __always_inline int ksz886x_cable
+ 	 */
+ 	dt = FIELD_GET(data_mask, status);
+ 
+-	if ((phydev->phy_id & MICREL_PHY_ID_MASK) == PHY_ID_LAN8814)
++	if (phydev_id_compare(phydev, PHY_ID_LAN8814))
+ 		return ((dt - 22) * 800) / 10;
+ 	else
+ 		return (dt * 400) / 10;
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -422,8 +422,7 @@ int phy_unregister_fixup(const char *bus
+ 		fixup = list_entry(pos, struct phy_fixup, list);
+ 
+ 		if ((!strcmp(fixup->bus_id, bus_id)) &&
+-		    ((fixup->phy_uid & phy_uid_mask) ==
+-		     (phy_uid & phy_uid_mask))) {
++		    phy_id_compare(fixup->phy_uid, phy_uid, phy_uid_mask)) {
+ 			list_del(&fixup->list);
+ 			kfree(fixup);
+ 			ret = 0;
+@@ -459,8 +458,8 @@ static int phy_needs_fixup(struct phy_de
+ 		if (strcmp(fixup->bus_id, PHY_ANY_ID) != 0)
+ 			return 0;
+ 
+-	if ((fixup->phy_uid & fixup->phy_uid_mask) !=
+-	    (phydev->phy_id & fixup->phy_uid_mask))
++	if (!phy_id_compare(phydev->phy_id, fixup->phy_uid,
++			    fixup->phy_uid_mask))
+ 		if (fixup->phy_uid != PHY_ANY_UID)
+ 			return 0;
+ 
+@@ -507,15 +506,14 @@ static int phy_bus_match(struct device *
+ 			if (phydev->c45_ids.device_ids[i] == 0xffffffff)
+ 				continue;
+ 
+-			if ((phydrv->phy_id & phydrv->phy_id_mask) ==
+-			    (phydev->c45_ids.device_ids[i] &
+-			     phydrv->phy_id_mask))
++			if (phy_id_compare(phydev->c45_ids.device_ids[i],
++					   phydrv->phy_id, phydrv->phy_id_mask))
+ 				return 1;
+ 		}
+ 		return 0;
+ 	} else {
+-		return (phydrv->phy_id & phydrv->phy_id_mask) ==
+-			(phydev->phy_id & phydrv->phy_id_mask);
++		return phy_id_compare(phydev->phy_id, phydrv->phy_id,
++				      phydrv->phy_id_mask);
+ 	}
+ }
+ 
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -3151,8 +3151,8 @@ static void phylink_sfp_link_up(void *up
+  */
+ static bool phylink_phy_no_inband(struct phy_device *phy)
+ {
+-	return phy->is_c45 &&
+-		(phy->c45_ids.device_ids[1] & 0xfffffff0) == 0xae025150;
++	return phy->is_c45 && phy_id_compare(phy->c45_ids.device_ids[1],
++					     0xae025150, 0xfffffff0);
+ }
+ 
+ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -993,6 +993,34 @@ struct phy_driver {
+ #define PHY_ID_MATCH_MODEL(id) .phy_id = (id), .phy_id_mask = GENMASK(31, 4)
+ #define PHY_ID_MATCH_VENDOR(id) .phy_id = (id), .phy_id_mask = GENMASK(31, 10)
+ 
++/**
++ * phy_id_compare - compare @id1 with @id2 taking account of @mask
++ * @id1: first PHY ID
++ * @id2: second PHY ID
++ * @mask: the PHY ID mask, set bits are significant in matching
++ *
++ * Return true if the bits from @id1 and @id2 specified by @mask match.
++ * This uses an equivalent test to (@id & @mask) == (@phy_id & @mask).
++ */
++static inline bool phy_id_compare(u32 id1, u32 id2, u32 mask)
++{
++	return !((id1 ^ id2) & mask);
++}
++
++/**
++ * phydev_id_compare - compare @id with the PHY's Clause 22 ID
++ * @phydev: the PHY device
++ * @id: the PHY ID to be matched
++ *
++ * Compare the @phydev clause 22 ID with the provided @id and return true or
++ * false depending whether it matches, using the bound driver mask. The
++ * @phydev must be bound to a driver.
++ */
++static inline bool phydev_id_compare(struct phy_device *phydev, u32 id)
++{
++	return phy_id_compare(id, phydev->phy_id, phydev->drv->phy_id_mask);
++}
++
+ /* A Structure for boards to register fixups with the PHY Lib */
+ struct phy_fixup {
+ 	struct list_head list;

+ 71 - 0
target/linux/generic/backport-6.6/715-18-v6.5-net-phylink-require-supported_interfaces-to-be-fille.patch

@@ -0,0 +1,71 @@
+From 441e1e44301fc5762a06737f8ec04bf1ce3fb039 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Sat, 20 May 2023 11:41:42 +0100
+Subject: [PATCH 16/21] net: phylink: require supported_interfaces to be filled
+
+We have been requiring the supported_interfaces bitmap to be filled in
+by MAC drivers that have a mac_select_pcs() method. Now that all MAC
+drivers fill in the supported_interfaces bitmap, it is time to enforce
+this. We have already required supported_interfaces to be set in order
+for optical SFPs to be configured in commit f81fa96d8a6c ("net: phylink:
+use phy_interface_t bitmaps for optical modules").
+
+Refuse phylink creation if supported_interfaces is empty, and remove
+code to deal with cases where this mask is empty.
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/phylink.c | 26 +++++++++++---------------
+ 1 file changed, 11 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -710,14 +710,11 @@ static int phylink_validate(struct phyli
+ {
+ 	const unsigned long *interfaces = pl->config->supported_interfaces;
+ 
+-	if (!phy_interface_empty(interfaces)) {
+-		if (state->interface == PHY_INTERFACE_MODE_NA)
+-			return phylink_validate_mask(pl, supported, state,
+-						     interfaces);
++	if (state->interface == PHY_INTERFACE_MODE_NA)
++		return phylink_validate_mask(pl, supported, state, interfaces);
+ 
+-		if (!test_bit(state->interface, interfaces))
+-			return -EINVAL;
+-	}
++	if (!test_bit(state->interface, interfaces))
++		return -EINVAL;
+ 
+ 	return phylink_validate_mac_and_pcs(pl, supported, state);
+ }
+@@ -1512,19 +1509,18 @@ struct phylink *phylink_create(struct ph
+ 	struct phylink *pl;
+ 	int ret;
+ 
+-	if (mac_ops->mac_select_pcs &&
+-	    mac_ops->mac_select_pcs(config, PHY_INTERFACE_MODE_NA) !=
+-	      ERR_PTR(-EOPNOTSUPP))
+-		using_mac_select_pcs = true;
+-
+ 	/* Validate the supplied configuration */
+-	if (using_mac_select_pcs &&
+-	    phy_interface_empty(config->supported_interfaces)) {
++	if (phy_interface_empty(config->supported_interfaces)) {
+ 		dev_err(config->dev,
+-			"phylink: error: empty supported_interfaces but mac_select_pcs() method present\n");
++			"phylink: error: empty supported_interfaces\n");
+ 		return ERR_PTR(-EINVAL);
+ 	}
+ 
++	if (mac_ops->mac_select_pcs &&
++	    mac_ops->mac_select_pcs(config, PHY_INTERFACE_MODE_NA) !=
++	      ERR_PTR(-EOPNOTSUPP))
++		using_mac_select_pcs = true;
++
+ 	pl = kzalloc(sizeof(*pl), GFP_KERNEL);
+ 	if (!pl)
+ 		return ERR_PTR(-ENOMEM);

+ 64 - 0
target/linux/generic/backport-6.6/715-19-v6.5-net-phylink-remove-duplicated-linkmode-pause-resolut.patch

@@ -0,0 +1,64 @@
+From 4b624a39f2ab523ca6a6ad9448fab1deb7b101e2 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Tue, 23 May 2023 11:15:53 +0100
+Subject: [PATCH 17/21] net: phylink: remove duplicated linkmode pause
+ resolution
+
+Phylink had two chunks of code virtually the same for resolving the
+negotiated pause modes. Factor this down to one function.
+
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/phylink.c | 15 ++++-----------
+ 1 file changed, 4 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -976,11 +976,10 @@ static void phylink_apply_manual_flow(st
+ 		state->pause = pl->link_config.pause;
+ }
+ 
+-static void phylink_resolve_flow(struct phylink_link_state *state)
++static void phylink_resolve_an_pause(struct phylink_link_state *state)
+ {
+ 	bool tx_pause, rx_pause;
+ 
+-	state->pause = MLO_PAUSE_NONE;
+ 	if (state->duplex == DUPLEX_FULL) {
+ 		linkmode_resolve_pause(state->advertising,
+ 				       state->lp_advertising,
+@@ -1192,7 +1191,8 @@ static void phylink_get_fixed_state(stru
+ 	else if (pl->link_gpio)
+ 		state->link = !!gpiod_get_value_cansleep(pl->link_gpio);
+ 
+-	phylink_resolve_flow(state);
++	state->pause = MLO_PAUSE_NONE;
++	phylink_resolve_an_pause(state);
+ }
+ 
+ static void phylink_mac_initial_config(struct phylink *pl, bool force_restart)
+@@ -3215,7 +3215,6 @@ static const struct sfp_upstream_ops sfp
+ static void phylink_decode_c37_word(struct phylink_link_state *state,
+ 				    uint16_t config_reg, int speed)
+ {
+-	bool tx_pause, rx_pause;
+ 	int fd_bit;
+ 
+ 	if (speed == SPEED_2500)
+@@ -3234,13 +3233,7 @@ static void phylink_decode_c37_word(stru
+ 		state->link = false;
+ 	}
+ 
+-	linkmode_resolve_pause(state->advertising, state->lp_advertising,
+-			       &tx_pause, &rx_pause);
+-
+-	if (tx_pause)
+-		state->pause |= MLO_PAUSE_TX;
+-	if (rx_pause)
+-		state->pause |= MLO_PAUSE_RX;
++	phylink_resolve_an_pause(state);
+ }
+ 
+ static void phylink_decode_sgmii_word(struct phylink_link_state *state,

+ 76 - 0
target/linux/generic/backport-6.6/715-20-v6.5-net-phylink-add-function-to-resolve-clause-73-negoti.patch

@@ -0,0 +1,76 @@
+From aa8b6bd2b1f235b262bd27f317a0516f196c2c6a Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Tue, 23 May 2023 11:15:58 +0100
+Subject: [PATCH 18/21] net: phylink: add function to resolve clause 73
+ negotiation
+
+Add a function to resolve clause 73 negotiation according to the
+priority resolution function described in clause 73.3.6.
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Reviewed-by: Andrew Lunn <[email protected]>
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/phylink.c | 39 +++++++++++++++++++++++++++++++++++++++
+ include/linux/phylink.h   |  2 ++
+ 2 files changed, 41 insertions(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -3212,6 +3212,45 @@ static const struct sfp_upstream_ops sfp
+ 
+ /* Helpers for MAC drivers */
+ 
++static struct {
++	int bit;
++	int speed;
++} phylink_c73_priority_resolution[] = {
++	{ ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, SPEED_100000 },
++	{ ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, SPEED_100000 },
++	/* 100GBASE-KP4 and 100GBASE-CR10 not supported */
++	{ ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, SPEED_40000 },
++	{ ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, SPEED_40000 },
++	{ ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, SPEED_10000 },
++	{ ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, SPEED_10000 },
++	/* 5GBASE-KR not supported */
++	{ ETHTOOL_LINK_MODE_2500baseX_Full_BIT, SPEED_2500 },
++	{ ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, SPEED_1000 },
++};
++
++void phylink_resolve_c73(struct phylink_link_state *state)
++{
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(phylink_c73_priority_resolution); i++) {
++		int bit = phylink_c73_priority_resolution[i].bit;
++		if (linkmode_test_bit(bit, state->advertising) &&
++		    linkmode_test_bit(bit, state->lp_advertising))
++			break;
++	}
++
++	if (i < ARRAY_SIZE(phylink_c73_priority_resolution)) {
++		state->speed = phylink_c73_priority_resolution[i].speed;
++		state->duplex = DUPLEX_FULL;
++	} else {
++		/* negotiation failure */
++		state->link = false;
++	}
++
++	phylink_resolve_an_pause(state);
++}
++EXPORT_SYMBOL_GPL(phylink_resolve_c73);
++
+ static void phylink_decode_c37_word(struct phylink_link_state *state,
+ 				    uint16_t config_reg, int speed)
+ {
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -656,6 +656,8 @@ int phylink_mii_c22_pcs_config(struct md
+ 			       const unsigned long *advertising);
+ void phylink_mii_c22_pcs_an_restart(struct mdio_device *pcs);
+ 
++void phylink_resolve_c73(struct phylink_link_state *state);
++
+ void phylink_mii_c45_pcs_get_state(struct mdio_device *pcs,
+ 				   struct phylink_link_state *state);
+ 

+ 100 - 0
target/linux/generic/backport-6.6/715-21-v6.5-net-phylink-provide-phylink_pcs_config-and-phylink_p.patch

@@ -0,0 +1,100 @@
+From 796d709363135a6bd6a8ccc07b509c939e5b855f Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Tue, 23 May 2023 16:31:50 +0100
+Subject: [PATCH 19/21] net: phylink: provide phylink_pcs_config() and
+ phylink_pcs_link_up()
+
+Add two helper functions for calling PCS methods. phylink_pcs_config()
+allows us to handle PCS configuration specifics in one location, rather
+than the two call sites. phylink_pcs_link_up() gives us consistency.
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/phylink.c | 53 ++++++++++++++++++++++++---------------
+ 1 file changed, 33 insertions(+), 20 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -991,6 +991,25 @@ static void phylink_resolve_an_pause(str
+ 	}
+ }
+ 
++static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
++			      const struct phylink_link_state *state,
++			      bool permit_pause_to_mac)
++{
++	if (!pcs)
++		return 0;
++
++	return pcs->ops->pcs_config(pcs, mode, state->interface,
++				    state->advertising, permit_pause_to_mac);
++}
++
++static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
++				phy_interface_t interface, int speed,
++				int duplex)
++{
++	if (pcs && pcs->ops->pcs_link_up)
++		pcs->ops->pcs_link_up(pcs, mode, interface, speed, duplex);
++}
++
+ static void phylink_pcs_poll_stop(struct phylink *pl)
+ {
+ 	if (pl->cfg_link_an_mode == MLO_AN_INBAND)
+@@ -1074,18 +1093,15 @@ static void phylink_major_config(struct
+ 
+ 	phylink_mac_config(pl, state);
+ 
+-	if (pl->pcs) {
+-		err = pl->pcs->ops->pcs_config(pl->pcs, pl->cur_link_an_mode,
+-					       state->interface,
+-					       state->advertising,
+-					       !!(pl->link_config.pause &
+-						  MLO_PAUSE_AN));
+-		if (err < 0)
+-			phylink_err(pl, "pcs_config failed: %pe\n",
+-				    ERR_PTR(err));
+-		if (err > 0)
+-			restart = true;
+-	}
++	err = phylink_pcs_config(pl->pcs, pl->cur_link_an_mode, state,
++				 !!(pl->link_config.pause &
++				    MLO_PAUSE_AN));
++	if (err < 0)
++		phylink_err(pl, "pcs_config failed: %pe\n",
++			    ERR_PTR(err));
++	else if (err > 0)
++		restart = true;
++
+ 	if (restart)
+ 		phylink_mac_pcs_an_restart(pl);
+ 
+@@ -1136,11 +1152,9 @@ static int phylink_change_inband_advert(
+ 	 * restart negotiation if the pcs_config() helper indicates that
+ 	 * the programmed advertisement has changed.
+ 	 */
+-	ret = pl->pcs->ops->pcs_config(pl->pcs, pl->cur_link_an_mode,
+-				       pl->link_config.interface,
+-				       pl->link_config.advertising,
+-				       !!(pl->link_config.pause &
+-					  MLO_PAUSE_AN));
++	ret = phylink_pcs_config(pl->pcs, pl->cur_link_an_mode,
++				 &pl->link_config,
++				 !!(pl->link_config.pause & MLO_PAUSE_AN));
+ 	if (ret < 0)
+ 		return ret;
+ 
+@@ -1272,9 +1286,8 @@ static void phylink_link_up(struct phyli
+ 
+ 	pl->cur_interface = link_state.interface;
+ 
+-	if (pl->pcs && pl->pcs->ops->pcs_link_up)
+-		pl->pcs->ops->pcs_link_up(pl->pcs, pl->cur_link_an_mode,
+-					  pl->cur_interface, speed, duplex);
++	phylink_pcs_link_up(pl->pcs, pl->cur_link_an_mode, pl->cur_interface,
++			    speed, duplex);
+ 
+ 	pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->cur_link_an_mode,
+ 				 pl->cur_interface, speed, duplex,

+ 55 - 0
target/linux/generic/backport-6.6/715-23-v6.4-net-phylink-actually-fix-ksettings_set-ethtool-call.patch

@@ -0,0 +1,55 @@
+From 11933aa76865621d8e82553c8f3bc07796a5aaa2 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Thu, 1 Jun 2023 10:12:06 +0100
+Subject: [PATCH 20/21] net: phylink: actually fix ksettings_set() ethtool call
+
+Raju Lakkaraju reported that the below commit caused a regression
+with Lan743x drivers and a 2.5G SFP. Sadly, this is because the commit
+was utterly wrong. Let's fix this properly by not moving the
+linkmode_and(), but instead copying the link ksettings and then
+modifying the advertising mask before passing the modified link
+ksettings to phylib.
+
+Fixes: df0acdc59b09 ("net: phylink: fix ksettings_set() ethtool call")
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/phylink.c | 15 ++++++++++-----
+ 1 file changed, 10 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -2259,11 +2259,13 @@ int phylink_ethtool_ksettings_set(struct
+ 
+ 	ASSERT_RTNL();
+ 
+-	/* Mask out unsupported advertisements */
+-	linkmode_and(config.advertising, kset->link_modes.advertising,
+-		     pl->supported);
+-
+ 	if (pl->phydev) {
++		struct ethtool_link_ksettings phy_kset = *kset;
++
++		linkmode_and(phy_kset.link_modes.advertising,
++			     phy_kset.link_modes.advertising,
++			     pl->supported);
++
+ 		/* We can rely on phylib for this update; we also do not need
+ 		 * to update the pl->link_config settings:
+ 		 * - the configuration returned via ksettings_get() will come
+@@ -2282,10 +2284,13 @@ int phylink_ethtool_ksettings_set(struct
+ 		 *   the presence of a PHY, this should not be changed as that
+ 		 *   should be determined from the media side advertisement.
+ 		 */
+-		return phy_ethtool_ksettings_set(pl->phydev, kset);
++		return phy_ethtool_ksettings_set(pl->phydev, &phy_kset);
+ 	}
+ 
+ 	config = pl->link_config;
++	/* Mask out unsupported advertisements */
++	linkmode_and(config.advertising, kset->link_modes.advertising,
++		     pl->supported);
+ 
+ 	/* FIXME: should we reject autoneg if phy/mac does not support it? */
+ 	switch (kset->base.autoneg) {

+ 324 - 0
target/linux/generic/backport-6.6/715-24-v6.5-net-phylink-add-PCS-negotiation-mode.patch

@@ -0,0 +1,324 @@
+From 79b07c3e9c4a2272927be8848c26b372516e1958 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Fri, 16 Jun 2023 13:06:22 +0100
+Subject: [PATCH 21/21] net: phylink: add PCS negotiation mode
+
+PCS have to work out whether they should enable PCS negotiation by
+looking at the "mode" and "interface" arguments, and the Autoneg bit
+in the advertising mask.
+
+This leads to some complex logic, so lets pull that out into phylink
+and instead pass a "neg_mode" argument to the PCS configuration and
+link up methods, instead of the "mode" argument.
+
+In order to transition drivers, add a "neg_mode" flag to the phylink
+PCS structure to PCS can indicate whether they want to be passed the
+neg_mode or the old mode argument.
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/phylink.c |  45 +++++++++++++----
+ include/linux/phylink.h   | 104 +++++++++++++++++++++++++++++++++++---
+ 2 files changed, 132 insertions(+), 17 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -71,6 +71,7 @@ struct phylink {
+ 	struct mutex state_mutex;
+ 	struct phylink_link_state phy_state;
+ 	struct work_struct resolve;
++	unsigned int pcs_neg_mode;
+ 
+ 	bool mac_link_dropped;
+ 	bool using_mac_select_pcs;
+@@ -991,23 +992,23 @@ static void phylink_resolve_an_pause(str
+ 	}
+ }
+ 
+-static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
++static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ 			      const struct phylink_link_state *state,
+ 			      bool permit_pause_to_mac)
+ {
+ 	if (!pcs)
+ 		return 0;
+ 
+-	return pcs->ops->pcs_config(pcs, mode, state->interface,
++	return pcs->ops->pcs_config(pcs, neg_mode, state->interface,
+ 				    state->advertising, permit_pause_to_mac);
+ }
+ 
+-static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
++static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
+ 				phy_interface_t interface, int speed,
+ 				int duplex)
+ {
+ 	if (pcs && pcs->ops->pcs_link_up)
+-		pcs->ops->pcs_link_up(pcs, mode, interface, speed, duplex);
++		pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex);
+ }
+ 
+ static void phylink_pcs_poll_stop(struct phylink *pl)
+@@ -1057,10 +1058,15 @@ static void phylink_major_config(struct
+ 	struct phylink_pcs *pcs = NULL;
+ 	bool pcs_changed = false;
+ 	unsigned int rate_kbd;
++	unsigned int neg_mode;
+ 	int err;
+ 
+ 	phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
+ 
++	pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
++						state->interface,
++						state->advertising);
++
+ 	if (pl->using_mac_select_pcs) {
+ 		pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+ 		if (IS_ERR(pcs)) {
+@@ -1093,9 +1099,12 @@ static void phylink_major_config(struct
+ 
+ 	phylink_mac_config(pl, state);
+ 
+-	err = phylink_pcs_config(pl->pcs, pl->cur_link_an_mode, state,
+-				 !!(pl->link_config.pause &
+-				    MLO_PAUSE_AN));
++	neg_mode = pl->cur_link_an_mode;
++	if (pl->pcs && pl->pcs->neg_mode)
++		neg_mode = pl->pcs_neg_mode;
++
++	err = phylink_pcs_config(pl->pcs, neg_mode, state,
++				 !!(pl->link_config.pause & MLO_PAUSE_AN));
+ 	if (err < 0)
+ 		phylink_err(pl, "pcs_config failed: %pe\n",
+ 			    ERR_PTR(err));
+@@ -1130,6 +1139,7 @@ static void phylink_major_config(struct
+  */
+ static int phylink_change_inband_advert(struct phylink *pl)
+ {
++	unsigned int neg_mode;
+ 	int ret;
+ 
+ 	if (test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state))
+@@ -1148,12 +1158,20 @@ static int phylink_change_inband_advert(
+ 		    __ETHTOOL_LINK_MODE_MASK_NBITS, pl->link_config.advertising,
+ 		    pl->link_config.pause);
+ 
++	/* Recompute the PCS neg mode */
++	pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
++					pl->link_config.interface,
++					pl->link_config.advertising);
++
++	neg_mode = pl->cur_link_an_mode;
++	if (pl->pcs->neg_mode)
++		neg_mode = pl->pcs_neg_mode;
++
+ 	/* Modern PCS-based method; update the advert at the PCS, and
+ 	 * restart negotiation if the pcs_config() helper indicates that
+ 	 * the programmed advertisement has changed.
+ 	 */
+-	ret = phylink_pcs_config(pl->pcs, pl->cur_link_an_mode,
+-				 &pl->link_config,
++	ret = phylink_pcs_config(pl->pcs, neg_mode, &pl->link_config,
+ 				 !!(pl->link_config.pause & MLO_PAUSE_AN));
+ 	if (ret < 0)
+ 		return ret;
+@@ -1256,6 +1274,7 @@ static void phylink_link_up(struct phyli
+ 			    struct phylink_link_state link_state)
+ {
+ 	struct net_device *ndev = pl->netdev;
++	unsigned int neg_mode;
+ 	int speed, duplex;
+ 	bool rx_pause;
+ 
+@@ -1286,8 +1305,12 @@ static void phylink_link_up(struct phyli
+ 
+ 	pl->cur_interface = link_state.interface;
+ 
+-	phylink_pcs_link_up(pl->pcs, pl->cur_link_an_mode, pl->cur_interface,
+-			    speed, duplex);
++	neg_mode = pl->cur_link_an_mode;
++	if (pl->pcs && pl->pcs->neg_mode)
++		neg_mode = pl->pcs_neg_mode;
++
++	phylink_pcs_link_up(pl->pcs, neg_mode, pl->cur_interface, speed,
++			    duplex);
+ 
+ 	pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->cur_link_an_mode,
+ 				 pl->cur_interface, speed, duplex,
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -21,6 +21,24 @@ enum {
+ 	MLO_AN_FIXED,	/* Fixed-link mode */
+ 	MLO_AN_INBAND,	/* In-band protocol */
+ 
++	/* PCS "negotiation" mode.
++	 *  PHYLINK_PCS_NEG_NONE - protocol has no inband capability
++	 *  PHYLINK_PCS_NEG_OUTBAND - some out of band or fixed link setting
++	 *  PHYLINK_PCS_NEG_INBAND_DISABLED - inband mode disabled, e.g.
++	 *				      1000base-X with autoneg off
++	 *  PHYLINK_PCS_NEG_INBAND_ENABLED - inband mode enabled
++	 * Additionally, this can be tested using bitmasks:
++	 *  PHYLINK_PCS_NEG_INBAND - inband mode selected
++	 *  PHYLINK_PCS_NEG_ENABLED - negotiation mode enabled
++	 */
++	PHYLINK_PCS_NEG_NONE = 0,
++	PHYLINK_PCS_NEG_ENABLED = BIT(4),
++	PHYLINK_PCS_NEG_OUTBAND = BIT(5),
++	PHYLINK_PCS_NEG_INBAND = BIT(6),
++	PHYLINK_PCS_NEG_INBAND_DISABLED = PHYLINK_PCS_NEG_INBAND,
++	PHYLINK_PCS_NEG_INBAND_ENABLED = PHYLINK_PCS_NEG_INBAND |
++					 PHYLINK_PCS_NEG_ENABLED,
++
+ 	/* MAC_SYM_PAUSE and MAC_ASYM_PAUSE are used when configuring our
+ 	 * autonegotiation advertisement. They correspond to the PAUSE and
+ 	 * ASM_DIR bits defined by 802.3, respectively.
+@@ -80,6 +98,70 @@ static inline bool phylink_autoneg_inban
+ }
+ 
+ /**
++ * phylink_pcs_neg_mode() - helper to determine PCS inband mode
++ * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
++ * @interface: interface mode to be used
++ * @advertising: adertisement ethtool link mode mask
++ *
++ * Determines the negotiation mode to be used by the PCS, and returns
++ * one of:
++ * %PHYLINK_PCS_NEG_NONE: interface mode does not support inband
++ * %PHYLINK_PCS_NEG_OUTBAND: an out of band mode (e.g. reading the PHY)
++ *   will be used.
++ * %PHYLINK_PCS_NEG_INBAND_DISABLED: inband mode selected but autoneg disabled
++ * %PHYLINK_PCS_NEG_INBAND_ENABLED: inband mode selected and autoneg enabled
++ *
++ * Note: this is for cases where the PCS itself is involved in negotiation
++ * (e.g. Clause 37, SGMII and similar) not Clause 73.
++ */
++static inline unsigned int phylink_pcs_neg_mode(unsigned int mode,
++						phy_interface_t interface,
++						const unsigned long *advertising)
++{
++	unsigned int neg_mode;
++
++	switch (interface) {
++	case PHY_INTERFACE_MODE_SGMII:
++	case PHY_INTERFACE_MODE_QSGMII:
++	case PHY_INTERFACE_MODE_QUSGMII:
++	case PHY_INTERFACE_MODE_USXGMII:
++		/* These protocols are designed for use with a PHY which
++		 * communicates its negotiation result back to the MAC via
++		 * inband communication. Note: there exist PHYs that run
++		 * with SGMII but do not send the inband data.
++		 */
++		if (!phylink_autoneg_inband(mode))
++			neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++		else
++			neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++		break;
++
++	case PHY_INTERFACE_MODE_1000BASEX:
++	case PHY_INTERFACE_MODE_2500BASEX:
++		/* 1000base-X is designed for use media-side for Fibre
++		 * connections, and thus the Autoneg bit needs to be
++		 * taken into account. We also do this for 2500base-X
++		 * as well, but drivers may not support this, so may
++		 * need to override this.
++		 */
++		if (!phylink_autoneg_inband(mode))
++			neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++		else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++					   advertising))
++			neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++		else
++			neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
++		break;
++
++	default:
++		neg_mode = PHYLINK_PCS_NEG_NONE;
++		break;
++	}
++
++	return neg_mode;
++}
++
++/**
+  * struct phylink_link_state - link state structure
+  * @advertising: ethtool bitmask containing advertised link modes
+  * @lp_advertising: ethtool bitmask containing link partner advertised link
+@@ -436,6 +518,7 @@ struct phylink_pcs_ops;
+ /**
+  * struct phylink_pcs - PHYLINK PCS instance
+  * @ops: a pointer to the &struct phylink_pcs_ops structure
++ * @neg_mode: provide PCS neg mode via "mode" argument
+  * @poll: poll the PCS for link changes
+  *
+  * This structure is designed to be embedded within the PCS private data,
+@@ -443,6 +526,7 @@ struct phylink_pcs_ops;
+  */
+ struct phylink_pcs {
+ 	const struct phylink_pcs_ops *ops;
++	bool neg_mode;
+ 	bool poll;
+ };
+ 
+@@ -460,12 +544,12 @@ struct phylink_pcs_ops {
+ 			    const struct phylink_link_state *state);
+ 	void (*pcs_get_state)(struct phylink_pcs *pcs,
+ 			      struct phylink_link_state *state);
+-	int (*pcs_config)(struct phylink_pcs *pcs, unsigned int mode,
++	int (*pcs_config)(struct phylink_pcs *pcs, unsigned int neg_mode,
+ 			  phy_interface_t interface,
+ 			  const unsigned long *advertising,
+ 			  bool permit_pause_to_mac);
+ 	void (*pcs_an_restart)(struct phylink_pcs *pcs);
+-	void (*pcs_link_up)(struct phylink_pcs *pcs, unsigned int mode,
++	void (*pcs_link_up)(struct phylink_pcs *pcs, unsigned int neg_mode,
+ 			    phy_interface_t interface, int speed, int duplex);
+ };
+ 
+@@ -508,7 +592,7 @@ void pcs_get_state(struct phylink_pcs *p
+ /**
+  * pcs_config() - Configure the PCS mode and advertisement
+  * @pcs: a pointer to a &struct phylink_pcs.
+- * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
++ * @neg_mode: link negotiation mode (see below)
+  * @interface: interface mode to be used
+  * @advertising: adertisement ethtool link mode mask
+  * @permit_pause_to_mac: permit forwarding pause resolution to MAC
+@@ -526,8 +610,12 @@ void pcs_get_state(struct phylink_pcs *p
+  * For 1000BASE-X, the advertisement should be programmed into the PCS.
+  *
+  * For most 10GBASE-R, there is no advertisement.
++ *
++ * The %neg_mode argument should be tested via the phylink_mode_*() family of
++ * functions, or for PCS that set pcs->neg_mode true, should be tested
++ * against the %PHYLINK_PCS_NEG_* definitions.
+  */
+-int pcs_config(struct phylink_pcs *pcs, unsigned int mode,
++int pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ 	       phy_interface_t interface, const unsigned long *advertising,
+ 	       bool permit_pause_to_mac);
+ 
+@@ -543,7 +631,7 @@ void pcs_an_restart(struct phylink_pcs *
+ /**
+  * pcs_link_up() - program the PCS for the resolved link configuration
+  * @pcs: a pointer to a &struct phylink_pcs.
+- * @mode: link autonegotiation mode
++ * @neg_mode: link negotiation mode (see below)
+  * @interface: link &typedef phy_interface_t mode
+  * @speed: link speed
+  * @duplex: link duplex
+@@ -552,8 +640,12 @@ void pcs_an_restart(struct phylink_pcs *
+  * the resolved link parameters. For example, a PCS operating in SGMII
+  * mode without in-band AN needs to be manually configured for the link
+  * and duplex setting. Otherwise, this should be a no-op.
++ *
++ * The %mode argument should be tested via the phylink_mode_*() family of
++ * functions, or for PCS that set pcs->neg_mode true, should be tested
++ * against the %PHYLINK_PCS_NEG_* definitions.
+  */
+-void pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
++void pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
+ 		 phy_interface_t interface, int speed, int duplex);
+ #endif
+ 

+ 45 - 0
target/linux/generic/backport-6.6/715-25-v6.5-net-phylink-convert-phylink_mii_c22_pcs_config-to-ne.patch

@@ -0,0 +1,45 @@
+From cdb08aa0473730315dbc088d5394e59622314034 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Fri, 16 Jun 2023 13:06:27 +0100
+Subject: [PATCH 1/2] net: phylink: convert phylink_mii_c22_pcs_config() to
+ neg_mode
+
+Use phylink_pcs_neg_mode() for phylink_mii_c22_pcs_config(). This
+results in no functional change.
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/phy/phylink.c | 9 ++++-----
+ 1 file changed, 4 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -3558,6 +3558,7 @@ int phylink_mii_c22_pcs_config(struct md
+ 			       phy_interface_t interface,
+ 			       const unsigned long *advertising)
+ {
++	unsigned int neg_mode;
+ 	bool changed = 0;
+ 	u16 bmcr;
+ 	int ret, adv;
+@@ -3571,15 +3572,13 @@ int phylink_mii_c22_pcs_config(struct md
+ 		changed = ret;
+ 	}
+ 
+-	/* Ensure ISOLATE bit is disabled */
+-	if (mode == MLO_AN_INBAND &&
+-	    (interface == PHY_INTERFACE_MODE_SGMII ||
+-	     interface == PHY_INTERFACE_MODE_QSGMII ||
+-	     linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, advertising)))
++	neg_mode = phylink_pcs_neg_mode(mode, interface, advertising);
++	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
+ 		bmcr = BMCR_ANENABLE;
+ 	else
+ 		bmcr = 0;
+ 
++	/* Configure the inband state. Ensure ISOLATE bit is disabled */
+ 	ret = mdiodev_modify(pcs, MII_BMCR, BMCR_ANENABLE | BMCR_ISOLATE, bmcr);
+ 	if (ret < 0)
+ 		return ret;

+ 187 - 0
target/linux/generic/backport-6.6/715-26-v6.5-net-phylink-pass-neg_mode-into-phylink_mii_c22_pcs_c.patch

@@ -0,0 +1,187 @@
+From febf2aaf05641f3258cc30e072aff65cffc7c82c Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Fri, 16 Jun 2023 13:06:32 +0100
+Subject: [PATCH 2/2] net: phylink: pass neg_mode into
+ phylink_mii_c22_pcs_config()
+
+Convert fman_dtsec, xilinx_axienet and pcs-lynx to pass the neg_mode
+into phylink_mii_c22_pcs_config(). Where appropriate, drivers are
+updated to have neg_mode passed into their pcs_config() and
+pcs_link_up() functions. For other drivers, we just hoist the call
+to phylink_pcs_neg_mode() to their pcs_config() method out of
+phylink_mii_c22_pcs_config().
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ .../net/ethernet/freescale/fman/fman_dtsec.c   |  7 ++++---
+ .../net/ethernet/xilinx/xilinx_axienet_main.c  |  6 ++++--
+ drivers/net/pcs/pcs-lynx.c                     | 18 ++++++++++++------
+ drivers/net/phy/phylink.c                      |  9 ++++-----
+ include/linux/phylink.h                        |  5 +++--
+ 5 files changed, 27 insertions(+), 18 deletions(-)
+
+--- a/drivers/net/ethernet/freescale/fman/fman_dtsec.c
++++ b/drivers/net/ethernet/freescale/fman/fman_dtsec.c
+@@ -763,15 +763,15 @@ static void dtsec_pcs_get_state(struct p
+ 	phylink_mii_c22_pcs_get_state(dtsec->tbidev, state);
+ }
+ 
+-static int dtsec_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
++static int dtsec_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ 			    phy_interface_t interface,
+ 			    const unsigned long *advertising,
+ 			    bool permit_pause_to_mac)
+ {
+ 	struct fman_mac *dtsec = pcs_to_dtsec(pcs);
+ 
+-	return phylink_mii_c22_pcs_config(dtsec->tbidev, mode, interface,
+-					  advertising);
++	return phylink_mii_c22_pcs_config(dtsec->tbidev, interface,
++					  advertising, neg_mode);
+ }
+ 
+ static void dtsec_pcs_an_restart(struct phylink_pcs *pcs)
+@@ -1447,6 +1447,7 @@ int dtsec_initialization(struct mac_devi
+ 		goto _return_fm_mac_free;
+ 	}
+ 	dtsec->pcs.ops = &dtsec_pcs_ops;
++	dtsec->pcs.neg_mode = true;
+ 	dtsec->pcs.poll = true;
+ 
+ 	supported = mac_dev->phylink_config.supported_interfaces;
+--- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
++++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
+@@ -1631,7 +1631,7 @@ static void axienet_pcs_an_restart(struc
+ 	phylink_mii_c22_pcs_an_restart(pcs_phy);
+ }
+ 
+-static int axienet_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
++static int axienet_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ 			      phy_interface_t interface,
+ 			      const unsigned long *advertising,
+ 			      bool permit_pause_to_mac)
+@@ -1653,7 +1653,8 @@ static int axienet_pcs_config(struct phy
+ 		}
+ 	}
+ 
+-	ret = phylink_mii_c22_pcs_config(pcs_phy, mode, interface, advertising);
++	ret = phylink_mii_c22_pcs_config(pcs_phy, interface, advertising,
++					 neg_mode);
+ 	if (ret < 0)
+ 		netdev_warn(ndev, "Failed to configure PCS: %d\n", ret);
+ 
+@@ -2129,6 +2130,7 @@ static int axienet_probe(struct platform
+ 		}
+ 		of_node_put(np);
+ 		lp->pcs.ops = &axienet_pcs_ops;
++		lp->pcs.neg_mode = true;
+ 		lp->pcs.poll = true;
+ 	}
+ 
+--- a/drivers/net/pcs/pcs-lynx.c
++++ b/drivers/net/pcs/pcs-lynx.c
+@@ -122,9 +122,10 @@ static void lynx_pcs_get_state(struct ph
+ 		state->link, state->an_complete);
+ }
+ 
+-static int lynx_pcs_config_giga(struct mdio_device *pcs, unsigned int mode,
++static int lynx_pcs_config_giga(struct mdio_device *pcs,
+ 				phy_interface_t interface,
+-				const unsigned long *advertising)
++				const unsigned long *advertising,
++				unsigned int neg_mode)
+ {
+ 	u32 link_timer;
+ 	u16 if_mode;
+@@ -137,8 +138,9 @@ static int lynx_pcs_config_giga(struct m
+ 
+ 		if_mode = 0;
+ 	} else {
++		/* SGMII and QSGMII */
+ 		if_mode = IF_MODE_SGMII_EN;
+-		if (mode == MLO_AN_INBAND) {
++		if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
+ 			if_mode |= IF_MODE_USE_SGMII_AN;
+ 
+ 			/* Adjust link timer for SGMII */
+@@ -154,7 +156,8 @@ static int lynx_pcs_config_giga(struct m
+ 	if (err)
+ 		return err;
+ 
+-	return phylink_mii_c22_pcs_config(pcs, mode, interface, advertising);
++	return phylink_mii_c22_pcs_config(pcs, interface, advertising,
++					  neg_mode);
+ }
+ 
+ static int lynx_pcs_config_usxgmii(struct mdio_device *pcs, unsigned int mode,
+@@ -181,13 +184,16 @@ static int lynx_pcs_config(struct phylin
+ 			   bool permit)
+ {
+ 	struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
++	unsigned int neg_mode;
++
++	neg_mode = phylink_pcs_neg_mode(mode, ifmode, advertising);
+ 
+ 	switch (ifmode) {
+ 	case PHY_INTERFACE_MODE_1000BASEX:
+ 	case PHY_INTERFACE_MODE_SGMII:
+ 	case PHY_INTERFACE_MODE_QSGMII:
+-		return lynx_pcs_config_giga(lynx->mdio, mode, ifmode,
+-					    advertising);
++		return lynx_pcs_config_giga(lynx->mdio, ifmode, advertising,
++					    neg_mode);
+ 	case PHY_INTERFACE_MODE_2500BASEX:
+ 		if (phylink_autoneg_inband(mode)) {
+ 			dev_err(&lynx->mdio->dev,
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -3545,20 +3545,20 @@ EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_en
+ /**
+  * phylink_mii_c22_pcs_config() - configure clause 22 PCS
+  * @pcs: a pointer to a &struct mdio_device.
+- * @mode: link autonegotiation mode
+  * @interface: the PHY interface mode being configured
+  * @advertising: the ethtool advertisement mask
++ * @neg_mode: PCS negotiation mode
+  *
+  * Configure a Clause 22 PCS PHY with the appropriate negotiation
+  * parameters for the @mode, @interface and @advertising parameters.
+  * Returns negative error number on failure, zero if the advertisement
+  * has not changed, or positive if there is a change.
+  */
+-int phylink_mii_c22_pcs_config(struct mdio_device *pcs, unsigned int mode,
++int phylink_mii_c22_pcs_config(struct mdio_device *pcs,
+ 			       phy_interface_t interface,
+-			       const unsigned long *advertising)
++			       const unsigned long *advertising,
++			       unsigned int neg_mode)
+ {
+-	unsigned int neg_mode;
+ 	bool changed = 0;
+ 	u16 bmcr;
+ 	int ret, adv;
+@@ -3572,7 +3572,6 @@ int phylink_mii_c22_pcs_config(struct md
+ 		changed = ret;
+ 	}
+ 
+-	neg_mode = phylink_pcs_neg_mode(mode, interface, advertising);
+ 	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
+ 		bmcr = BMCR_ANENABLE;
+ 	else
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -743,9 +743,10 @@ void phylink_mii_c22_pcs_get_state(struc
+ 				   struct phylink_link_state *state);
+ int phylink_mii_c22_pcs_encode_advertisement(phy_interface_t interface,
+ 					     const unsigned long *advertising);
+-int phylink_mii_c22_pcs_config(struct mdio_device *pcs, unsigned int mode,
++int phylink_mii_c22_pcs_config(struct mdio_device *pcs,
+ 			       phy_interface_t interface,
+-			       const unsigned long *advertising);
++			       const unsigned long *advertising,
++			       unsigned int neg_mode);
+ void phylink_mii_c22_pcs_an_restart(struct mdio_device *pcs);
+ 
+ void phylink_resolve_c73(struct phylink_link_state *state);

+ 101 - 0
target/linux/generic/backport-6.6/715-27-v6.5-net-pcs-lynxi-update-PCS-driver-to-use-neg_mode.patch

@@ -0,0 +1,101 @@
+From 3b2de56a146f34e3f70a84cc3a1897064e445d16 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <[email protected]>
+Date: Fri, 16 Jun 2023 13:06:43 +0100
+Subject: [PATCH] net: pcs: lynxi: update PCS driver to use neg_mode
+
+Update the Lynxi PCS driver to use neg_mode rather than the mode
+argument. This ensures that the link_up() method will always program
+the speed and duplex when negotiation is disabled.
+
+Signed-off-by: Russell King (Oracle) <[email protected]>
+Link: https://lore.kernel.org/r/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/pcs/pcs-mtk-lynxi.c | 39 ++++++++++++++-------------------
+ 1 file changed, 16 insertions(+), 23 deletions(-)
+
+--- a/drivers/net/pcs/pcs-mtk-lynxi.c
++++ b/drivers/net/pcs/pcs-mtk-lynxi.c
+@@ -102,13 +102,13 @@ static void mtk_pcs_lynxi_get_state(stru
+ 					 FIELD_GET(SGMII_LPA, adv));
+ }
+ 
+-static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int mode,
++static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ 				phy_interface_t interface,
+ 				const unsigned long *advertising,
+ 				bool permit_pause_to_mac)
+ {
+ 	struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
+-	bool mode_changed = false, changed, use_an;
++	bool mode_changed = false, changed;
+ 	unsigned int rgc3, sgm_mode, bmcr;
+ 	int advertise, link_timer;
+ 
+@@ -121,30 +121,22 @@ static int mtk_pcs_lynxi_config(struct p
+ 	 * we assume that fixes it's speed at bitrate = line rate (in
+ 	 * other words, 1000Mbps or 2500Mbps).
+ 	 */
+-	if (interface == PHY_INTERFACE_MODE_SGMII) {
++	if (interface == PHY_INTERFACE_MODE_SGMII)
+ 		sgm_mode = SGMII_IF_MODE_SGMII;
+-		if (phylink_autoneg_inband(mode)) {
+-			sgm_mode |= SGMII_REMOTE_FAULT_DIS |
+-				    SGMII_SPEED_DUPLEX_AN;
+-			use_an = true;
+-		} else {
+-			use_an = false;
+-		}
+-	} else if (phylink_autoneg_inband(mode)) {
+-		/* 1000base-X or 2500base-X autoneg */
+-		sgm_mode = SGMII_REMOTE_FAULT_DIS;
+-		use_an = linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+-					   advertising);
+-	} else {
++	else
+ 		/* 1000base-X or 2500base-X without autoneg */
+ 		sgm_mode = 0;
+-		use_an = false;
+-	}
+ 
+-	if (use_an)
++	if (neg_mode & PHYLINK_PCS_NEG_INBAND)
++		sgm_mode |= SGMII_REMOTE_FAULT_DIS;
++
++	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
++		if (interface == PHY_INTERFACE_MODE_SGMII)
++			sgm_mode |= SGMII_SPEED_DUPLEX_AN;
+ 		bmcr = BMCR_ANENABLE;
+-	else
++	} else {
+ 		bmcr = 0;
++	}
+ 
+ 	if (mpcs->interface != interface) {
+ 		link_timer = phylink_get_link_timer_ns(interface);
+@@ -216,14 +208,15 @@ static void mtk_pcs_lynxi_restart_an(str
+ 	regmap_set_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1, BMCR_ANRESTART);
+ }
+ 
+-static void mtk_pcs_lynxi_link_up(struct phylink_pcs *pcs, unsigned int mode,
++static void mtk_pcs_lynxi_link_up(struct phylink_pcs *pcs,
++				  unsigned int neg_mode,
+ 				  phy_interface_t interface, int speed,
+ 				  int duplex)
+ {
+ 	struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
+ 	unsigned int sgm_mode;
+ 
+-	if (!phylink_autoneg_inband(mode)) {
++	if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) {
+ 		/* Force the speed and duplex setting */
+ 		if (speed == SPEED_10)
+ 			sgm_mode = SGMII_SPEED_10;
+@@ -286,6 +279,7 @@ struct phylink_pcs *mtk_pcs_lynxi_create
+ 	mpcs->regmap = regmap;
+ 	mpcs->flags = flags;
+ 	mpcs->pcs.ops = &mtk_pcs_lynxi_ops;
++	mpcs->pcs.neg_mode = true;
+ 	mpcs->pcs.poll = true;
+ 	mpcs->interface = PHY_INTERFACE_MODE_NA;
+ 

部分文件因为文件数量过多而无法显示