OSDN Git Service

Merge tag 'leds-next-6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds
authorLinus Torvalds <torvalds@linux-foundation.org>
Mon, 3 Jul 2023 18:26:05 +0000 (11:26 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 3 Jul 2023 18:26:05 +0000 (11:26 -0700)
Pull LED updates from Lee Jones:
 "New Drivers:
   - Add support for Intel Cherry Trail Whiskey Cove PMIC LEDs
   - Add support for Awinic AW20036/AW20054/AW20072 LEDs

  New Device Support:
   - Add support for PMI632 LPG to QCom LPG
   - Add support for PMI8998 to QCom Flash
   - Add support for MT6331, WLEDs and MT6332 to Mediatek MT6323 PMIC

  New Functionality:
   - Implement the LP55xx Charge Pump
   - Add support for suspend / resume to Intel Cherry Trail Whiskey Cove PMIC
   - Add support for breathing mode to Intel Cherry Trail Whiskey Cove PMIC
   - Enable per-pin resolution Pinctrl in LEDs GPIO

  Fix-ups:
   - Allow thread to sleep by switching from spinlock to mutex
   - Add lots of Device Tree bindings / support
   - Adapt relationships / dependencies driven by Kconfig
   - Switch I2C drivers from .probe_new() to .probe()
   - Remove superfluous / duplicate code
   - Replace strlcpy() with strscpy() for efficiency and overflow prevention
   - Staticify various functions
   - Trivial: Fixing coding style
   - Simplify / reduce code

  Bug Fixes:
   - Prevent NETDEV_LED_MODE_LINKUP from being cleared on rename
   - Repair race between led_set_brightness(LED_{OFF,FULL})
   - Fix Oops relating to sleeping in critical sections
   - Clear LED_INIT_DEFAULT_TRIGGER flag when clearing the current trigger
   - Do not leak resources in error handling paths
   - Fix unsigned comparison which can never be negative
   - Provide missing NULL terminating entries in tables
   - Fix misnaming issues"

* tag 'leds-next-6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (53 commits)
  leds: leds-mt6323: Adjust return/parameter types in wled get/set callbacks
  leds: sgm3140: Add richtek,rt5033-led compatible
  dt-bindings: leds: sgm3140: Document richtek,rt5033 compatible
  dt-bindings: backlight: kinetic,ktz8866: Add missing type for "current-num-sinks"
  dt-bindings: leds: Drop unneeded quotes
  leds: Fix config reference for AW200xx driver
  leds: leds-mt6323: Add support for WLEDs and MT6332
  leds: leds-mt6323: Add support for MT6331 leds
  leds: leds-mt6323: Open code and drop MT6323_CAL_HW_DUTY macro
  leds: leds-mt6323: Drop MT6323_ prefix from macros and defines
  leds: leds-mt6323: Specify registers and specs in platform data
  dt-bindings: leds: leds-mt6323: Document mt6332 compatible
  dt-bindings: leds: leds-mt6323: Document mt6331 compatible
  leds: simatic-ipc-leds-gpio: Introduce more Kconfig switches
  leds: simatic-ipc-leds-gpio: Split up into multiple drivers
  leds: simatic-ipc-leds-gpio: Move two extra gpio pins into another table
  leds: simatic-ipc-leds-gpio: Add terminating entries to gpio tables
  leds: flash: leds-qcom-flash: Fix an unsigned comparison which can never be negative
  leds: cht-wcove: Remove unneeded semicolon
  leds: cht-wcove: Fix an unsigned comparison which can never be negative
  ...

1  2 
Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml
drivers/leds/rgb/leds-qcom-lpg.c
drivers/leds/trigger/ledtrig-netdev.c
drivers/power/supply/power_supply_leds.c
include/linux/leds.h

@@@ -71,6 -71,7 +71,7 @@@ properties
            - qcom,pm8998
            - qcom,pma8084
            - qcom,pmd9635
+           - qcom,pmi632
            - qcom,pmi8950
            - qcom,pmi8962
            - qcom,pmi8994
@@@ -146,10 -147,6 +147,10 @@@ patternProperties
      type: object
      $ref: /schemas/nvmem/qcom,spmi-sdam.yaml#
  
 +  "phy@[0-9a-f]+$":
 +    type: object
 +    $ref: /schemas/phy/qcom,snps-eusb2-repeater.yaml#
 +
    "pon@[0-9a-f]+$":
      type: object
      $ref: /schemas/power/reset/qcom,pon.yaml#
@@@ -312,14 -312,14 +312,14 @@@ static int lpg_calc_freq(struct lpg_cha
                max_res = LPG_RESOLUTION_9BIT;
        }
  
 -      min_period = (u64)NSEC_PER_SEC *
 -                      div64_u64((1 << pwm_resolution_arr[0]), clk_rate_arr[clk_len - 1]);
 +      min_period = div64_u64((u64)NSEC_PER_SEC * (1 << pwm_resolution_arr[0]),
 +                             clk_rate_arr[clk_len - 1]);
        if (period <= min_period)
                return -EINVAL;
  
        /* Limit period to largest possible value, to avoid overflows */
 -      max_period = (u64)NSEC_PER_SEC * max_res * LPG_MAX_PREDIV *
 -                      div64_u64((1 << LPG_MAX_M), 1024);
 +      max_period = div64_u64((u64)NSEC_PER_SEC * max_res * LPG_MAX_PREDIV * (1 << LPG_MAX_M),
 +                             1024);
        if (period > max_period)
                period = max_period;
  
@@@ -1173,8 -1173,10 +1173,10 @@@ static int lpg_add_led(struct lpg *lpg
                i = 0;
                for_each_available_child_of_node(np, child) {
                        ret = lpg_parse_channel(lpg, child, &led->channels[i]);
-                       if (ret < 0)
+                       if (ret < 0) {
+                               of_node_put(child);
                                return ret;
+                       }
  
                        info[i].color_index = led->channels[i]->color;
                        info[i].intensity = 0;
@@@ -1352,8 -1354,10 +1354,10 @@@ static int lpg_probe(struct platform_de
  
        for_each_available_child_of_node(pdev->dev.of_node, np) {
                ret = lpg_add_led(lpg, np);
-               if (ret)
+               if (ret) {
+                       of_node_put(np);
                        return ret;
+               }
        }
  
        for (i = 0; i < lpg->num_channels; i++)
@@@ -1414,6 -1418,20 +1418,20 @@@ static const struct lpg_data pm8994_lpg
        },
  };
  
+ /* PMI632 uses SDAM instead of LUT for pattern */
+ static const struct lpg_data pmi632_lpg_data = {
+       .triled_base = 0xd000,
+       .num_channels = 5,
+       .channels = (const struct lpg_channel_data[]) {
+               { .base = 0xb300, .triled_mask = BIT(7) },
+               { .base = 0xb400, .triled_mask = BIT(6) },
+               { .base = 0xb500, .triled_mask = BIT(5) },
+               { .base = 0xb600 },
+               { .base = 0xb700 },
+       },
+ };
  static const struct lpg_data pmi8994_lpg_data = {
        .lut_base = 0xb000,
        .lut_size = 24,
@@@ -1505,6 -1523,7 +1523,7 @@@ static const struct of_device_id lpg_of
        { .compatible = "qcom,pm8916-pwm", .data = &pm8916_pwm_data },
        { .compatible = "qcom,pm8941-lpg", .data = &pm8941_lpg_data },
        { .compatible = "qcom,pm8994-lpg", .data = &pm8994_lpg_data },
+       { .compatible = "qcom,pmi632-lpg", .data = &pmi632_lpg_data },
        { .compatible = "qcom,pmi8994-lpg", .data = &pmi8994_lpg_data },
        { .compatible = "qcom,pmi8998-lpg", .data = &pmi8998_lpg_data },
        { .compatible = "qcom,pmc8180c-lpg", .data = &pm8150l_lpg_data },
@@@ -13,7 -13,6 +13,7 @@@
  #include <linux/atomic.h>
  #include <linux/ctype.h>
  #include <linux/device.h>
 +#include <linux/ethtool.h>
  #include <linux/init.h>
  #include <linux/jiffies.h>
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/netdevice.h>
  #include <linux/mutex.h>
 +#include <linux/rtnetlink.h>
  #include <linux/timer.h>
  #include "../leds.h"
  
 +#define NETDEV_LED_DEFAULT_INTERVAL   50
 +
  /*
   * Configurable sysfs attributes:
   *
@@@ -54,11 -50,16 +54,11 @@@ struct led_netdev_data 
        unsigned int last_activity;
  
        unsigned long mode;
 -      bool carrier_link_up;
 -};
 +      int link_speed;
 +      u8 duplex;
  
 -enum led_trigger_netdev_modes {
 -      TRIGGER_NETDEV_LINK = 0,
 -      TRIGGER_NETDEV_TX,
 -      TRIGGER_NETDEV_RX,
 -
 -      /* Keep last */
 -      __TRIGGER_NETDEV_MAX,
 +      bool carrier_link_up;
 +      bool hw_control;
  };
  
  static void set_baseline_state(struct led_netdev_data *trigger_data)
        int current_brightness;
        struct led_classdev *led_cdev = trigger_data->led_cdev;
  
 +      /* Already validated, hw control is possible with the requested mode */
 +      if (trigger_data->hw_control) {
 +              led_cdev->hw_control_set(led_cdev, trigger_data->mode);
 +
 +              return;
 +      }
 +
        current_brightness = led_cdev->brightness;
        if (current_brightness)
                led_cdev->blink_brightness = current_brightness;
        if (!trigger_data->carrier_link_up) {
                led_set_brightness(led_cdev, LED_OFF);
        } else {
 +              bool blink_on = false;
 +
                if (test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode))
 +                      blink_on = true;
 +
 +              if (test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) &&
 +                  trigger_data->link_speed == SPEED_10)
 +                      blink_on = true;
 +
 +              if (test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) &&
 +                  trigger_data->link_speed == SPEED_100)
 +                      blink_on = true;
 +
 +              if (test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) &&
 +                  trigger_data->link_speed == SPEED_1000)
 +                      blink_on = true;
 +
 +              if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) &&
 +                  trigger_data->duplex == DUPLEX_HALF)
 +                      blink_on = true;
 +
 +              if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode) &&
 +                  trigger_data->duplex == DUPLEX_FULL)
 +                      blink_on = true;
 +
 +              if (blink_on)
                        led_set_brightness(led_cdev,
                                           led_cdev->blink_brightness);
                else
        }
  }
  
 +static bool supports_hw_control(struct led_classdev *led_cdev)
 +{
 +      if (!led_cdev->hw_control_get || !led_cdev->hw_control_set ||
 +          !led_cdev->hw_control_is_supported)
 +              return false;
 +
 +      return !strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name);
 +}
 +
 +/*
 + * Validate the configured netdev is the same as the one associated with
 + * the LED driver in hw control.
 + */
 +static bool validate_net_dev(struct led_classdev *led_cdev,
 +                           struct net_device *net_dev)
 +{
 +      struct device *dev = led_cdev->hw_control_get_device(led_cdev);
 +      struct net_device *ndev;
 +
 +      if (!dev)
 +              return false;
 +
 +      ndev = to_net_dev(dev);
 +
 +      return ndev == net_dev;
 +}
 +
 +static bool can_hw_control(struct led_netdev_data *trigger_data)
 +{
 +      unsigned long default_interval = msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL);
 +      unsigned int interval = atomic_read(&trigger_data->interval);
 +      struct led_classdev *led_cdev = trigger_data->led_cdev;
 +      int ret;
 +
 +      if (!supports_hw_control(led_cdev))
 +              return false;
 +
 +      /*
 +       * Interval must be set to the default
 +       * value. Any different value is rejected if in hw
 +       * control.
 +       */
 +      if (interval != default_interval)
 +              return false;
 +
 +      /*
 +       * net_dev must be set with hw control, otherwise no
 +       * blinking can be happening and there is nothing to
 +       * offloaded. Additionally, for hw control to be
 +       * valid, the configured netdev must be the same as
 +       * netdev associated to the LED.
 +       */
 +      if (!validate_net_dev(led_cdev, trigger_data->net_dev))
 +              return false;
 +
 +      /* Check if the requested mode is supported */
 +      ret = led_cdev->hw_control_is_supported(led_cdev, trigger_data->mode);
 +      /* Fall back to software blinking if not supported */
 +      if (ret == -EOPNOTSUPP)
 +              return false;
 +      if (ret) {
 +              dev_warn(led_cdev->dev,
 +                       "Current mode check failed with error %d\n", ret);
 +              return false;
 +      }
 +
 +      return true;
 +}
 +
 +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)
 +              return;
 +
 +      if (!__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd)) {
 +              trigger_data->link_speed = cmd.base.speed;
 +              trigger_data->duplex = cmd.base.duplex;
 +      }
 +}
 +
  static ssize_t device_name_show(struct device *dev,
                                struct device_attribute *attr, char *buf)
  {
        return len;
  }
  
 -static ssize_t device_name_store(struct device *dev,
 -                               struct device_attribute *attr, const char *buf,
 -                               size_t size)
 +static int set_device_name(struct led_netdev_data *trigger_data,
 +                         const char *name, size_t size)
  {
 -      struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
 -
 -      if (size >= IFNAMSIZ)
 -              return -EINVAL;
 -
        cancel_delayed_work_sync(&trigger_data->work);
  
        mutex_lock(&trigger_data->lock);
                trigger_data->net_dev = NULL;
        }
  
 -      memcpy(trigger_data->device_name, buf, size);
 +      memcpy(trigger_data->device_name, name, size);
        trigger_data->device_name[size] = 0;
        if (size > 0 && trigger_data->device_name[size - 1] == '\n')
                trigger_data->device_name[size - 1] = 0;
                    dev_get_by_name(&init_net, trigger_data->device_name);
  
        trigger_data->carrier_link_up = false;
 -      if (trigger_data->net_dev != NULL)
 -              trigger_data->carrier_link_up = netif_carrier_ok(trigger_data->net_dev);
 +      trigger_data->link_speed = SPEED_UNKNOWN;
 +      trigger_data->duplex = DUPLEX_UNKNOWN;
 +      if (trigger_data->net_dev != NULL) {
 +              rtnl_lock();
 +              get_device_state(trigger_data);
 +              rtnl_unlock();
 +      }
  
        trigger_data->last_activity = 0;
  
        set_baseline_state(trigger_data);
        mutex_unlock(&trigger_data->lock);
  
 +      return 0;
 +}
 +
 +static ssize_t device_name_store(struct device *dev,
 +                               struct device_attribute *attr, const char *buf,
 +                               size_t size)
 +{
 +      struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
 +      int ret;
 +
 +      if (size >= IFNAMSIZ)
 +              return -EINVAL;
 +
 +      ret = set_device_name(trigger_data, buf, size);
 +
 +      if (ret < 0)
 +              return ret;
        return size;
  }
  
