This commit adds a new trigger that can turn on LED when USB device gets connected to the USB port. This can be useful for various home routers that have USB port and a proper LED telling user a device is connected. Right now this trigger is usable with a proper DT only, there isn't a way to specify USB ports from user space. This may change in a future. Signed-off-by: Rafał Miłecki <zajec5@xxxxxxxxx> --- V2: The first version got support for specifying list of USB ports from user space only. There was a (big try &) discussion on adding DT support. It led to a pretty simple solution of comparing of_node of usb_device to of_node specified in usb-ports property. Since it appeared DT support may be simpler and non-DT a bit more complex, this version drops previous support for "ports" and "new_port" and focuses on DT only. The plan is to see if this solution with DT is OK, get it accepted and then work on non-DT. Felipe: if there won't be any objections I'd like to ask for your Ack. --- Documentation/devicetree/bindings/leds/common.txt | 11 ++ Documentation/leds/ledtrig-usbport.txt | 19 ++ drivers/leds/trigger/Kconfig | 8 + drivers/leds/trigger/Makefile | 1 + drivers/leds/trigger/ledtrig-usbport.c | 206 ++++++++++++++++++++++ 5 files changed, 245 insertions(+) create mode 100644 Documentation/leds/ledtrig-usbport.txt create mode 100644 drivers/leds/trigger/ledtrig-usbport.c diff --git a/Documentation/devicetree/bindings/leds/common.txt b/Documentation/devicetree/bindings/leds/common.txt index af10678..75536f7 100644 --- a/Documentation/devicetree/bindings/leds/common.txt +++ b/Documentation/devicetree/bindings/leds/common.txt @@ -50,6 +50,12 @@ property can be omitted. For controllers that have no configurable timeout the flash-max-timeout-us property can be omitted. +Trigger specific properties for child nodes: + +usbport trigger: +- usb-ports : List of USB ports that usbport should observed for turning on a + given LED. + Examples: system-status { @@ -58,6 +64,11 @@ system-status { ... }; +usb { + label = "USB"; + usb-ports = <&ohci_port1>, <&ehci_port1>; +}; + camera-flash { label = "Flash"; led-sources = <0>, <1>; diff --git a/Documentation/leds/ledtrig-usbport.txt b/Documentation/leds/ledtrig-usbport.txt new file mode 100644 index 0000000..642c4cd --- /dev/null +++ b/Documentation/leds/ledtrig-usbport.txt @@ -0,0 +1,19 @@ +USB port LED trigger +==================== + +This LED trigger can be used for signaling user a presence of USB device in a +given port. It simply turns on LED when device appears and turns it off when it +disappears. + +It requires specifying a list of USB ports that should be observed. This can be +done in DT by setting a proper property with list of a phandles. If more than +one port is specified, LED will be turned on as along as there is at least one +device connected to any of ports. + +This trigger can be activated from user space on led class devices as shown +below: + + echo usbport > trigger + +Nevertheless, current there isn't a way to specify list of USB ports from user +space. diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index 9893d91..5b8e7c7 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -126,4 +126,12 @@ config LEDS_TRIGGER_PANIC a different trigger. If unsure, say Y. +config LEDS_TRIGGER_USBPORT + tristate "USB port LED trigger" + depends on LEDS_TRIGGERS && USB && OF + help + This allows LEDs to be controlled by USB events. This trigger will + enable LED if some USB device gets connected to any of ports specified + in DT. + endif # LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile index 8cc64a4..80e2494 100644 --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o 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_USBPORT) += ledtrig-usbport.o diff --git a/drivers/leds/trigger/ledtrig-usbport.c b/drivers/leds/trigger/ledtrig-usbport.c new file mode 100644 index 0000000..97b064c --- /dev/null +++ b/drivers/leds/trigger/ledtrig-usbport.c @@ -0,0 +1,206 @@ +/* + * USB port LED trigger + * + * Copyright (C) 2016 Rafał Miłecki <rafal.milecki@xxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include <linux/device.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include "../leds.h" + +struct usbport_trig_port { + struct device_node *np; + struct list_head list; +}; + +struct usbport_trig_data { + struct led_classdev *led_cdev; + struct list_head ports; + struct notifier_block nb; + int count; /* Amount of connected matching devices */ +}; + +static int usbport_trig_check_usb_dev(struct usb_device *usb_dev, void *data) +{ + struct usbport_trig_data *usbport_data = data; + struct device_node *np = usb_dev->dev.of_node; + struct usbport_trig_port *port; + + list_for_each_entry(port, &usbport_data->ports, list) { + if (port->np == np) { + usbport_data->count++; + break; + } + } + + return 0; +} + +static int usbport_trig_get_ports(struct usbport_trig_data *usbport_data) +{ + struct led_classdev *led_cdev = usbport_data->led_cdev; + struct device *dev = led_cdev->dev; + struct device_node *np = dev->of_node; + struct of_phandle_args args; + struct usbport_trig_port *port; + int count, i; + + if (!np) + return -ENOENT; + + count = of_count_phandle_with_args(np, "usb-ports", NULL); + if (count < 0) { + dev_warn(dev, "Failed to get USB ports for %s\n", + np->full_name); + return count; + } + + if (!count) { + dev_warn(dev, "There aren't USB ports assigned to the %s\n", + np->full_name); + return -ENOENT; + } + + for (i = 0; i < count; i++) { + int err; + + err = of_parse_phandle_with_args(np, "usb-ports", NULL, i, + &args); + if (err) { + dev_err(dev, "Failed to get USB port phandle at index %d: %d\n", + i, err); + continue; + } + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (port) { + port->np = args.np; + list_add_tail(&port->list, &usbport_data->ports); + } + + of_node_put(args.np); + } + + usb_for_each_dev(usbport_data, usbport_trig_check_usb_dev); + if (usbport_data->count) + led_set_brightness_nosleep(led_cdev, LED_FULL); + + list_for_each_entry(port, &usbport_data->ports, list) { + dev_dbg(dev, "Added USB port %s\n", port->np->full_name); + } + + return 0; +} + +static bool usbport_trig_match(struct usbport_trig_data *usbport_data, + struct usb_device *usb_dev) +{ + struct device_node *np = usb_dev->dev.of_node; + struct usbport_trig_port *port; + + if (!np) + return false; + + list_for_each_entry(port, &usbport_data->ports, list) { + if (port->np == np) + return true; + } + + return false; +} + +static int usbport_trig_notify(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct usbport_trig_data *usbport_data = container_of(nb, struct usbport_trig_data, nb); + struct led_classdev *led_cdev = usbport_data->led_cdev; + + switch (action) { + case USB_DEVICE_ADD: + if (usbport_trig_match(usbport_data, data)) { + if (usbport_data->count++ == 0) + led_set_brightness_nosleep(led_cdev, LED_FULL); + } + break; + case USB_DEVICE_REMOVE: + if (usbport_trig_match(usbport_data, data)) { + if (--usbport_data->count == 0) + led_set_brightness_nosleep(led_cdev, LED_OFF); + } + break; + } + + return NOTIFY_OK; +} + +static void usbport_trig_activate(struct led_classdev *led_cdev) +{ + struct usbport_trig_data *usbport_data; + + usbport_data = kzalloc(sizeof(*usbport_data), GFP_KERNEL); + if (!usbport_data) + return; + led_cdev->trigger_data = usbport_data; + + usbport_data->led_cdev = led_cdev; + + INIT_LIST_HEAD(&usbport_data->ports); + usbport_trig_get_ports(usbport_data); + + usbport_data->nb.notifier_call = usbport_trig_notify, + usb_register_notify(&usbport_data->nb); + + led_cdev->activated = true; +} + +static void usbport_trig_deactivate(struct led_classdev *led_cdev) +{ + struct usbport_trig_data *usbport_data = led_cdev->trigger_data; + struct usbport_trig_port *port, *tmp; + + if (!led_cdev->activated) + return; + + usb_unregister_notify(&usbport_data->nb); + + list_for_each_entry_safe(port, tmp, &usbport_data->ports, list) { + list_del(&port->list); + kfree(port); + } + + kfree(usbport_data); + + led_cdev->activated = false; +} + +static struct led_trigger usbport_led_trigger = { + .name = "usbport", + .activate = usbport_trig_activate, + .deactivate = usbport_trig_deactivate, +}; + +static int __init usbport_trig_init(void) +{ + return led_trigger_register(&usbport_led_trigger); +} + +static void __exit usbport_trig_exit(void) +{ + led_trigger_unregister(&usbport_led_trigger); +} + +module_init(usbport_trig_init); +module_exit(usbport_trig_exit); + +MODULE_AUTHOR("Rafał Miłecki <rafal.milecki@xxxxxxxxx>"); +MODULE_DESCRIPTION("USB port trigger"); +MODULE_LICENSE("GPL"); -- 1.8.4.5 -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html