diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig
index 18a8970bfae6..ea7b33995e8d 100644
--- a/drivers/leds/trigger/Kconfig
+++ b/drivers/leds/trigger/Kconfig
@@ -162,4 +162,31 @@ config LEDS_TRIGGER_TTY
 
 	  When build as a module this driver will be called ledtrig-tty.
 
+config LEDS_TRIGGER_HARDWARE_PHY_ACTIVITY
+	tristate "LED Trigger for PHY Activity for Hardware Controlled LED"
+	help
+	  This allows LEDs to run by hardware and offloaded based on some
+	  rules. The LED will blink or be on based on the PHY
+	  activity for example on packet receive or based on the link speed.
+
+	  The current supported offload triggers are:
+	  - blink_tx: Blink LED on tx packet receive
+	  - blink_rx: Blink LED on rx packet receive
+	  - keep_link_10m: Keep LED on with 10m link speed
+	  - keep_link_100m: Keep LED on with 100m link speed
+	  - keep_link_1000m: Keep LED on with 1000m link speed
+	  - keep_half_duplex: Keep LED on with half duplex link
+	  - keep_full_duplex: Keep LED on with full duplex link
+	  - option_linkup_over: Blink rules are ignored with absent link
+	  - option_power_on_reset: Power ON Led on Switch/PHY reset
+	  - option_blink_2hz: Set blink speed at 2hz for every blink event
+	  - option_blink_4hz: Set blink speed at 4hz for every blink event
+	  - option_blink_8hz: Set blink speed at 8hz for every blink event
+
+	  These blink modes are present in the LED sysfs dir under
+	  hardware-phy-activity if supported by the LED driver.
+
+	  This trigger can be used only by LEDs that support Hardware mode.
+
+
 endif # LEDS_TRIGGERS
diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile
index 25c4db97cdd4..f5d0d0057d2b 100644
--- a/drivers/leds/trigger/Makefile
+++ b/drivers/leds/trigger/Makefile
@@ -16,3 +16,4 @@ obj-$(CONFIG_LEDS_TRIGGER_NETDEV)	+= ledtrig-netdev.o
 obj-$(CONFIG_LEDS_TRIGGER_PATTERN)	+= ledtrig-pattern.o
 obj-$(CONFIG_LEDS_TRIGGER_AUDIO)	+= ledtrig-audio.o
 obj-$(CONFIG_LEDS_TRIGGER_TTY)		+= ledtrig-tty.o