@@@ -283,11 -153,6 +283,11 @@@ static ssize_t netdev_led_attr_show(str
  
        switch (attr) {
        case TRIGGER_NETDEV_LINK:
 +      case TRIGGER_NETDEV_LINK_10:
 +      case TRIGGER_NETDEV_LINK_100:
 +      case TRIGGER_NETDEV_LINK_1000:
 +      case TRIGGER_NETDEV_HALF_DUPLEX:
 +      case TRIGGER_NETDEV_FULL_DUPLEX:
        case TRIGGER_NETDEV_TX:
        case TRIGGER_NETDEV_RX:
                bit = attr;
@@@ -303,7 -168,7 +303,7 @@@ static ssize_t netdev_led_attr_store(st
                                     size_t size, enum led_trigger_netdev_modes attr)
  {
        struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
 -      unsigned long state;
 +      unsigned long state, mode = trigger_data->mode;
        int ret;
        int bit;
  
  
        switch (attr) {
        case TRIGGER_NETDEV_LINK:
 +      case TRIGGER_NETDEV_LINK_10:
 +      case TRIGGER_NETDEV_LINK_100:
 +      case TRIGGER_NETDEV_LINK_1000:
 +      case TRIGGER_NETDEV_HALF_DUPLEX:
 +      case TRIGGER_NETDEV_FULL_DUPLEX:
        case TRIGGER_NETDEV_TX:
        case TRIGGER_NETDEV_RX:
                bit = attr;
                return -EINVAL;
        }
  
 -      cancel_delayed_work_sync(&trigger_data->work);
 -
        if (state)
 -              set_bit(bit, &trigger_data->mode);
 +              set_bit(bit, &mode);
        else
 -              clear_bit(bit, &trigger_data->mode);
 +              clear_bit(bit, &mode);
 +
 +      if (test_bit(TRIGGER_NETDEV_LINK, &mode) &&
 +          (test_bit(TRIGGER_NETDEV_LINK_10, &mode) ||
 +           test_bit(TRIGGER_NETDEV_LINK_100, &mode) ||
 +           test_bit(TRIGGER_NETDEV_LINK_1000, &mode)))
 +              return -EINVAL;
 +
 +      cancel_delayed_work_sync(&trigger_data->work);
 +
 +      trigger_data->mode = mode;
 +      trigger_data->hw_control = can_hw_control(trigger_data);
  
        set_baseline_state(trigger_data);
  
        static DEVICE_ATTR_RW(trigger_name)
  
  DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK);
 +DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10);
 +DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100);
 +DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000);
 +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);
  
