If the LED does not support different brightness levels, this
                should be 1.
 
+What:          /sys/class/leds/<led>/brightness_hw_changed
+Date:          January 2017
+KernelVersion: 4.11
+Description:
+               Last hardware set brightness level for this LED. Some LEDs
+               may be changed autonomously by hardware/firmware. Only LEDs
+               where this happens and the driver can detect this, will have
+               this file.
+
+               This file supports poll() to detect when the hardware changes
+               the brightness.
+
+               Reading this file will return the last brightness level set
+               by the hardware, this may be different from the current
+               brightness. Reading this file when no hw brightness change
+               event has happened will return an ENODATA error.
+
 What:          /sys/class/leds/<led>/trigger
 Date:          March 2006
 KernelVersion: 2.6.17
 
                blinking, returns -EBUSY if software blink fallback is enabled.
 
 
+LED registration API
+====================
+
+A driver wanting to register a LED classdev for use by other drivers /
+userspace needs to allocate and fill a led_classdev struct and then call
+[devm_]led_classdev_register. If the non devm version is used the driver
+must call led_classdev_unregister from its remove function before
+free-ing the led_classdev struct.
+
+If the driver can detect hardware initiated brightness changes and thus
+wants to have a brightness_hw_changed attribute then the LED_BRIGHT_HW_CHANGED
+flag must be set in flags before registering. Calling
+led_classdev_notify_brightness_hw_changed on a classdev not registered with
+the LED_BRIGHT_HW_CHANGED flag is a bug and will trigger a WARN_ON.
+
 Hardware accelerated blink of LEDs
 ==================================
 
 
        NULL,
 };
 
+#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
+static ssize_t brightness_hw_changed_show(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+
+       if (led_cdev->brightness_hw_changed == -1)
+               return -ENODATA;
+
+       return sprintf(buf, "%u\n", led_cdev->brightness_hw_changed);
+}
+
+static DEVICE_ATTR_RO(brightness_hw_changed);
+
+static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
+{
+       struct device *dev = led_cdev->dev;
+       int ret;
+
+       ret = device_create_file(dev, &dev_attr_brightness_hw_changed);
+       if (ret) {
+               dev_err(dev, "Error creating brightness_hw_changed\n");
+               return ret;
+       }
+
+       led_cdev->brightness_hw_changed_kn =
+               sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed");
+       if (!led_cdev->brightness_hw_changed_kn) {
+               dev_err(dev, "Error getting brightness_hw_changed kn\n");
+               device_remove_file(dev, &dev_attr_brightness_hw_changed);
+               return -ENXIO;
+       }
+
+       return 0;
+}
+
+static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
+{
+       sysfs_put(led_cdev->brightness_hw_changed_kn);
+       device_remove_file(led_cdev->dev, &dev_attr_brightness_hw_changed);
+}
+
+void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev,
+                                              enum led_brightness brightness)
+{
+       if (WARN_ON(!led_cdev->brightness_hw_changed_kn))
+               return;
+
+       led_cdev->brightness_hw_changed = brightness;
+       sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn);
+}
+EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed);
+#else
+static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
+{
+       return 0;
+}
+static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
+{
+}
+#endif
+
 /**
  * led_classdev_suspend - suspend an led_classdev.
  * @led_cdev: the led_classdev to suspend.
                dev_warn(parent, "Led %s renamed to %s due to name collision",
                                led_cdev->name, dev_name(led_cdev->dev));
 
+       if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
+               ret = led_add_brightness_hw_changed(led_cdev);
+               if (ret) {
+                       device_unregister(led_cdev->dev);
+                       return ret;
+               }
+       }
+
        led_cdev->work_flags = 0;
 #ifdef CONFIG_LEDS_TRIGGERS
        init_rwsem(&led_cdev->trigger_lock);
+#endif
+#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
+       led_cdev->brightness_hw_changed = -1;
 #endif
        mutex_init(&led_cdev->led_access);
        /* add to the list of leds */
 
        flush_work(&led_cdev->set_brightness_work);
 
+       if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
+               led_remove_brightness_hw_changed(led_cdev);
+
        device_unregister(led_cdev->dev);
 
        down_write(&leds_list_lock);
 
 #define __LINUX_LEDS_H_INCLUDED
 
 #include <linux/device.h>
+#include <linux/kernfs.h>
 #include <linux/list.h>
 #include <linux/mutex.h>
 #include <linux/rwsem.h>
 #define LED_DEV_CAP_FLASH      (1 << 18)
 #define LED_HW_PLUGGABLE       (1 << 19)
 #define LED_PANIC_INDICATOR    (1 << 20)
+#define LED_BRIGHT_HW_CHANGED  (1 << 21)
 
        /* set_brightness_work / blink_timer flags, atomic, private. */
        unsigned long           work_flags;
        bool                    activated;
 #endif
 
+#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
+       int                      brightness_hw_changed;
+       struct kernfs_node      *brightness_hw_changed_kn;
+#endif
+
        /* Ensures consistent access to the LED Flash Class device */
        struct mutex            led_access;
 };
 }
 #endif
 
+#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
+extern void led_classdev_notify_brightness_hw_changed(
+       struct led_classdev *led_cdev, enum led_brightness brightness);
+#else
+static inline void led_classdev_notify_brightness_hw_changed(
+       struct led_classdev *led_cdev, enum led_brightness brightness) { }
+#endif
+
 #endif         /* __LINUX_LEDS_H_INCLUDED */