--- /dev/null
+From d5e01266e7f5fa12400d4c8aa4e86fe89dcc61e9 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 19 Jun 2023 22:46:58 +0200
+Subject: [PATCH 1/3] leds: trigger: netdev: add additional specific link speed
+ mode
+
+Add additional modes for specific link speed. Use ethtool APIs to get the
+current link speed and enable the LED accordingly. Under netdev event
+handler the rtnl lock is already held and is not needed to be set to
+access ethtool APIs.
+
+This is especially useful for PHY and Switch that supports LEDs hw
+control for specific link speed. (example scenario a PHY that have 2 LED
+connected one green and one orange where the green is turned on with
+1000mbps speed and orange is turned on with 10mpbs speed)
+
+On mode set from sysfs we check if we have enabled split link speed mode
+and reject enabling generic link mode to prevent wrong and redundant
+configuration.
+
+Rework logic on the set baseline state to support these new modes to
+select if we need to turn on or off the LED.
+
+Add additional modes:
+- link_10: Turn on LED when link speed is 10mbps
+- link_100: Turn on LED when link speed is 100mbps
+- link_1000: Turn on LED when link speed is 1000mbps
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Acked-by: Lee Jones <lee@kernel.org>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 80 +++++++++++++++++++++++----
+ include/linux/leds.h | 3 +
+ 2 files changed, 73 insertions(+), 10 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -13,6 +13,7 @@
+ #include <linux/atomic.h>
+ #include <linux/ctype.h>
+ #include <linux/device.h>
++#include <linux/ethtool.h>
+ #include <linux/init.h>
+ #include <linux/jiffies.h>
+ #include <linux/kernel.h>
+@@ -21,6 +22,7 @@
+ #include <linux/module.h>
+ #include <linux/netdevice.h>
+ #include <linux/mutex.h>
++#include <linux/rtnetlink.h>
+ #include <linux/timer.h>
+ #include "../leds.h"
+
+@@ -52,6 +54,8 @@ struct led_netdev_data {
+ unsigned int last_activity;
+
+ unsigned long mode;
++ int link_speed;
++
+ bool carrier_link_up;
+ bool hw_control;
+ };
+@@ -77,7 +81,24 @@ static void set_baseline_state(struct le
+ if (!trigger_data->carrier_link_up) {
+ led_set_brightness(led_cdev, LED_OFF);
+ } else {
++ bool blink_on = false;
++
+ if (test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode))
++ blink_on = true;
++
++ if (test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) &&
++ trigger_data->link_speed == SPEED_10)
++ blink_on = true;
++
++ if (test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) &&
++ trigger_data->link_speed == SPEED_100)
++ blink_on = true;
++
++ if (test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) &&
++ trigger_data->link_speed == SPEED_1000)
++ blink_on = true;
++
++ if (blink_on)
+ led_set_brightness(led_cdev,
+ led_cdev->blink_brightness);
+ else
+@@ -161,6 +182,18 @@ static bool can_hw_control(struct led_ne
+ return true;
+ }
+
++static void get_device_state(struct led_netdev_data *trigger_data)
++{
++ struct ethtool_link_ksettings cmd;
++
++ trigger_data->carrier_link_up = netif_carrier_ok(trigger_data->net_dev);
++ if (!trigger_data->carrier_link_up)
++ return;
++
++ if (!__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd))
++ trigger_data->link_speed = cmd.base.speed;
++}
++
+ static ssize_t device_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+ {
+@@ -196,8 +229,12 @@ static int set_device_name(struct led_ne
+ dev_get_by_name(&init_net, trigger_data->device_name);
+
+ trigger_data->carrier_link_up = false;
+- if (trigger_data->net_dev != NULL)
+- trigger_data->carrier_link_up = netif_carrier_ok(trigger_data->net_dev);
++ trigger_data->link_speed = SPEED_UNKNOWN;
++ if (trigger_data->net_dev != NULL) {
++ rtnl_lock();
++ get_device_state(trigger_data);
++ rtnl_unlock();
++ }
+
+ trigger_data->last_activity = 0;
+
+@@ -234,6 +271,9 @@ static ssize_t netdev_led_attr_show(stru
+
+ switch (attr) {
+ case TRIGGER_NETDEV_LINK:
++ case TRIGGER_NETDEV_LINK_10:
++ case TRIGGER_NETDEV_LINK_100:
++ case TRIGGER_NETDEV_LINK_1000:
+ case TRIGGER_NETDEV_TX:
+ case TRIGGER_NETDEV_RX:
+ bit = attr;
+@@ -249,7 +289,7 @@ static ssize_t netdev_led_attr_store(str
+ size_t size, enum led_trigger_netdev_modes attr)
+ {
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+- unsigned long state;
++ unsigned long state, mode = trigger_data->mode;
+ int ret;
+ int bit;
+
+@@ -259,6 +299,9 @@ static ssize_t netdev_led_attr_store(str
+
+ switch (attr) {
+ case TRIGGER_NETDEV_LINK:
++ case TRIGGER_NETDEV_LINK_10:
++ case TRIGGER_NETDEV_LINK_100:
++ case TRIGGER_NETDEV_LINK_1000:
+ case TRIGGER_NETDEV_TX:
+ case TRIGGER_NETDEV_RX:
+ bit = attr;
+@@ -267,13 +310,20 @@ static ssize_t netdev_led_attr_store(str
+ return -EINVAL;
+ }
+
+- cancel_delayed_work_sync(&trigger_data->work);
+-
+ if (state)
+- set_bit(bit, &trigger_data->mode);
++ set_bit(bit, &mode);
+ else
+- clear_bit(bit, &trigger_data->mode);
++ clear_bit(bit, &mode);
++
++ if (test_bit(TRIGGER_NETDEV_LINK, &mode) &&
++ (test_bit(TRIGGER_NETDEV_LINK_10, &mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_100, &mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_1000, &mode)))
++ return -EINVAL;
++
++ cancel_delayed_work_sync(&trigger_data->work);
+
++ trigger_data->mode = mode;
+ trigger_data->hw_control = can_hw_control(trigger_data);
+
+ set_baseline_state(trigger_data);
+@@ -295,6 +345,9 @@ static ssize_t netdev_led_attr_store(str
+ static DEVICE_ATTR_RW(trigger_name)
+
+ DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK);
++DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10);
++DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100);
++DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000);
+ DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
+ DEFINE_NETDEV_TRIGGER(rx, TRIGGER_NETDEV_RX);
+
+@@ -338,6 +391,9 @@ static DEVICE_ATTR_RW(interval);
+ static struct attribute *netdev_trig_attrs[] = {
+ &dev_attr_device_name.attr,
+ &dev_attr_link.attr,
++ &dev_attr_link_10.attr,
++ &dev_attr_link_100.attr,
++ &dev_attr_link_1000.attr,
+ &dev_attr_rx.attr,
+ &dev_attr_tx.attr,
+ &dev_attr_interval.attr,
+@@ -368,9 +424,10 @@ static int netdev_trig_notify(struct not
+ mutex_lock(&trigger_data->lock);
+
+ trigger_data->carrier_link_up = false;
++ trigger_data->link_speed = SPEED_UNKNOWN;
+ switch (evt) {
+ case NETDEV_CHANGENAME:
+- trigger_data->carrier_link_up = netif_carrier_ok(dev);
++ get_device_state(trigger_data);
+ fallthrough;
+ case NETDEV_REGISTER:
+ if (trigger_data->net_dev)
+@@ -384,7 +441,7 @@ static int netdev_trig_notify(struct not
+ break;
+ case NETDEV_UP:
+ case NETDEV_CHANGE:
+- trigger_data->carrier_link_up = netif_carrier_ok(dev);
++ get_device_state(trigger_data);
+ break;
+ }
+
+@@ -427,7 +484,10 @@ static void netdev_trig_work(struct work
+ if (trigger_data->last_activity != new_activity) {
+ led_stop_software_blink(trigger_data->led_cdev);
+
+- invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode);
++ invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode);
+ interval = jiffies_to_msecs(
+ atomic_read(&trigger_data->interval));
+ /* base state is ON (link present) */
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -530,6 +530,9 @@ static inline void *led_get_trigger_data
+ /* Trigger specific enum */
+ enum led_trigger_netdev_modes {
+ TRIGGER_NETDEV_LINK = 0,
++ TRIGGER_NETDEV_LINK_10,
++ TRIGGER_NETDEV_LINK_100,
++ TRIGGER_NETDEV_LINK_1000,
+ TRIGGER_NETDEV_TX,
+ TRIGGER_NETDEV_RX,
+
--- /dev/null
+From f22f95b9ff1551c9bab13104131929f33d51f23f Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 19 Jun 2023 22:46:59 +0200
+Subject: [PATCH 2/3] leds: trigger: netdev: add additional specific link
+ duplex mode
+
+Add additional modes for specific link duplex. Use ethtool APIs to get the
+current link duplex and enable the LED accordingly. Under netdev event
+handler the rtnl lock is already held and is not needed to be set to
+access ethtool APIs.
+
+This is especially useful for PHY and Switch that supports LEDs hw
+control for specific link duplex.
+
+Add additional modes:
+- half_duplex: Turn on LED when link is half duplex
+- full_duplex: Turn on LED when link is full duplex
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Acked-by: Lee Jones <lee@kernel.org>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 27 +++++++++++++++++++++++++--
+ include/linux/leds.h | 2 ++
+ 2 files changed, 27 insertions(+), 2 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -55,6 +55,7 @@ struct led_netdev_data {
+
+ unsigned long mode;
+ int link_speed;
++ u8 duplex;
+
+ bool carrier_link_up;
+ bool hw_control;
+@@ -98,6 +99,14 @@ static void set_baseline_state(struct le
+ trigger_data->link_speed == SPEED_1000)
+ blink_on = true;
+
++ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) &&
++ trigger_data->duplex == DUPLEX_HALF)
++ blink_on = true;
++
++ if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode) &&
++ trigger_data->duplex == DUPLEX_FULL)
++ blink_on = true;
++
+ if (blink_on)
+ led_set_brightness(led_cdev,
+ led_cdev->blink_brightness);
+@@ -190,8 +199,10 @@ static void get_device_state(struct led_
+ if (!trigger_data->carrier_link_up)
+ return;
+
+- if (!__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd))
++ if (!__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd)) {
+ trigger_data->link_speed = cmd.base.speed;
++ trigger_data->duplex = cmd.base.duplex;
++ }
+ }
+
+ static ssize_t device_name_show(struct device *dev,
+@@ -230,6 +241,7 @@ static int set_device_name(struct led_ne
+
+ trigger_data->carrier_link_up = false;
+ trigger_data->link_speed = SPEED_UNKNOWN;
++ trigger_data->duplex = DUPLEX_UNKNOWN;
+ if (trigger_data->net_dev != NULL) {
+ rtnl_lock();
+ get_device_state(trigger_data);
+@@ -274,6 +286,8 @@ static ssize_t netdev_led_attr_show(stru
+ case TRIGGER_NETDEV_LINK_10:
+ case TRIGGER_NETDEV_LINK_100:
+ case TRIGGER_NETDEV_LINK_1000:
++ case TRIGGER_NETDEV_HALF_DUPLEX:
++ case TRIGGER_NETDEV_FULL_DUPLEX:
+ case TRIGGER_NETDEV_TX:
+ case TRIGGER_NETDEV_RX:
+ bit = attr;
+@@ -302,6 +316,8 @@ static ssize_t netdev_led_attr_store(str
+ case TRIGGER_NETDEV_LINK_10:
+ case TRIGGER_NETDEV_LINK_100:
+ case TRIGGER_NETDEV_LINK_1000:
++ case TRIGGER_NETDEV_HALF_DUPLEX:
++ case TRIGGER_NETDEV_FULL_DUPLEX:
+ case TRIGGER_NETDEV_TX:
+ case TRIGGER_NETDEV_RX:
+ bit = attr;
+@@ -348,6 +364,8 @@ DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETD
+ DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10);
+ DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100);
+ DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000);
++DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX);
++DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX);
+ DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
+ DEFINE_NETDEV_TRIGGER(rx, TRIGGER_NETDEV_RX);
+
+@@ -394,6 +412,8 @@ static struct attribute *netdev_trig_att
+ &dev_attr_link_10.attr,
+ &dev_attr_link_100.attr,
+ &dev_attr_link_1000.attr,
++ &dev_attr_full_duplex.attr,
++ &dev_attr_half_duplex.attr,
+ &dev_attr_rx.attr,
+ &dev_attr_tx.attr,
+ &dev_attr_interval.attr,
+@@ -425,6 +445,7 @@ static int netdev_trig_notify(struct not
+
+ trigger_data->carrier_link_up = false;
+ trigger_data->link_speed = SPEED_UNKNOWN;
++ trigger_data->duplex = DUPLEX_UNKNOWN;
+ switch (evt) {
+ case NETDEV_CHANGENAME:
+ get_device_state(trigger_data);
+@@ -487,7 +508,9 @@ static void netdev_trig_work(struct work
+ invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) ||
+- test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode);
++ test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode);
+ interval = jiffies_to_msecs(
+ atomic_read(&trigger_data->interval));
+ /* base state is ON (link present) */
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -533,6 +533,8 @@ enum led_trigger_netdev_modes {
+ TRIGGER_NETDEV_LINK_10,
+ TRIGGER_NETDEV_LINK_100,
+ TRIGGER_NETDEV_LINK_1000,
++ TRIGGER_NETDEV_HALF_DUPLEX,
++ TRIGGER_NETDEV_FULL_DUPLEX,
+ TRIGGER_NETDEV_TX,
+ TRIGGER_NETDEV_RX,
+