@@@ -386,9 -232,6 +386,9 @@@ static ssize_t interval_store(struct de
        unsigned long value;
        int ret;
  
 +      if (trigger_data->hw_control)
 +              return -EINVAL;
 +
        ret = kstrtoul(buf, 0, &value);
        if (ret)
                return ret;
  
  static DEVICE_ATTR_RW(interval);
  
 +static ssize_t hw_control_show(struct device *dev,
 +                             struct device_attribute *attr, char *buf)
 +{
 +      struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
 +
 +      return sprintf(buf, "%d\n", trigger_data->hw_control);
 +}
 +
 +static DEVICE_ATTR_RO(hw_control);
 +
  static struct attribute *netdev_trig_attrs[] = {
        &dev_attr_device_name.attr,
        &dev_attr_link.attr,
 +      &dev_attr_link_10.attr,
 +      &dev_attr_link_100.attr,
 +      &dev_attr_link_1000.attr,
 +      &dev_attr_full_duplex.attr,
 +      &dev_attr_half_duplex.attr,
        &dev_attr_rx.attr,
        &dev_attr_tx.attr,
        &dev_attr_interval.attr,
 +      &dev_attr_hw_control.attr,
        NULL
  };
  ATTRIBUTE_GROUPS(netdev_trig);
@@@ -455,15 -282,12 +455,14 @@@ static int netdev_trig_notify(struct no
        mutex_lock(&trigger_data->lock);
  
        trigger_data->carrier_link_up = false;
 +      trigger_data->link_speed = SPEED_UNKNOWN;
 +      trigger_data->duplex = DUPLEX_UNKNOWN;
        switch (evt) {
        case NETDEV_CHANGENAME:
 -              trigger_data->carrier_link_up = netif_carrier_ok(dev);
 +              get_device_state(trigger_data);
                fallthrough;
        case NETDEV_REGISTER:
-               if (trigger_data->net_dev)
-                       dev_put(trigger_data->net_dev);
+               dev_put(trigger_data->net_dev);
                dev_hold(dev);
                trigger_data->net_dev = dev;
                break;
                break;
        case NETDEV_UP:
        case NETDEV_CHANGE:
 -              trigger_data->carrier_link_up = netif_carrier_ok(dev);
 +              get_device_state(trigger_data);
                break;
        }
  
@@@ -516,12 -340,7 +515,12 @@@ static void netdev_trig_work(struct wor
        if (trigger_data->last_activity != new_activity) {
                led_stop_software_blink(trigger_data->led_cdev);
  
 -              invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode);
 +              invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode) ||
 +                       test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) ||
 +                       test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) ||
 +                       test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) ||
 +                       test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) ||
 +                       test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode);
                interval = jiffies_to_msecs(
                                atomic_read(&trigger_data->interval));
                /* base state is ON (link present) */
  static int netdev_trig_activate(struct led_classdev *led_cdev)
  {
        struct led_netdev_data *trigger_data;
 +      unsigned long mode = 0;
 +      struct device *dev;
        int rc;
  
        trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL);
        trigger_data->device_name[0] = 0;
  
        trigger_data->mode = 0;
 -      atomic_set(&trigger_data->interval, msecs_to_jiffies(50));
 +      atomic_set(&trigger_data->interval, msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL));
        trigger_data->last_activity = 0;
  
 +      /* Check if hw control is active by default on the LED.
 +       * Init already enabled mode in hw control.
 +       */
 +      if (supports_hw_control(led_cdev) &&
 +          !led_cdev->hw_control_get(led_cdev, &mode)) {
 +              dev = led_cdev->hw_control_get_device(led_cdev);
 +              if (dev) {
 +                      const char *name = dev_name(dev);
 +
 +                      set_device_name(trigger_data, name, strlen(name));
 +                      trigger_data->hw_control = true;
 +                      trigger_data->mode = mode;
 +              }
 +      }
 +
        led_set_trigger_data(led_cdev, trigger_data);
  
        rc = register_netdevice_notifier(&trigger_data->notifier);
