This is only a proof of concept driver that makes an LED blink on UART activity. Feedback about the concept is very welcome. Without-S-o-b-on-purpose-by: Uwe Kleine-König <u.kleine-koenig@xxxxxxxxxxxxxx> --- drivers/leds/trigger/Kconfig | 7 ++ drivers/leds/trigger/Makefile | 1 + drivers/leds/trigger/ledtrig-uart.c | 138 ++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 drivers/leds/trigger/ledtrig-uart.c diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index 4018af769969..fcea6a87adc7 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -129,4 +129,11 @@ config LEDS_TRIGGER_NETDEV This allows LEDs to be controlled by network device activity. If unsure, say Y. +config LEDS_TRIGGER_UART + tristate "LED Trigger for UART devices" + depends on SERIAL_CORE + help + This allows LEDs to be controlled by activity on serial devices like + /dev/ttyS0. + endif # LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile index f3cfe1950538..9e1341b711fd 100644 --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o 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_UART) += ledtrig-uart.o diff --git a/drivers/leds/trigger/ledtrig-uart.c b/drivers/leds/trigger/ledtrig-uart.c new file mode 100644 index 000000000000..6f0ece9829de --- /dev/null +++ b/drivers/leds/trigger/ledtrig-uart.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/module.h> +#include <linux/leds.h> +#include <linux/slab.h> +#include <linux/serial_core.h> + +struct ledtrig_uart_data { + struct led_classdev *led_cdev; + const char *devname; + struct delayed_work dwork; + struct uart_icount icount; +}; + +static void ledtrig_uart_halt(struct ledtrig_uart_data *trigger_data) +{ + cancel_delayed_work_sync(&trigger_data->dwork); +} + +static void ledtrig_uart_restart(struct ledtrig_uart_data *trigger_data) +{ + pr_info("%s:%d: devname = %s\n", __func__, __LINE__, trigger_data->devname); + if (!trigger_data->devname) + return; + + schedule_delayed_work(&trigger_data->dwork, 0); +} + +static ssize_t devname_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ledtrig_uart_data *trigger_data = led_trigger_get_drvdata(dev); + ssize_t len; + + len = sprintf(buf, "%s\n", trigger_data->devname); + + return len; +} + +static ssize_t devname_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + struct ledtrig_uart_data *trigger_data = led_trigger_get_drvdata(dev); + char *devname; + + if (size == 0 || (size == 1 || buf[0] == '\n')) { + devname = NULL; + } else { + devname = kstrndup(buf, size, GFP_KERNEL); + if (!devname) + return -ENOMEM; + + if (devname[size - 1] == '\n') + devname[size - 1] = '\0'; + } + + ledtrig_uart_halt(trigger_data); + + kfree(trigger_data->devname); + trigger_data->devname = devname; + + ledtrig_uart_restart(trigger_data); + + return size; +} +static DEVICE_ATTR_RW(devname); + +static void ledtrig_uart_work(struct work_struct *work) +{ + struct ledtrig_uart_data *trigger_data = + container_of(work, struct ledtrig_uart_data, dwork.work); + const struct uart_port *port = uart_get_port_by_name(trigger_data->devname); + + pr_info("%s:%d: devname = %s, port = %px\n", __func__, __LINE__, trigger_data->devname, port); + + if (!port) { + led_set_brightness(trigger_data->led_cdev, LED_OFF); + return; + } + + if (port->icount.rx > trigger_data->icount.rx || + port->icount.tx > trigger_data->icount.tx) { + unsigned long delay_on = 0, delay_off = 0; + + led_blink_set_oneshot(trigger_data->led_cdev, + &delay_on, &delay_off, 0); + + trigger_data->icount = port->icount; + } + + schedule_delayed_work(&trigger_data->dwork, msecs_to_jiffies(1000)); +} + +static struct attribute *ledtrig_uart_attrs[] = { + &dev_attr_devname.attr, + NULL +}; +ATTRIBUTE_GROUPS(ledtrig_uart); + +static int ledtrig_uart_activate(struct led_classdev *led_cdev) +{ + struct ledtrig_uart_data *trigger_data; + + trigger_data = kzalloc(sizeof(*trigger_data), GFP_KERNEL); + if (!trigger_data) + return -ENOMEM; + + led_set_trigger_data(led_cdev, trigger_data); + + INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_uart_work); + trigger_data->led_cdev = led_cdev; + + pr_info("%s:%d\n", __func__, __LINE__); + + return 0; +} + +static void ledtrig_uart_deactivate(struct led_classdev *led_cdev) +{ + struct ledtrig_uart_data *trigger_data = led_get_trigger_data(led_cdev); + + cancel_delayed_work_sync(&trigger_data->dwork); + + kfree(trigger_data); +} + +struct led_trigger ledtrig_uart = { + .name = "uart", + .activate = ledtrig_uart_activate, + .deactivate = ledtrig_uart_deactivate, + .groups = ledtrig_uart_groups, +}; +module_led_trigger(ledtrig_uart); + +MODULE_AUTHOR("Uwe Kleine-König <u.kleine-koenig@xxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("UART LED trigger"); +MODULE_LICENSE("GPL v2"); -- 2.19.1