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,
.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,
}, {
.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 },
--- 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 */
--- /dev/null
+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,
--- /dev/null
+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);
--- /dev/null
+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);
--- /dev/null
+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);
--- /dev/null
+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");
--- /dev/null
+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
--- /dev/null
+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);
--- /dev/null
+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);
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;
}
/**
* 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;
#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
* @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
/*
* 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
/**
--- 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;
}
}
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;
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
/**
* 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);
--- 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;
}
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;
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);
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.
*/
--- 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;
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;
--- 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;
}
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;
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);
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);
--- 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;
}
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;
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);
--- 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;
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;
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)
/**
* 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);
# 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
--- 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;
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);
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;
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;