@@@ -594,8 -396,7 +593,7 @@@ static void netdev_trig_deactivate(stru
  
        cancel_delayed_work_sync(&trigger_data->work);
  
-       if (trigger_data->net_dev)
-               dev_put(trigger_data->net_dev);
+       dev_put(trigger_data->net_dev);
  
        kfree(trigger_data);
  }
@@@ -22,8 -22,6 +22,6 @@@
  static void power_supply_update_bat_leds(struct power_supply *psy)
  {
        union power_supply_propval status;
-       unsigned long delay_on = 0;
-       unsigned long delay_off = 0;
  
        if (power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &status))
                return;
                led_trigger_event(psy->charging_full_trig, LED_FULL);
                led_trigger_event(psy->charging_trig, LED_OFF);
                led_trigger_event(psy->full_trig, LED_FULL);
 -              led_trigger_event(psy->charging_blink_full_solid_trig,
 -                      LED_FULL);
 +              /* Going from blink to LED on requires a LED_OFF event to stop blink */
 +              led_trigger_event(psy->charging_blink_full_solid_trig, LED_OFF);
 +              led_trigger_event(psy->charging_blink_full_solid_trig, LED_FULL);
                break;
        case POWER_SUPPLY_STATUS_CHARGING:
                led_trigger_event(psy->charging_full_trig, LED_FULL);
                led_trigger_event(psy->charging_trig, LED_FULL);
                led_trigger_event(psy->full_trig, LED_OFF);
