generic: 6.1: backport QCA807x PHY patches
authorChristian Marangi <ansuelsmth@gmail.com>
Sat, 10 Feb 2024 18:27:33 +0000 (19:27 +0100)
committerChristian Marangi <ansuelsmth@gmail.com>
Sun, 11 Feb 2024 20:08:26 +0000 (21:08 +0100)
Backport QCA807x PHY patches merged upstream that introduce the new
concept of PHY package.

Also add in generic config the new Kconfig CONFIG_QCA807X_PHY.

All affected patch automatically refreshed with make
target/linux/refresh.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
22 files changed:
target/linux/bcm27xx/patches-6.1/950-0286-phy-broadcom-split-out-the-BCM54213PE-from-the-BCM54.patch
target/linux/bcm27xx/patches-6.1/950-0427-Populate-phy-driver-block-for-BCM54213PE.patch
target/linux/generic/backport-6.1/716-v6.9-02-net-phy-add-support-for-scanning-PHY-in-PHY-packages.patch [new file with mode: 0644]
target/linux/generic/backport-6.1/716-v6.9-03-net-phy-add-devm-of_phy_package_join-helper.patch [new file with mode: 0644]
target/linux/generic/backport-6.1/716-v6.9-04-net-phy-qcom-move-more-function-to-shared-library.patch [new file with mode: 0644]
target/linux/generic/backport-6.1/716-v6.9-06-net-phy-provide-whether-link-has-changed-in-c37_read.patch [new file with mode: 0644]
target/linux/generic/backport-6.1/716-v6.9-07-net-phy-qcom-add-support-for-QCA807x-PHY-Family.patch [new file with mode: 0644]
target/linux/generic/backport-6.1/716-v6.9-08-net-phy-qcom-move-common-qca808x-LED-define-to-share.patch [new file with mode: 0644]
target/linux/generic/backport-6.1/716-v6.9-09-net-phy-qcom-generalize-some-qca808x-LED-functions.patch [new file with mode: 0644]
target/linux/generic/backport-6.1/716-v6.9-10-net-phy-qca807x-add-support-for-configurable-LED.patch [new file with mode: 0644]
target/linux/generic/backport-6.1/801-v6.4-05-net-phy-Add-a-binding-for-PHY-LEDs.patch
target/linux/generic/backport-6.1/801-v6.4-06-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch
target/linux/generic/backport-6.1/801-v6.4-08-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch
target/linux/generic/backport-6.1/820-v6.4-net-phy-fix-circular-LEDS_CLASS-dependencies.patch
target/linux/generic/backport-6.1/821-v6.4-net-phy-Fix-reading-LED-reg-property.patch
target/linux/generic/backport-6.1/822-v6.4-net-phy-Manual-remove-LEDs-to-ensure-correct-orderin.patch
target/linux/generic/backport-6.1/826-v6.6-02-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch
target/linux/generic/backport-6.1/835-v6.9-net-phy-add-support-for-PHY-LEDs-polarity-modes.patch
target/linux/generic/config-6.1
target/linux/generic/pending-6.1/703-phy-add-detach-callback-to-struct-phy_driver.patch
target/linux/ramips/patches-6.1/720-Revert-net-phy-simplify-phy_link_change-arguments.patch
target/linux/ramips/patches-6.1/721-NET-no-auto-carrier-off-support.patch

index ec4cd0993e311afe347262ffd7e0faef723e79d0..55c881286e1c02979f15071bcca248dfc59721bc 100644 (file)
@@ -26,7 +26,7 @@ Signed-off-by: Jonathan Bell <jonathan@raspberrypi.org>
                return;
  
        val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3);