+obj-$(CONFIG_LEDS_TRIGGER_HARDWARE_PHY_ACTIVITY) += ledtrig-hardware-phy-activity.o
diff --git a/drivers/leds/trigger/ledtrig-hardware-phy-activity.c b/drivers/leds/trigger/ledtrig-hardware-phy-activity.c
new file mode 100644
index 000000000000..4ac51011d029
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-hardware-phy-activity.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+
+#define PHY_ACTIVITY_MAX_BLINK_MODE 9
+
+static ssize_t blink_mode_common_show(int blink_mode, struct device *dev,
+				      struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = led_trigger_get_led(dev);
+	struct hardware_phy_activity_data *trigger_data = led_cdev->trigger_data;
+	int val;
+
+	val = test_bit(blink_mode, &trigger_data->mode);
+	return sprintf(buf, "%d\n", val ? 1 : 0);
+}
+
+static ssize_t blink_mode_common_store(int blink_mode, struct device *dev,
+				       struct device_attribute *attr,
+				       const char *buf, size_t size)
+{
+	struct led_classdev *led_cdev = led_trigger_get_led(dev);
+	struct hardware_phy_activity_data *trigger_data = led_cdev->trigger_data;
+	unsigned long state;
+	int ret;
+
+	ret = kstrtoul(buf, 0, &state);
+	if (ret)
+		return ret;
+
+	if (!!state)
+		set_bit(blink_mode, &trigger_data->mode);
+	else
+		clear_bit(blink_mode, &trigger_data->mode);
+
+	/* Update the configuration with every change */
+	led_cdev->blink_set(led_cdev, 0, 0);
+	return size;
+}
+
+#define DEFINE_HW_BLINK_MODE(blink_mode_name, blink_bit) \
+	static ssize_t blink_mode_name##_show(struct device *dev, \
+				struct device_attribute *attr, char *buf) \
+	{ \
+		return blink_mode_common_show(blink_bit, dev, attr, buf); \
+	} \
+	static ssize_t blink_mode_name##_store(struct device *dev, \
+					struct device_attribute *attr, \
+					const char *buf, size_t size) \
+	{ \
+		return blink_mode_common_store(blink_bit, dev, attr, buf, size); \
+	} \
+	DEVICE_ATTR_RW(blink_mode_name)
+
+/* Expose sysfs for every blink to be configurable from userspace */
+DEFINE_HW_BLINK_MODE(blink_tx, TRIGGER_PHY_ACTIVITY_BLINK_TX);
+DEFINE_HW_BLINK_MODE(blink_rx, TRIGGER_PHY_ACTIVITY_BLINK_RX);
+DEFINE_HW_BLINK_MODE(link_10M, TRIGGER_PHY_ACTIVITY_LINK_10M);
+DEFINE_HW_BLINK_MODE(link_100M, TRIGGER_PHY_ACTIVITY_LINK_100M);
+DEFINE_HW_BLINK_MODE(link_1000M, TRIGGER_PHY_ACTIVITY_LINK_1000M);
+DEFINE_HW_BLINK_MODE(half_duplex, TRIGGER_PHY_ACTIVITY_HALF_DUPLEX);
+DEFINE_HW_BLINK_MODE(full_duplex, TRIGGER_PHY_ACTIVITY_FULL_DUPLEX);
+DEFINE_HW_BLINK_MODE(option_linkup_over, TRIGGER_PHY_ACTIVITY_OPTION_LINKUP_OVER);
+DEFINE_HW_BLINK_MODE(option_power_on_reset, TRIGGER_PHY_ACTIVITY_OPTION_POWER_ON_RESET);
+
+static struct attribute *blink_mode_tbl[PHY_ACTIVITY_MAX_BLINK_MODE] = {
+	&dev_attr_blink_tx.attr,
+	&dev_attr_blink_rx.attr,
+	&dev_attr_link_10M.attr,
+	&dev_attr_link_100M.attr,
+	&dev_attr_link_1000M.attr,
+	&dev_attr_half_duplex.attr,
+	&dev_attr_full_duplex.attr,
+	&dev_attr_option_linkup_over.attr,
+	&dev_attr_option_power_on_reset.attr,
+};
+
+/* The attrs will be placed dynamically based on the supported triggers */
+// static struct attribute *phy_activity_attrs[PHY_ACTIVITY_MAX_TRIGGERS + 1];
+
+static int hardware_phy_activity_activate(struct led_classdev *led_cdev)
+{
+	struct hardware_phy_activity_data *trigger_data;
+	struct attribute_group *phy_activity_group;
+	struct attribute **phy_activity_attrs;
+	unsigned long supported_mode = 0;
+	int i, j, count = 0, ret;
+
+	trigger_data = kzalloc(sizeof(*trigger_data), GFP_KERNEL);
+	if (!trigger_data)
+		return -ENOMEM;
+
+	led_set_trigger_data(led_cdev, trigger_data);
+
+	/* Check supported blink modes
+	 * Request one mode at time and check if it can run in hardware mode
+	 */
+	for (i = 0; i < PHY_ACTIVITY_MAX_BLINK_MODE; i++) {
+		trigger_data->mode = 0;
+
+		set_bit(i, &trigger_data->mode);
+
+		if (!led_cdev->blink_set(led_cdev, 0, 0)) {
+			set_bit(i, &supported_mode);
+			count++;
+		}
+	}
+
+	if (!count) {
+		ret = -EINVAL;
+		goto fail_alloc_driver_data;
+	}
+
+	phy_activity_group = kzalloc(sizeof(phy_activity_group), GFP_KERNEL);
+	if (!phy_activity_group) {
+		ret = -ENOMEM;
+		goto fail_alloc_driver_data;
+	}
+
+	phy_activity_attrs = kcalloc(count + 1, sizeof(struct attribute *), GFP_KERNEL);
+	if (!phy_activity_attrs)
+		goto fail_alloc_attrs;
+
+	phy_activity_group->name = "hardware-phy-activity";
+	phy_activity_group->attrs = phy_activity_attrs;
+
+	for (i = 0, j = 0; i < count; i++)
+		if (test_bit(i, &supported_mode))
+			phy_activity_attrs[j++] = blink_mode_tbl[i];
+
+	ret = device_add_group(led_cdev->dev, phy_activity_group);
+	if (ret)
+		goto fail_alloc_group;
+
+	trigger_data->group = phy_activity_group;
+
+	/* Enable hardware mode. No custom configuration is applied,
+	 * the LED driver will use whatever default configuration is
+	 * currently configured.
+	 */
+	ret = led_cdev->hw_control_start(led_cdev);
+	if (ret)
+		goto fail_alloc_group;
+
+	return 0;
+fail_alloc_driver_data:
+	kfree(trigger_data);
+fail_alloc_group:
+	kfree(phy_activity_attrs);
+fail_alloc_attrs:
+	kfree(phy_activity_group);
+	return ret;
+}
+
+static void hardware_phy_activity_deactivate(struct led_classdev *led_cdev)
+{
+	struct hardware_phy_activity_data *trigger_data = led_get_trigger_data(led_cdev);
+
+	led_cdev->hw_control_stop(led_cdev);
+	device_remove_group(led_cdev->dev, trigger_data->group);
+	kfree(trigger_data->group->attrs);
+	kfree(trigger_data->group);
+	kfree(trigger_data);
+}
+
+static struct led_trigger hardware_phy_activity_trigger = {
+	.supported_blink_modes = HARDWARE_ONLY,
+	.name       = "hardware-phy-activity",
+	.activate   = hardware_phy_activity_activate,
+	.deactivate = hardware_phy_activity_deactivate,
+};
+
+static int __init hardware_phy_activity_init(void)
+{
+	return led_trigger_register(&hardware_phy_activity_trigger);
+}
+device_initcall(hardware_phy_activity_init);
diff --git a/include/linux/leds.h b/include/linux/leds.h
index 89ed009eecad..f6cdca894c53 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -532,6 +532,30 @@ enum led_trigger_netdev_modes {
 };
 #endif
 