-               led_trigger_blink(psy->charging_blink_full_solid_trig,
-                       &delay_on, &delay_off);
+               led_trigger_blink(psy->charging_blink_full_solid_trig, 0, 0);
                break;
        default:
                led_trigger_event(psy->charging_full_trig, LED_OFF);
diff --combined include/linux/leds.h
@@@ -124,6 -124,10 +124,10 @@@ struct led_classdev 
  #define LED_BLINK_INVERT              3
  #define LED_BLINK_BRIGHTNESS_CHANGE   4
  #define LED_BLINK_DISABLE             5
+       /* Brightness off also disables hw-blinking so it is a separate action */
+ #define LED_SET_BRIGHTNESS_OFF                6
+ #define LED_SET_BRIGHTNESS            7
+ #define LED_SET_BLINK                 8
  
        /* Set LED brightness level
         * Must not sleep. Use brightness_set_blocking for drivers
         * match the values specified exactly.
         * Deactivate blinking again when the brightness is set to LED_OFF
         * via the brightness_set() callback.
+        * For led_blink_set_nosleep() the LED core assumes that blink_set
+        * implementations, of drivers which do not use brightness_set_blocking,
+        * will not sleep. Therefor if brightness_set_blocking is not set
+        * this function must not sleep!
         */
        int             (*blink_set)(struct led_classdev *led_cdev,
                                     unsigned long *delay_on,
  
        struct work_struct      set_brightness_work;
        int                     delayed_set_value;
+       unsigned long           delayed_delay_on;
+       unsigned long           delayed_delay_off;
  
  #ifdef CONFIG_LEDS_TRIGGERS
        /* Protects the trigger data below */
  
        /* LEDs that have private triggers have this set */
        struct led_hw_trigger_type      *trigger_type;
 +
 +      /* Unique trigger name supported by LED set in hw control mode */
 +      const char              *hw_control_trigger;
 +      /*
 +       * Check if the LED driver supports the requested mode provided by the
 +       * defined supported trigger to setup the LED to hw control mode.
 +       *
 +       * Return 0 on success. Return -EOPNOTSUPP when the passed flags are not
 +       * supported and software fallback needs to be used.
 +       * Return a negative error number on any other case  for check fail due
 +       * to various reason like device not ready or timeouts.
 +       */
 +      int                     (*hw_control_is_supported)(struct led_classdev *led_cdev,
 +                                                         unsigned long flags);
 +      /*
 +       * Activate hardware control, LED driver will use the provided flags
 +       * from the supported trigger and setup the LED to be driven by hardware
 +       * following the requested mode from the trigger flags.
 +       * Deactivate hardware blink control by setting brightness to LED_OFF via
 +       * the brightness_set() callback.
 +       *
 +       * Return 0 on success, a negative error number on flags apply fail.
 +       */
 +      int                     (*hw_control_set)(struct led_classdev *led_cdev,
 +                                                unsigned long flags);
 +      /*
 +       * Get from the LED driver the current mode that the LED is set in hw
 +       * control mode and put them in flags.
 +       * Trigger can use this to get the initial state of a LED already set in
 +       * hardware blink control.
 +       *
 +       * Return 0 on success, a negative error number on failing parsing the
 +       * initial mode. Error from this function is NOT FATAL as the device
 +       * may be in a not supported initial state by the attached LED trigger.
 +       */
 +      int                     (*hw_control_get)(struct led_classdev *led_cdev,
 +                                                unsigned long *flags);
 +      /*
 +       * Get the device this LED blinks in response to.
 +       * e.g. for a PHY LED, it is the network device. If the LED is
 +       * not yet associated to a device, return NULL.
 +       */
 +      struct device           *(*hw_control_get_device)(struct led_classdev *led_cdev);
  #endif
  
  #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
@@@ -315,12 -282,27 +325,27 @@@ struct led_classdev *__must_check devm_
   * software blinking if there is no hardware blinking or if
   * the LED refuses the passed values.
   *
+  * This function may sleep!
+  *
   * Note that if software blinking is active, simply calling
   * led_cdev->brightness_set() will not stop the blinking,
   * use led_set_brightness() instead.
   */
  void led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
                   unsigned long *delay_off);
