Backport patch adding support for LED PHY directly in PHY ops struct.
Add new PHYLIB_LEDS config and refresh patches.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
--- /dev/null
+From 4bb7aac70b5d8a4bddf4ee0791b834f9f56883d2 Mon Sep 17 00:00:00 2001
+From: Arnd Bergmann <arnd@arndb.de>
+Date: Thu, 20 Apr 2023 10:45:51 +0200
+Subject: [PATCH] net: phy: fix circular LEDS_CLASS dependencies
+
+The CONFIG_PHYLIB symbol is selected by a number of device drivers that
+need PHY support, but it now has a dependency on CONFIG_LEDS_CLASS,
+which may not be enabled, causing build failures.
+
+Avoid the risk of missing and circular dependencies by guarding the
+phylib LED support itself in another Kconfig symbol that can only be
+enabled if the dependency is met.
+
+This could be made a hidden symbol and always enabled when both CONFIG_OF
+and CONFIG_LEDS_CLASS are reachable from the phylib, but there may be an
+advantage in having users see this option when they have a misconfigured
+kernel without built-in LED support.
+
+Fixes: 01e5b728e9e4 ("net: phy: Add a binding for PHY LEDs")
+Signed-off-by: Arnd Bergmann <arnd@arndb.de>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20230420084624.3005701-1-arnd@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/Kconfig | 9 ++++++++-
+ drivers/net/phy/phy_device.c | 3 ++-
+ 2 files changed, 10 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -18,7 +18,6 @@ menuconfig PHYLIB
+ depends on NETDEVICES
+ select MDIO_DEVICE
+ select MDIO_DEVRES
+- depends on LEDS_CLASS || LEDS_CLASS=n
+ help
+ Ethernet controllers are usually attached to PHY
+ devices. This option provides infrastructure for
+@@ -45,6 +44,14 @@ config LED_TRIGGER_PHY
+ <Speed in megabits>Mbps OR <Speed in gigabits>Gbps OR link
+ for any speed known to the PHY.
+
++config PHYLIB_LEDS
++ bool "Support probing LEDs from device tree"
++ depends on LEDS_CLASS=y || LEDS_CLASS=PHYLIB
++ depends on OF
++ default y
++ help
++ When LED class support is enabled, phylib can automatically
++ probe LED setting from device tree.
+
+ config FIXED_PHY
+ tristate "MDIO Bus/PHY emulation with fixed speed/link PHYs"
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -3208,7 +3208,8 @@ static int phy_probe(struct device *dev)
+ /* Get the LEDs from the device tree, and instantiate standard
+ * LEDs for them.
+ */
+- err = of_phy_leds(phydev);
++ if (IS_ENABLED(CONFIG_PHYLIB_LEDS))
++ err = of_phy_leds(phydev);
+
+ out:
+ /* Re-assert the reset signal on error */
--- /dev/null
+From aed8fdad2152d946add50bec00a6b07c457bdcdf Mon Sep 17 00:00:00 2001
+From: Alexander Stein <alexander.stein@ew.tq-group.com>
+Date: Mon, 24 Apr 2023 16:16:48 +0200
+Subject: [PATCH] net: phy: Fix reading LED reg property
+
+'reg' is always encoded in 32 bits, thus it has to be read using the
+function with the corresponding bit width.
+
+Fixes: 01e5b728e9e4 ("net: phy: Add a binding for PHY LEDs")
+Signed-off-by: Alexander Stein <alexander.stein@ew.tq-group.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Link: https://lore.kernel.org/r/20230424141648.317944-1-alexander.stein@ew.tq-group.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phy_device.c | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -2971,6 +2971,7 @@ static int of_phy_led(struct phy_device
+ struct led_init_data init_data = {};
+ struct led_classdev *cdev;
+ struct phy_led *phyled;
++ u32 index;
+ int err;
+
+ phyled = devm_kzalloc(dev, sizeof(*phyled), GFP_KERNEL);
+@@ -2980,10 +2981,13 @@ static int of_phy_led(struct phy_device
+ cdev = &phyled->led_cdev;
+ phyled->phydev = phydev;
+
+- err = of_property_read_u8(led, "reg", &phyled->index);
++ err = of_property_read_u32(led, "reg", &index);
+ if (err)
+ return err;
++ if (index > U8_MAX)
++ return -EINVAL;
+
++ phyled->index = index;
+ if (phydev->drv->led_brightness_set)
+ cdev->brightness_set_blocking = phy_led_set_brightness;
+ if (phydev->drv->led_blink_set)
--- /dev/null
+From c938ab4da0eb1620ae3243b0b24c572ddfc318fc Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Sat, 17 Jun 2023 17:55:00 +0200
+Subject: [PATCH] net: phy: Manual remove LEDs to ensure correct ordering
+
+If the core is left to remove the LEDs via devm_, it is performed too
+late, after the PHY driver is removed from the PHY. This results in
+dereferencing a NULL pointer when the LED core tries to turn the LED
+off before destroying the LED.
+
+Manually unregister the LEDs at a safe point in phy_remove.
+
+Cc: stable@vger.kernel.org
+Reported-by: Florian Fainelli <f.fainelli@gmail.com>
+Suggested-by: Florian Fainelli <f.fainelli@gmail.com>
+Fixes: 01e5b728e9e4 ("net: phy: Add a binding for PHY LEDs")
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phy_device.c | 15 ++++++++++++++-
+ 1 file changed, 14 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -2964,6 +2964,15 @@ static int phy_led_blink_set(struct led_
+ return err;
+ }
+
++static void phy_leds_unregister(struct phy_device *phydev)
++{
++ struct phy_led *phyled;
++
++ list_for_each_entry(phyled, &phydev->leds, list) {
++ led_classdev_unregister(&phyled->led_cdev);
++ }
++}
++
+ static int of_phy_led(struct phy_device *phydev,
+ struct device_node *led)
+ {
+@@ -2997,7 +3006,7 @@ static int of_phy_led(struct phy_device
+ init_data.fwnode = of_fwnode_handle(led);
+ init_data.devname_mandatory = true;
+
+- err = devm_led_classdev_register_ext(dev, cdev, &init_data);
++ err = led_classdev_register_ext(dev, cdev, &init_data);
+ if (err)
+ return err;
+
+@@ -3026,6 +3035,7 @@ static int of_phy_leds(struct phy_device
+ err = of_phy_led(phydev, led);
+ if (err) {
+ of_node_put(led);
++ phy_leds_unregister(phydev);
+ return err;
+ }
+ }
+@@ -3229,6 +3239,9 @@ static int phy_remove(struct device *dev
+
+ cancel_delayed_work_sync(&phydev->state_queue);
+
++ if (IS_ENABLED(CONFIG_PHYLIB_LEDS))
++ phy_leds_unregister(phydev);
++
+ phydev->state = PHY_DOWN;
+
+ sfp_bus_del_upstream(phydev->sfp_bus);
--- /dev/null
+From af7320ecae0ce646fd2c4a88341a3fbc243553da Mon Sep 17 00:00:00 2001
+From: Yang Li <yang.lee@linux.alibaba.com>
+Date: Thu, 11 May 2023 15:08:20 +0800
+Subject: [PATCH] leds: trigger: netdev: Remove NULL check before dev_{put,
+ hold}
+
+The call netdev_{put, hold} of dev_{put, hold} will check NULL,
+so there is no need to check before using dev_{put, hold},
+remove it to silence the warnings:
+
+./drivers/leds/trigger/ledtrig-netdev.c:291:3-10: WARNING: NULL check before dev_{put, hold} functions is not needed.
+./drivers/leds/trigger/ledtrig-netdev.c:401:2-9: WARNING: NULL check before dev_{put, hold} functions is not needed.
+
+Reported-by: Abaci Robot <abaci@linux.alibaba.com>
+Closes: https://bugzilla.openanolis.cn/show_bug.cgi?id=4929
+Signed-off-by: Yang Li <yang.lee@linux.alibaba.com>
+Link: https://lore.kernel.org/r/20230511070820.52731-1-yang.lee@linux.alibaba.com
+Signed-off-by: Lee Jones <lee@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -462,8 +462,7 @@ static int netdev_trig_notify(struct not
+ get_device_state(trigger_data);
+ fallthrough;
+ case NETDEV_REGISTER:
+- if (trigger_data->net_dev)
+- dev_put(trigger_data->net_dev);
++ dev_put(trigger_data->net_dev);
+ dev_hold(dev);
+ trigger_data->net_dev = dev;
+ break;
+@@ -594,8 +593,7 @@ static void netdev_trig_deactivate(struc
+
+ cancel_delayed_work_sync(&trigger_data->work);
+
+- if (trigger_data->net_dev)
+- dev_put(trigger_data->net_dev);
++ dev_put(trigger_data->net_dev);
+
+ kfree(trigger_data);
+ }
--- /dev/null
+From 97c5209b3d374a25ebdb4c2ea9e9c1b121768da0 Mon Sep 17 00:00:00 2001
+From: Dan Carpenter <dan.carpenter@linaro.org>
+Date: Wed, 14 Jun 2023 10:03:59 +0300
+Subject: [PATCH] leds: trigger: netdev: uninitialized variable in
+ netdev_trig_activate()
+
+The qca8k_cled_hw_control_get() function which implements ->hw_control_get
+sets the appropriate bits but does not clear them. This leads to an
+uninitialized variable bug. Fix this by setting mode to zero at the
+start.
+
+Fixes: e0256648c831 ("net: dsa: qca8k: implement hw_control ops")
+Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Acked-by: Lee Jones <lee@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -538,7 +538,7 @@ static void netdev_trig_work(struct work
+ static int netdev_trig_activate(struct led_classdev *led_cdev)
+ {
+ struct led_netdev_data *trigger_data;
+- unsigned long mode;
++ unsigned long mode = 0;
+ struct device *dev;
+ int rc;
+
--- /dev/null
+From 7df1f14c04cbb1950e79c19793420f87227c3e80 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Tue, 8 Aug 2023 23:04:33 +0200
+Subject: [PATCH 1/4] led: trig: netdev: Fix requesting offload device
+
+When the netdev trigger is activates, it tries to determine what
+device the LED blinks for, and what the current blink mode is.
+
+The documentation for hw_control_get() says:
+
+ * Return 0 on success, a negative error number on failing parsing the
+ * initial mode. Error from this function is NOT FATAL as the device
+ * may be in a not supported initial state by the attached LED trigger.
+ */
+
+For the Marvell PHY and the Armada 370-rd board, the initial LED blink
+mode is not supported by the trigger, so it returns an error. This
+resulted in not getting the device the LED is blinking for. As a
+result, the device is unknown and offloaded is never performed.
+
+Change to condition to always get the device if offloading is
+supported, and reduce the scope of testing for an error from
+hw_control_get() to skip setting trigger internal state if there is an
+error.
+
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/20230808210436.838995-2-andrew@lunn.ch
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -564,15 +564,17 @@ static int netdev_trig_activate(struct l
+ /* Check if hw control is active by default on the LED.
+ * Init already enabled mode in hw control.
+ */
+- if (supports_hw_control(led_cdev) &&
+- !led_cdev->hw_control_get(led_cdev, &mode)) {
++ if (supports_hw_control(led_cdev)) {
+ dev = led_cdev->hw_control_get_device(led_cdev);
+ if (dev) {
+ const char *name = dev_name(dev);
+
+ set_device_name(trigger_data, name, strlen(name));
+ trigger_data->hw_control = true;
+- trigger_data->mode = mode;
++
++ rc = led_cdev->hw_control_get(led_cdev, &mode);
++ if (!rc)
++ trigger_data->mode = mode;
+ }
+ }
+
--- /dev/null
+From 1dcc03c9a7a824a31eaaecdfaa03542fb25feb6c Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Tue, 8 Aug 2023 23:04:34 +0200
+Subject: [PATCH 2/4] net: phy: phy_device: Call into the PHY driver to set LED
+ offload
+
+Linux LEDs can be requested to perform hardware accelerated blinking
+to indicate link, RX, TX etc. Pass the rules for blinking to the PHY
+driver, if it implements the ops needed to determine if a given
+pattern can be offloaded, to offload it, and what the current offload
+is. Additionally implement the op needed to get what device the LED is
+for.
+
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/20230808210436.838995-3-andrew@lunn.ch
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phy_device.c | 68 ++++++++++++++++++++++++++++++++++++
+ include/linux/phy.h | 33 +++++++++++++++++
+ 2 files changed, 101 insertions(+)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -2964,6 +2964,61 @@ static int phy_led_blink_set(struct led_
+ return err;
+ }
+
++static __maybe_unused struct device *
++phy_led_hw_control_get_device(struct led_classdev *led_cdev)
++{
++ struct phy_led *phyled = to_phy_led(led_cdev);
++ struct phy_device *phydev = phyled->phydev;
++
++ if (phydev->attached_dev)
++ return &phydev->attached_dev->dev;
++ return NULL;
++}
++
++static int __maybe_unused
++phy_led_hw_control_get(struct led_classdev *led_cdev,
++ unsigned long *rules)
++{
++ struct phy_led *phyled = to_phy_led(led_cdev);
++ struct phy_device *phydev = phyled->phydev;
++ int err;
++
++ mutex_lock(&phydev->lock);
++ err = phydev->drv->led_hw_control_get(phydev, phyled->index, rules);
++ mutex_unlock(&phydev->lock);
++
++ return err;
++}
++
++static int __maybe_unused
++phy_led_hw_control_set(struct led_classdev *led_cdev,
++ unsigned long rules)
++{
++ struct phy_led *phyled = to_phy_led(led_cdev);
++ struct phy_device *phydev = phyled->phydev;
++ int err;
++
++ mutex_lock(&phydev->lock);
++ err = phydev->drv->led_hw_control_set(phydev, phyled->index, rules);
++ mutex_unlock(&phydev->lock);
++
++ return err;
++}
++
++static __maybe_unused int phy_led_hw_is_supported(struct led_classdev *led_cdev,
++ unsigned long rules)
++{
++ struct phy_led *phyled = to_phy_led(led_cdev);
++ struct phy_device *phydev = phyled->phydev;
++ int err;
++
++ mutex_lock(&phydev->lock);
++ err = phydev->drv->led_hw_is_supported(phydev, phyled->index, rules);
++ mutex_unlock(&phydev->lock);
++
++ return err;
++}
++
+ static void phy_leds_unregister(struct phy_device *phydev)
+ {
+ struct phy_led *phyled;
+@@ -3001,6 +3056,19 @@ static int of_phy_led(struct phy_device
+ cdev->brightness_set_blocking = phy_led_set_brightness;
+ if (phydev->drv->led_blink_set)
+ cdev->blink_set = phy_led_blink_set;
++
++#ifdef CONFIG_LEDS_TRIGGERS
++ if (phydev->drv->led_hw_is_supported &&
++ phydev->drv->led_hw_control_set &&
++ phydev->drv->led_hw_control_get) {
++ cdev->hw_control_is_supported = phy_led_hw_is_supported;
++ cdev->hw_control_set = phy_led_hw_control_set;
++ cdev->hw_control_get = phy_led_hw_control_get;
++ cdev->hw_control_trigger = "netdev";
++ }
++
++ cdev->hw_control_get_device = phy_led_hw_control_get_device;
++#endif
+ cdev->max_brightness = 1;
+ init_data.devicename = dev_name(&phydev->mdio.dev);
+ init_data.fwnode = of_fwnode_handle(led);
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1013,6 +1013,39 @@ struct phy_driver {
+ int (*led_blink_set)(struct phy_device *dev, u8 index,
+ unsigned long *delay_on,
+ unsigned long *delay_off);
++ /**
++ * @led_hw_is_supported: Can the HW support the given rules.
++ * @dev: PHY device which has the LED
++ * @index: Which LED of the PHY device
++ * @rules The core is interested in these rules
++ *
++ * Return 0 if yes, -EOPNOTSUPP if not, or an error code.
++ */
++ int (*led_hw_is_supported)(struct phy_device *dev, u8 index,
++ unsigned long rules);
++ /**
++ * @led_hw_control_set: Set the HW to control the LED
++ * @dev: PHY device which has the LED
++ * @index: Which LED of the PHY device
++ * @rules The rules used to control the LED
++ *
++ * Returns 0, or a an error code.
++ */
++ int (*led_hw_control_set)(struct phy_device *dev, u8 index,
++ unsigned long rules);
++ /**
++ * @led_hw_control_get: Get how the HW is controlling the LED
++ * @dev: PHY device which has the LED
++ * @index: Which LED of the PHY device
++ * @rules Pointer to the rules used to control the LED
++ *
++ * Set *@rules to how the HW is currently blinking. Returns 0
++ * on success, or a error code if the current blinking cannot
++ * be represented in rules, or some other error happens.
++ */
++ int (*led_hw_control_get)(struct phy_device *dev, u8 index,
++ unsigned long *rules);
++
+ };
+ #define to_phy_driver(d) container_of(to_mdio_common_driver(d), \
+ struct phy_driver, mdiodrv)
--- /dev/null
+From 460b0b648fab24f576c481424e0de5479ffb9786 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Tue, 8 Aug 2023 23:04:35 +0200
+Subject: [PATCH 3/4] net: phy: marvell: Add support for offloading LED
+ blinking
+
+Add the code needed to indicate if a given blinking pattern can be
+offloaded, to offload a pattern and to try to return the current
+pattern.
+
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/20230808210436.838995-4-andrew@lunn.ch
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/marvell.c | 281 ++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 281 insertions(+)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -2893,6 +2893,272 @@ static int m88e1318_led_blink_set(struct
+ MII_88E1318S_PHY_LED_FUNC, reg);
+ }
+
++struct marvell_led_rules {
++ int mode;
++ unsigned long rules;
++};
++
++static const struct marvell_led_rules marvell_led0[] = {
++ {
++ .mode = 0,
++ .rules = BIT(TRIGGER_NETDEV_LINK),
++ },
++ {
++ .mode = 1,
++ .rules = (BIT(TRIGGER_NETDEV_LINK) |
++ BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 3,
++ .rules = (BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 4,
++ .rules = (BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 5,
++ .rules = BIT(TRIGGER_NETDEV_TX),
++ },
++ {
++ .mode = 6,
++ .rules = BIT(TRIGGER_NETDEV_LINK),
++ },
++ {
++ .mode = 7,
++ .rules = BIT(TRIGGER_NETDEV_LINK_1000),
++ },
++ {
++ .mode = 8,
++ .rules = 0,
++ },
++};
++
++static const struct marvell_led_rules marvell_led1[] = {
++ {
++ .mode = 1,
++ .rules = (BIT(TRIGGER_NETDEV_LINK) |
++ BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 2,
++ .rules = (BIT(TRIGGER_NETDEV_LINK) |
++ BIT(TRIGGER_NETDEV_RX)),
++ },
++ {
++ .mode = 3,
++ .rules = (BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 4,
++ .rules = (BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 6,
++ .rules = (BIT(TRIGGER_NETDEV_LINK_100) |
++ BIT(TRIGGER_NETDEV_LINK_1000)),
++ },
++ {
++ .mode = 7,
++ .rules = BIT(TRIGGER_NETDEV_LINK_100),
++ },
++ {
++ .mode = 8,
++ .rules = 0,
++ },
++};
++
++static const struct marvell_led_rules marvell_led2[] = {
++ {
++ .mode = 0,
++ .rules = BIT(TRIGGER_NETDEV_LINK),
++ },
++ {
++ .mode = 1,
++ .rules = (BIT(TRIGGER_NETDEV_LINK) |
++ BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 3,
++ .rules = (BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 4,
++ .rules = (BIT(TRIGGER_NETDEV_RX) |
++ BIT(TRIGGER_NETDEV_TX)),
++ },
++ {
++ .mode = 5,
++ .rules = BIT(TRIGGER_NETDEV_TX),
++ },
++ {
++ .mode = 6,
++ .rules = (BIT(TRIGGER_NETDEV_LINK_10) |
++ BIT(TRIGGER_NETDEV_LINK_1000)),
++ },
++ {
++ .mode = 7,
++ .rules = BIT(TRIGGER_NETDEV_LINK_10),
++ },
++ {
++ .mode = 8,
++ .rules = 0,
++ },
++};
++
++static int marvell_find_led_mode(unsigned long rules,
++ const struct marvell_led_rules *marvell_rules,
++ int count,
++ int *mode)
++{
++ int i;
++
++ for (i = 0; i < count; i++) {
++ if (marvell_rules[i].rules == rules) {
++ *mode = marvell_rules[i].mode;
++ return 0;
++ }
++ }
++ return -EOPNOTSUPP;
++}
++
++static int marvell_get_led_mode(u8 index, unsigned long rules, int *mode)
++{
++ int ret;
++
++ switch (index) {
++ case 0:
++ ret = marvell_find_led_mode(rules, marvell_led0,
++ ARRAY_SIZE(marvell_led0), mode);
++ break;
++ case 1:
++ ret = marvell_find_led_mode(rules, marvell_led1,
++ ARRAY_SIZE(marvell_led1), mode);
++ break;
++ case 2:
++ ret = marvell_find_led_mode(rules, marvell_led2,
++ ARRAY_SIZE(marvell_led2), mode);
++ break;
++ default:
++ ret = -EINVAL;
++ }
++
++ return ret;
++}
++
++static int marvell_find_led_rules(unsigned long *rules,
++ const struct marvell_led_rules *marvell_rules,
++ int count,
++ int mode)
++{
++ int i;
++
++ for (i = 0; i < count; i++) {
++ if (marvell_rules[i].mode == mode) {
++ *rules = marvell_rules[i].rules;
++ return 0;
++ }
++ }
++ return -EOPNOTSUPP;
++}
++
++static int marvell_get_led_rules(u8 index, unsigned long *rules, int mode)
++{
++ int ret;
++
++ switch (index) {
++ case 0:
++ ret = marvell_find_led_rules(rules, marvell_led0,
++ ARRAY_SIZE(marvell_led0), mode);
++ break;
++ case 1:
++ ret = marvell_find_led_rules(rules, marvell_led1,
++ ARRAY_SIZE(marvell_led1), mode);
++ break;
++ case 2:
++ ret = marvell_find_led_rules(rules, marvell_led2,
++ ARRAY_SIZE(marvell_led2), mode);
++ break;
++ default:
++ ret = -EOPNOTSUPP;
++ }
++
++ return ret;
++}
++
++static int m88e1318_led_hw_is_supported(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ int mode, ret;
++
++ switch (index) {
++ case 0:
++ case 1:
++ case 2:
++ ret = marvell_get_led_mode(index, rules, &mode);
++ break;
++ default:
++ ret = -EINVAL;
++ }
++
++ return ret;
++}
++
++static int m88e1318_led_hw_control_set(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ int mode, ret, reg;
++
++ switch (index) {
++ case 0:
++ case 1:
++ case 2:
++ ret = marvell_get_led_mode(index, rules, &mode);
++ break;
++ default:
++ ret = -EINVAL;
++ }
++
++ if (ret < 0)
++ return ret;
++
++ reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC);
++ if (reg < 0)
++ return reg;
++
++ reg &= ~(0xf << (4 * index));
++ reg |= mode << (4 * index);
++ return phy_write_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC, reg);
++}
++
++static int m88e1318_led_hw_control_get(struct phy_device *phydev, u8 index,
++ unsigned long *rules)
++{
++ int mode, reg;
++
++ if (index > 2)
++ return -EINVAL;
++
++ reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC);
++ if (reg < 0)
++ return reg;
++
++ mode = (reg >> (4 * index)) & 0xf;
++
++ return marvell_get_led_rules(index, rules, mode);
++}
++
+ static int marvell_probe(struct phy_device *phydev)
+ {
+ struct marvell_priv *priv;
+@@ -3144,6 +3410,9 @@ static struct phy_driver marvell_drivers
+ .get_stats = marvell_get_stats,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
++ .led_hw_is_supported = m88e1318_led_hw_is_supported,
++ .led_hw_control_set = m88e1318_led_hw_control_set,
++ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1145,
+@@ -3252,6 +3521,9 @@ static struct phy_driver marvell_drivers
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
++ .led_hw_is_supported = m88e1318_led_hw_is_supported,
++ .led_hw_control_set = m88e1318_led_hw_control_set,
++ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1540,
+@@ -3280,6 +3552,9 @@ static struct phy_driver marvell_drivers
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
++ .led_hw_is_supported = m88e1318_led_hw_is_supported,
++ .led_hw_control_set = m88e1318_led_hw_control_set,
++ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1545,
+@@ -3308,6 +3583,9 @@ static struct phy_driver marvell_drivers
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
++ .led_hw_is_supported = m88e1318_led_hw_is_supported,
++ .led_hw_control_set = m88e1318_led_hw_control_set,
++ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E3016,
+@@ -3451,6 +3729,9 @@ static struct phy_driver marvell_drivers
+ .set_tunable = m88e1540_set_tunable,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
++ .led_hw_is_supported = m88e1318_led_hw_is_supported,
++ .led_hw_control_set = m88e1318_led_hw_control_set,
++ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ };
+
--- /dev/null
+From e8fbcc47a8e935f36f044d85f21a99acecbd7bfb Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Tue, 8 Aug 2023 23:04:36 +0200
+Subject: [PATCH 4/4] leds: trig-netdev: Disable offload on deactivation of
+ trigger
+
+Ensure that the offloading of blinking is stopped when the trigger is
+deactivated. Calling led_set_brightness() is documented as stopping
+offload and setting the LED to a constant brightness.
+
+Suggested-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Simon Horman <simon.horman@corigine.com>
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://lore.kernel.org/r/20230808210436.838995-5-andrew@lunn.ch
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -595,6 +595,8 @@ static void netdev_trig_deactivate(struc
+
+ cancel_delayed_work_sync(&trigger_data->work);
+
++ led_set_brightness(led_cdev, LED_OFF);
++
+ dev_put(trigger_data->net_dev);
+
+ kfree(trigger_data);
# CONFIG_PHANTOM is not set
# CONFIG_PHONET is not set
# CONFIG_PHYLIB is not set
+# CONFIG_PHYLIB_LEDS is not set
# CONFIG_PHYS_ADDR_T_64BIT is not set
# CONFIG_PHY_CADENCE_DP is not set
# CONFIG_PHY_CADENCE_DPHY is not set
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
-@@ -62,6 +62,80 @@ config SFP
+@@ -69,6 +69,80 @@ config SFP
depends on HWMON || HWMON=n
select MDIO_I2C