summaryrefslogtreecommitdiff
path: root/drivers/leds/trigger
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds/trigger')
-rw-r--r--drivers/leds/trigger/Kconfig23
-rw-r--r--drivers/leds/trigger/Makefile2
-rw-r--r--drivers/leds/trigger/ledtrig-activity.c2
-rw-r--r--drivers/leds/trigger/ledtrig-audio.c65
-rw-r--r--drivers/leds/trigger/ledtrig-default-on.c1
-rw-r--r--drivers/leds/trigger/ledtrig-input-events.c165
-rw-r--r--drivers/leds/trigger/ledtrig-netdev.c130
-rw-r--r--drivers/leds/trigger/ledtrig-panic.c23
-rw-r--r--drivers/leds/trigger/ledtrig-pattern.c126
-rw-r--r--drivers/leds/trigger/ledtrig-timer.c5
10 files changed, 409 insertions, 133 deletions
diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig
index d11d80176fc0..c11282a74b5a 100644
--- a/drivers/leds/trigger/Kconfig
+++ b/drivers/leds/trigger/Kconfig
@@ -136,13 +136,6 @@ config LEDS_TRIGGER_PATTERN
which is a series of tuples, of brightness and duration (ms).
If unsure, say N
-config LEDS_TRIGGER_AUDIO
- tristate "Audio Mute LED Trigger"
- help
- This allows LEDs to be controlled by audio drivers for following
- the audio mute and mic-mute changes.
- If unsure, say N
-
config LEDS_TRIGGER_TTY
tristate "LED Trigger for TTY devices"
depends on TTY
@@ -152,4 +145,20 @@ config LEDS_TRIGGER_TTY
When build as a module this driver will be called ledtrig-tty.
+config LEDS_TRIGGER_INPUT_EVENTS
+ tristate "LED Input events trigger"
+ depends on INPUT
+ help
+ Turn LEDs on when there is input (/dev/input/event*) activity and turn
+ them back off again after there has been no activity for 5 seconds.
+
+ This is primarily intended to control LEDs which are a backlight for
+ capacitive touch-buttons, such as e.g. the menu / home / back buttons
+ found on the bottom bezel of many older smartphones and tablets.
+
+ This can also be used to turn on the keyboard backlight LED on
+ input events and turn the keyboard backlight off again when idle.
+
+ When build as a module this driver will be called ledtrig-input-events.
+
endif # LEDS_TRIGGERS
diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile
index 25c4db97cdd4..3b3628889f68 100644
--- a/drivers/leds/trigger/Makefile
+++ b/drivers/leds/trigger/Makefile
@@ -14,5 +14,5 @@ obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o
obj-$(CONFIG_LEDS_TRIGGER_PANIC) += ledtrig-panic.o
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_INPUT_EVENTS) += ledtrig-input-events.o
diff --git a/drivers/leds/trigger/ledtrig-activity.c b/drivers/leds/trigger/ledtrig-activity.c
index 33cbf8413658..b3ee33aed36e 100644
--- a/drivers/leds/trigger/ledtrig-activity.c
+++ b/drivers/leds/trigger/ledtrig-activity.c
@@ -156,7 +156,7 @@ static ssize_t led_invert_show(struct device *dev,
{
struct activity_data *activity_data = led_trigger_get_drvdata(dev);
- return sprintf(buf, "%u\n", activity_data->invert);
+ return sprintf(buf, "%d\n", activity_data->invert);
}
static ssize_t led_invert_store(struct device *dev,
diff --git a/drivers/leds/trigger/ledtrig-audio.c b/drivers/leds/trigger/ledtrig-audio.c
deleted file mode 100644
index c6b437e6369b..000000000000
--- a/drivers/leds/trigger/ledtrig-audio.c
+++ /dev/null
@@ -1,65 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-//
-// Audio Mute LED trigger
-//
-
-#include <linux/kernel.h>
-#include <linux/leds.h>
-#include <linux/module.h>
-#include "../leds.h"
-
-static enum led_brightness audio_state[NUM_AUDIO_LEDS];
-
-static int ledtrig_audio_mute_activate(struct led_classdev *led_cdev)
-{
- led_set_brightness_nosleep(led_cdev, audio_state[LED_AUDIO_MUTE]);
- return 0;
-}
-
-static int ledtrig_audio_micmute_activate(struct led_classdev *led_cdev)
-{
- led_set_brightness_nosleep(led_cdev, audio_state[LED_AUDIO_MICMUTE]);
- return 0;
-}
-
-static struct led_trigger ledtrig_audio[NUM_AUDIO_LEDS] = {
- [LED_AUDIO_MUTE] = {
- .name = "audio-mute",
- .activate = ledtrig_audio_mute_activate,
- },
- [LED_AUDIO_MICMUTE] = {
- .name = "audio-micmute",
- .activate = ledtrig_audio_micmute_activate,
- },
-};
-
-enum led_brightness ledtrig_audio_get(enum led_audio type)
-{
- return audio_state[type];
-}
-EXPORT_SYMBOL_GPL(ledtrig_audio_get);
-
-void ledtrig_audio_set(enum led_audio type, enum led_brightness state)
-{
- audio_state[type] = state;
- led_trigger_event(&ledtrig_audio[type], state);
-}
-EXPORT_SYMBOL_GPL(ledtrig_audio_set);
-
-static int __init ledtrig_audio_init(void)
-{
- led_trigger_register(&ledtrig_audio[LED_AUDIO_MUTE]);
- led_trigger_register(&ledtrig_audio[LED_AUDIO_MICMUTE]);
- return 0;
-}
-module_init(ledtrig_audio_init);
-
-static void __exit ledtrig_audio_exit(void)
-{
- led_trigger_unregister(&ledtrig_audio[LED_AUDIO_MUTE]);
- led_trigger_unregister(&ledtrig_audio[LED_AUDIO_MICMUTE]);
-}
-module_exit(ledtrig_audio_exit);
-
-MODULE_DESCRIPTION("LED trigger for audio mute control");
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/trigger/ledtrig-default-on.c b/drivers/leds/trigger/ledtrig-default-on.c
index 8207f85eceb1..8678e64a5c33 100644
--- a/drivers/leds/trigger/ledtrig-default-on.c
+++ b/drivers/leds/trigger/ledtrig-default-on.c
@@ -28,3 +28,4 @@ module_led_trigger(defon_led_trigger);
MODULE_AUTHOR("Nick Forbes <nick.forbes@incepta.com>");
MODULE_DESCRIPTION("Default-ON LED trigger");
MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("ledtrig:default-on");
diff --git a/drivers/leds/trigger/ledtrig-input-events.c b/drivers/leds/trigger/ledtrig-input-events.c
new file mode 100644
index 000000000000..1c79731562c2
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-input-events.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Input Events LED trigger
+ *
+ * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
+ */
+
+#include <linux/input.h>
+#include <linux/jiffies.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include "../leds.h"
+
+static unsigned long led_off_delay_ms = 5000;
+module_param(led_off_delay_ms, ulong, 0644);
+MODULE_PARM_DESC(led_off_delay_ms,
+ "Specify delay in ms for turning LEDs off after last input event");
+
+static struct input_events_data {
+ struct delayed_work work;
+ spinlock_t lock;
+ /* To avoid repeatedly setting the brightness while there are events */
+ bool led_on;
+ unsigned long led_off_time;
+} input_events_data;
+
+static struct led_trigger *input_events_led_trigger;
+
+static void led_input_events_work(struct work_struct *work)
+{
+ struct input_events_data *data =
+ container_of(work, struct input_events_data, work.work);
+
+ spin_lock_irq(&data->lock);
+
+ /*
+ * This time_after_eq() check avoids a race where this work starts
+ * running before a new event pushed led_off_time back.
+ */
+ if (time_after_eq(jiffies, data->led_off_time)) {
+ led_trigger_event(input_events_led_trigger, LED_OFF);
+ data->led_on = false;
+ }
+
+ spin_unlock_irq(&data->lock);
+}
+
+static void input_events_event(struct input_handle *handle, unsigned int type,
+ unsigned int code, int val)
+{
+ struct input_events_data *data = &input_events_data;
+ unsigned long led_off_delay = msecs_to_jiffies(led_off_delay_ms);
+ unsigned long flags;
+
+ spin_lock_irqsave(&data->lock, flags);
+
+ if (!data->led_on) {
+ led_trigger_event(input_events_led_trigger, LED_FULL);
+ data->led_on = true;
+ }
+ data->led_off_time = jiffies + led_off_delay;
+
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ mod_delayed_work(system_wq, &data->work, led_off_delay);
+}
+
+static int input_events_connect(struct input_handler *handler, struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct input_handle *handle;
+ int ret;
+
+ handle = kzalloc(sizeof(*handle), GFP_KERNEL);
+ if (!handle)
+ return -ENOMEM;
+
+ handle->dev = dev;
+ handle->handler = handler;
+ handle->name = KBUILD_MODNAME;
+
+ ret = input_register_handle(handle);
+ if (ret)
+ goto err_free_handle;
+
+ ret = input_open_device(handle);
+ if (ret)
+ goto err_unregister_handle;
+
+ return 0;
+
+err_unregister_handle:
+ input_unregister_handle(handle);
+err_free_handle:
+ kfree(handle);
+ return ret;
+}
+
+static void input_events_disconnect(struct input_handle *handle)
+{
+ input_close_device(handle);
+ input_unregister_handle(handle);
+ kfree(handle);
+}
+
+static const struct input_device_id input_events_ids[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_KEY) },
+ },
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_REL) },
+ },
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_ABS) },
+ },
+ { }
+};
+
+static struct input_handler input_events_handler = {
+ .name = KBUILD_MODNAME,
+ .event = input_events_event,
+ .connect = input_events_connect,
+ .disconnect = input_events_disconnect,
+ .id_table = input_events_ids,
+};
+
+static int __init input_events_init(void)
+{
+ int ret;
+
+ INIT_DELAYED_WORK(&input_events_data.work, led_input_events_work);
+ spin_lock_init(&input_events_data.lock);
+
+ led_trigger_register_simple("input-events", &input_events_led_trigger);
+
+ ret = input_register_handler(&input_events_handler);
+ if (ret) {
+ led_trigger_unregister_simple(input_events_led_trigger);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit input_events_exit(void)
+{
+ input_unregister_handler(&input_events_handler);
+ cancel_delayed_work_sync(&input_events_data.work);
+ led_trigger_unregister_simple(input_events_led_trigger);
+}
+
+module_init(input_events_init);
+module_exit(input_events_exit);
+
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_DESCRIPTION("Input Events LED trigger");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("ledtrig:input-events");
diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
index 8e5475819590..c15efe3e5078 100644
--- a/drivers/leds/trigger/ledtrig-netdev.c
+++ b/drivers/leds/trigger/ledtrig-netdev.c
@@ -18,10 +18,12 @@
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/leds.h>
+#include <linux/linkmode.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/mutex.h>
+#include <linux/phy.h>
#include <linux/rtnetlink.h>
#include <linux/timer.h>
#include "../leds.h"
@@ -37,6 +39,8 @@
* (has carrier) or not
* tx - LED blinks on transmitted data
* rx - LED blinks on receive data
+ * tx_err - LED blinks on transmit error
+ * rx_err - LED blinks on receive error
*
* Note: If the user selects a mode that is not supported by hw, default
* behavior is to fall back to software control of the LED. However not every
@@ -65,12 +69,15 @@ struct led_netdev_data {
unsigned long mode;
int link_speed;
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(supported_link_modes);
u8 duplex;
bool carrier_link_up;
bool hw_control;
};
+static const struct attribute_group netdev_trig_link_speed_attrs_group;
+
static void set_baseline_state(struct led_netdev_data *trigger_data)
{
int current_brightness;
@@ -139,7 +146,9 @@ static void set_baseline_state(struct led_netdev_data *trigger_data)
* checking stats
*/
if (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ||
- test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode))
+ test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode))
schedule_delayed_work(&trigger_data->work, 0);
}
}
@@ -218,13 +227,20 @@ 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)
+
+ if (__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd))
return;
- if (!__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd)) {
+ if (trigger_data->carrier_link_up) {
trigger_data->link_speed = cmd.base.speed;
trigger_data->duplex = cmd.base.duplex;
}
+
+ /*
+ * Have a local copy of the link speed supported to avoid rtnl lock every time
+ * modes are refreshed on any change event
+ */
+ linkmode_copy(trigger_data->supported_link_modes, cmd.link_modes.supported);
}
static ssize_t device_name_show(struct device *dev,
@@ -277,7 +293,10 @@ static int set_device_name(struct led_netdev_data *trigger_data,
trigger_data->last_activity = 0;
- set_baseline_state(trigger_data);
+ /* Skip if we're called from netdev_trig_activate() and hw_control is true */
+ if (!trigger_data->hw_control || led_get_trigger_data(trigger_data->led_cdev))
+ set_baseline_state(trigger_data);
+
mutex_unlock(&trigger_data->lock);
rtnl_unlock();
@@ -295,6 +314,10 @@ static ssize_t device_name_store(struct device *dev,
if (ret < 0)
return ret;
+
+ /* Refresh link_speed visibility */
+ sysfs_update_group(&dev->kobj, &netdev_trig_link_speed_attrs_group);
+
return size;
}
@@ -318,6 +341,8 @@ static ssize_t netdev_led_attr_show(struct device *dev, char *buf,
case TRIGGER_NETDEV_FULL_DUPLEX:
case TRIGGER_NETDEV_TX:
case TRIGGER_NETDEV_RX:
+ case TRIGGER_NETDEV_TX_ERR:
+ case TRIGGER_NETDEV_RX_ERR:
bit = attr;
break;
default:
@@ -352,6 +377,8 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
case TRIGGER_NETDEV_FULL_DUPLEX:
case TRIGGER_NETDEV_TX:
case TRIGGER_NETDEV_RX:
+ case TRIGGER_NETDEV_TX_ERR:
+ case TRIGGER_NETDEV_RX_ERR:
bit = attr;
break;
default:
@@ -410,6 +437,8 @@ 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);
+DEFINE_NETDEV_TRIGGER(tx_err, TRIGGER_NETDEV_TX_ERR);
+DEFINE_NETDEV_TRIGGER(rx_err, TRIGGER_NETDEV_RX_ERR);
static ssize_t interval_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -458,24 +487,83 @@ static ssize_t offloaded_show(struct device *dev,
static DEVICE_ATTR_RO(offloaded);
-static struct attribute *netdev_trig_attrs[] = {
- &dev_attr_device_name.attr,
- &dev_attr_link.attr,
+#define CHECK_LINK_MODE_ATTR(link_speed) \
+ do { \
+ if (attr == &dev_attr_link_##link_speed.attr && \
+ link_ksettings.base.speed == SPEED_##link_speed) \
+ return attr->mode; \
+ } while (0)
+
+static umode_t netdev_trig_link_speed_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct led_netdev_data *trigger_data;
+ unsigned long *supported_link_modes;
+ u32 mode;
+
+ trigger_data = led_trigger_get_drvdata(dev);
+ supported_link_modes = trigger_data->supported_link_modes;
+
+ /*
+ * Search in the supported link mode mask a matching supported mode.
+ * Stop at the first matching entry as we care only to check if a particular
+ * speed is supported and not the kind.
+ */
+ for_each_set_bit(mode, supported_link_modes, __ETHTOOL_LINK_MODE_MASK_NBITS) {
+ struct ethtool_link_ksettings link_ksettings;
+
+ ethtool_params_from_link_mode(&link_ksettings, mode);
+
+ CHECK_LINK_MODE_ATTR(10);
+ CHECK_LINK_MODE_ATTR(100);
+ CHECK_LINK_MODE_ATTR(1000);
+ CHECK_LINK_MODE_ATTR(2500);
+ CHECK_LINK_MODE_ATTR(5000);
+ CHECK_LINK_MODE_ATTR(10000);
+ }
+
+ return 0;
+}
+
+static struct attribute *netdev_trig_link_speed_attrs[] = {
&dev_attr_link_10.attr,
&dev_attr_link_100.attr,
&dev_attr_link_1000.attr,
&dev_attr_link_2500.attr,
&dev_attr_link_5000.attr,
&dev_attr_link_10000.attr,
+ NULL
+};
+
+static const struct attribute_group netdev_trig_link_speed_attrs_group = {
+ .attrs = netdev_trig_link_speed_attrs,
+ .is_visible = netdev_trig_link_speed_visible,
+};
+
+static struct attribute *netdev_trig_attrs[] = {
+ &dev_attr_device_name.attr,
+ &dev_attr_link.attr,
&dev_attr_full_duplex.attr,
&dev_attr_half_duplex.attr,
&dev_attr_rx.attr,
&dev_attr_tx.attr,
+ &dev_attr_rx_err.attr,
+ &dev_attr_tx_err.attr,
&dev_attr_interval.attr,
&dev_attr_offloaded.attr,
NULL
};
-ATTRIBUTE_GROUPS(netdev_trig);
+
+static const struct attribute_group netdev_trig_attrs_group = {
+ .attrs = netdev_trig_attrs,
+};
+
+static const struct attribute_group *netdev_trig_groups[] = {
+ &netdev_trig_attrs_group,
+ &netdev_trig_link_speed_attrs_group,
+ NULL,
+};
static int netdev_trig_notify(struct notifier_block *nb,
unsigned long evt, void *dv)
@@ -484,6 +572,7 @@ static int netdev_trig_notify(struct notifier_block *nb,
netdev_notifier_info_to_dev((struct netdev_notifier_info *)dv);
struct led_netdev_data *trigger_data =
container_of(nb, struct led_netdev_data, notifier);
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE
&& evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER
@@ -504,20 +593,26 @@ static int netdev_trig_notify(struct notifier_block *nb,
trigger_data->duplex = DUPLEX_UNKNOWN;
switch (evt) {
case NETDEV_CHANGENAME:
- get_device_state(trigger_data);
- fallthrough;
case NETDEV_REGISTER:
dev_put(trigger_data->net_dev);
dev_hold(dev);
trigger_data->net_dev = dev;
+ if (evt == NETDEV_CHANGENAME)
+ get_device_state(trigger_data);
break;
case NETDEV_UNREGISTER:
dev_put(trigger_data->net_dev);
trigger_data->net_dev = NULL;
break;
case NETDEV_UP:
+ trigger_data->hw_control = can_hw_control(trigger_data);
+ fallthrough;
case NETDEV_CHANGE:
get_device_state(trigger_data);
+ /* Refresh link_speed visibility */
+ if (evt == NETDEV_CHANGE)
+ sysfs_update_group(&led_cdev->dev->kobj,
+ &netdev_trig_link_speed_attrs_group);
break;
}
@@ -547,7 +642,9 @@ static void netdev_trig_work(struct work_struct *work)
/* If we are not looking for RX/TX then return */
if (!test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) &&
- !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode))
+ !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) &&
+ !test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) &&
+ !test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode))
return;
dev_stats = dev_get_stats(trigger_data->net_dev, &temp);
@@ -555,7 +652,11 @@ static void netdev_trig_work(struct work_struct *work)
(test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ?
dev_stats->tx_packets : 0) +
(test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ?
- dev_stats->rx_packets : 0);
+ dev_stats->rx_packets : 0) +
+ (test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) ?
+ dev_stats->tx_errors : 0) +
+ (test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode) ?
+ dev_stats->rx_errors : 0);
if (trigger_data->last_activity != new_activity) {
led_stop_software_blink(trigger_data->led_cdev);
@@ -617,8 +718,8 @@ static int netdev_trig_activate(struct led_classdev *led_cdev)
if (dev) {
const char *name = dev_name(dev);
- set_device_name(trigger_data, name, strlen(name));
trigger_data->hw_control = true;
+ set_device_name(trigger_data, name, strlen(name));
rc = led_cdev->hw_control_get(led_cdev, &mode);
if (!rc)
@@ -643,8 +744,6 @@ static void netdev_trig_deactivate(struct led_classdev *led_cdev)
cancel_delayed_work_sync(&trigger_data->work);
- led_set_brightness(led_cdev, LED_OFF);
-
dev_put(trigger_data->net_dev);
kfree(trigger_data);
@@ -663,3 +762,4 @@ MODULE_AUTHOR("Ben Whitten <ben.whitten@gmail.com>");
MODULE_AUTHOR("Oliver Jowett <oliver@opencloud.com>");
MODULE_DESCRIPTION("Netdev LED trigger");
MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("ledtrig:netdev");
diff --git a/drivers/leds/trigger/ledtrig-panic.c b/drivers/leds/trigger/ledtrig-panic.c
index 5a6b21bfeb9a..1d49c1078091 100644
--- a/drivers/leds/trigger/ledtrig-panic.c
+++ b/drivers/leds/trigger/ledtrig-panic.c
@@ -21,24 +21,15 @@ static struct led_trigger *trigger;
*/
static void led_trigger_set_panic(struct led_classdev *led_cdev)
{
- struct led_trigger *trig;
+ if (led_cdev->trigger)
+ list_del(&led_cdev->trig_list);
+ list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs);
- list_for_each_entry(trig, &trigger_list, next_trig) {
- if (strcmp("panic", trig->name))
- continue;
- if (led_cdev->trigger)
- list_del(&led_cdev->trig_list);
- list_add_tail(&led_cdev->trig_list, &trig->led_cdevs);
+ /* Avoid the delayed blink path */
+ led_cdev->blink_delay_on = 0;
+ led_cdev->blink_delay_off = 0;
- /* Avoid the delayed blink path */
- led_cdev->blink_delay_on = 0;
- led_cdev->blink_delay_off = 0;
-
- led_cdev->trigger = trig;
- if (trig->activate)
- trig->activate(led_cdev);
- break;
- }
+ led_cdev->trigger = trigger;
}
static int led_trigger_panic_notifier(struct notifier_block *nb,
diff --git a/drivers/leds/trigger/ledtrig-pattern.c b/drivers/leds/trigger/ledtrig-pattern.c
index fadd87dbe993..aad48c2540fc 100644
--- a/drivers/leds/trigger/ledtrig-pattern.c
+++ b/drivers/leds/trigger/ledtrig-pattern.c
@@ -13,6 +13,7 @@
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/timer.h>
+#include <linux/hrtimer.h>
#define MAX_PATTERNS 1024
/*
@@ -21,6 +22,12 @@
*/
#define UPDATE_INTERVAL 50
+enum pattern_type {
+ PATTERN_TYPE_SW, /* Use standard timer for software pattern */
+ PATTERN_TYPE_HR, /* Use hrtimer for software pattern */
+ PATTERN_TYPE_HW, /* Hardware pattern */
+};
+
struct pattern_trig_data {
struct led_classdev *led_cdev;
struct led_pattern patterns[MAX_PATTERNS];
@@ -32,8 +39,9 @@ struct pattern_trig_data {
int last_repeat;
int delta_t;
bool is_indefinite;
- bool is_hw_pattern;
+ enum pattern_type type;
struct timer_list timer;
+ struct hrtimer hrtimer;
};
static void pattern_trig_update_patterns(struct pattern_trig_data *data)
@@ -71,10 +79,35 @@ static int pattern_trig_compute_brightness(struct pattern_trig_data *data)
return data->curr->brightness - step_brightness;
}
-static void pattern_trig_timer_function(struct timer_list *t)
+static void pattern_trig_timer_start(struct pattern_trig_data *data)
{
- struct pattern_trig_data *data = from_timer(data, t, timer);
+ if (data->type == PATTERN_TYPE_HR) {
+ hrtimer_start(&data->hrtimer, ns_to_ktime(0), HRTIMER_MODE_REL);
+ } else {
+ data->timer.expires = jiffies;
+ add_timer(&data->timer);
+ }
+}
+static void pattern_trig_timer_cancel(struct pattern_trig_data *data)
+{
+ if (data->type == PATTERN_TYPE_HR)
+ hrtimer_cancel(&data->hrtimer);
+ else
+ del_timer_sync(&data->timer);
+}
+
+static void pattern_trig_timer_restart(struct pattern_trig_data *data,
+ unsigned long interval)
+{
+ if (data->type == PATTERN_TYPE_HR)
+ hrtimer_forward_now(&data->hrtimer, ms_to_ktime(interval));
+ else
+ mod_timer(&data->timer, jiffies + msecs_to_jiffies(interval));
+}
+
+static void pattern_trig_timer_common_function(struct pattern_trig_data *data)
+{
for (;;) {
if (!data->is_indefinite && !data->repeat)
break;
@@ -83,8 +116,7 @@ static void pattern_trig_timer_function(struct timer_list *t)
/* Step change of brightness */
led_set_brightness(data->led_cdev,
data->curr->brightness);
- mod_timer(&data->timer,
- jiffies + msecs_to_jiffies(data->curr->delta_t));
+ pattern_trig_timer_restart(data, data->curr->delta_t);
if (!data->next->delta_t) {
/* Skip the tuple with zero duration */
pattern_trig_update_patterns(data);
@@ -106,8 +138,7 @@ static void pattern_trig_timer_function(struct timer_list *t)
led_set_brightness(data->led_cdev,
pattern_trig_compute_brightness(data));
- mod_timer(&data->timer,
- jiffies + msecs_to_jiffies(UPDATE_INTERVAL));
+ pattern_trig_timer_restart(data, UPDATE_INTERVAL);
/* Accumulate the gradual dimming time */
data->delta_t += UPDATE_INTERVAL;
@@ -117,6 +148,25 @@ static void pattern_trig_timer_function(struct timer_list *t)
}
}
+static void pattern_trig_timer_function(struct timer_list *t)
+{
+ struct pattern_trig_data *data = from_timer(data, t, timer);
+
+ return pattern_trig_timer_common_function(data);
+}
+
+static enum hrtimer_restart pattern_trig_hrtimer_function(struct hrtimer *t)
+{
+ struct pattern_trig_data *data =
+ container_of(t, struct pattern_trig_data, hrtimer);
+
+ pattern_trig_timer_common_function(data);
+ if (!data->is_indefinite && !data->repeat)
+ return HRTIMER_NORESTART;
+
+ return HRTIMER_RESTART;
+}
+
static int pattern_trig_start_pattern(struct led_classdev *led_cdev)
{
struct pattern_trig_data *data = led_cdev->trigger_data;
@@ -124,7 +174,7 @@ static int pattern_trig_start_pattern(struct led_classdev *led_cdev)
if (!data->npatterns)
return 0;
- if (data->is_hw_pattern) {
+ if (data->type == PATTERN_TYPE_HW) {
return led_cdev->pattern_set(led_cdev, data->patterns,
data->npatterns, data->repeat);
}
@@ -136,8 +186,7 @@ static int pattern_trig_start_pattern(struct led_classdev *led_cdev)
data->delta_t = 0;
data->curr = data->patterns;
data->next = data->patterns + 1;
- data->timer.expires = jiffies;
- add_timer(&data->timer);
+ pattern_trig_timer_start(data);
return 0;
}
@@ -175,9 +224,9 @@ static ssize_t repeat_store(struct device *dev, struct device_attribute *attr,
mutex_lock(&data->lock);
- del_timer_sync(&data->timer);
+ pattern_trig_timer_cancel(data);
- if (data->is_hw_pattern)
+ if (data->type == PATTERN_TYPE_HW)
led_cdev->pattern_clear(led_cdev);
data->last_repeat = data->repeat = res;
@@ -196,14 +245,14 @@ static ssize_t repeat_store(struct device *dev, struct device_attribute *attr,
static DEVICE_ATTR_RW(repeat);
static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data,
- char *buf, bool hw_pattern)
+ char *buf, enum pattern_type type)
{
ssize_t count = 0;
int i;
mutex_lock(&data->lock);
- if (!data->npatterns || (data->is_hw_pattern ^ hw_pattern))
+ if (!data->npatterns || data->type != type)
goto out;
for (i = 0; i < data->npatterns; i++) {
@@ -260,19 +309,19 @@ static int pattern_trig_store_patterns_int(struct pattern_trig_data *data,
static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev,
const char *buf, const u32 *buf_int,
- size_t count, bool hw_pattern)
+ size_t count, enum pattern_type type)
{
struct pattern_trig_data *data = led_cdev->trigger_data;
int err = 0;
mutex_lock(&data->lock);
- del_timer_sync(&data->timer);
+ pattern_trig_timer_cancel(data);
- if (data->is_hw_pattern)
+ if (data->type == PATTERN_TYPE_HW)
led_cdev->pattern_clear(led_cdev);
- data->is_hw_pattern = hw_pattern;
+ data->type = type;
data->npatterns = 0;
if (buf)
@@ -297,7 +346,7 @@ static ssize_t pattern_show(struct device *dev, struct device_attribute *attr,
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct pattern_trig_data *data = led_cdev->trigger_data;
- return pattern_trig_show_patterns(data, buf, false);
+ return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_SW);
}
static ssize_t pattern_store(struct device *dev, struct device_attribute *attr,
@@ -305,7 +354,8 @@ static ssize_t pattern_store(struct device *dev, struct device_attribute *attr,
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
- return pattern_trig_store_patterns(led_cdev, buf, NULL, count, false);
+ return pattern_trig_store_patterns(led_cdev, buf, NULL, count,
+ PATTERN_TYPE_SW);
}
static DEVICE_ATTR_RW(pattern);
@@ -316,7 +366,7 @@ static ssize_t hw_pattern_show(struct device *dev,
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct pattern_trig_data *data = led_cdev->trigger_data;
- return pattern_trig_show_patterns(data, buf, true);
+ return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HW);
}
static ssize_t hw_pattern_store(struct device *dev,
@@ -325,11 +375,33 @@ static ssize_t hw_pattern_store(struct device *dev,
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
- return pattern_trig_store_patterns(led_cdev, buf, NULL, count, true);
+ return pattern_trig_store_patterns(led_cdev, buf, NULL, count,
+ PATTERN_TYPE_HW);
}
static DEVICE_ATTR_RW(hw_pattern);
+static ssize_t hr_pattern_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct pattern_trig_data *data = led_cdev->trigger_data;
+
+ return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HR);
+}
+
+static ssize_t hr_pattern_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+
+ return pattern_trig_store_patterns(led_cdev, buf, NULL, count,
+ PATTERN_TYPE_HR);
+}
+
+static DEVICE_ATTR_RW(hr_pattern);
+
static umode_t pattern_trig_attrs_mode(struct kobject *kobj,
struct attribute *attr, int index)
{
@@ -338,6 +410,8 @@ static umode_t pattern_trig_attrs_mode(struct kobject *kobj,
if (attr == &dev_attr_repeat.attr || attr == &dev_attr_pattern.attr)
return attr->mode;
+ else if (attr == &dev_attr_hr_pattern.attr)
+ return attr->mode;
else if (attr == &dev_attr_hw_pattern.attr && led_cdev->pattern_set)
return attr->mode;
@@ -347,6 +421,7 @@ static umode_t pattern_trig_attrs_mode(struct kobject *kobj,
static struct attribute *pattern_trig_attrs[] = {
&dev_attr_pattern.attr,
&dev_attr_hw_pattern.attr,
+ &dev_attr_hr_pattern.attr,
&dev_attr_repeat.attr,
NULL
};
@@ -376,7 +451,8 @@ static void pattern_init(struct led_classdev *led_cdev)
goto out;
}
- err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size, false);
+ err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size,
+ PATTERN_TYPE_SW);
if (err < 0)
dev_warn(led_cdev->dev,
"Pattern initialization failed with error %d\n", err);
@@ -400,12 +476,15 @@ static int pattern_trig_activate(struct led_classdev *led_cdev)
led_cdev->pattern_clear = NULL;
}
+ data->type = PATTERN_TYPE_SW;
data->is_indefinite = true;
data->last_repeat = -1;
mutex_init(&data->lock);
data->led_cdev = led_cdev;
led_set_trigger_data(led_cdev, data);
timer_setup(&data->timer, pattern_trig_timer_function, 0);
+ hrtimer_init(&data->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ data->hrtimer.function = pattern_trig_hrtimer_function;
led_cdev->activated = true;
if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) {
@@ -431,6 +510,7 @@ static void pattern_trig_deactivate(struct led_classdev *led_cdev)
led_cdev->pattern_clear(led_cdev);
timer_shutdown_sync(&data->timer);
+ hrtimer_cancel(&data->hrtimer);
led_set_brightness(led_cdev, LED_OFF);
kfree(data);
diff --git a/drivers/leds/trigger/ledtrig-timer.c b/drivers/leds/trigger/ledtrig-timer.c
index b4688d1d9d2b..1d213c999d40 100644
--- a/drivers/leds/trigger/ledtrig-timer.c
+++ b/drivers/leds/trigger/ledtrig-timer.c
@@ -110,11 +110,6 @@ static int timer_trig_activate(struct led_classdev *led_cdev)
led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
}
- /*
- * If "set brightness to 0" is pending in workqueue, we don't
- * want that to be reordered after blink_set()
- */
- flush_work(&led_cdev->set_brightness_work);
led_blink_set(led_cdev, &led_cdev->blink_delay_on,
&led_cdev->blink_delay_off);