-@@ -905,7 +906,7 @@ static struct phy_driver broadcom_driver
+@@ -906,7 +907,7 @@ static struct phy_driver broadcom_driver
        .link_change_notify     = bcm54xx_link_change_notify,
  }, {
        .phy_id         = PHY_ID_BCM54210E,
@@ -35,7 +35,7 @@ Signed-off-by: Jonathan Bell <jonathan@raspberrypi.org>
        .name           = "Broadcom BCM54210E",
        /* PHY_GBIT_FEATURES */
        .get_sset_count = bcm_phy_get_sset_count,
-@@ -919,6 +920,13 @@ static struct phy_driver broadcom_driver
+@@ -920,6 +921,13 @@ static struct phy_driver broadcom_driver
        .suspend        = bcm54xx_suspend,
        .resume         = bcm54xx_resume,
  }, {
@@ -49,7 +49,7 @@ Signed-off-by: Jonathan Bell <jonathan@raspberrypi.org>
        .phy_id         = PHY_ID_BCM5461,
        .phy_id_mask    = 0xfffffff0,
        .name           = "Broadcom BCM5461",
-@@ -1155,7 +1163,8 @@ module_phy_driver(broadcom_drivers);
+@@ -1156,7 +1164,8 @@ module_phy_driver(broadcom_drivers);
  static struct mdio_device_id __maybe_unused broadcom_tbl[] = {
        { PHY_ID_BCM5411, 0xfffffff0 },
        { PHY_ID_BCM5421, 0xfffffff0 },
index 18e654071c32c182c651421f5e207afc92f2ca3d..cdfc996abf5fd3cfd8e49d22012ca9736df2af76 100644 (file)
@@ -16,7 +16,7 @@ Signed-off-by: Jonathan Lemon <jonathan.lemon@gmail.com>
 
 --- a/drivers/net/phy/broadcom.c
 +++ b/drivers/net/phy/broadcom.c
-@@ -932,8 +932,14 @@ static struct phy_driver broadcom_driver
+@@ -933,8 +933,14 @@ static struct phy_driver broadcom_driver
        .phy_id_mask    = 0xffffffff,
        .name           = "Broadcom BCM54213PE",
        /* PHY_GBIT_FEATURES */
diff --git a/target/linux/generic/backport-6.1/716-v6.9-02-net-phy-add-support-for-scanning-PHY-in-PHY-packages.patch b/target/linux/generic/backport-6.1/716-v6.9-02-net-phy-add-support-for-scanning-PHY-in-PHY-packages.patch
new file mode 100644 (file)
index 0000000..b355031
--- /dev/null
@@ -0,0 +1,211 @@
+From 385ef48f468696d6d172eb367656a3466fa0408d Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:05 +0100
+Subject: [PATCH 02/10] net: phy: add support for scanning PHY in PHY packages
+ nodes
+
+Add support for scanning PHY in PHY package nodes. PHY packages nodes
+are just container for actual PHY on the MDIO bus.
+
+Their PHY address defined in the PHY package node are absolute and
+reflect the address on the MDIO bus.
+
+mdio_bus.c and of_mdio.c is updated to now support and parse also
+PHY package subnode by checking if the node name match
+"ethernet-phy-package".
+
+As PHY package reg is mandatory and each PHY in the PHY package must
+have a reg, every invalid PHY Package node is ignored and will be
+skipped by the autoscan fallback.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/mdio/of_mdio.c | 79 +++++++++++++++++++++++++++-----------
+ drivers/net/phy/mdio_bus.c | 44 +++++++++++++++++----
+ 2 files changed, 92 insertions(+), 31 deletions(-)
+
+--- a/drivers/net/mdio/of_mdio.c
++++ b/drivers/net/mdio/of_mdio.c
+@@ -138,6 +138,53 @@ bool of_mdiobus_child_is_phy(struct devi
+ }
+ EXPORT_SYMBOL(of_mdiobus_child_is_phy);
++static int __of_mdiobus_parse_phys(struct mii_bus *mdio, struct device_node *np,
++                                 bool *scanphys)
++{
++      struct device_node *child;
++      int addr, rc = 0;
++
++      /* Loop over the child nodes and register a phy_device for each phy */
++      for_each_available_child_of_node(np, child) {
++              if (of_node_name_eq(child, "ethernet-phy-package")) {
++                      /* Ignore invalid ethernet-phy-package node */
++                      if (!of_find_property(child, "reg", NULL))
++                              continue;
++
++                      rc = __of_mdiobus_parse_phys(mdio, child, NULL);
++                      if (rc && rc != -ENODEV)
++                              goto exit;
++
++                      continue;
++              }
++
++              addr = of_mdio_parse_addr(&mdio->dev, child);
++              if (addr < 0) {
++                      /* Skip scanning for invalid ethernet-phy-package node */
++                      if (scanphys)
++                              *scanphys = true;
++                      continue;
++              }
++
++              if (of_mdiobus_child_is_phy(child))
++                      rc = of_mdiobus_register_phy(mdio, child, addr);
++              else
++                      rc = of_mdiobus_register_device(mdio, child, addr);
++
++              if (rc == -ENODEV)
++                      dev_err(&mdio->dev,
++                              "MDIO device at address %d is missing.\n",
++                              addr);
++              else if (rc)
++                      goto exit;
++      }
++
++      return 0;
++exit:
++      of_node_put(child);
++      return rc;
++}
++
+ /**
+  * __of_mdiobus_register - Register mii_bus and create PHYs from the device tree
+  * @mdio: pointer to mii_bus structure
+@@ -179,33 +226,18 @@ int __of_mdiobus_register(struct mii_bus
+               return rc;
+       /* Loop over the child nodes and register a phy_device for each phy */
+-      for_each_available_child_of_node(np, child) {
+-              addr = of_mdio_parse_addr(&mdio->dev, child);
+-              if (addr < 0) {
+-                      scanphys = true;
+-                      continue;
+-              }
+-
+-              if (of_mdiobus_child_is_phy(child))
+-                      rc = of_mdiobus_register_phy(mdio, child, addr);
+-              else
+-                      rc = of_mdiobus_register_device(mdio, child, addr);
+-
+-              if (rc == -ENODEV)
+-                      dev_err(&mdio->dev,
+-                              "MDIO device at address %d is missing.\n",
+-                              addr);
+-              else if (rc)
+-                      goto unregister;
+-      }
++      rc = __of_mdiobus_parse_phys(mdio, np, &scanphys);
++      if (rc)
++              goto unregister;
+       if (!scanphys)
+               return 0;
+       /* auto scan for PHYs with empty reg property */
+       for_each_available_child_of_node(np, child) {
+-              /* Skip PHYs with reg property set */
+-              if (of_find_property(child, "reg", NULL))
++              /* Skip PHYs with reg property set or ethernet-phy-package node */
++              if (of_find_property(child, "reg", NULL) ||
++                  of_node_name_eq(child, "ethernet-phy-package"))
+                       continue;
+               for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
+@@ -226,15 +258,16 @@ int __of_mdiobus_register(struct mii_bus
+                               if (!rc)
+                                       break;
+                               if (rc != -ENODEV)
+-                                      goto unregister;
++                                      goto put_unregister;
+                       }
+               }
+       }
+       return 0;
+-unregister:
++put_unregister:
+       of_node_put(child);
++unregister:
+       mdiobus_unregister(mdio);
+       return rc;
+ }
+--- a/drivers/net/phy/mdio_bus.c
++++ b/drivers/net/phy/mdio_bus.c
+@@ -448,19 +448,34 @@ EXPORT_SYMBOL(of_mdio_find_bus);
+  * found, set the of_node pointer for the mdio device. This allows
+  * auto-probed phy devices to be supplied with information passed in
+  * via DT.
++ * If a PHY package is found, PHY is searched also there.
+  */
+-static void of_mdiobus_link_mdiodev(struct mii_bus *bus,
+-                                  struct mdio_device *mdiodev)
++static int of_mdiobus_find_phy(struct device *dev, struct mdio_device *mdiodev,
++                             struct device_node *np)
+ {
+-      struct device *dev = &mdiodev->dev;
+       struct device_node *child;
+-      if (dev->of_node || !bus->dev.of_node)
+-              return;
+-
+-      for_each_available_child_of_node(bus->dev.of_node, child) {
++      for_each_available_child_of_node(np, child) {
+               int addr;
++              if (of_node_name_eq(child, "ethernet-phy-package")) {
++                      /* Validate PHY package reg presence */
++                      if (!of_find_property(child, "reg", NULL)) {
++                              of_node_put(child);
++                              return -EINVAL;
++                      }
++
++                      if (!of_mdiobus_find_phy(dev, mdiodev, child)) {
++                              /* The refcount for the PHY package will be
++                               * incremented later when PHY join the Package.
++                               */
++                              of_node_put(child);
++                              return 0;
++                      }
++
++                      continue;
++              }
++
+               addr = of_mdio_parse_addr(dev, child);
+               if (addr < 0)
+                       continue;
+@@ -470,9 +485,22 @@ static void of_mdiobus_link_mdiodev(stru
+                       /* The refcount on "child" is passed to the mdio
+                        * device. Do _not_ use of_node_put(child) here.
+                        */
+-                      return;
++                      return 0;
+               }
+       }
++
++      return -ENODEV;
++}
++
++static void of_mdiobus_link_mdiodev(struct mii_bus *bus,
++                                  struct mdio_device *mdiodev)
++{
++      struct device *dev = &mdiodev->dev;
++
++      if (dev->of_node || !bus->dev.of_node)
++              return;
++
++      of_mdiobus_find_phy(dev, mdiodev, bus->dev.of_node);
+ }
+ #else /* !IS_ENABLED(CONFIG_OF_MDIO) */
+ static inline void of_mdiobus_link_mdiodev(struct mii_bus *mdio,
diff --git a/target/linux/generic/backport-6.1/716-v6.9-03-net-phy-add-devm-of_phy_package_join-helper.patch b/target/linux/generic/backport-6.1/716-v6.9-03-net-phy-add-devm-of_phy_package_join-helper.patch
new file mode 100644 (file)
index 0000000..2e38269
--- /dev/null
@@ -0,0 +1,185 @@
+From 471e8fd3afcef5a9f9089f0bd21965ad9ba35c91 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:06 +0100
+Subject: [PATCH 03/10] net: phy: add devm/of_phy_package_join helper
+
+Add devm/of_phy_package_join helper to join PHYs in a PHY package. These
+are variant of the manual phy_package_join with the difference that
+these will use DT nodes to derive the base_addr instead of manually
+passing an hardcoded value.
+
+An additional value is added in phy_package_shared, "np" to reference
+the PHY package node pointer in specific PHY driver probe_once and
+config_init_once functions to make use of additional specific properties
+defined in the PHY package node in DT.
+
+The np value is filled only with of_phy_package_join if a valid PHY
+package node is found. A valid PHY package node must have the node name
+set to "ethernet-phy-package".
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phy_device.c | 96 ++++++++++++++++++++++++++++++++++++
+ include/linux/phy.h          |  6 +++
+ 2 files changed, 102 insertions(+)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -1650,6 +1650,7 @@ int phy_package_join(struct phy_device *
+                       shared->priv_size = priv_size;
+               }
+               shared->base_addr = base_addr;
++              shared->np = NULL;
+               refcount_set(&shared->refcnt, 1);
+               bus->shared[base_addr] = shared;
+       } else {
+@@ -1673,6 +1674,63 @@ err_unlock:
+ EXPORT_SYMBOL_GPL(phy_package_join);
+ /**
++ * of_phy_package_join - join a common PHY group in PHY package
++ * @phydev: target phy_device struct
++ * @priv_size: if non-zero allocate this amount of bytes for private data
++ *
++ * This is a variant of phy_package_join for PHY package defined in DT.
++ *
++ * The parent node of the @phydev is checked as a valid PHY package node
++ * structure (by matching the node name "ethernet-phy-package") and the
++ * base_addr for the PHY package is passed to phy_package_join.
++ *
++ * With this configuration the shared struct will also have the np value
++ * filled to use additional DT defined properties in PHY specific
++ * probe_once and config_init_once PHY package OPs.
++ *
++ * Returns < 0 on error, 0 on success. Esp. calling phy_package_join()
++ * with the same cookie but a different priv_size is an error. Or a parent
++ * node is not detected or is not valid or doesn't match the expected node
++ * name for PHY package.
++ */
++int of_phy_package_join(struct phy_device *phydev, size_t priv_size)
++{
++      struct device_node *node = phydev->mdio.dev.of_node;
++      struct device_node *package_node;
++      u32 base_addr;
++      int ret;
++
++      if (!node)
++              return -EINVAL;
++
++      package_node = of_get_parent(node);
++      if (!package_node)
++              return -EINVAL;
++
++      if (!of_node_name_eq(package_node, "ethernet-phy-package")) {
++              ret = -EINVAL;
++              goto exit;
++      }
++
++      if (of_property_read_u32(package_node, "reg", &base_addr)) {
++              ret = -EINVAL;
++              goto exit;
++      }
++
++      ret = phy_package_join(phydev, base_addr, priv_size);
++      if (ret)
++              goto exit;
++
++      phydev->shared->np = package_node;
++
++      return 0;
++exit:
++      of_node_put(package_node);
++      return ret;
++}
++EXPORT_SYMBOL_GPL(of_phy_package_join);
++
++/**
+  * phy_package_leave - leave a common PHY group
+  * @phydev: target phy_device struct
+  *
+@@ -1688,6 +1746,10 @@ void phy_package_leave(struct phy_device
+       if (!shared)
+               return;
++      /* Decrease the node refcount on leave if present */
++      if (shared->np)
++              of_node_put(shared->np);
++
+       if (refcount_dec_and_mutex_lock(&shared->refcnt, &bus->shared_lock)) {
+               bus->shared[shared->base_addr] = NULL;
+               mutex_unlock(&bus->shared_lock);
+@@ -1741,6 +1803,40 @@ int devm_phy_package_join(struct device
+ EXPORT_SYMBOL_GPL(devm_phy_package_join);
+ /**
++ * devm_of_phy_package_join - resource managed of_phy_package_join()
++ * @dev: device that is registering this PHY package
++ * @phydev: target phy_device struct
++ * @priv_size: if non-zero allocate this amount of bytes for private data
++ *
++ * Managed of_phy_package_join(). Shared storage fetched by this function,
++ * phy_package_leave() is automatically called on driver detach. See
++ * of_phy_package_join() for more information.
++ */
++int devm_of_phy_package_join(struct device *dev, struct phy_device *phydev,
++                           size_t priv_size)
++{
++      struct phy_device **ptr;
++      int ret;
++
++      ptr = devres_alloc(devm_phy_package_leave, sizeof(*ptr),
++                         GFP_KERNEL);
++      if (!ptr)
++              return -ENOMEM;
++
++      ret = of_phy_package_join(phydev, priv_size);
++
++      if (!ret) {
++              *ptr = phydev;
++              devres_add(dev, ptr);
++      } else {
++              devres_free(ptr);
++      }
++
++      return ret;
++}
++EXPORT_SYMBOL_GPL(devm_of_phy_package_join);
++
++/**
+  * phy_detach - detach a PHY device from its network device
+  * @phydev: target phy_device struct
+  *
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -321,6 +321,7 @@ struct mdio_bus_stats {
+  * struct phy_package_shared - Shared information in PHY packages
+  * @base_addr: Base PHY address of PHY package used to combine PHYs
+  *   in one package and for offset calculation of phy_package_read/write
++ * @np: Pointer to the Device Node if PHY package defined in DT
+  * @refcnt: Number of PHYs connected to this shared data
+  * @flags: Initialization of PHY package
+  * @priv_size: Size of the shared private data @priv
+@@ -332,6 +333,8 @@ struct mdio_bus_stats {
+  */
+ struct phy_package_shared {
+       u8 base_addr;
++      /* With PHY package defined in DT this points to the PHY package node */
++      struct device_node *np;
+       refcount_t refcnt;
+       unsigned long flags;
+       size_t priv_size;
+@@ -1765,9 +1768,12 @@ int phy_ethtool_set_link_ksettings(struc
+                                  const struct ethtool_link_ksettings *cmd);
+ int phy_ethtool_nway_reset(struct net_device *ndev);
+ int phy_package_join(struct phy_device *phydev, int base_addr, size_t priv_size);
++int of_phy_package_join(struct phy_device *phydev, size_t priv_size);
+ void phy_package_leave(struct phy_device *phydev);
+ int devm_phy_package_join(struct device *dev, struct phy_device *phydev,
+                         int base_addr, size_t priv_size);
++int devm_of_phy_package_join(struct device *dev, struct phy_device *phydev,
++                           size_t priv_size);
+ #if IS_ENABLED(CONFIG_PHYLIB)
+ int __init mdio_bus_init(void);
diff --git a/target/linux/generic/backport-6.1/716-v6.9-04-net-phy-qcom-move-more-function-to-shared-library.patch b/target/linux/generic/backport-6.1/716-v6.9-04-net-phy-qcom-move-more-function-to-shared-library.patch
new file mode 100644 (file)
index 0000000..e935725
--- /dev/null
@@ -0,0 +1,583 @@
+From 737eb75a815f9c08dcbb6631db57f4f4b0540a5b Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:07 +0100
+Subject: [PATCH 04/10] net: phy: qcom: move more function to shared library
+
+Move more function to shared library in preparation for introduction of
+new PHY Family qca807x that will make use of both functions from at803x
+and qca808x as it's a transition PHY with some implementation of at803x
+and some from the new qca808x.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/qcom/at803x.c       |  35 -----
+ drivers/net/phy/qcom/qca808x.c      | 205 ----------------------------
+ drivers/net/phy/qcom/qcom-phy-lib.c | 193 ++++++++++++++++++++++++++
+ drivers/net/phy/qcom/qcom.h         |  51 +++++++
+ 4 files changed, 244 insertions(+), 240 deletions(-)
+
+--- a/drivers/net/phy/qcom/at803x.c
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -504,41 +504,6 @@ static void at803x_link_change_notify(st
+       }
+ }
+-static int at803x_read_status(struct phy_device *phydev)
+-{
+-      struct at803x_ss_mask ss_mask = { 0 };
+-      int err, old_link = phydev->link;
+-
+-      /* Update the link, but return if there was an error */
+-      err = genphy_update_link(phydev);
+-      if (err)
+-              return err;
+-
+-      /* why bother the PHY if nothing can have changed */
+-      if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
+-              return 0;
+-
+-      phydev->speed = SPEED_UNKNOWN;
+-      phydev->duplex = DUPLEX_UNKNOWN;
+-      phydev->pause = 0;
+-      phydev->asym_pause = 0;
+-
+-      err = genphy_read_lpa(phydev);
+-      if (err < 0)
+-              return err;
+-
+-      ss_mask.speed_mask = AT803X_SS_SPEED_MASK;
+-      ss_mask.speed_shift = __bf_shf(AT803X_SS_SPEED_MASK);
+-      err = at803x_read_specific_status(phydev, ss_mask);
+-      if (err < 0)
+-              return err;
+-
+-      if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)
+-              phy_resolve_aneg_pause(phydev);
+-
+-      return 0;
+-}
+-
+ static int at803x_config_aneg(struct phy_device *phydev)
+ {
+       struct at803x_priv *priv = phydev->priv;
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -2,7 +2,6 @@
+ #include <linux/phy.h>
+ #include <linux/module.h>
+-#include <linux/ethtool_netlink.h>
+ #include "qcom.h"
+@@ -63,55 +62,6 @@
+ #define QCA808X_DBG_AN_TEST                   0xb
+ #define QCA808X_HIBERNATION_EN                        BIT(15)
+-#define QCA808X_CDT_ENABLE_TEST                       BIT(15)
+-#define QCA808X_CDT_INTER_CHECK_DIS           BIT(13)
+-#define QCA808X_CDT_STATUS                    BIT(11)
+-#define QCA808X_CDT_LENGTH_UNIT                       BIT(10)
+-
+-#define QCA808X_MMD3_CDT_STATUS                       0x8064
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_A          0x8065
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_B          0x8066
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_C          0x8067
+-#define QCA808X_MMD3_CDT_DIAG_PAIR_D          0x8068
+-#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT    GENMASK(15, 8)
+-#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT   GENMASK(7, 0)
+-
+-#define QCA808X_CDT_CODE_PAIR_A                       GENMASK(15, 12)
+-#define QCA808X_CDT_CODE_PAIR_B                       GENMASK(11, 8)
+-#define QCA808X_CDT_CODE_PAIR_C                       GENMASK(7, 4)
+-#define QCA808X_CDT_CODE_PAIR_D                       GENMASK(3, 0)
+-
+-#define QCA808X_CDT_STATUS_STAT_TYPE          GENMASK(1, 0)
+-#define QCA808X_CDT_STATUS_STAT_FAIL          FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
+-#define QCA808X_CDT_STATUS_STAT_NORMAL                FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
+-#define QCA808X_CDT_STATUS_STAT_SAME_OPEN     FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
+-#define QCA808X_CDT_STATUS_STAT_SAME_SHORT    FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
+-
+-#define QCA808X_CDT_STATUS_STAT_MDI           GENMASK(3, 2)
+-#define QCA808X_CDT_STATUS_STAT_MDI1          FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
+-#define QCA808X_CDT_STATUS_STAT_MDI2          FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
+-#define QCA808X_CDT_STATUS_STAT_MDI3          FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
+-
+-/* NORMAL are MDI with type set to 0 */
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL     QCA808X_CDT_STATUS_STAT_MDI1
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN               (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+-                                                                       QCA808X_CDT_STATUS_STAT_MDI1)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT      (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+-                                                                       QCA808X_CDT_STATUS_STAT_MDI1)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL     QCA808X_CDT_STATUS_STAT_MDI2
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN               (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+-                                                                       QCA808X_CDT_STATUS_STAT_MDI2)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT      (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+-                                                                       QCA808X_CDT_STATUS_STAT_MDI2)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL     QCA808X_CDT_STATUS_STAT_MDI3
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN               (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
+-                                                                       QCA808X_CDT_STATUS_STAT_MDI3)
+-#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT      (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
+-                                                                       QCA808X_CDT_STATUS_STAT_MDI3)
+-
+-/* Added for reference of existence but should be handled by wait_for_completion already */
+-#define QCA808X_CDT_STATUS_STAT_BUSY          (BIT(1) | BIT(3))
+-
+ #define QCA808X_MMD7_LED_GLOBAL                       0x8073
+ #define QCA808X_LED_BLINK_1                   GENMASK(11, 6)
+ #define QCA808X_LED_BLINK_2                   GENMASK(5, 0)
+@@ -406,86 +356,6 @@ static int qca808x_soft_reset(struct phy
+       return ret;
+ }
+-static bool qca808x_cdt_fault_length_valid(int cdt_code)
+-{
+-      switch (cdt_code) {
+-      case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+-      case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+-              return true;
+-      default:
+-              return false;
+-      }
+-}
+-
+-static int qca808x_cable_test_result_trans(int cdt_code)
+-{
+-      switch (cdt_code) {
+-      case QCA808X_CDT_STATUS_STAT_NORMAL:
+-              return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+-      case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
+-              return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+-      case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
+-              return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
+-      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
+-              return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+-      case QCA808X_CDT_STATUS_STAT_FAIL:
+-      default:
+-              return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+-      }
+-}
+-
+-static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
+-                                  int result)
+-{
+-      int val;
+-      u32 cdt_length_reg = 0;
+-
+-      switch (pair) {
+-      case ETHTOOL_A_CABLE_PAIR_A:
+-              cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;
+-              break;
+-      case ETHTOOL_A_CABLE_PAIR_B:
+-              cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;
+-              break;
+-      case ETHTOOL_A_CABLE_PAIR_C:
+-              cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;
+-              break;
+-      case ETHTOOL_A_CABLE_PAIR_D:
+-              cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;
+-              break;
+-      default:
+-              return -EINVAL;
+-      }
+-
+-      val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);
+-      if (val < 0)
+-              return val;
+-
+-      if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
+-              val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
+-      else
+-              val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
+-
+-      return at803x_cdt_fault_length(val);
+-}
+-
+ static int qca808x_cable_test_start(struct phy_device *phydev)
+ {
+       int ret;
+@@ -526,81 +396,6 @@ static int qca808x_cable_test_start(stru
+       return 0;
+ }
+-
+-static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
+-                                            u16 status)
+-{
+-      int length, result;
+-      u16 pair_code;
+-
+-      switch (pair) {
+-      case ETHTOOL_A_CABLE_PAIR_A:
+-              pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
+-              break;
+-      case ETHTOOL_A_CABLE_PAIR_B:
+-              pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
+-              break;
+-      case ETHTOOL_A_CABLE_PAIR_C:
+-              pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
+-              break;
+-      case ETHTOOL_A_CABLE_PAIR_D:
+-              pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
+-              break;
+-      default:
+-              return -EINVAL;
+-      }
+-
+-      result = qca808x_cable_test_result_trans(pair_code);
+-      ethnl_cable_test_result(phydev, pair, result);
+-
+-      if (qca808x_cdt_fault_length_valid(pair_code)) {
+-              length = qca808x_cdt_fault_length(phydev, pair, result);
+-              ethnl_cable_test_fault_length(phydev, pair, length);
+-      }
+-
+-      return 0;
+-}
+-
+-static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
+-{
+-      int ret, val;
+-
+-      *finished = false;
+-
+-      val = QCA808X_CDT_ENABLE_TEST |
+-            QCA808X_CDT_LENGTH_UNIT;
+-      ret = at803x_cdt_start(phydev, val);
+-      if (ret)
+-              return ret;
+-
+-      ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
+-      if (ret)
+-              return ret;
+-
+-      val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);
+-      if (val < 0)
+-              return val;
+-
+-      ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
+-      if (ret)
+-              return ret;
+-
+-      ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
+-      if (ret)
+-              return ret;
+-
+-      ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
+-      if (ret)
+-              return ret;
+-
+-      ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
+-      if (ret)
+-              return ret;
+-
+-      *finished = true;
+-
+-      return 0;
+-}
+ static int qca808x_get_features(struct phy_device *phydev)
+ {
+--- a/drivers/net/phy/qcom/qcom-phy-lib.c
++++ b/drivers/net/phy/qcom/qcom-phy-lib.c
+@@ -5,6 +5,7 @@
+ #include <linux/netdevice.h>
+ #include <linux/etherdevice.h>
++#include <linux/ethtool_netlink.h>
+ #include "qcom.h"
+@@ -311,6 +312,42 @@ int at803x_prepare_config_aneg(struct ph
+ }
+ EXPORT_SYMBOL_GPL(at803x_prepare_config_aneg);
++int at803x_read_status(struct phy_device *phydev)
++{
++      struct at803x_ss_mask ss_mask = { 0 };
++      int err, old_link = phydev->link;
++
++      /* Update the link, but return if there was an error */
++      err = genphy_update_link(phydev);
++      if (err)
++              return err;
++
++      /* why bother the PHY if nothing can have changed */
++      if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
++              return 0;
++
++      phydev->speed = SPEED_UNKNOWN;
++      phydev->duplex = DUPLEX_UNKNOWN;
++      phydev->pause = 0;
++      phydev->asym_pause = 0;
++
++      err = genphy_read_lpa(phydev);
++      if (err < 0)
++              return err;
++
++      ss_mask.speed_mask = AT803X_SS_SPEED_MASK;
++      ss_mask.speed_shift = __bf_shf(AT803X_SS_SPEED_MASK);
++      err = at803x_read_specific_status(phydev, ss_mask);
++      if (err < 0)
++              return err;
++
++      if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)
++              phy_resolve_aneg_pause(phydev);
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(at803x_read_status);
++
+ static int at803x_get_downshift(struct phy_device *phydev, u8 *d)
+ {
+       int val;
+@@ -427,3 +464,159 @@ int at803x_cdt_wait_for_completion(struc
+       return ret < 0 ? ret : 0;
+ }
+ EXPORT_SYMBOL_GPL(at803x_cdt_wait_for_completion);
++
++static bool qca808x_cdt_fault_length_valid(int cdt_code)
++{
++      switch (cdt_code) {
++      case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++      case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++              return true;
++      default:
++              return false;
++      }
++}
++
++static int qca808x_cable_test_result_trans(int cdt_code)
++{
++      switch (cdt_code) {
++      case QCA808X_CDT_STATUS_STAT_NORMAL:
++              return ETHTOOL_A_CABLE_RESULT_CODE_OK;
++      case QCA808X_CDT_STATUS_STAT_SAME_SHORT:
++              return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
++      case QCA808X_CDT_STATUS_STAT_SAME_OPEN:
++              return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:
++      case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:
++              return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
++      case QCA808X_CDT_STATUS_STAT_FAIL:
++      default:
++              return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
++      }
++}
++
++static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,
++                                  int result)
++{
++      int val;
++      u32 cdt_length_reg = 0;
++
++      switch (pair) {
++      case ETHTOOL_A_CABLE_PAIR_A:
++              cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;
++              break;
++      case ETHTOOL_A_CABLE_PAIR_B:
++              cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;
++              break;
++      case ETHTOOL_A_CABLE_PAIR_C:
++              cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;
++              break;
++      case ETHTOOL_A_CABLE_PAIR_D:
++              cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);
++      if (val < 0)
++              return val;
++
++      if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)
++              val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);
++      else
++              val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);
++
++      return at803x_cdt_fault_length(val);
++}
++
++static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,
++                                            u16 status)
++{
++      int length, result;
++      u16 pair_code;
++
++      switch (pair) {
++      case ETHTOOL_A_CABLE_PAIR_A:
++              pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);
++              break;
++      case ETHTOOL_A_CABLE_PAIR_B:
++              pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);
++              break;
++      case ETHTOOL_A_CABLE_PAIR_C:
++              pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);
++              break;
++      case ETHTOOL_A_CABLE_PAIR_D:
++              pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      result = qca808x_cable_test_result_trans(pair_code);
++      ethnl_cable_test_result(phydev, pair, result);
++
++      if (qca808x_cdt_fault_length_valid(pair_code)) {
++              length = qca808x_cdt_fault_length(phydev, pair, result);
++              ethnl_cable_test_fault_length(phydev, pair, length);
++      }
++
++      return 0;
++}
++
++int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
++{
++      int ret, val;
++
++      *finished = false;
++
++      val = QCA808X_CDT_ENABLE_TEST |
++            QCA808X_CDT_LENGTH_UNIT;
++      ret = at803x_cdt_start(phydev, val);
++      if (ret)
++              return ret;
++
++      ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);
++      if (ret)
++              return ret;
++
++      val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);
++      if (val < 0)
++              return val;
++
++      ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);
++      if (ret)
++              return ret;
++
++      ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);
++      if (ret)
++              return ret;
++
++      ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);
++      if (ret)
++              return ret;
++
++      ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);
++      if (ret)
++              return ret;
++
++      *finished = true;
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(qca808x_cable_test_get_status);
+--- a/drivers/net/phy/qcom/qcom.h
++++ b/drivers/net/phy/qcom/qcom.h
+@@ -54,6 +54,55 @@
+ #define AT803X_CDT_STATUS_STAT_MASK           GENMASK(9, 8)
+ #define AT803X_CDT_STATUS_DELTA_TIME_MASK     GENMASK(7, 0)
++#define QCA808X_CDT_ENABLE_TEST                       BIT(15)
++#define QCA808X_CDT_INTER_CHECK_DIS           BIT(13)
++#define QCA808X_CDT_STATUS                    BIT(11)
++#define QCA808X_CDT_LENGTH_UNIT                       BIT(10)
++
++#define QCA808X_MMD3_CDT_STATUS                       0x8064
++#define QCA808X_MMD3_CDT_DIAG_PAIR_A          0x8065
++#define QCA808X_MMD3_CDT_DIAG_PAIR_B          0x8066
++#define QCA808X_MMD3_CDT_DIAG_PAIR_C          0x8067
++#define QCA808X_MMD3_CDT_DIAG_PAIR_D          0x8068
++#define QCA808X_CDT_DIAG_LENGTH_SAME_SHORT    GENMASK(15, 8)
++#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT   GENMASK(7, 0)
++
++#define QCA808X_CDT_CODE_PAIR_A                       GENMASK(15, 12)
++#define QCA808X_CDT_CODE_PAIR_B                       GENMASK(11, 8)
++#define QCA808X_CDT_CODE_PAIR_C                       GENMASK(7, 4)
++#define QCA808X_CDT_CODE_PAIR_D                       GENMASK(3, 0)
++
++#define QCA808X_CDT_STATUS_STAT_TYPE          GENMASK(1, 0)
++#define QCA808X_CDT_STATUS_STAT_FAIL          FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0)
++#define QCA808X_CDT_STATUS_STAT_NORMAL                FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1)
++#define QCA808X_CDT_STATUS_STAT_SAME_OPEN     FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2)
++#define QCA808X_CDT_STATUS_STAT_SAME_SHORT    FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3)
++
++#define QCA808X_CDT_STATUS_STAT_MDI           GENMASK(3, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI1          FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1)
++#define QCA808X_CDT_STATUS_STAT_MDI2          FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2)
++#define QCA808X_CDT_STATUS_STAT_MDI3          FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3)
++
++/* NORMAL are MDI with type set to 0 */
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL     QCA808X_CDT_STATUS_STAT_MDI1
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN               (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++                                                                       QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT      (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++                                                                       QCA808X_CDT_STATUS_STAT_MDI1)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL     QCA808X_CDT_STATUS_STAT_MDI2
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN               (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++                                                                       QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT      (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++                                                                       QCA808X_CDT_STATUS_STAT_MDI2)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL     QCA808X_CDT_STATUS_STAT_MDI3
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN               (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\
++                                                                       QCA808X_CDT_STATUS_STAT_MDI3)
++#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT      (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\
++                                                                       QCA808X_CDT_STATUS_STAT_MDI3)
++
++/* Added for reference of existence but should be handled by wait_for_completion already */
++#define QCA808X_CDT_STATUS_STAT_BUSY          (BIT(1) | BIT(3))
++
+ #define AT803X_LOC_MAC_ADDR_0_15_OFFSET               0x804C
+ #define AT803X_LOC_MAC_ADDR_16_31_OFFSET      0x804B
+ #define AT803X_LOC_MAC_ADDR_32_47_OFFSET      0x804A
+@@ -110,6 +159,7 @@ int at803x_read_specific_status(struct p
+                               struct at803x_ss_mask ss_mask);
+ int at803x_config_mdix(struct phy_device *phydev, u8 ctrl);
+ int at803x_prepare_config_aneg(struct phy_device *phydev);
++int at803x_read_status(struct phy_device *phydev);
+ int at803x_get_tunable(struct phy_device *phydev,
+                      struct ethtool_tunable *tuna, void *data);
+ int at803x_set_tunable(struct phy_device *phydev,
+@@ -118,3 +168,4 @@ int at803x_cdt_fault_length(int dt);
+ int at803x_cdt_start(struct phy_device *phydev, u32 cdt_start);
+ int at803x_cdt_wait_for_completion(struct phy_device *phydev,
+                                  u32 cdt_en);
++int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished);
diff --git a/target/linux/generic/backport-6.1/716-v6.9-06-net-phy-provide-whether-link-has-changed-in-c37_read.patch b/target/linux/generic/backport-6.1/716-v6.9-06-net-phy-provide-whether-link-has-changed-in-c37_read.patch
new file mode 100644 (file)
index 0000000..4371599
--- /dev/null
@@ -0,0 +1,100 @@
+From 9b1d5e055508393561e26bd1720f4c2639b03b1a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:09 +0100
+Subject: [PATCH 06/10] net: phy: provide whether link has changed in
+ c37_read_status
+
+Some PHY driver might require additional regs call after
+genphy_c37_read_status() is called.
+
+Expand genphy_c37_read_status to provide a bool wheather the link has
+changed or not to permit PHY driver to skip additional regs call if
+nothing has changed.
+
+Every user of genphy_c37_read_status() is updated with the new
+additional bool.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/broadcom.c    |  3 ++-
+ drivers/net/phy/phy_device.c  | 11 +++++++++--
+ drivers/net/phy/qcom/at803x.c |  3 ++-
+ include/linux/phy.h           |  2 +-
+ 4 files changed, 14 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/broadcom.c
++++ b/drivers/net/phy/broadcom.c
+@@ -609,10 +609,11 @@ static int bcm54616s_config_aneg(struct
+ static int bcm54616s_read_status(struct phy_device *phydev)
+ {
+       struct bcm54616s_phy_priv *priv = phydev->priv;
++      bool changed;
+       int err;
+       if (priv->mode_1000bx_en)
+-              err = genphy_c37_read_status(phydev);
++              err = genphy_c37_read_status(phydev, &changed);
+       else
+               err = genphy_read_status(phydev);
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -2551,12 +2551,15 @@ EXPORT_SYMBOL(genphy_read_status);
+ /**
+  * genphy_c37_read_status - check the link status and update current link state
+  * @phydev: target phy_device struct
++ * @changed: pointer where to store if link changed
+  *
+  * Description: Check the link, then figure out the current state
+  *   by comparing what we advertise with what the link partner
+  *   advertises. This function is for Clause 37 1000Base-X mode.
++ *
++ *   If link has changed, @changed is set to true, false otherwise.
+  */
+-int genphy_c37_read_status(struct phy_device *phydev)
++int genphy_c37_read_status(struct phy_device *phydev, bool *changed)
+ {
+       int lpa, err, old_link = phydev->link;
+@@ -2566,9 +2569,13 @@ int genphy_c37_read_status(struct phy_de
+               return err;
+       /* why bother the PHY if nothing can have changed */
+-      if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
++      if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link) {
++              *changed = false;
+               return 0;
++      }
++      /* Signal link has changed */
++      *changed = true;
+       phydev->duplex = DUPLEX_UNKNOWN;
+       phydev->pause = 0;
+       phydev->asym_pause = 0;
+--- a/drivers/net/phy/qcom/at803x.c
++++ b/drivers/net/phy/qcom/at803x.c
+@@ -912,9 +912,10 @@ static int at8031_config_intr(struct phy
+ static int at8031_read_status(struct phy_device *phydev)
+ {
+       struct at803x_priv *priv = phydev->priv;
++      bool changed;
+       if (priv->is_1000basex)
+-              return genphy_c37_read_status(phydev);
++              return genphy_c37_read_status(phydev, &changed);
+       return at803x_read_status(phydev);
+ }
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1660,7 +1660,7 @@ int genphy_write_mmd_unsupported(struct
+ /* Clause 37 */
+ int genphy_c37_config_aneg(struct phy_device *phydev);
+-int genphy_c37_read_status(struct phy_device *phydev);
++int genphy_c37_read_status(struct phy_device *phydev, bool *changed);
+ /* Clause 45 PHY */
+ int genphy_c45_restart_aneg(struct phy_device *phydev);
diff --git a/target/linux/generic/backport-6.1/716-v6.9-07-net-phy-qcom-add-support-for-QCA807x-PHY-Family.patch b/target/linux/generic/backport-6.1/716-v6.9-07-net-phy-qcom-add-support-for-QCA807x-PHY-Family.patch
new file mode 100644 (file)
index 0000000..bbf0f76
--- /dev/null
@@ -0,0 +1,668 @@
+From d1cb613efbd3cd7d0c000167816beb3f248f5eb8 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robert.marko@sartura.hr>
+Date: Tue, 6 Feb 2024 18:31:10 +0100
+Subject: [PATCH 07/10] net: phy: qcom: add support for QCA807x PHY Family
+
+This adds driver for the Qualcomm QCA8072 and QCA8075 PHY-s.
+
+They are 2 or 5 port IEEE 802.3 clause 22 compliant 10BASE-Te,
+100BASE-TX and 1000BASE-T PHY-s.
+
+They feature 2 SerDes, one for PSGMII or QSGMII connection with
+MAC, while second one is SGMII for connection to MAC or fiber.
+
+Both models have a combo port that supports 1000BASE-X and
+100BASE-FX fiber.
+
+PHY package can be configured in 3 mode following this table:
+
+              First Serdes mode       Second Serdes mode
+Option 1      PSGMII for copper       Disabled
+              ports 0-4
+Option 2      PSGMII for copper       1000BASE-X / 100BASE-FX
+              ports 0-4
+Option 3      QSGMII for copper       SGMII for
+              ports 0-3               copper port 4
+
+Each PHY inside of QCA807x series has 4 digitally controlled
+output only pins that natively drive LED-s.
+But some vendors used these to driver generic LED-s controlled
+by userspace, so lets enable registering each PHY as GPIO
+controller and add driver for it.
+
+These are commonly used in Qualcomm IPQ40xx, IPQ60xx and IPQ807x
+boards.
+
+Co-developed-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Robert Marko <robert.marko@sartura.hr>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/qcom/Kconfig   |   8 +
+ drivers/net/phy/qcom/Makefile  |   1 +
+ drivers/net/phy/qcom/qca807x.c | 597 +++++++++++++++++++++++++++++++++
+ 3 files changed, 606 insertions(+)
+ create mode 100644 drivers/net/phy/qcom/qca807x.c
+
+--- a/drivers/net/phy/qcom/Kconfig
++++ b/drivers/net/phy/qcom/Kconfig
+@@ -20,3 +20,11 @@ config QCA808X_PHY
+       select QCOM_NET_PHYLIB
+       help
+         Currently supports the QCA8081 model
++
++config QCA807X_PHY
++      tristate "Qualcomm QCA807x PHYs"
++      select QCOM_NET_PHYLIB
++      depends on OF_MDIO
++      help
++        Currently supports the Qualcomm QCA8072, QCA8075 and the PSGMII
++        control PHY.
+--- a/drivers/net/phy/qcom/Makefile
++++ b/drivers/net/phy/qcom/Makefile
+@@ -3,3 +3,4 @@ obj-$(CONFIG_QCOM_NET_PHYLIB)  += qcom-ph
+ obj-$(CONFIG_AT803X_PHY)      += at803x.o
+ obj-$(CONFIG_QCA83XX_PHY)     += qca83xx.o
+ obj-$(CONFIG_QCA808X_PHY)     += qca808x.o
++obj-$(CONFIG_QCA807X_PHY)     += qca807x.o
+--- /dev/null
++++ b/drivers/net/phy/qcom/qca807x.c
+@@ -0,0 +1,597 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (c) 2023 Sartura Ltd.
++ *
++ * Author: Robert Marko <robert.marko@sartura.hr>
++ *         Christian Marangi <ansuelsmth@gmail.com>
++ *
++ * Qualcomm QCA8072 and QCA8075 PHY driver
++ */
++
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/phy.h>
++#include <linux/bitfield.h>
++#include <linux/gpio/driver.h>
++#include <linux/sfp.h>
++
++#include "qcom.h"
++
++#define QCA807X_CHIP_CONFIGURATION                            0x1f
++#define QCA807X_BT_BX_REG_SEL                                 BIT(15)
++#define QCA807X_BT_BX_REG_SEL_FIBER                           0
++#define QCA807X_BT_BX_REG_SEL_COPPER                          1
++#define QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK              GENMASK(3, 0)
++#define QCA807X_CHIP_CONFIGURATION_MODE_QSGMII_SGMII          4
++#define QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_FIBER          3
++#define QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_ALL_COPPER     0
++
++#define QCA807X_MEDIA_SELECT_STATUS                           0x1a
++#define QCA807X_MEDIA_DETECTED_COPPER                         BIT(5)
++#define QCA807X_MEDIA_DETECTED_1000_BASE_X                    BIT(4)
++#define QCA807X_MEDIA_DETECTED_100_BASE_FX                    BIT(3)
++
++#define QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION                        0x807e
++#define QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION_EN             BIT(0)
++
++#define QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH   0x801a
++#define QCA807X_CONTROL_DAC_MASK                              GENMASK(2, 0)
++/* List of tweaks enabled by this bit:
++ * - With both FULL amplitude and FULL bias current: bias current
++ *   is set to half.
++ * - With only DSP amplitude: bias current is set to half and
++ *   is set to 1/4 with cable < 10m.
++ * - With DSP bias current (included both DSP amplitude and
++ *   DSP bias current): bias current is half the detected current
++ *   with cable < 10m.
++ */
++#define QCA807X_CONTROL_DAC_BIAS_CURRENT_TWEAK                        BIT(2)
++#define QCA807X_CONTROL_DAC_DSP_BIAS_CURRENT                  BIT(1)
++#define QCA807X_CONTROL_DAC_DSP_AMPLITUDE                     BIT(0)
++
++#define QCA807X_MMD7_LED_100N_1                               0x8074
++#define QCA807X_MMD7_LED_100N_2                               0x8075
++#define QCA807X_MMD7_LED_1000N_1                      0x8076
++#define QCA807X_MMD7_LED_1000N_2                      0x8077
++
++#define QCA807X_MMD7_LED_CTRL(x)                      (0x8074 + ((x) * 2))
++#define QCA807X_MMD7_LED_FORCE_CTRL(x)                        (0x8075 + ((x) * 2))
++
++#define QCA807X_GPIO_FORCE_EN                         BIT(15)
++#define QCA807X_GPIO_FORCE_MODE_MASK                  GENMASK(14, 13)
++
++#define QCA807X_FUNCTION_CONTROL                      0x10
++#define QCA807X_FC_MDI_CROSSOVER_MODE_MASK            GENMASK(6, 5)
++#define QCA807X_FC_MDI_CROSSOVER_AUTO                 3
++#define QCA807X_FC_MDI_CROSSOVER_MANUAL_MDIX          1
++#define QCA807X_FC_MDI_CROSSOVER_MANUAL_MDI           0
++
++/* PQSGMII Analog PHY specific */
++#define PQSGMII_CTRL_REG                              0x0
++#define PQSGMII_ANALOG_SW_RESET                               BIT(6)
++#define PQSGMII_DRIVE_CONTROL_1                               0xb
++#define PQSGMII_TX_DRIVER_MASK                                GENMASK(7, 4)
++#define PQSGMII_TX_DRIVER_140MV                               0x0
++#define PQSGMII_TX_DRIVER_160MV                               0x1
++#define PQSGMII_TX_DRIVER_180MV                               0x2
++#define PQSGMII_TX_DRIVER_200MV                               0x3
++#define PQSGMII_TX_DRIVER_220MV                               0x4
++#define PQSGMII_TX_DRIVER_240MV                               0x5
++#define PQSGMII_TX_DRIVER_260MV                               0x6
++#define PQSGMII_TX_DRIVER_280MV                               0x7
++#define PQSGMII_TX_DRIVER_300MV                               0x8
++#define PQSGMII_TX_DRIVER_320MV                               0x9
++#define PQSGMII_TX_DRIVER_400MV                               0xa
++#define PQSGMII_TX_DRIVER_500MV                               0xb
++#define PQSGMII_TX_DRIVER_600MV                               0xc
++#define PQSGMII_MODE_CTRL                             0x6d
++#define PQSGMII_MODE_CTRL_AZ_WORKAROUND_MASK          BIT(0)
++#define PQSGMII_MMD3_SERDES_CONTROL                   0x805a
++
++#define PHY_ID_QCA8072                0x004dd0b2
++#define PHY_ID_QCA8075                0x004dd0b1
++
++#define QCA807X_COMBO_ADDR_OFFSET                     4
++#define QCA807X_PQSGMII_ADDR_OFFSET                   5
++#define SERDES_RESET_SLEEP                            100
++
++enum qca807x_global_phy {
++      QCA807X_COMBO_ADDR = 4,
++      QCA807X_PQSGMII_ADDR = 5,
++};
++
++struct qca807x_shared_priv {
++      unsigned int package_mode;
++      u32 tx_drive_strength;
++};
++
++struct qca807x_gpio_priv {
++      struct phy_device *phy;
++};
++
++struct qca807x_priv {
++      bool dac_full_amplitude;
++      bool dac_full_bias_current;
++      bool dac_disable_bias_current_tweak;
++};
++
++static int qca807x_cable_test_start(struct phy_device *phydev)
++{
++      /* we do all the (time consuming) work later */
++      return 0;
++}
++
++#ifdef CONFIG_GPIOLIB
++static int qca807x_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
++{
++      return GPIO_LINE_DIRECTION_OUT;
++}
++
++static int qca807x_gpio_get(struct gpio_chip *gc, unsigned int offset)
++{
++      struct qca807x_gpio_priv *priv = gpiochip_get_data(gc);
++      u16 reg;
++      int val;
++
++      reg = QCA807X_MMD7_LED_FORCE_CTRL(offset);
++      val = phy_read_mmd(priv->phy, MDIO_MMD_AN, reg);
++
++      return FIELD_GET(QCA807X_GPIO_FORCE_MODE_MASK, val);
++}
++
++static void qca807x_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
++{
++      struct qca807x_gpio_priv *priv = gpiochip_get_data(gc);
++      u16 reg;
++      int val;
++
++      reg = QCA807X_MMD7_LED_FORCE_CTRL(offset);
++
++      val = phy_read_mmd(priv->phy, MDIO_MMD_AN, reg);
++      val &= ~QCA807X_GPIO_FORCE_MODE_MASK;
++      val |= QCA807X_GPIO_FORCE_EN;
++      val |= FIELD_PREP(QCA807X_GPIO_FORCE_MODE_MASK, value);
++
++      phy_write_mmd(priv->phy, MDIO_MMD_AN, reg, val);
++}
++
++static int qca807x_gpio_dir_out(struct gpio_chip *gc, unsigned int offset, int value)
++{
++      qca807x_gpio_set(gc, offset, value);
++
++      return 0;
++}
++
++static int qca807x_gpio(struct phy_device *phydev)
++{
++      struct device *dev = &phydev->mdio.dev;
++      struct qca807x_gpio_priv *priv;
++      struct gpio_chip *gc;
++
++      priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++      if (!priv)
++              return -ENOMEM;
++
++      priv->phy = phydev;
++
++      gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL);
++      if (!gc)
++              return -ENOMEM;
++
++      gc->label = dev_name(dev);
++      gc->base = -1;
++      gc->ngpio = 2;
++      gc->parent = dev;
++      gc->owner = THIS_MODULE;
++      gc->can_sleep = true;
++      gc->get_direction = qca807x_gpio_get_direction;
++      gc->direction_output = qca807x_gpio_dir_out;
++      gc->get = qca807x_gpio_get;
++      gc->set = qca807x_gpio_set;
++
++      return devm_gpiochip_add_data(dev, gc, priv);
++}
++#endif
++
++static int qca807x_read_fiber_status(struct phy_device *phydev)
++{
++      bool changed;
++      int ss, err;
++
++      err = genphy_c37_read_status(phydev, &changed);
++      if (err || !changed)
++              return err;
++
++      /* Read the QCA807x PHY-Specific Status register fiber page,
++       * which indicates the speed and duplex that the PHY is actually
++       * using, irrespective of whether we are in autoneg mode or not.
++       */
++      ss = phy_read(phydev, AT803X_SPECIFIC_STATUS);
++      if (ss < 0)
++              return ss;
++
++      phydev->speed = SPEED_UNKNOWN;
++      phydev->duplex = DUPLEX_UNKNOWN;
++      if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) {
++              switch (FIELD_GET(AT803X_SS_SPEED_MASK, ss)) {
++              case AT803X_SS_SPEED_100:
++                      phydev->speed = SPEED_100;
++                      break;
++              case AT803X_SS_SPEED_1000:
++                      phydev->speed = SPEED_1000;
++                      break;
++              }
++
++              if (ss & AT803X_SS_DUPLEX)
++                      phydev->duplex = DUPLEX_FULL;
++              else
++                      phydev->duplex = DUPLEX_HALF;
++      }
++
++      return 0;
++}
++
++static int qca807x_read_status(struct phy_device *phydev)
++{
++      if (linkmode_test_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported)) {
++              switch (phydev->port) {
++              case PORT_FIBRE:
++                      return qca807x_read_fiber_status(phydev);
++              case PORT_TP:
++                      return at803x_read_status(phydev);
++              default:
++                      return -EINVAL;
++              }
++      }
++
++      return at803x_read_status(phydev);
++}
++
++static int qca807x_phy_package_probe_once(struct phy_device *phydev)
++{
++      struct phy_package_shared *shared = phydev->shared;
++      struct qca807x_shared_priv *priv = shared->priv;
++      unsigned int tx_drive_strength;
++      const char *package_mode_name;
++
++      /* Default to 600mw if not defined */
++      if (of_property_read_u32(shared->np, "qcom,tx-drive-strength-milliwatt",
++                               &tx_drive_strength))
++              tx_drive_strength = 600;
++
++      switch (tx_drive_strength) {
++      case 140:
++              priv->tx_drive_strength = PQSGMII_TX_DRIVER_140MV;
++              break;
++      case 160:
++              priv->tx_drive_strength = PQSGMII_TX_DRIVER_160MV;
++              break;
++      case 180:
++              priv->tx_drive_strength = PQSGMII_TX_DRIVER_180MV;
++              break;
++      case 200:
++              priv->tx_drive_strength = PQSGMII_TX_DRIVER_200MV;
++              break;
++      case 220:
++              priv->tx_drive_strength = PQSGMII_TX_DRIVER_220MV;
++              break;
++      case 240:
++              priv->tx_drive_strength = PQSGMII_TX_DRIVER_240MV;
++              break;
++      case 260:
++              priv->tx_drive_strength = PQSGMII_TX_DRIVER_260MV;
++              break;
++      case 280:
++              priv->tx_drive_strength = PQSGMII_TX_DRIVER_280MV;
++              break;
++      case 300:
++              priv->tx_drive_strength = PQSGMII_TX_DRIVER_300MV;
++              break;
++      case 320:
++              priv->tx_drive_strength = PQSGMII_TX_DRIVER_320MV;
++              break;
++      case 400:
++              priv->tx_drive_strength = PQSGMII_TX_DRIVER_400MV;
++              break;
++      case 500:
++              priv->tx_drive_strength = PQSGMII_TX_DRIVER_500MV;
++              break;
++      case 600:
++              priv->tx_drive_strength = PQSGMII_TX_DRIVER_600MV;
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      priv->package_mode = PHY_INTERFACE_MODE_NA;
++      if (!of_property_read_string(shared->np, "qcom,package-mode",
++                                   &package_mode_name)) {
++              if (!strcasecmp(package_mode_name,
++                              phy_modes(PHY_INTERFACE_MODE_PSGMII)))
++                      priv->package_mode = PHY_INTERFACE_MODE_PSGMII;
++              else if (!strcasecmp(package_mode_name,
++                                   phy_modes(PHY_INTERFACE_MODE_QSGMII)))
++                      priv->package_mode = PHY_INTERFACE_MODE_QSGMII;
++              else
++                      return -EINVAL;
++      }
++
++      return 0;
++}
++
++static int qca807x_phy_package_config_init_once(struct phy_device *phydev)
++{
++      struct phy_package_shared *shared = phydev->shared;
++      struct qca807x_shared_priv *priv = shared->priv;
++      int val, ret;
++
++      phy_lock_mdio_bus(phydev);
++
++      /* Set correct PHY package mode */
++      val = __phy_package_read(phydev, QCA807X_COMBO_ADDR,
++                               QCA807X_CHIP_CONFIGURATION);
++      val &= ~QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK;
++      /* package_mode can be QSGMII or PSGMII and we validate
++       * this in probe_once.
++       * With package_mode to NA, we default to PSGMII.
++       */
++      switch (priv->package_mode) {
++      case PHY_INTERFACE_MODE_QSGMII:
++              val |= QCA807X_CHIP_CONFIGURATION_MODE_QSGMII_SGMII;
++              break;
++      case PHY_INTERFACE_MODE_PSGMII:
++      default:
++              val |= QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_ALL_COPPER;
++      }
++      ret = __phy_package_write(phydev, QCA807X_COMBO_ADDR,
++                                QCA807X_CHIP_CONFIGURATION, val);
++      if (ret)
++              goto exit;
++
++      /* After mode change Serdes reset is required */
++      val = __phy_package_read(phydev, QCA807X_PQSGMII_ADDR,
++                               PQSGMII_CTRL_REG);
++      val &= ~PQSGMII_ANALOG_SW_RESET;
++      ret = __phy_package_write(phydev, QCA807X_PQSGMII_ADDR,
++                                PQSGMII_CTRL_REG, val);
++      if (ret)
++              goto exit;
++
++      msleep(SERDES_RESET_SLEEP);
++
++      val = __phy_package_read(phydev, QCA807X_PQSGMII_ADDR,
++                               PQSGMII_CTRL_REG);
++      val |= PQSGMII_ANALOG_SW_RESET;
++      ret = __phy_package_write(phydev, QCA807X_PQSGMII_ADDR,
++                                PQSGMII_CTRL_REG, val);
++      if (ret)
++              goto exit;
++
++      /* Workaround to enable AZ transmitting ability */
++      val = __phy_package_read_mmd(phydev, QCA807X_PQSGMII_ADDR,
++                                   MDIO_MMD_PMAPMD, PQSGMII_MODE_CTRL);
++      val &= ~PQSGMII_MODE_CTRL_AZ_WORKAROUND_MASK;
++      ret = __phy_package_write_mmd(phydev, QCA807X_PQSGMII_ADDR,
++                                    MDIO_MMD_PMAPMD, PQSGMII_MODE_CTRL, val);
++      if (ret)
++              goto exit;
++
++      /* Set PQSGMII TX AMP strength */
++      val = __phy_package_read(phydev, QCA807X_PQSGMII_ADDR,
++                               PQSGMII_DRIVE_CONTROL_1);
++      val &= ~PQSGMII_TX_DRIVER_MASK;
++      val |= FIELD_PREP(PQSGMII_TX_DRIVER_MASK, priv->tx_drive_strength);
++      ret = __phy_package_write(phydev, QCA807X_PQSGMII_ADDR,
++                                PQSGMII_DRIVE_CONTROL_1, val);
++      if (ret)
++              goto exit;
++
++      /* Prevent PSGMII going into hibernation via PSGMII self test */
++      val = __phy_package_read_mmd(phydev, QCA807X_COMBO_ADDR,
++                                   MDIO_MMD_PCS, PQSGMII_MMD3_SERDES_CONTROL);
++      val &= ~BIT(1);
++      ret = __phy_package_write_mmd(phydev, QCA807X_COMBO_ADDR,
++                                    MDIO_MMD_PCS, PQSGMII_MMD3_SERDES_CONTROL, val);
++
++exit:
++      phy_unlock_mdio_bus(phydev);
++
++      return ret;
++}
++
++static int qca807x_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
++{
++      struct phy_device *phydev = upstream;
++      __ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, };
++      phy_interface_t iface;
++      int ret;
++      DECLARE_PHY_INTERFACE_MASK(interfaces);
++
++      sfp_parse_support(phydev->sfp_bus, id, support, interfaces);
++      iface = sfp_select_interface(phydev->sfp_bus, support);
++
++      dev_info(&phydev->mdio.dev, "%s SFP module inserted\n", phy_modes(iface));
++
++      switch (iface) {
++      case PHY_INTERFACE_MODE_1000BASEX:
++      case PHY_INTERFACE_MODE_100BASEX:
++              /* Set PHY mode to PSGMII combo (1/4 copper + combo ports) mode */
++              ret = phy_modify(phydev,
++                               QCA807X_CHIP_CONFIGURATION,
++                               QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK,
++                               QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_FIBER);
++              /* Enable fiber mode autodection (1000Base-X or 100Base-FX) */
++              ret = phy_set_bits_mmd(phydev,
++                                     MDIO_MMD_AN,
++                                     QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION,
++                                     QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION_EN);
++              /* Select fiber page */
++              ret = phy_clear_bits(phydev,
++                                   QCA807X_CHIP_CONFIGURATION,
++                                   QCA807X_BT_BX_REG_SEL);
++
++              phydev->port = PORT_FIBRE;
++              break;
++      default:
++              dev_err(&phydev->mdio.dev, "Incompatible SFP module inserted\n");
++              return -EINVAL;
++      }
++
++      return ret;
++}
++
++static void qca807x_sfp_remove(void *upstream)
++{
++      struct phy_device *phydev = upstream;
++
++      /* Select copper page */
++      phy_set_bits(phydev,
++                   QCA807X_CHIP_CONFIGURATION,
++                   QCA807X_BT_BX_REG_SEL);
++
++      phydev->port = PORT_TP;
++}
++
++static const struct sfp_upstream_ops qca807x_sfp_ops = {
++      .attach = phy_sfp_attach,
++      .detach = phy_sfp_detach,
++      .module_insert = qca807x_sfp_insert,
++      .module_remove = qca807x_sfp_remove,
++};
++
++static int qca807x_probe(struct phy_device *phydev)
++{
++      struct device_node *node = phydev->mdio.dev.of_node;
++      struct qca807x_shared_priv *shared_priv;
++      struct device *dev = &phydev->mdio.dev;
++      struct phy_package_shared *shared;
++      struct qca807x_priv *priv;
++      int ret;
++
++      ret = devm_of_phy_package_join(dev, phydev, sizeof(*shared_priv));
++      if (ret)
++              return ret;
++
++      if (phy_package_probe_once(phydev)) {
++              ret = qca807x_phy_package_probe_once(phydev);
++              if (ret)
++                      return ret;
++      }
++
++      shared = phydev->shared;
++      shared_priv = shared->priv;
++
++      /* Make sure PHY follow PHY package mode if enforced */
++      if (shared_priv->package_mode != PHY_INTERFACE_MODE_NA &&
++          phydev->interface != shared_priv->package_mode)
++              return -EINVAL;
++
++      priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++      if (!priv)
++              return -ENOMEM;
++
++      priv->dac_full_amplitude = of_property_read_bool(node, "qcom,dac-full-amplitude");
++      priv->dac_full_bias_current = of_property_read_bool(node, "qcom,dac-full-bias-current");
++      priv->dac_disable_bias_current_tweak = of_property_read_bool(node,
++                                                                   "qcom,dac-disable-bias-current-tweak");
++
++      if (IS_ENABLED(CONFIG_GPIOLIB)) {
++              /* Do not register a GPIO controller unless flagged for it */
++              if (of_property_read_bool(node, "gpio-controller")) {
++                      ret = qca807x_gpio(phydev);
++                      if (ret)
++                              return ret;
++              }
++      }
++
++      /* Attach SFP bus on combo port*/
++      if (phy_read(phydev, QCA807X_CHIP_CONFIGURATION)) {
++              ret = phy_sfp_probe(phydev, &qca807x_sfp_ops);
++              if (ret)
++                      return ret;
++              linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported);
++              linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->advertising);
++      }
++
++      phydev->priv = priv;
++
++      return 0;
++}
++
++static int qca807x_config_init(struct phy_device *phydev)
++{
++      struct qca807x_priv *priv = phydev->priv;
++      u16 control_dac;
++      int ret;
++
++      if (phy_package_init_once(phydev)) {
++              ret = qca807x_phy_package_config_init_once(phydev);
++              if (ret)
++                      return ret;
++      }
++
++      control_dac = phy_read_mmd(phydev, MDIO_MMD_AN,
++                                 QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH);
++      control_dac &= ~QCA807X_CONTROL_DAC_MASK;
++      if (!priv->dac_full_amplitude)
++              control_dac |= QCA807X_CONTROL_DAC_DSP_AMPLITUDE;
++      if (!priv->dac_full_amplitude)
++              control_dac |= QCA807X_CONTROL_DAC_DSP_BIAS_CURRENT;
++      if (!priv->dac_disable_bias_current_tweak)
++              control_dac |= QCA807X_CONTROL_DAC_BIAS_CURRENT_TWEAK;
++      return phy_write_mmd(phydev, MDIO_MMD_AN,
++                           QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH,
++                           control_dac);
++}
++
++static struct phy_driver qca807x_drivers[] = {
++      {
++              PHY_ID_MATCH_EXACT(PHY_ID_QCA8072),
++              .name           = "Qualcomm QCA8072",
++              .flags          = PHY_POLL_CABLE_TEST,
++              /* PHY_GBIT_FEATURES */
++              .probe          = qca807x_probe,
++              .config_init    = qca807x_config_init,
++              .read_status    = qca807x_read_status,
++              .config_intr    = at803x_config_intr,
++              .handle_interrupt = at803x_handle_interrupt,
++              .soft_reset     = genphy_soft_reset,
++              .get_tunable    = at803x_get_tunable,
++              .set_tunable    = at803x_set_tunable,
++              .resume         = genphy_resume,
++              .suspend        = genphy_suspend,
++              .cable_test_start       = qca807x_cable_test_start,
++              .cable_test_get_status  = qca808x_cable_test_get_status,
++      },
++      {
++              PHY_ID_MATCH_EXACT(PHY_ID_QCA8075),
++              .name           = "Qualcomm QCA8075",
++              .flags          = PHY_POLL_CABLE_TEST,
++              /* PHY_GBIT_FEATURES */
++              .probe          = qca807x_probe,
++              .config_init    = qca807x_config_init,
++              .read_status    = qca807x_read_status,
++              .config_intr    = at803x_config_intr,
++              .handle_interrupt = at803x_handle_interrupt,
++              .soft_reset     = genphy_soft_reset,
++              .get_tunable    = at803x_get_tunable,
++              .set_tunable    = at803x_set_tunable,
++              .resume         = genphy_resume,
++              .suspend        = genphy_suspend,
++              .cable_test_start       = qca807x_cable_test_start,
++              .cable_test_get_status  = qca808x_cable_test_get_status,
++      },
++};
++module_phy_driver(qca807x_drivers);
++
++static struct mdio_device_id __maybe_unused qca807x_tbl[] = {
++      { PHY_ID_MATCH_EXACT(PHY_ID_QCA8072) },
++      { PHY_ID_MATCH_EXACT(PHY_ID_QCA8075) },
++      { }
++};
++
++MODULE_AUTHOR("Robert Marko <robert.marko@sartura.hr>");
++MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
++MODULE_DESCRIPTION("Qualcomm QCA807x PHY driver");
++MODULE_DEVICE_TABLE(mdio, qca807x_tbl);
++MODULE_LICENSE("GPL");
diff --git a/target/linux/generic/backport-6.1/716-v6.9-08-net-phy-qcom-move-common-qca808x-LED-define-to-share.patch b/target/linux/generic/backport-6.1/716-v6.9-08-net-phy-qcom-move-common-qca808x-LED-define-to-share.patch
new file mode 100644 (file)
index 0000000..cf4d74e
--- /dev/null
@@ -0,0 +1,179 @@
+From ee9d9807bee0e6af8ca2a4db6f0d1dc0e5b41f44 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:11 +0100
+Subject: [PATCH 08/10] net: phy: qcom: move common qca808x LED define to
+ shared header
+
+The LED implementation of qca808x and qca807x is the same but qca807x
+supports also Fiber port and have different hw control bits for Fiber
+port.
+
+In preparation for qca807x introduction, move all the common define to
+shared header.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/qcom/qca808x.c | 65 ----------------------------------
+ drivers/net/phy/qcom/qcom.h    | 65 ++++++++++++++++++++++++++++++++++
+ 2 files changed, 65 insertions(+), 65 deletions(-)
+
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -62,29 +62,6 @@
+ #define QCA808X_DBG_AN_TEST                   0xb
+ #define QCA808X_HIBERNATION_EN                        BIT(15)
+-#define QCA808X_MMD7_LED_GLOBAL                       0x8073
+-#define QCA808X_LED_BLINK_1                   GENMASK(11, 6)
+-#define QCA808X_LED_BLINK_2                   GENMASK(5, 0)
+-/* Values are the same for both BLINK_1 and BLINK_2 */
+-#define QCA808X_LED_BLINK_FREQ_MASK           GENMASK(5, 3)
+-#define QCA808X_LED_BLINK_FREQ_2HZ            FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
+-#define QCA808X_LED_BLINK_FREQ_4HZ            FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
+-#define QCA808X_LED_BLINK_FREQ_8HZ            FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
+-#define QCA808X_LED_BLINK_FREQ_16HZ           FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
+-#define QCA808X_LED_BLINK_FREQ_32HZ           FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
+-#define QCA808X_LED_BLINK_FREQ_64HZ           FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
+-#define QCA808X_LED_BLINK_FREQ_128HZ          FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
+-#define QCA808X_LED_BLINK_FREQ_256HZ          FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
+-#define QCA808X_LED_BLINK_DUTY_MASK           GENMASK(2, 0)
+-#define QCA808X_LED_BLINK_DUTY_50_50          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
+-#define QCA808X_LED_BLINK_DUTY_75_25          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
+-#define QCA808X_LED_BLINK_DUTY_25_75          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
+-#define QCA808X_LED_BLINK_DUTY_33_67          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
+-#define QCA808X_LED_BLINK_DUTY_67_33          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
+-#define QCA808X_LED_BLINK_DUTY_17_83          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
+-#define QCA808X_LED_BLINK_DUTY_83_17          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
+-#define QCA808X_LED_BLINK_DUTY_8_92           FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
+-
+ #define QCA808X_MMD7_LED2_CTRL                        0x8074
+ #define QCA808X_MMD7_LED2_FORCE_CTRL          0x8075
+ #define QCA808X_MMD7_LED1_CTRL                        0x8076
+@@ -92,51 +69,9 @@
+ #define QCA808X_MMD7_LED0_CTRL                        0x8078
+ #define QCA808X_MMD7_LED_CTRL(x)              (0x8078 - ((x) * 2))
+-/* LED hw control pattern is the same for every LED */
+-#define QCA808X_LED_PATTERN_MASK              GENMASK(15, 0)
+-#define QCA808X_LED_SPEED2500_ON              BIT(15)
+-#define QCA808X_LED_SPEED2500_BLINK           BIT(14)
+-/* Follow blink trigger even if duplex or speed condition doesn't match */
+-#define QCA808X_LED_BLINK_CHECK_BYPASS                BIT(13)
+-#define QCA808X_LED_FULL_DUPLEX_ON            BIT(12)
+-#define QCA808X_LED_HALF_DUPLEX_ON            BIT(11)
+-#define QCA808X_LED_TX_BLINK                  BIT(10)
+-#define QCA808X_LED_RX_BLINK                  BIT(9)
+-#define QCA808X_LED_TX_ON_10MS                        BIT(8)
+-#define QCA808X_LED_RX_ON_10MS                        BIT(7)
+-#define QCA808X_LED_SPEED1000_ON              BIT(6)
+-#define QCA808X_LED_SPEED100_ON                       BIT(5)
+-#define QCA808X_LED_SPEED10_ON                        BIT(4)
+-#define QCA808X_LED_COLLISION_BLINK           BIT(3)
+-#define QCA808X_LED_SPEED1000_BLINK           BIT(2)
+-#define QCA808X_LED_SPEED100_BLINK            BIT(1)
+-#define QCA808X_LED_SPEED10_BLINK             BIT(0)
+-
+ #define QCA808X_MMD7_LED0_FORCE_CTRL          0x8079
+ #define QCA808X_MMD7_LED_FORCE_CTRL(x)                (0x8079 - ((x) * 2))
+-/* LED force ctrl is the same for every LED
+- * No documentation exist for this, not even internal one
+- * with NDA as QCOM gives only info about configuring
+- * hw control pattern rules and doesn't indicate any way
+- * to force the LED to specific mode.
+- * These define comes from reverse and testing and maybe
+- * lack of some info or some info are not entirely correct.
+- * For the basic LED control and hw control these finding
+- * are enough to support LED control in all the required APIs.
+- *
+- * On doing some comparison with implementation with qca807x,
+- * it was found that it's 1:1 equal to it and confirms all the
+- * reverse done. It was also found further specification with the
+- * force mode and the blink modes.
+- */
+-#define QCA808X_LED_FORCE_EN                  BIT(15)
+-#define QCA808X_LED_FORCE_MODE_MASK           GENMASK(14, 13)
+-#define QCA808X_LED_FORCE_BLINK_1             FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
+-#define QCA808X_LED_FORCE_BLINK_2             FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
+-#define QCA808X_LED_FORCE_ON                  FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
+-#define QCA808X_LED_FORCE_OFF                 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
+-
+ #define QCA808X_MMD7_LED_POLARITY_CTRL                0x901a
+ /* QSDK sets by default 0x46 to this reg that sets BIT 6 for
+  * LED to active high. It's not clear what BIT 3 and BIT 4 does.
+--- a/drivers/net/phy/qcom/qcom.h
++++ b/drivers/net/phy/qcom/qcom.h
+@@ -103,6 +103,71 @@
+ /* Added for reference of existence but should be handled by wait_for_completion already */
+ #define QCA808X_CDT_STATUS_STAT_BUSY          (BIT(1) | BIT(3))
++#define QCA808X_MMD7_LED_GLOBAL                       0x8073
++#define QCA808X_LED_BLINK_1                   GENMASK(11, 6)
++#define QCA808X_LED_BLINK_2                   GENMASK(5, 0)
++/* Values are the same for both BLINK_1 and BLINK_2 */
++#define QCA808X_LED_BLINK_FREQ_MASK           GENMASK(5, 3)
++#define QCA808X_LED_BLINK_FREQ_2HZ            FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
++#define QCA808X_LED_BLINK_FREQ_4HZ            FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
++#define QCA808X_LED_BLINK_FREQ_8HZ            FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
++#define QCA808X_LED_BLINK_FREQ_16HZ           FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
++#define QCA808X_LED_BLINK_FREQ_32HZ           FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
++#define QCA808X_LED_BLINK_FREQ_64HZ           FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
++#define QCA808X_LED_BLINK_FREQ_128HZ          FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
++#define QCA808X_LED_BLINK_FREQ_256HZ          FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
++#define QCA808X_LED_BLINK_DUTY_MASK           GENMASK(2, 0)
++#define QCA808X_LED_BLINK_DUTY_50_50          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
++#define QCA808X_LED_BLINK_DUTY_75_25          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
++#define QCA808X_LED_BLINK_DUTY_25_75          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
++#define QCA808X_LED_BLINK_DUTY_33_67          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
++#define QCA808X_LED_BLINK_DUTY_67_33          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
++#define QCA808X_LED_BLINK_DUTY_17_83          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
++#define QCA808X_LED_BLINK_DUTY_83_17          FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
++#define QCA808X_LED_BLINK_DUTY_8_92           FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
++
++/* LED hw control pattern is the same for every LED */
++#define QCA808X_LED_PATTERN_MASK              GENMASK(15, 0)
++#define QCA808X_LED_SPEED2500_ON              BIT(15)
++#define QCA808X_LED_SPEED2500_BLINK           BIT(14)
++/* Follow blink trigger even if duplex or speed condition doesn't match */
++#define QCA808X_LED_BLINK_CHECK_BYPASS                BIT(13)
++#define QCA808X_LED_FULL_DUPLEX_ON            BIT(12)
++#define QCA808X_LED_HALF_DUPLEX_ON            BIT(11)
++#define QCA808X_LED_TX_BLINK                  BIT(10)
++#define QCA808X_LED_RX_BLINK                  BIT(9)
++#define QCA808X_LED_TX_ON_10MS                        BIT(8)
++#define QCA808X_LED_RX_ON_10MS                        BIT(7)
++#define QCA808X_LED_SPEED1000_ON              BIT(6)
++#define QCA808X_LED_SPEED100_ON                       BIT(5)
++#define QCA808X_LED_SPEED10_ON                        BIT(4)
++#define QCA808X_LED_COLLISION_BLINK           BIT(3)
++#define QCA808X_LED_SPEED1000_BLINK           BIT(2)
++#define QCA808X_LED_SPEED100_BLINK            BIT(1)
++#define QCA808X_LED_SPEED10_BLINK             BIT(0)
++
++/* LED force ctrl is the same for every LED
++ * No documentation exist for this, not even internal one
++ * with NDA as QCOM gives only info about configuring
++ * hw control pattern rules and doesn't indicate any way
++ * to force the LED to specific mode.
++ * These define comes from reverse and testing and maybe
++ * lack of some info or some info are not entirely correct.
++ * For the basic LED control and hw control these finding
++ * are enough to support LED control in all the required APIs.
++ *
++ * On doing some comparison with implementation with qca807x,
++ * it was found that it's 1:1 equal to it and confirms all the
++ * reverse done. It was also found further specification with the
++ * force mode and the blink modes.
++ */
++#define QCA808X_LED_FORCE_EN                  BIT(15)
++#define QCA808X_LED_FORCE_MODE_MASK           GENMASK(14, 13)
++#define QCA808X_LED_FORCE_BLINK_1             FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
++#define QCA808X_LED_FORCE_BLINK_2             FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
++#define QCA808X_LED_FORCE_ON                  FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
++#define QCA808X_LED_FORCE_OFF                 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
++
+ #define AT803X_LOC_MAC_ADDR_0_15_OFFSET               0x804C
+ #define AT803X_LOC_MAC_ADDR_16_31_OFFSET      0x804B
+ #define AT803X_LOC_MAC_ADDR_32_47_OFFSET      0x804A
diff --git a/target/linux/generic/backport-6.1/716-v6.9-09-net-phy-qcom-generalize-some-qca808x-LED-functions.patch b/target/linux/generic/backport-6.1/716-v6.9-09-net-phy-qcom-generalize-some-qca808x-LED-functions.patch
new file mode 100644 (file)
index 0000000..da73c1d
--- /dev/null
@@ -0,0 +1,172 @@
+From 47b930d0dd437af927145dba50a2e2ea1ba97c67 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:12 +0100
+Subject: [PATCH 09/10] net: phy: qcom: generalize some qca808x LED functions
+
+Generalize some qca808x LED functions in preparation for qca807x LED
+support.
+
+The LED implementation of qca808x and qca807x is the same but qca807x
+supports also Fiber port and have different hw control bits for Fiber
+port. To limit code duplication introduce micro functions that takes reg
+instead of LED index to tweak all the supported LED modes.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/qcom/qca808x.c      | 38 +++-----------------
+ drivers/net/phy/qcom/qcom-phy-lib.c | 54 +++++++++++++++++++++++++++++
+ drivers/net/phy/qcom/qcom.h         |  7 ++++
+ 3 files changed, 65 insertions(+), 34 deletions(-)
+
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -437,9 +437,7 @@ static int qca808x_led_hw_control_enable
+               return -EINVAL;
+       reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+-      return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
+-                                QCA808X_LED_FORCE_EN);
++      return qca808x_led_reg_hw_control_enable(phydev, reg);
+ }
+ static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
+@@ -480,16 +478,12 @@ static int qca808x_led_hw_control_set(st
+ static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
+ {
+       u16 reg;
+-      int val;
+       if (index > 2)
+               return false;
+       reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+-      val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+-
+-      return !(val & QCA808X_LED_FORCE_EN);
++      return qca808x_led_reg_hw_control_status(phydev, reg);
+ }
+ static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
+@@ -557,44 +551,20 @@ static int qca808x_led_brightness_set(st
+       }
+       reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+-      return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+-                            QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
+-                            QCA808X_LED_FORCE_EN | (value ? QCA808X_LED_FORCE_ON :
+-                                                           QCA808X_LED_FORCE_OFF));
++      return qca808x_led_reg_brightness_set(phydev, reg, value);
+ }
+ static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
+                                unsigned long *delay_on,
+                                unsigned long *delay_off)
+ {
+-      int ret;
+       u16 reg;
+       if (index > 2)
+               return -EINVAL;
+       reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
+-
+-      /* Set blink to 50% off, 50% on at 4Hz by default */
+-      ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
+-                           QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
+-                           QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
+-      if (ret)
+-              return ret;
+-
+-      /* We use BLINK_1 for normal blinking */
+-      ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
+-                           QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
+-                           QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
+-      if (ret)
+-              return ret;
+-
+-      /* We set blink to 4Hz, aka 250ms */
+-      *delay_on = 250 / 2;
+-      *delay_off = 250 / 2;
+-
+-      return 0;
++      return qca808x_led_reg_blink_set(phydev, reg, delay_on, delay_off);
+ }
+ static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
+--- a/drivers/net/phy/qcom/qcom-phy-lib.c
++++ b/drivers/net/phy/qcom/qcom-phy-lib.c
+@@ -620,3 +620,57 @@ int qca808x_cable_test_get_status(struct
+       return 0;
+ }
+ EXPORT_SYMBOL_GPL(qca808x_cable_test_get_status);
++
++int qca808x_led_reg_hw_control_enable(struct phy_device *phydev, u16 reg)
++{
++      return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
++                                QCA808X_LED_FORCE_EN);
++}
++EXPORT_SYMBOL_GPL(qca808x_led_reg_hw_control_enable);
++
++bool qca808x_led_reg_hw_control_status(struct phy_device *phydev, u16 reg)
++{
++      int val;
++
++      val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++      return !(val & QCA808X_LED_FORCE_EN);
++}
++EXPORT_SYMBOL_GPL(qca808x_led_reg_hw_control_status);
++
++int qca808x_led_reg_brightness_set(struct phy_device *phydev,
++                                 u16 reg, enum led_brightness value)
++{
++      return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++                            QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++                            QCA808X_LED_FORCE_EN | (value ? QCA808X_LED_FORCE_ON :
++                                                            QCA808X_LED_FORCE_OFF));
++}
++EXPORT_SYMBOL_GPL(qca808x_led_reg_brightness_set);
++
++int qca808x_led_reg_blink_set(struct phy_device *phydev, u16 reg,
++                            unsigned long *delay_on,
++                            unsigned long *delay_off)
++{
++      int ret;
++
++      /* Set blink to 50% off, 50% on at 4Hz by default */
++      ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
++                           QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
++                           QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
++      if (ret)
++              return ret;
++
++      /* We use BLINK_1 for normal blinking */
++      ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
++                           QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
++                           QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
++      if (ret)
++              return ret;
++
++      /* We set blink to 4Hz, aka 250ms */
++      *delay_on = 250 / 2;
++      *delay_off = 250 / 2;
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(qca808x_led_reg_blink_set);
+--- a/drivers/net/phy/qcom/qcom.h
++++ b/drivers/net/phy/qcom/qcom.h
+@@ -234,3 +234,10 @@ int at803x_cdt_start(struct phy_device *
+ int at803x_cdt_wait_for_completion(struct phy_device *phydev,
+                                  u32 cdt_en);
+ int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished);
++int qca808x_led_reg_hw_control_enable(struct phy_device *phydev, u16 reg);
++bool qca808x_led_reg_hw_control_status(struct phy_device *phydev, u16 reg);
++int qca808x_led_reg_brightness_set(struct phy_device *phydev,
++                                 u16 reg, enum led_brightness value);
++int qca808x_led_reg_blink_set(struct phy_device *phydev, u16 reg,
++                            unsigned long *delay_on,
++                            unsigned long *delay_off);
diff --git a/target/linux/generic/backport-6.1/716-v6.9-10-net-phy-qca807x-add-support-for-configurable-LED.patch b/target/linux/generic/backport-6.1/716-v6.9-10-net-phy-qca807x-add-support-for-configurable-LED.patch
new file mode 100644 (file)
index 0000000..3bd36f6
--- /dev/null
@@ -0,0 +1,326 @@
+From f508a226b517a6a8afd78a317de46bc83e3e3d51 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 6 Feb 2024 18:31:13 +0100
+Subject: [PATCH 10/10] net: phy: qca807x: add support for configurable LED
+
+QCA8072/5 have up to 2 LEDs attached for PHY.
+
+LEDs can be configured to be ON/hw blink or be set to HW control.
+
+Hw blink mode is set to blink at 4Hz or 250ms.
+
+PHY can support both copper (TP) or fiber (FIBRE) kind and supports
+different HW control modes based on the port type.
+
+HW control modes supported for netdev trigger for copper ports are:
+- LINK_10
+- LINK_100
+- LINK_1000
+- TX
+- RX
+- FULL_DUPLEX
+- HALF_DUPLEX
+
+HW control modes supported for netdev trigger for fiber ports are:
+- LINK_100
+- LINK_1000
+- TX
+- RX
+- FULL_DUPLEX
+- HALF_DUPLEX
+
+LED support conflicts with GPIO controller feature and must be disabled
+if gpio-controller is used for the PHY.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/qcom/qca807x.c | 256 ++++++++++++++++++++++++++++++++-
+ 1 file changed, 254 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/qcom/qca807x.c
++++ b/drivers/net/phy/qcom/qca807x.c
+@@ -57,8 +57,18 @@
+ #define QCA807X_MMD7_LED_CTRL(x)                      (0x8074 + ((x) * 2))
+ #define QCA807X_MMD7_LED_FORCE_CTRL(x)                        (0x8075 + ((x) * 2))
+-#define QCA807X_GPIO_FORCE_EN                         BIT(15)
+-#define QCA807X_GPIO_FORCE_MODE_MASK                  GENMASK(14, 13)
++/* LED hw control pattern for fiber port */
++#define QCA807X_LED_FIBER_PATTERN_MASK                        GENMASK(11, 1)
++#define QCA807X_LED_FIBER_TXACT_BLK_EN                        BIT(10)
++#define QCA807X_LED_FIBER_RXACT_BLK_EN                        BIT(9)
++#define QCA807X_LED_FIBER_FDX_ON_EN                   BIT(6)
++#define QCA807X_LED_FIBER_HDX_ON_EN                   BIT(5)
++#define QCA807X_LED_FIBER_1000BX_ON_EN                        BIT(2)
++#define QCA807X_LED_FIBER_100FX_ON_EN                 BIT(1)
++
++/* Some device repurpose the LED as GPIO out */
++#define QCA807X_GPIO_FORCE_EN                         QCA808X_LED_FORCE_EN
++#define QCA807X_GPIO_FORCE_MODE_MASK                  QCA808X_LED_FORCE_MODE_MASK
+ #define QCA807X_FUNCTION_CONTROL                      0x10
+ #define QCA807X_FC_MDI_CROSSOVER_MODE_MASK            GENMASK(6, 5)
+@@ -121,6 +131,233 @@ static int qca807x_cable_test_start(stru
+       return 0;
+ }
++static int qca807x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
++                                  u16 *offload_trigger)
++{
++      /* Parsing specific to netdev trigger */
++      switch (phydev->port) {
++      case PORT_TP:
++              if (test_bit(TRIGGER_NETDEV_TX, &rules))
++                      *offload_trigger |= QCA808X_LED_TX_BLINK;
++              if (test_bit(TRIGGER_NETDEV_RX, &rules))
++                      *offload_trigger |= QCA808X_LED_RX_BLINK;
++              if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
++                      *offload_trigger |= QCA808X_LED_SPEED10_ON;
++              if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++                      *offload_trigger |= QCA808X_LED_SPEED100_ON;
++              if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++                      *offload_trigger |= QCA808X_LED_SPEED1000_ON;
++              if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
++                      *offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
++              if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
++                      *offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
++              break;
++      case PORT_FIBRE:
++              if (test_bit(TRIGGER_NETDEV_TX, &rules))
++                      *offload_trigger |= QCA807X_LED_FIBER_TXACT_BLK_EN;
++              if (test_bit(TRIGGER_NETDEV_RX, &rules))
++                      *offload_trigger |= QCA807X_LED_FIBER_RXACT_BLK_EN;
++              if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
++                      *offload_trigger |= QCA807X_LED_FIBER_100FX_ON_EN;
++              if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
++                      *offload_trigger |= QCA807X_LED_FIBER_1000BX_ON_EN;
++              if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
++                      *offload_trigger |= QCA807X_LED_FIBER_HDX_ON_EN;
++              if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
++                      *offload_trigger |= QCA807X_LED_FIBER_FDX_ON_EN;
++              break;
++      default:
++              return -EOPNOTSUPP;
++      }
++
++      if (rules && !*offload_trigger)
++              return -EOPNOTSUPP;
++
++      return 0;
++}
++
++static int qca807x_led_hw_control_enable(struct phy_device *phydev, u8 index)
++{
++      u16 reg;
++
++      if (index > 1)
++              return -EINVAL;
++
++      reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++      return qca808x_led_reg_hw_control_enable(phydev, reg);
++}
++
++static int qca807x_led_hw_is_supported(struct phy_device *phydev, u8 index,
++                                     unsigned long rules)
++{
++      u16 offload_trigger = 0;
++
++      if (index > 1)
++              return -EINVAL;
++
++      return qca807x_led_parse_netdev(phydev, rules, &offload_trigger);
++}
++
++static int qca807x_led_hw_control_set(struct phy_device *phydev, u8 index,
++                                    unsigned long rules)
++{
++      u16 reg, mask, offload_trigger = 0;
++      int ret;
++
++      if (index > 1)
++              return -EINVAL;
++
++      ret = qca807x_led_parse_netdev(phydev, rules, &offload_trigger);
++      if (ret)
++              return ret;
++
++      ret = qca807x_led_hw_control_enable(phydev, index);
++      if (ret)
++              return ret;
++
++      switch (phydev->port) {
++      case PORT_TP:
++              reg = QCA807X_MMD7_LED_CTRL(index);
++              mask = QCA808X_LED_PATTERN_MASK;
++              break;
++      case PORT_FIBRE:
++              /* HW control pattern bits are in LED FORCE reg */
++              reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++              mask = QCA807X_LED_FIBER_PATTERN_MASK;
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      return phy_modify_mmd(phydev, MDIO_MMD_AN, reg, mask,
++                            offload_trigger);
++}
++
++static bool qca807x_led_hw_control_status(struct phy_device *phydev, u8 index)
++{
++      u16 reg;
++
++      if (index > 1)
++              return false;
++
++      reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++      return qca808x_led_reg_hw_control_status(phydev, reg);
++}
++
++static int qca807x_led_hw_control_get(struct phy_device *phydev, u8 index,
++                                    unsigned long *rules)
++{
++      u16 reg;
++      int val;
++
++      if (index > 1)
++              return -EINVAL;
++
++      /* Check if we have hw control enabled */
++      if (qca807x_led_hw_control_status(phydev, index))
++              return -EINVAL;
++
++      /* Parsing specific to netdev trigger */
++      switch (phydev->port) {
++      case PORT_TP:
++              reg = QCA807X_MMD7_LED_CTRL(index);
++              val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++              if (val & QCA808X_LED_TX_BLINK)
++                      set_bit(TRIGGER_NETDEV_TX, rules);
++              if (val & QCA808X_LED_RX_BLINK)
++                      set_bit(TRIGGER_NETDEV_RX, rules);
++              if (val & QCA808X_LED_SPEED10_ON)
++                      set_bit(TRIGGER_NETDEV_LINK_10, rules);
++              if (val & QCA808X_LED_SPEED100_ON)
++                      set_bit(TRIGGER_NETDEV_LINK_100, rules);
++              if (val & QCA808X_LED_SPEED1000_ON)
++                      set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++              if (val & QCA808X_LED_HALF_DUPLEX_ON)
++                      set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
++              if (val & QCA808X_LED_FULL_DUPLEX_ON)
++                      set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
++              break;
++      case PORT_FIBRE:
++              /* HW control pattern bits are in LED FORCE reg */
++              reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++              val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
++              if (val & QCA807X_LED_FIBER_TXACT_BLK_EN)
++                      set_bit(TRIGGER_NETDEV_TX, rules);
++              if (val & QCA807X_LED_FIBER_RXACT_BLK_EN)
++                      set_bit(TRIGGER_NETDEV_RX, rules);
++              if (val & QCA807X_LED_FIBER_100FX_ON_EN)
++                      set_bit(TRIGGER_NETDEV_LINK_100, rules);
++              if (val & QCA807X_LED_FIBER_1000BX_ON_EN)
++                      set_bit(TRIGGER_NETDEV_LINK_1000, rules);
++              if (val & QCA807X_LED_FIBER_HDX_ON_EN)
++                      set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
++              if (val & QCA807X_LED_FIBER_FDX_ON_EN)
++                      set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      return 0;
++}
++
++static int qca807x_led_hw_control_reset(struct phy_device *phydev, u8 index)
++{
++      u16 reg, mask;
++
++      if (index > 1)
++              return -EINVAL;
++
++      switch (phydev->port) {
++      case PORT_TP:
++              reg = QCA807X_MMD7_LED_CTRL(index);
++              mask = QCA808X_LED_PATTERN_MASK;
++              break;
++      case PORT_FIBRE:
++              /* HW control pattern bits are in LED FORCE reg */
++              reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++              mask = QCA807X_LED_FIBER_PATTERN_MASK;
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg, mask);
++}
++
++static int qca807x_led_brightness_set(struct phy_device *phydev,
++                                    u8 index, enum led_brightness value)
++{
++      u16 reg;
++      int ret;
++
++      if (index > 1)
++              return -EINVAL;
++
++      /* If we are setting off the LED reset any hw control rule */
++      if (!value) {
++              ret = qca807x_led_hw_control_reset(phydev, index);
++              if (ret)
++                      return ret;
++      }
++
++      reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++      return qca808x_led_reg_brightness_set(phydev, reg, value);
++}
++
++static int qca807x_led_blink_set(struct phy_device *phydev, u8 index,
++                               unsigned long *delay_on,
++                               unsigned long *delay_off)
++{
++      u16 reg;
++
++      if (index > 1)
++              return -EINVAL;
++
++      reg = QCA807X_MMD7_LED_FORCE_CTRL(index);
++      return qca808x_led_reg_blink_set(phydev, reg, delay_on, delay_off);
++}
++
+ #ifdef CONFIG_GPIOLIB
+ static int qca807x_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+ {
+@@ -496,6 +733,16 @@ static int qca807x_probe(struct phy_devi
+                                                                    "qcom,dac-disable-bias-current-tweak");
+       if (IS_ENABLED(CONFIG_GPIOLIB)) {
++              /* Make sure we don't have mixed leds node and gpio-controller
++               * to prevent registering leds and having gpio-controller usage
++               * conflicting with them.
++               */
++              if (of_find_property(node, "leds", NULL) &&
++                  of_find_property(node, "gpio-controller", NULL)) {
++                      phydev_err(phydev, "Invalid property detected. LEDs and gpio-controller are mutually exclusive.");
++                      return -EINVAL;
++              }
++
+               /* Do not register a GPIO controller unless flagged for it */
+               if (of_property_read_bool(node, "gpio-controller")) {
+                       ret = qca807x_gpio(phydev);
+@@ -580,6 +827,11 @@ static struct phy_driver qca807x_drivers
+               .suspend        = genphy_suspend,
+               .cable_test_start       = qca807x_cable_test_start,
+               .cable_test_get_status  = qca808x_cable_test_get_status,
++              .led_brightness_set = qca807x_led_brightness_set,
++              .led_blink_set = qca807x_led_blink_set,
++              .led_hw_is_supported = qca807x_led_hw_is_supported,
++              .led_hw_control_set = qca807x_led_hw_control_set,
++              .led_hw_control_get = qca807x_led_hw_control_get,
+       },
+ };
+ module_phy_driver(qca807x_drivers);
index c958362e96d68ed9d77c893b9d0c5bd1a16ada4d..70a3a4ad5c3c5ecfb90481bbae043184924fd00c 100644 (file)
@@ -56,7 +56,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
  
        mutex_init(&dev->lock);
        INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
-@@ -2934,6 +2937,74 @@ static bool phy_drv_supports_irq(struct
+@@ -3037,6 +3040,74 @@ static bool phy_drv_supports_irq(struct
        return phydrv->config_intr && phydrv->handle_interrupt;
  }
  
@@ -131,7 +131,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
  /**
   * fwnode_mdio_find_device - Given a fwnode, find the mdio_device
   * @fwnode: pointer to the mdio_device's fwnode
-@@ -3112,6 +3183,11 @@ static int phy_probe(struct device *dev)
+@@ -3215,6 +3286,11 @@ static int phy_probe(struct device *dev)
        /* Set the state to READY by default */
        phydev->state = PHY_READY;
  
@@ -153,7 +153,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
  #include <linux/linkmode.h>
  #include <linux/netlink.h>
  #include <linux/mdio.h>
-@@ -603,6 +604,7 @@ struct macsec_ops;
+@@ -606,6 +607,7 @@ struct macsec_ops;
   * @phy_num_led_triggers: Number of triggers in @phy_led_triggers
   * @led_link_trigger: LED trigger for link up/down
   * @last_triggered: last LED trigger for link speed
@@ -161,7 +161,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
   * @master_slave_set: User requested master/slave configuration
   * @master_slave_get: Current master/slave advertisement
   * @master_slave_state: Current master/slave configuration
-@@ -695,6 +697,7 @@ struct phy_device {
+@@ -698,6 +700,7 @@ struct phy_device {
  
        struct phy_led_trigger *led_link_trigger;
  #endif
@@ -169,7 +169,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
  
        /*
         * Interrupt number for this PHY
-@@ -769,6 +772,19 @@ struct phy_tdr_config {
+@@ -772,6 +775,19 @@ struct phy_tdr_config {
  #define PHY_PAIR_ALL -1
  
  /**
index 3c2477a63a13c697f8f1927771f25acdf06ed353..e2e7bd65b1edd32e0fa68a8bf9e97a28737eea03 100644 (file)
@@ -20,7 +20,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
 
 --- a/drivers/net/phy/phy_device.c
 +++ b/drivers/net/phy/phy_device.c
-@@ -2937,11 +2937,18 @@ static bool phy_drv_supports_irq(struct
+@@ -3040,11 +3040,18 @@ static bool phy_drv_supports_irq(struct
        return phydrv->config_intr && phydrv->handle_interrupt;
  }
  
@@ -41,7 +41,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
  }
  
  static int of_phy_led(struct phy_device *phydev,
-@@ -2958,12 +2965,14 @@ static int of_phy_led(struct phy_device
+@@ -3061,12 +3068,14 @@ static int of_phy_led(struct phy_device
                return -ENOMEM;
  
        cdev = &phyled->led_cdev;
@@ -59,7 +59,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
        init_data.fwnode = of_fwnode_handle(led);
 --- a/include/linux/phy.h
 +++ b/include/linux/phy.h
-@@ -775,15 +775,19 @@ struct phy_tdr_config {
+@@ -778,15 +778,19 @@ struct phy_tdr_config {
   * struct phy_led: An LED driven by the PHY
   *
   * @list: List of LEDs
@@ -79,7 +79,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
  /**
   * struct phy_driver - Driver structure for a particular PHY type
   *
-@@ -998,6 +1002,15 @@ struct phy_driver {
+@@ -1001,6 +1005,15 @@ struct phy_driver {
        int (*get_sqi)(struct phy_device *dev);
        /** @get_sqi_max: Get the maximum signal quality indication */
        int (*get_sqi_max)(struct phy_device *dev);
index 35d83d8a6a507d1469922e5905b8b0253a8cd429..804caf5d3565a3e879efb7412d57f38160c5736f 100644 (file)
@@ -18,7 +18,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
 
 --- a/drivers/net/phy/phy_device.c
 +++ b/drivers/net/phy/phy_device.c
-@@ -2951,6 +2951,22 @@ static int phy_led_set_brightness(struct
+@@ -3054,6 +3054,22 @@ static int phy_led_set_brightness(struct
        return err;
  }
  
@@ -41,7 +41,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
  static int of_phy_led(struct phy_device *phydev,
                      struct device_node *led)
  {
-@@ -2973,6 +2989,8 @@ static int of_phy_led(struct phy_device
+@@ -3076,6 +3092,8 @@ static int of_phy_led(struct phy_device
  
        if (phydev->drv->led_brightness_set)
                cdev->brightness_set_blocking = phy_led_set_brightness;
@@ -52,7 +52,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
        init_data.fwnode = of_fwnode_handle(led);
 --- a/include/linux/phy.h
 +++ b/include/linux/phy.h
-@@ -1011,6 +1011,18 @@ struct phy_driver {
+@@ -1014,6 +1014,18 @@ struct phy_driver {
         */
        int (*led_brightness_set)(struct phy_device *dev,
                                  u8 index, enum led_brightness value);
index 66dbcb8a904c95969882486b502e1559703be39b..226c135f3c8ca6705e328f0169f71ea3f1ea69a0 100644 (file)
@@ -53,7 +53,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
        tristate "MDIO Bus/PHY emulation with fixed speed/link PHYs"
 --- a/drivers/net/phy/phy_device.c
 +++ b/drivers/net/phy/phy_device.c
-@@ -3213,7 +3213,8 @@ static int phy_probe(struct device *dev)
+@@ -3316,7 +3316,8 @@ static int phy_probe(struct device *dev)
        /* Get the LEDs from the device tree, and instantiate standard
         * LEDs for them.
         */
index 3710cfade2032857e14602b8616343c9ff7c489e..5bb92bc946445545c8f6d32a2cb55f8a8e40bbc9 100644 (file)
@@ -18,7 +18,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 
 --- a/drivers/net/phy/phy_device.c
 +++ b/drivers/net/phy/phy_device.c
-@@ -2974,6 +2974,7 @@ static int of_phy_led(struct phy_device
+@@ -3077,6 +3077,7 @@ static int of_phy_led(struct phy_device
        struct led_init_data init_data = {};
        struct led_classdev *cdev;
        struct phy_led *phyled;
@@ -26,7 +26,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
        int err;
  
        phyled = devm_kzalloc(dev, sizeof(*phyled), GFP_KERNEL);
-@@ -2983,10 +2984,13 @@ static int of_phy_led(struct phy_device
+@@ -3086,10 +3087,13 @@ static int of_phy_led(struct phy_device
        cdev = &phyled->led_cdev;
        phyled->phydev = phydev;
  
index 80ac785cdb48f843383e773687973310313fe4f0..f7952d9f0c3318d582715cee0c1fdc56b5219167 100644 (file)
@@ -22,7 +22,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
 
 --- a/drivers/net/phy/phy_device.c
 +++ b/drivers/net/phy/phy_device.c
-@@ -2967,6 +2967,15 @@ static int phy_led_blink_set(struct led_
+@@ -3070,6 +3070,15 @@ static int phy_led_blink_set(struct led_
        return err;
  }
  
@@ -38,7 +38,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
  static int of_phy_led(struct phy_device *phydev,
                      struct device_node *led)
  {
-@@ -3000,7 +3009,7 @@ static int of_phy_led(struct phy_device
+@@ -3103,7 +3112,7 @@ static int of_phy_led(struct phy_device
        init_data.fwnode = of_fwnode_handle(led);
        init_data.devname_mandatory = true;
  
@@ -47,7 +47,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
        if (err)
                return err;
  
-@@ -3029,6 +3038,7 @@ static int of_phy_leds(struct phy_device
+@@ -3132,6 +3141,7 @@ static int of_phy_leds(struct phy_device
                err = of_phy_led(phydev, led);
                if (err) {
                        of_node_put(led);
@@ -55,7 +55,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
                        return err;
                }
        }
-@@ -3234,6 +3244,9 @@ static int phy_remove(struct device *dev
+@@ -3337,6 +3347,9 @@ static int phy_remove(struct device *dev
  
        cancel_delayed_work_sync(&phydev->state_queue);
  
index 0bf1e03c49e990e5bdd55aa8c8911706cbc1d7dd..947db79f064f091f7dea999699bd49107f8dd191 100644 (file)
@@ -23,7 +23,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 
 --- a/drivers/net/phy/phy_device.c
 +++ b/drivers/net/phy/phy_device.c
-@@ -2967,6 +2967,61 @@ static int phy_led_blink_set(struct led_
+@@ -3070,6 +3070,61 @@ static int phy_led_blink_set(struct led_
        return err;
  }
  
@@ -85,7 +85,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
  static void phy_leds_unregister(struct phy_device *phydev)
  {
        struct phy_led *phyled;
-@@ -3004,6 +3059,19 @@ static int of_phy_led(struct phy_device
+@@ -3107,6 +3162,19 @@ static int of_phy_led(struct phy_device
                cdev->brightness_set_blocking = phy_led_set_brightness;
        if (phydev->drv->led_blink_set)
                cdev->blink_set = phy_led_blink_set;
@@ -107,7 +107,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
        init_data.fwnode = of_fwnode_handle(led);
 --- a/include/linux/phy.h
 +++ b/include/linux/phy.h
-@@ -1023,6 +1023,39 @@ struct phy_driver {
+@@ -1026,6 +1026,39 @@ struct phy_driver {
        int (*led_blink_set)(struct phy_device *dev, u8 index,
                             unsigned long *delay_on,
                             unsigned long *delay_off);
index 97fbd2bfc5bb4f34076e2207492b35d591c7c82f..1f49b3af0cc82c237626aac602d0f4639d263d6a 100644 (file)
@@ -28,7 +28,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 
 --- a/drivers/net/phy/phy_device.c
 +++ b/drivers/net/phy/phy_device.c
-@@ -3037,6 +3037,7 @@ static int of_phy_led(struct phy_device
+@@ -3140,6 +3140,7 @@ static int of_phy_led(struct phy_device
        struct device *dev = &phydev->mdio.dev;
        struct led_init_data init_data = {};
        struct led_classdev *cdev;
@@ -36,7 +36,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
        struct phy_led *phyled;
        u32 index;
        int err;
-@@ -3054,6 +3055,21 @@ static int of_phy_led(struct phy_device
+@@ -3157,6 +3158,21 @@ static int of_phy_led(struct phy_device
        if (index > U8_MAX)
                return -EINVAL;
  
@@ -60,7 +60,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
                cdev->brightness_set_blocking = phy_led_set_brightness;
 --- a/include/linux/phy.h
 +++ b/include/linux/phy.h
-@@ -788,6 +788,15 @@ struct phy_led {
+@@ -791,6 +791,15 @@ struct phy_led {
  
  #define to_phy_led(d) container_of(d, struct phy_led, led_cdev)
  
@@ -76,7 +76,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
  /**
   * struct phy_driver - Driver structure for a particular PHY type
   *
-@@ -1056,6 +1065,19 @@ struct phy_driver {
+@@ -1059,6 +1068,19 @@ struct phy_driver {
        int (*led_hw_control_get)(struct phy_device *dev, u8 index,
                                  unsigned long *rules);
  
index 3730e584ace781c50e8745dbba7eec016c10f150..7da5f21532bb2a4ae3f953a521f59e18b3901533 100644 (file)
@@ -5078,6 +5078,7 @@ CONFIG_PWRSEQ_SIMPLE=y
 # CONFIG_QCA7000_SPI is not set
 # CONFIG_QCA7000_UART is not set
 # CONFIG_QCA83XX_PHY is not set
+# CONFIG_QCA807X_PHY is not set
 # CONFIG_QCA808X_PHY is not set
 # CONFIG_QCOM_A7PLL is not set
 # CONFIG_QCOM_BAM_DMUX is not set
index b658d5205e8d2b29d15c770ac26291742f22955c..0c8810919213a73eb5cbad95041f8655db96a828 100644 (file)
@@ -11,7 +11,7 @@ Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
 
 --- a/drivers/net/phy/phy_device.c
 +++ b/drivers/net/phy/phy_device.c
-@@ -1756,6 +1756,9 @@ void phy_detach(struct phy_device *phyde
+@@ -1852,6 +1852,9 @@ void phy_detach(struct phy_device *phyde
        struct module *ndev_owner = NULL;
        struct mii_bus *bus;
  
@@ -23,7 +23,7 @@ Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
                        sysfs_remove_link(&dev->dev.kobj, "phydev");
 --- a/include/linux/phy.h
 +++ b/include/linux/phy.h
-@@ -897,6 +897,12 @@ struct phy_driver {
+@@ -900,6 +900,12 @@ struct phy_driver {
        /** @handle_interrupt: Override default interrupt handling */
        irqreturn_t (*handle_interrupt)(struct phy_device *phydev);
  
index 26d66d6d4af6a3fc98226bcc685972b1b585ed85..73ed55a0853883c11b2829278f1d73af501426d7 100644 (file)
@@ -107,7 +107,7 @@ still required by target/linux/ramips/files/drivers/net/ethernet/ralink/mdio.c
        bool tx_pause, rx_pause;
 --- a/include/linux/phy.h
 +++ b/include/linux/phy.h
-@@ -736,7 +736,7 @@ struct phy_device {
+@@ -739,7 +739,7 @@ struct phy_device {
  
        int pma_extable;
  
index 9f1c3e05f15f0ffc7e3d3844336bc8e356d26901..2594c66048fe2e20dcae0f6c68b63c15c40994fa 100644 (file)
@@ -37,7 +37,7 @@ Signed-off-by: John Crispin <blogic@openwrt.org>
                break;
 --- a/include/linux/phy.h
 +++ b/include/linux/phy.h
-@@ -644,6 +644,7 @@ struct phy_device {
+@@ -647,6 +647,7 @@ struct phy_device {
        unsigned downshifted_rate:1;
        unsigned is_on_sfp_module:1;
        unsigned mac_managed_pm:1;