+ /**
+  * led_blink_set_nosleep - set blinking, guaranteed to not sleep
+  * @led_cdev: the LED to start blinking
+  * @delay_on: the time it should be on (in ms)
+  * @delay_off: the time it should ble off (in ms)
+  *
+  * This function makes the LED blink and is guaranteed to not sleep. Otherwise
+  * this is the same as led_blink_set(), see led_blink_set() for details.
+  */
+ void led_blink_set_nosleep(struct led_classdev *led_cdev, unsigned long delay_on,
+                          unsigned long delay_off);
  /**
   * led_blink_set_oneshot - do a oneshot software blink
   * @led_cdev: the LED to start blinking
   *
   * If invert is set, led blinks for delay_off first, then for
   * delay_on and leave the led on after the on-off cycle.
+  *
+  * This function is guaranteed not to sleep.
   */
  void led_blink_set_oneshot(struct led_classdev *led_cdev,
                           unsigned long *delay_on, unsigned long *delay_off,
@@@ -476,11 -460,11 +503,11 @@@ void led_trigger_register_simple(const 
                                struct led_trigger **trigger);
  void led_trigger_unregister_simple(struct led_trigger *trigger);
  void led_trigger_event(struct led_trigger *trigger,  enum led_brightness event);
- void led_trigger_blink(struct led_trigger *trigger, unsigned long *delay_on,
-                      unsigned long *delay_off);
+ void led_trigger_blink(struct led_trigger *trigger, unsigned long delay_on,
+                      unsigned long delay_off);
  void led_trigger_blink_oneshot(struct led_trigger *trigger,
-                              unsigned long *delay_on,
-                              unsigned long *delay_off,
+                              unsigned long delay_on,
+                              unsigned long delay_off,
                               int invert);
  void led_trigger_set_default(struct led_classdev *led_cdev);
  int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger);
