@@ -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
@@ -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
new file mode 100644
@@ -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);
@@ -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);
Add Hardware Only Trigger for PHY Activity. This special trigger is used to configure and expose the different HW trigger that are provided by the PHY. Each blink mode can be configured by sysfs and on trigger activation the hardware mode is enabled. This currently implement these hw triggers: - blink_tx: Blink LED on tx packet receive - blink_rx: Blink LED on rx packet receive - link_10m: Keep LED on with 10m link speed - link_100m: Keep LED on with 100m link speed - link_1000m: Keep LED on with 1000m link speed - half_duplex: Keep LED on with half duplex link - full_duplex: Keep LED on with full duplex link - option_linkup_over: Ignore blink tx/rx with link keep not active - option_power_on_reset: Keep LED on with switch reset The trigger will check if the LED driver support the various blink modes and will expose the blink modes in sysfs. It will finally enable hw mode for the LED without configuring any rule. It's in the led driver interest the detection and knowing how to elaborate the passed flags and should report -EOPNOTSUPP otherwise. The different hw triggers are exposed in the led sysfs dir under the hardware-phy-activity subdir. Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com> --- drivers/leds/trigger/Kconfig | 27 +++ drivers/leds/trigger/Makefile | 1 + .../trigger/ledtrig-hardware-phy-activity.c | 180 ++++++++++++++++++ include/linux/leds.h | 24 +++ 4 files changed, 232 insertions(+) create mode 100644 drivers/leds/trigger/ledtrig-hardware-phy-activity.c