+#ifdef CONFIG_LEDS_TRIGGER_HARDWARE_PHY_ACTIVITY
+/* 3 main category for offload trigger:
+ * - blink: the led will blink on trigger
+ * - keep: the led will be kept on with condition
+ * - option: a configuration value internal to the led on how offload works
+ */
+enum hardware_phy_activity {
+	TRIGGER_PHY_ACTIVITY_BLINK_TX, /* Blink on TX traffic */
+	TRIGGER_PHY_ACTIVITY_BLINK_RX, /* Blink on RX traffic */
+	TRIGGER_PHY_ACTIVITY_LINK_10M, /* LED on with 10M link */
+	TRIGGER_PHY_ACTIVITY_LINK_100M, /* LED on with 100M link */
+	TRIGGER_PHY_ACTIVITY_LINK_1000M, /* LED on with 1000M link */
+	TRIGGER_PHY_ACTIVITY_HALF_DUPLEX, /* LED on with Half-Duplex link */
+	TRIGGER_PHY_ACTIVITY_FULL_DUPLEX, /* LED on with Full-Duplex link */
+	TRIGGER_PHY_ACTIVITY_OPTION_LINKUP_OVER, /* LED will be on with KEEP blink mode and blink on BLINK traffic */
+	TRIGGER_PHY_ACTIVITY_OPTION_POWER_ON_RESET, /* LED will be on Switch reset */
+};
+
+struct hardware_phy_activity_data {
+	unsigned long mode;
+	struct attribute_group *group;
+};
+#endif
+
 /* Trigger specific functions */
 #ifdef CONFIG_LEDS_TRIGGER_DISK
 void ledtrig_disk_activity(bool write);