@@@ -530,11 -514,11 +557,11 @@@ static inline void led_trigger_unregist
  static inline void led_trigger_event(struct led_trigger *trigger,
                                enum led_brightness event) {}
  static inline void led_trigger_blink(struct led_trigger *trigger,
-                                     unsigned long *delay_on,
-                                     unsigned long *delay_off) {}
+                                     unsigned long delay_on,
+                                     unsigned long delay_off) {}
  static inline void led_trigger_blink_oneshot(struct led_trigger *trigger,
-                                     unsigned long *delay_on,
-                                     unsigned long *delay_off,
+                                     unsigned long delay_on,
+                                     unsigned long delay_off,
                                      int invert) {}
  static inline void led_trigger_set_default(struct led_classdev *led_cdev) {}
  static inline int led_trigger_set(struct led_classdev *led_cdev,
@@@ -552,21 -536,6 +579,21 @@@ static inline void *led_get_trigger_dat
  
  #endif /* CONFIG_LEDS_TRIGGERS */
  
 +/* Trigger specific enum */
 +enum led_trigger_netdev_modes {
 +      TRIGGER_NETDEV_LINK = 0,
 +      TRIGGER_NETDEV_LINK_10,
 +      TRIGGER_NETDEV_LINK_100,
 +      TRIGGER_NETDEV_LINK_1000,
 +      TRIGGER_NETDEV_HALF_DUPLEX,
 +      TRIGGER_NETDEV_FULL_DUPLEX,
 +      TRIGGER_NETDEV_TX,
 +      TRIGGER_NETDEV_RX,
 +
 +      /* Keep last */
 +      __TRIGGER_NETDEV_MAX,
 +};
 +
  /* Trigger specific functions */
  #ifdef CONFIG_LEDS_TRIGGER_DISK
  void ledtrig_disk_activity(bool write);