lkml.org 
[lkml]   [2016]   [Jul]   [15]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH V2] leds: trigger: Introduce an USB port trigger
    Date
    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@gmail.com>
    ---
    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@gmail.com>
    + *
    + * 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@gmail.com>");
    +MODULE_DESCRIPTION("USB port trigger");
    +MODULE_LICENSE("GPL");
    --
    1.8.4.5
    \
     
     \ /
      Last update: 2016-07-16 00:01    [W:3.272 / U:1.356 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site