Merge tag 'leds-next-6.6' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds
authorLinus Torvalds <torvalds@linux-foundation.org>
Mon, 4 Sep 2023 20:52:58 +0000 (13:52 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 4 Sep 2023 20:52:58 +0000 (13:52 -0700)
Pull LED updates from Lee Jones:
 "Core Frameworks:
   - Add new framework to support Group Multi-Color (GMC) LEDs
   - Offer an 'optional' API for non-essential LEDs
   - Support obtaining 'max brightness' values from Device Tree
   - Provide new led_classdev member 'color' (settable via DT and SYFS)
   - Stop TTY Trigger from using the old LED_ON constraints
   - Statically allocate leds_class

  New Drivers:
   - Add support for NXP PCA995x I2C Constant Current LED Driver

  New Device Support:
   - Add support for Siemens Simatic IPC BX-21 to Simatic IPC

  Fix-ups:
   - Some dependency / Kconfig tweaking
   - Move final probe() functions back over from .probe_new()
   - Simplify obtaining resources (memory, device data) using unified
     API helpers
   - Bunch of Device Tree additions, conversions and adaptions
   - Fix trivial styling issues; comments
   - Ensure correct includes are present and remove some that are not
     required
   - Omit the use of redundant casts and if relevant replace with better
     ones
   - Use purpose-built APIs for various actions; sysfs_emit(),
     module_led_trigger()
   - Remove a bunch of superfluous locking

  Bug Fixes:
   - Ensure error codes are correctly propagated back up the call chain
   - Fix incorrect error values from being returned (missing '-')
   - Ensure get'ed resources are put'ed to prevent leaks
   - Use correct class when exporting module resources
   - Fixing rounding (or lack there of) issues
   - Fix 'always false' LED_COLOR_ID_MULTI BUG() check"

* tag 'leds-next-6.6' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (40 commits)
  leds: aw2013: Enable pull-up supply for interrupt and I2C
  dt-bindings: leds: Document pull-up supply for interrupt and I2C
  dt-bindings: leds: aw2013: Document interrupt
  leds: uleds: Use module_misc_device macro to simplify the code
  leds: trigger: netdev: Use module_led_trigger macro to simplify the code
  dt-bindings: leds: Fix reference to definition of default-state
  leds: turris-omnia: Drop unnecessary mutex locking
  leds: turris-omnia: Use sysfs_emit() instead of sprintf()
  leds: Make leds_class a static const structure
  leds: Remove redundant of_match_ptr()
  dt-bindings: leds: Add gpio-line-names to PCA9532 GPIO
  leds: trigger: tty: Do not use LED_ON/OFF constants, use led_blink_set_oneshot instead
  dt-bindings: leds: rohm,bd71828: Drop select:false
  leds: Fix BUG_ON check for LED_COLOR_ID_MULTI that is always false
  leds: multicolor: Use rounded division when calculating color components
  leds: rgb: Add a multicolor LED driver to group monochromatic LEDs
  dt-bindings: leds: Add binding for a multicolor group of LEDs
  leds: class: Store the color index in struct led_classdev
  leds: Provide devm_of_led_get_optional()
  leds: pca995x: Fix MODULE_DEVICE_TABLE for OF
  ...

54 files changed:
Documentation/ABI/testing/sysfs-class-led
Documentation/devicetree/bindings/leds/common.yaml
Documentation/devicetree/bindings/leds/leds-an30259a.txt [deleted file]
Documentation/devicetree/bindings/leds/leds-aw2013.yaml
Documentation/devicetree/bindings/leds/leds-group-multicolor.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/leds/nxp,pca953x.yaml
Documentation/devicetree/bindings/leds/nxp,pca995x.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/leds/panasonic,an30259a.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/leds/rohm,bd2606mvv.yaml
Documentation/devicetree/bindings/leds/rohm,bd71828-leds.yaml
drivers/leds/Kconfig
drivers/leds/Makefile
drivers/leds/blink/Kconfig
drivers/leds/flash/Kconfig
drivers/leds/flash/leds-qcom-flash.c
drivers/leds/led-class-multicolor.c
drivers/leds/led-class.c
drivers/leds/led-core.c
drivers/leds/leds-an30259a.c
drivers/leds/leds-ariel.c
drivers/leds/leds-aw200xx.c
drivers/leds/leds-aw2013.c
drivers/leds/leds-cpcap.c
drivers/leds/leds-cr0014114.c
drivers/leds/leds-ip30.c
drivers/leds/leds-is31fl32xx.c
drivers/leds/leds-lp5521.c
drivers/leds/leds-lp5523.c
drivers/leds/leds-lp5562.c
drivers/leds/leds-lp8501.c
drivers/leds/leds-mlxreg.c
drivers/leds/leds-ns2.c
drivers/leds/leds-pca9532.c
drivers/leds/leds-pca995x.c [new file with mode: 0644]
drivers/leds/leds-pm8058.c
drivers/leds/leds-pwm.c
drivers/leds/leds-spi-byte.c
drivers/leds/leds-syscon.c
drivers/leds/leds-ti-lmu-common.c
drivers/leds/leds-tlc591xx.c
drivers/leds/leds-turris-omnia.c
drivers/leds/rgb/Kconfig
drivers/leds/rgb/Makefile
drivers/leds/rgb/leds-group-multicolor.c [new file with mode: 0644]
drivers/leds/rgb/leds-qcom-lpg.c
drivers/leds/simple/Kconfig
drivers/leds/simple/Makefile
drivers/leds/simple/simatic-ipc-leds-gpio-core.c
drivers/leds/simple/simatic-ipc-leds-gpio-elkhartlake.c [new file with mode: 0644]
drivers/leds/simple/simatic-ipc-leds-gpio.h
drivers/leds/trigger/ledtrig-netdev.c
drivers/leds/trigger/ledtrig-tty.c
drivers/leds/uleds.c
include/linux/leds.h

index 2e24ac3..b2ff001 100644 (file)
@@ -59,6 +59,15 @@ Description:
                brightness. Reading this file when no hw brightness change
                event has happened will return an ENODATA error.
 
+What:          /sys/class/leds/<led>/color
+Date:          June 2023
+KernelVersion: 6.5
+Description:
+               Color of the LED.
+
+               This is a read-only file. Reading this file returns the color
+               of the LED as a string (e.g: "red", "green", "multicolor").
+
 What:          /sys/class/leds/<led>/trigger
 Date:          March 2006
 KernelVersion: 2.6.17
index 58b492d..5fb7007 100644 (file)
@@ -83,8 +83,7 @@ properties:
       - enum:
             # LED will act as a back-light, controlled by the framebuffer system
           - backlight
-            # LED will turn on (but for leds-gpio see "default-state" property in
-            # Documentation/devicetree/bindings/leds/leds-gpio.yaml)
+            # LED will turn on (see also "default-state" property)
           - default-on
             # LED "double" flashes at a load average based rate
           - heartbeat
@@ -158,6 +157,18 @@ properties:
       For flash LED controllers with configurable current this property is
       mandatory for the LEDs in the non-flash modes (e.g. torch or indicator).
 
+  max-brightness:
+    description:
+      Normally, the maximum brightness is determined by the hardware, and this
+      property is not required. This property is used to set a software limit.
+      It could happen that an LED is made so bright that it gets damaged or
+      causes damage due to restrictions in a specific system, such as mounting
+      conditions.
+      Note that this flag is mainly used for PWM-LEDs, where it is not possible
+      to map brightness to current. Drivers for other controllers should use
+      led-max-microamp.
+    $ref: /schemas/types.yaml#definitions/uint32
+
   panic-indicator:
     description:
       This property specifies that the LED should be used, if at all possible,
diff --git a/Documentation/devicetree/bindings/leds/leds-an30259a.txt b/Documentation/devicetree/bindings/leds/leds-an30259a.txt
deleted file mode 100644 (file)
index cbd8339..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-* Panasonic AN30259A 3-channel LED driver
-
-The AN30259A is a LED controller capable of driving three LEDs independently. It supports
-constant current output and sloping current output modes. The chip is connected over I2C.
-
-Required properties:
-       - compatible: Must be "panasonic,an30259a".
-       - reg: I2C slave address.
-       - #address-cells: Must be 1.
-       - #size-cells: Must be 0.
-
-Each LED is represented as a sub-node of the panasonic,an30259a node.
-
-Required sub-node properties:
-       - reg: Pin that the LED is connected to. Must be 1, 2, or 3.
-
-Optional sub-node properties:
-       - function :
-               see Documentation/devicetree/bindings/leds/common.txt
-       - color :
-               see Documentation/devicetree/bindings/leds/common.txt
-       - label :
-               see Documentation/devicetree/bindings/leds/common.txt (deprecated)
-       - linux,default-trigger :
-               see Documentation/devicetree/bindings/leds/common.txt
-
-Example:
-
-#include <dt-bindings/leds/common.h>
-
-led-controller@30 {
-       compatible = "panasonic,an30259a";
-       reg = <0x30>;
-       #address-cells = <1>;
-       #size-cells = <0>;
-
-       led@1 {
-               reg = <1>;
-               linux,default-trigger = "heartbeat";
-               function = LED_FUNCTION_INDICATOR;
-               color = <LED_COLOR_ID_RED>;
-       };
-
-       led@2 {
-               reg = <2>;
-               function = LED_FUNCTION_INDICATOR;
-               color = <LED_COLOR_ID_GREEN>;
-       };
-
-       led@3 {
-               reg = <3>;
-               function = LED_FUNCTION_INDICATOR;
-               color = <LED_COLOR_ID_BLUE>;
-       };
-};
index 08f3e1c..2623844 100644 (file)
@@ -20,9 +20,20 @@ properties:
   reg:
     maxItems: 1
 
+  interrupts:
+    maxItems: 1
+    description: Open-drain, low active interrupt pin "INTN".
+      Used to report completion of operations (power up, LED breath effects).
+
   vcc-supply:
     description: Regulator providing power to the "VCC" pin.
 
+  vio-supply:
+    description: Regulator providing power for pull-up of the I/O lines.
+      "VIO1" in the typical application circuit example of the datasheet.
+      Note that this regulator does not directly connect to AW2013, but is
+      needed for the correct operation of the interrupt and I2C lines.
+
   "#address-cells":
     const: 1
 
@@ -52,6 +63,7 @@ additionalProperties: false
 examples:
   - |
     #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
     #include <dt-bindings/leds/common.h>
 
     i2c {
@@ -61,6 +73,7 @@ examples:
         led-controller@45 {
             compatible = "awinic,aw2013";
             reg = <0x45>;
+            interrupts = <42 IRQ_TYPE_LEVEL_LOW>;
             #address-cells = <1>;
             #size-cells = <0>;
 
diff --git a/Documentation/devicetree/bindings/leds/leds-group-multicolor.yaml b/Documentation/devicetree/bindings/leds/leds-group-multicolor.yaml
new file mode 100644 (file)
index 0000000..8ed059a
--- /dev/null
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/leds-group-multicolor.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Multi-color LED built with monochromatic LEDs
+
+maintainers:
+  - Jean-Jacques Hiblot <jjhiblot@traphandler.com>
+
+description: |
+  This driver combines several monochromatic LEDs into one multi-color
+  LED using the multicolor LED class.
+
+properties:
+  compatible:
+    const: leds-group-multicolor
+
+  leds:
+    description:
+      An aray of monochromatic leds
+    $ref: /schemas/types.yaml#/definitions/phandle-array
+
+required:
+  - leds
+
+allOf:
+  - $ref: leds-class-multicolor.yaml#
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/leds/common.h>
+
+    monochromatic-leds {
+        compatible = "gpio-leds";
+
+        led0: led-0 {
+            gpios = <&mcu_pio 0 GPIO_ACTIVE_LOW>;
+            color = <LED_COLOR_ID_RED>;
+        };
+
+        led1: led-1 {
+            gpios = <&mcu_pio 1 GPIO_ACTIVE_HIGH>;
+            color = <LED_COLOR_ID_GREEN>;
+        };
+
+        led2: led-2 {
+            gpios = <&mcu_pio 2 GPIO_ACTIVE_HIGH>;
+            color = <LED_COLOR_ID_BLUE>;
+        };
+    };
+
+    multi-led {
+        compatible = "leds-group-multicolor";
+        color = <LED_COLOR_ID_RGB>;
+        function = LED_FUNCTION_INDICATOR;
+        leds = <&led0>, <&led1>, <&led2>;
+    };
+
+...
index edf6f55..9610bca 100644 (file)
@@ -29,6 +29,10 @@ properties:
 
   gpio-controller: true
 
+  gpio-line-names:
+    minItems: 1
+    maxItems: 16
+
   '#gpio-cells':
     const: 2
 
diff --git a/Documentation/devicetree/bindings/leds/nxp,pca995x.yaml b/Documentation/devicetree/bindings/leds/nxp,pca995x.yaml
new file mode 100644 (file)
index 0000000..654915c
--- /dev/null
@@ -0,0 +1,81 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/nxp,pca995x.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP PCA995x LED controllers
+
+maintainers:
+  - Isai Gaspar <isaiezequiel.gaspar@nxp.com>
+  - Marek Vasut <marex@denx.de>
+
+description:
+  The NXP PCA9952/PCA9955B are programmable LED controllers connected via I2C
+  that can drive 16 separate lines. Each of them can be individually switched
+  on and off, and brightness can be controlled via individual PWM.
+
+  Datasheets are available at
+  https://www.nxp.com/docs/en/data-sheet/PCA9952_PCA9955.pdf
+  https://www.nxp.com/docs/en/data-sheet/PCA9955B.pdf
+
+properties:
+  compatible:
+    enum:
+      - nxp,pca9952
+      - nxp,pca9955b
+
+  reg:
+    maxItems: 1
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+patternProperties:
+  "^led@[0-9a-f]+$":
+    type: object
+    $ref: common.yaml#
+    unevaluatedProperties: false
+
+    properties:
+      reg:
+        minimum: 0
+        maximum: 15
+
+    required:
+      - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/leds/common.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        led-controller@1 {
+            compatible = "nxp,pca9955b";
+            reg = <0x01>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            led@0 {
+                reg = <0x0>;
+                color = <LED_COLOR_ID_RED>;
+                function = LED_FUNCTION_POWER;
+            };
+
+            led@2 {
+                reg = <0x2>;
+                color = <LED_COLOR_ID_WHITE>;
+                function = LED_FUNCTION_STATUS;
+            };
+        };
+    };
+
+...
diff --git a/Documentation/devicetree/bindings/leds/panasonic,an30259a.yaml b/Documentation/devicetree/bindings/leds/panasonic,an30259a.yaml
new file mode 100644 (file)
index 0000000..e918dce
--- /dev/null
@@ -0,0 +1,84 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/panasonic,an30259a.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Panasonic AN30259A 3-channel LED controller
+
+maintainers:
+  - Iskren Chernev <me@iskren.info>
+
+description:
+  The AN30259A is a LED controller capable of driving three LEDs independently.
+  It supports constant current output and sloping current output modes. The chip
+  is connected over I2C.
+
+properties:
+  compatible:
+    const: panasonic,an30259a
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+patternProperties:
+  "^led@[1-3]$":
+    $ref: common.yaml#
+    unevaluatedProperties: false
+
+    properties:
+      reg:
+        enum: [ 1, 2, 3 ]
+
+required:
+  - compatible
+  - reg
+  - "#address-cells"
+  - "#size-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/leds/common.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        led-controller@30 {
+            compatible = "panasonic,an30259a";
+            reg = <0x30>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            led@1 {
+                reg = <1>;
+                linux,default-trigger = "heartbeat";
+                function = LED_FUNCTION_INDICATOR;
+                color = <LED_COLOR_ID_RED>;
+            };
+
+            led@2 {
+                reg = <2>;
+                function = LED_FUNCTION_INDICATOR;
+                color = <LED_COLOR_ID_GREEN>;
+            };
+
+            led@3 {
+                reg = <3>;
+                function = LED_FUNCTION_INDICATOR;
+                color = <LED_COLOR_ID_BLUE>;
+            };
+        };
+    };
+...
index 14700a2..44dd91a 100644 (file)
@@ -35,7 +35,7 @@ properties:
     description: GPIO pin to enable/disable the device.
 
 patternProperties:
-  "^led@[0-6]$":
+  "^led@[0-5]$":
     type: object
     $ref: common.yaml#
     unevaluatedProperties: false
@@ -43,7 +43,7 @@ patternProperties:
     properties:
       reg:
         minimum: 0
-        maximum: 6
+        maximum: 5
 
     required:
       - reg
index 58f0d94..b7a3ef7 100644 (file)
@@ -18,8 +18,6 @@ description: |
 
   The device has two LED outputs referred as GRNLED and AMBLED in data-sheet.
 
-select: false
-
 properties:
   compatible:
     const: rohm,bd71828-leds
index 6046dfe..b92208e 100644 (file)
@@ -521,6 +521,15 @@ config LEDS_PCA963X
          LED driver chip accessed via the I2C bus. Supported
          devices include PCA9633 and PCA9634
 
+config LEDS_PCA995X
+       tristate "LED Support for PCA995x I2C chips"
+       depends on LEDS_CLASS
+       depends on I2C
+       help
+         This option enables support for LEDs connected to PCA995x
+         LED driver chips accessed via the I2C bus. Supported
+         devices include PCA9955BTW, PCA9952TW and PCA9955TW.
+
 config LEDS_WM831X_STATUS
        tristate "LED support for status LEDs on WM831x PMICs"
        depends on LEDS_CLASS
index d71f122..d7348e8 100644 (file)
@@ -72,6 +72,7 @@ obj-$(CONFIG_LEDS_OT200)              += leds-ot200.o
 obj-$(CONFIG_LEDS_PCA9532)             += leds-pca9532.o
 obj-$(CONFIG_LEDS_PCA955X)             += leds-pca955x.o
 obj-$(CONFIG_LEDS_PCA963X)             += leds-pca963x.o
+obj-$(CONFIG_LEDS_PCA995X)             += leds-pca995x.o
 obj-$(CONFIG_LEDS_PM8058)              += leds-pm8058.o
 obj-$(CONFIG_LEDS_POWERNV)             += leds-powernv.o
 obj-$(CONFIG_LEDS_PWM)                 += leds-pwm.o
index 945c842..bdcb737 100644 (file)
@@ -1,10 +1,10 @@
 config LEDS_BCM63138
        tristate "LED Support for Broadcom BCM63138 SoC"
        depends on LEDS_CLASS
-       depends on ARCH_BCM4908 || ARCH_BCM_5301X || BCM63XX || COMPILE_TEST
+       depends on ARCH_BCMBCA || ARCH_BCM_5301X || BCM63XX || COMPILE_TEST
        depends on HAS_IOMEM
        depends on OF
-       default ARCH_BCM4908
+       default ARCH_BCMBCA
        help
          This option enables support for LED controller that is part of
          BCM63138 SoC. The same hardware block is known to be also used
index 4ed2efc..4e08dbc 100644 (file)
@@ -89,6 +89,8 @@ config LEDS_QCOM_FLASH
          the total LED current will be split symmetrically on each channel and
          they will be enabled/disabled at the same time.
 
+         This driver can be built as a module, it will be called "leds-qcom-flash".
+
 config LEDS_RT4505
        tristate "LED support for RT4505 flashlight controller"
        depends on I2C && OF
index b089ca1..a73d3ea 100644 (file)
@@ -309,6 +309,10 @@ static int qcom_flash_strobe_set(struct led_classdev_flash *fled_cdev, bool stat
        struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
        int rc;
 
+       rc = set_flash_strobe(led, SW_STROBE, false);
+       if (rc)
+               return rc;
+
        rc = set_flash_current(led, led->flash_current_ma, FLASH_MODE);
        if (rc)
                return rc;
@@ -745,6 +749,7 @@ static int qcom_flash_led_probe(struct platform_device *pdev)
        return 0;
 
 release:
+       fwnode_handle_put(child);
        while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count)
                v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]);
        return rc;
index e317408..ec62a48 100644 (file)
@@ -6,6 +6,7 @@
 #include <linux/device.h>
 #include <linux/init.h>
 #include <linux/led-class-multicolor.h>
+#include <linux/math.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/uaccess.h>
@@ -19,9 +20,10 @@ int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev,
        int i;
 
        for (i = 0; i < mcled_cdev->num_colors; i++)
-               mcled_cdev->subled_info[i].brightness = brightness *
-                                       mcled_cdev->subled_info[i].intensity /
-                                       led_cdev->max_brightness;
+               mcled_cdev->subled_info[i].brightness =
+                       DIV_ROUND_CLOSEST(brightness *
+                                         mcled_cdev->subled_info[i].intensity,
+                                         led_cdev->max_brightness);
 
        return 0;
 }
index 6dae56b..974b84f 100644 (file)
@@ -22,7 +22,6 @@
 #include <linux/of.h>
 #include "leds.h"
 
-static struct class *leds_class;
 static DEFINE_MUTEX(leds_lookup_lock);
 static LIST_HEAD(leds_lookup_list);
 
@@ -76,6 +75,19 @@ static ssize_t max_brightness_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(max_brightness);
 
+static ssize_t color_show(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       const char *color_text = "invalid";
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+
+       if (led_cdev->color < LED_COLOR_ID_MAX)
+               color_text = led_colors[led_cdev->color];
+
+       return sysfs_emit(buf, "%s\n", color_text);
+}
+static DEVICE_ATTR_RO(color);
+
 #ifdef CONFIG_LEDS_TRIGGERS
 static BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0);
 static struct bin_attribute *led_trigger_bin_attrs[] = {
@@ -90,6 +102,7 @@ static const struct attribute_group led_trigger_group = {
 static struct attribute *led_class_attrs[] = {
        &dev_attr_brightness.attr,
        &dev_attr_max_brightness.attr,
+       &dev_attr_color.attr,
        NULL,
 };
 
@@ -234,6 +247,12 @@ static struct led_classdev *led_module_get(struct device *led_dev)
        return led_cdev;
 }
 
+static const struct class leds_class = {
+       .name = "leds",
+       .dev_groups = led_groups,
+       .pm = &leds_class_dev_pm_ops,
+};
+
 /**
  * of_led_get() - request a LED device via the LED framework
  * @np: device node to get the LED device from
@@ -251,7 +270,7 @@ struct led_classdev *of_led_get(struct device_node *np, int index)
        if (!led_node)
                return ERR_PTR(-ENOENT);
 
-       led_dev = class_find_device_by_of_node(leds_class, led_node);
+       led_dev = class_find_device_by_of_node(&leds_class, led_node);
        of_node_put(led_node);
        put_device(led_dev);
 
@@ -346,7 +365,7 @@ struct led_classdev *led_get(struct device *dev, char *con_id)
        if (!provider)
                return ERR_PTR(-ENOENT);
 
-       led_dev = class_find_device_by_name(leds_class, provider);
+       led_dev = class_find_device_by_name(&leds_class, provider);
        kfree_const(provider);
 
        return led_module_get(led_dev);
@@ -402,6 +421,31 @@ void led_remove_lookup(struct led_lookup_data *led_lookup)
 }
 EXPORT_SYMBOL_GPL(led_remove_lookup);
 
+/**
+ * devm_of_led_get_optional - Resource-managed request of an optional LED device
+ * @dev:       LED consumer
+ * @index:     index of the LED to obtain in the consumer
+ *
+ * The device node of the device is parsed to find the requested LED device.
+ * The LED device returned from this function is automatically released
+ * on driver detach.
+ *
+ * @return a pointer to a LED device, ERR_PTR(errno) on failure and NULL if the
+ * led was not found.
+ */
+struct led_classdev *__must_check devm_of_led_get_optional(struct device *dev,
+                                                       int index)
+{
+       struct led_classdev *led;
+
+       led = devm_of_led_get(dev, index);
+       if (IS_ERR(led) && PTR_ERR(led) == -ENOENT)
+               return NULL;
+
+       return led;
+}
+EXPORT_SYMBOL_GPL(devm_of_led_get_optional);
+
 static int led_classdev_next_name(const char *init_name, char *name,
                                  size_t len)
 {
@@ -412,7 +456,7 @@ static int led_classdev_next_name(const char *init_name, char *name,
        strscpy(name, init_name, len);
 
        while ((ret < len) &&
-              (dev = class_find_device_by_name(leds_class, name))) {
+              (dev = class_find_device_by_name(&leds_class, name))) {
                put_device(dev);
                ret = snprintf(name, len, "%s_%u", init_name, ++i);
        }
@@ -457,6 +501,14 @@ int led_classdev_register_ext(struct device *parent,
                        if (fwnode_property_present(init_data->fwnode,
                                                    "retain-state-shutdown"))
                                led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN;
+
+                       fwnode_property_read_u32(init_data->fwnode,
+                               "max-brightness",
+                               &led_cdev->max_brightness);
+
+                       if (fwnode_property_present(init_data->fwnode, "color"))
+                               fwnode_property_read_u32(init_data->fwnode, "color",
+                                                        &led_cdev->color);
                }
        } else {
                proposed_name = led_cdev->name;
@@ -466,10 +518,13 @@ int led_classdev_register_ext(struct device *parent,
        if (ret < 0)
                return ret;
 
+       if (led_cdev->color >= LED_COLOR_ID_MAX)
+               dev_warn(parent, "LED %s color identifier out of range\n", final_name);
+
        mutex_init(&led_cdev->led_access);
        mutex_lock(&led_cdev->led_access);
-       led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
-                               led_cdev, led_cdev->groups, "%s", final_name);
+       led_cdev->dev = device_create_with_groups(&leds_class, parent, 0,
+                                                 led_cdev, led_cdev->groups, "%s", final_name);
        if (IS_ERR(led_cdev->dev)) {
                mutex_unlock(&led_cdev->led_access);
                return PTR_ERR(led_cdev->dev);
@@ -626,17 +681,12 @@ EXPORT_SYMBOL_GPL(devm_led_classdev_unregister);
 
 static int __init leds_init(void)
 {
-       leds_class = class_create("leds");
-       if (IS_ERR(leds_class))
-               return PTR_ERR(leds_class);
-       leds_class->pm = &leds_class_dev_pm_ops;
-       leds_class->dev_groups = led_groups;
-       return 0;
+       return class_register(&leds_class);
 }
 
 static void __exit leds_exit(void)
 {
-       class_destroy(leds_class);
+       class_unregister(&leds_class);
 }
 
 subsys_initcall(leds_init);
index b9b1295..04f9ea6 100644 (file)
@@ -474,15 +474,15 @@ int led_compose_name(struct device *dev, struct led_init_data *init_data,
        struct fwnode_handle *fwnode = init_data->fwnode;
        const char *devicename = init_data->devicename;
 
-       /* We want to label LEDs that can produce full range of colors
-        * as RGB, not multicolor */
-       BUG_ON(props.color == LED_COLOR_ID_MULTI);
-
        if (!led_classdev_name)
                return -EINVAL;
 
        led_parse_fwnode_props(dev, fwnode, &props);
 
+       /* We want to label LEDs that can produce full range of colors
+        * as RGB, not multicolor */
+       BUG_ON(props.color == LED_COLOR_ID_MULTI);
+
        if (props.label) {
                /*
                 * If init_data.devicename is NULL, then it indicates that
index 24b1041..0216afe 100644 (file)
@@ -344,7 +344,7 @@ MODULE_DEVICE_TABLE(i2c, an30259a_id);
 static struct i2c_driver an30259a_driver = {
        .driver = {
                .name = "leds-an30259a",
-               .of_match_table = of_match_ptr(an30259a_match_table),
+               .of_match_table = an30259a_match_table,
        },
        .probe = an30259a_probe,
        .remove = an30259a_remove,
index 49e1bdd..dd319c7 100644 (file)
@@ -7,8 +7,8 @@
 
 #include <linux/module.h>
 #include <linux/leds.h>
+#include <linux/platform_device.h>
 #include <linux/regmap.h>
-#include <linux/of_platform.h>
 
 enum ec_index {
        EC_BLUE_LED     = 0x01,
index 96979b8..691a743 100644 (file)
@@ -368,7 +368,7 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
 
        if (!chip->display_rows ||
            chip->display_rows > chip->cdef->display_size_rows_max) {
-               return dev_err_probe(dev, ret,
+               return dev_err_probe(dev, -EINVAL,
                                     "Invalid leds display size %u\n",
                                     chip->display_rows);
        }
@@ -583,7 +583,7 @@ static struct i2c_driver aw200xx_driver = {
                .name = "aw200xx",
                .of_match_table = aw200xx_match_table,
        },
-       .probe_new = aw200xx_probe,
+       .probe = aw200xx_probe,
        .remove = aw200xx_remove,
        .id_table = aw200xx_id,
 };
index 5976564..91f44b2 100644 (file)
@@ -62,7 +62,7 @@ struct aw2013_led {
 
 struct aw2013 {
        struct mutex mutex; /* held when writing to registers */
-       struct regulator *vcc_regulator;
+       struct regulator_bulk_data regulators[2];
        struct i2c_client *client;
        struct aw2013_led leds[AW2013_MAX_LEDS];
        struct regmap *regmap;
@@ -106,10 +106,11 @@ static void aw2013_chip_disable(struct aw2013 *chip)
 
        regmap_write(chip->regmap, AW2013_GCR, 0);
 
-       ret = regulator_disable(chip->vcc_regulator);
+       ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators),
+                                    chip->regulators);
        if (ret) {
                dev_err(&chip->client->dev,
-                       "Failed to disable regulator: %d\n", ret);
+                       "Failed to disable regulators: %d\n", ret);
                return;
        }
 
@@ -123,10 +124,11 @@ static int aw2013_chip_enable(struct aw2013 *chip)
        if (chip->enabled)
                return 0;
 
-       ret = regulator_enable(chip->vcc_regulator);
+       ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators),
+                                   chip->regulators);
        if (ret) {
                dev_err(&chip->client->dev,
-                       "Failed to enable regulator: %d\n", ret);
+                       "Failed to enable regulators: %d\n", ret);
                return ret;
        }
        chip->enabled = true;
@@ -348,19 +350,23 @@ static int aw2013_probe(struct i2c_client *client)
                goto error;
        }
 
-       chip->vcc_regulator = devm_regulator_get(&client->dev, "vcc");
-       ret = PTR_ERR_OR_ZERO(chip->vcc_regulator);
-       if (ret) {
+       chip->regulators[0].supply = "vcc";
+       chip->regulators[1].supply = "vio";
+       ret = devm_regulator_bulk_get(&client->dev,
+                                     ARRAY_SIZE(chip->regulators),
+                                     chip->regulators);
+       if (ret < 0) {
                if (ret != -EPROBE_DEFER)
                        dev_err(&client->dev,
-                               "Failed to request regulator: %d\n", ret);
+                               "Failed to request regulators: %d\n", ret);
                goto error;
        }
 
-       ret = regulator_enable(chip->vcc_regulator);
+       ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators),
+                                   chip->regulators);
        if (ret) {
                dev_err(&client->dev,
-                       "Failed to enable regulator: %d\n", ret);
+                       "Failed to enable regulators: %d\n", ret);
                goto error;
        }
 
@@ -382,10 +388,11 @@ static int aw2013_probe(struct i2c_client *client)
        if (ret < 0)
                goto error_reg;
 
-       ret = regulator_disable(chip->vcc_regulator);
+       ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators),
+                                    chip->regulators);
        if (ret) {
                dev_err(&client->dev,
-                       "Failed to disable regulator: %d\n", ret);
+                       "Failed to disable regulators: %d\n", ret);
                goto error;
        }
 
@@ -394,7 +401,8 @@ static int aw2013_probe(struct i2c_client *client)
        return 0;
 
 error_reg:
-       regulator_disable(chip->vcc_regulator);
+       regulator_bulk_disable(ARRAY_SIZE(chip->regulators),
+                              chip->regulators);
 
 error:
        mutex_destroy(&chip->mutex);
@@ -420,7 +428,7 @@ MODULE_DEVICE_TABLE(of, aw2013_match_table);
 static struct i2c_driver aw2013_driver = {
        .driver = {
                .name = "leds-aw2013",
-               .of_match_table = of_match_ptr(aw2013_match_table),
+               .of_match_table = aw2013_match_table,
        },
        .probe = aw2013_probe,
        .remove = aw2013_remove,
index 7d41ce8..87354f1 100644 (file)
@@ -7,7 +7,7 @@
 #include <linux/mfd/motorola-cpcap.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
index c87686b..b33bca3 100644 (file)
@@ -4,8 +4,8 @@
 
 #include <linux/delay.h>
 #include <linux/leds.h>
+#include <linux/mod_devicetable.h>
 #include <linux/module.h>
-#include <linux/of_device.h>
 #include <linux/spi/spi.h>
 #include <linux/workqueue.h>
 
index 1f952ba..2df24c3 100644 (file)
@@ -27,22 +27,16 @@ static void ip30led_set(struct led_classdev *led_cdev,
 
 static int ip30led_create(struct platform_device *pdev, int num)
 {
-       struct resource *res;
        struct ip30_led *data;
 
-       res = platform_get_resource(pdev, IORESOURCE_MEM, num);
-       if (!res)
-               return -EBUSY;
-
        data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
        if (!data)
                return -ENOMEM;
 
-       data->reg = devm_ioremap_resource(&pdev->dev, res);
+       data->reg = devm_platform_ioremap_resource(pdev, num);
        if (IS_ERR(data->reg))
                return PTR_ERR(data->reg);
 
-
        switch (num) {
        case IP30_LED_SYSTEM:
                data->cdev.name = "white:power";
index 72cb56d..b0a0be7 100644 (file)
@@ -15,7 +15,6 @@
 #include <linux/leds.h>
 #include <linux/module.h>
 #include <linux/of.h>
-#include <linux/of_device.h>
 
 /* Used to indicate a device has no such register */
 #define IS31FL32XX_REG_NONE 0xFF
index 030c040..2ef19ad 100644 (file)
@@ -594,18 +594,17 @@ static const struct i2c_device_id lp5521_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, lp5521_id);
 
-#ifdef CONFIG_OF
 static const struct of_device_id of_lp5521_leds_match[] = {
        { .compatible = "national,lp5521", },
        {},
 };
 
 MODULE_DEVICE_TABLE(of, of_lp5521_leds_match);
-#endif
+
 static struct i2c_driver lp5521_driver = {
        .driver = {
                .name   = "lp5521",
-               .of_match_table = of_match_ptr(of_lp5521_leds_match),
+               .of_match_table = of_lp5521_leds_match,
        },
        .probe          = lp5521_probe,
        .remove         = lp5521_remove,
index daa6a16..38de853 100644 (file)
@@ -972,7 +972,6 @@ static const struct i2c_device_id lp5523_id[] = {
 
 MODULE_DEVICE_TABLE(i2c, lp5523_id);
 
-#ifdef CONFIG_OF
 static const struct of_device_id of_lp5523_leds_match[] = {
        { .compatible = "national,lp5523", },
        { .compatible = "ti,lp55231", },
@@ -980,12 +979,11 @@ static const struct of_device_id of_lp5523_leds_match[] = {
 };
 
 MODULE_DEVICE_TABLE(of, of_lp5523_leds_match);
-#endif
 
 static struct i2c_driver lp5523_driver = {
        .driver = {
                .name   = "lp5523x",
-               .of_match_table = of_match_ptr(of_lp5523_leds_match),
+               .of_match_table = of_lp5523_leds_match,
        },
        .probe          = lp5523_probe,
        .remove         = lp5523_remove,
index 4565cc1..39db9ae 100644 (file)
@@ -589,19 +589,17 @@ static const struct i2c_device_id lp5562_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, lp5562_id);
 
-#ifdef CONFIG_OF
 static const struct of_device_id of_lp5562_leds_match[] = {
        { .compatible = "ti,lp5562", },
        {},
 };
 
 MODULE_DEVICE_TABLE(of, of_lp5562_leds_match);
-#endif
 
 static struct i2c_driver lp5562_driver = {
        .driver = {
                .name   = "lp5562",
-               .of_match_table = of_match_ptr(of_lp5562_leds_match),
+               .of_match_table = of_lp5562_leds_match,
        },
        .probe          = lp5562_probe,
        .remove         = lp5562_remove,
index f11886a..ac50aa8 100644 (file)
@@ -380,19 +380,17 @@ static const struct i2c_device_id lp8501_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, lp8501_id);
 
-#ifdef CONFIG_OF
 static const struct of_device_id of_lp8501_leds_match[] = {
        { .compatible = "ti,lp8501", },
        {},
 };
 
 MODULE_DEVICE_TABLE(of, of_lp8501_leds_match);
-#endif
 
 static struct i2c_driver lp8501_driver = {
        .driver = {
                .name   = "lp8501",
-               .of_match_table = of_match_ptr(of_lp8501_leds_match),
+               .of_match_table = of_lp8501_leds_match,
        },
        .probe          = lp8501_probe,
        .remove         = lp8501_remove,
index b7855c9..3921065 100644 (file)
@@ -8,7 +8,6 @@
 #include <linux/io.h>
 #include <linux/leds.h>
 #include <linux/module.h>
-#include <linux/of_device.h>
 #include <linux/platform_data/mlxreg.h>
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
index 1677d66..f3010c4 100644 (file)
@@ -247,7 +247,7 @@ static int ns2_led_probe(struct platform_device *pdev)
        if (!count)
                return -ENODEV;
 
-       leds = devm_kzalloc(dev, array_size(sizeof(*leds), count), GFP_KERNEL);
+       leds = devm_kcalloc(dev, count, sizeof(*leds), GFP_KERNEL);
        if (!leds)
                return -ENOMEM;
 
index 8b5c620..bf8bb8f 100644 (file)
@@ -18,7 +18,6 @@
 #include <linux/leds-pca9532.h>
 #include <linux/gpio/driver.h>
 #include <linux/of.h>
-#include <linux/of_device.h>
 
 /* m =  num_leds*/
 #define PCA9532_REG_INPUT(i)   ((i) >> 3)
diff --git a/drivers/leds/leds-pca995x.c b/drivers/leds/leds-pca995x.c
new file mode 100644 (file)
index 0000000..78215df
--- /dev/null
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED driver for PCA995x I2C LED drivers
+ *
+ * Copyright 2011 bct electronic GmbH
+ * Copyright 2013 Qtechnology/AS
+ * Copyright 2022 NXP
+ * Copyright 2023 Marek Vasut
+ */
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+/* Register definition */
+#define PCA995X_MODE1                  0x00
+#define PCA995X_MODE2                  0x01
+#define PCA995X_LEDOUT0                        0x02
+#define PCA9955B_PWM0                  0x08
+#define PCA9952_PWM0                   0x0A
+#define PCA9952_IREFALL                        0x43
+#define PCA9955B_IREFALL               0x45
+
+/* Auto-increment disabled. Normal mode */
+#define PCA995X_MODE1_CFG              0x00
+
+/* LED select registers determine the source that drives LED outputs */
+#define PCA995X_LED_OFF                        0x0
+#define PCA995X_LED_ON                 0x1
+#define PCA995X_LED_PWM_MODE           0x2
+#define PCA995X_LDRX_MASK              0x3
+#define PCA995X_LDRX_BITS              2
+
+#define PCA995X_MAX_OUTPUTS            16
+#define PCA995X_OUTPUTS_PER_REG                4
+
+#define PCA995X_IREFALL_FULL_CFG       0xFF
+#define PCA995X_IREFALL_HALF_CFG       (PCA995X_IREFALL_FULL_CFG / 2)
+
+#define PCA995X_TYPE_NON_B             0
+#define PCA995X_TYPE_B                 1
+
+#define ldev_to_led(c) container_of(c, struct pca995x_led, ldev)
+
+struct pca995x_led {
+       unsigned int led_no;
+       struct led_classdev ldev;
+       struct pca995x_chip *chip;
+};
+
+struct pca995x_chip {
+       struct regmap *regmap;
+       struct pca995x_led leds[PCA995X_MAX_OUTPUTS];
+       int btype;
+};
+
+static int pca995x_brightness_set(struct led_classdev *led_cdev,
+                                 enum led_brightness brightness)
+{
+       struct pca995x_led *led = ldev_to_led(led_cdev);
+       struct pca995x_chip *chip = led->chip;
+       u8 ledout_addr, pwmout_addr;
+       int shift, ret;
+
+       pwmout_addr = (chip->btype ? PCA9955B_PWM0 : PCA9952_PWM0) + led->led_no;
+       ledout_addr = PCA995X_LEDOUT0 + (led->led_no / PCA995X_OUTPUTS_PER_REG);
+       shift = PCA995X_LDRX_BITS * (led->led_no % PCA995X_OUTPUTS_PER_REG);
+
+       switch (brightness) {
+       case LED_FULL:
+               return regmap_update_bits(chip->regmap, ledout_addr,
+                                         PCA995X_LDRX_MASK << shift,
+                                         PCA995X_LED_ON << shift);
+       case LED_OFF:
+               return regmap_update_bits(chip->regmap, ledout_addr,
+                                         PCA995X_LDRX_MASK << shift, 0);
+       default:
+               /* Adjust brightness as per user input by changing individual PWM */
+               ret = regmap_write(chip->regmap, pwmout_addr, brightness);
+               if (ret)
+                       return ret;
+
+               /*
+                * Change LDRx configuration to individual brightness via PWM.
+                * LED will stop blinking if it's doing so.
+                */
+               return regmap_update_bits(chip->regmap, ledout_addr,
+                                         PCA995X_LDRX_MASK << shift,
+                                         PCA995X_LED_PWM_MODE << shift);
+       }
+}
+
+static const struct regmap_config pca995x_regmap = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .max_register = 0x49,
+};
+
+static int pca995x_probe(struct i2c_client *client)
+{
+       struct fwnode_handle *led_fwnodes[PCA995X_MAX_OUTPUTS] = { 0 };
+       struct fwnode_handle *np, *child;
+       struct device *dev = &client->dev;
+       struct pca995x_chip *chip;
+       struct pca995x_led *led;
+       int i, btype, reg, ret;
+
+       btype = (unsigned long)device_get_match_data(&client->dev);
+
+       np = dev_fwnode(dev);
+       if (!np)
+               return -ENODEV;
+
+       chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+       if (!chip)
+               return -ENOMEM;
+
+       chip->btype = btype;
+       chip->regmap = devm_regmap_init_i2c(client, &pca995x_regmap);
+       if (IS_ERR(chip->regmap))
+               return PTR_ERR(chip->regmap);
+
+       i2c_set_clientdata(client, chip);
+
+       fwnode_for_each_available_child_node(np, child) {
+               ret = fwnode_property_read_u32(child, "reg", &reg);
+               if (ret) {
+                       fwnode_handle_put(child);
+                       return ret;
+               }
+
+               if (reg < 0 || reg >= PCA995X_MAX_OUTPUTS || led_fwnodes[reg]) {
+                       fwnode_handle_put(child);
+                       return -EINVAL;
+               }
+
+               led = &chip->leds[reg];
+               led_fwnodes[reg] = child;
+               led->chip = chip;
+               led->led_no = reg;
+               led->ldev.brightness_set_blocking = pca995x_brightness_set;
+               led->ldev.max_brightness = 255;
+       }
+
+       for (i = 0; i < PCA995X_MAX_OUTPUTS; i++) {
+               struct led_init_data init_data = {};
+
+               if (!led_fwnodes[i])
+                       continue;
+
+               init_data.fwnode = led_fwnodes[i];
+
+               ret = devm_led_classdev_register_ext(dev,
+                                                    &chip->leds[i].ldev,
+                                                    &init_data);
+               if (ret < 0) {
+                       fwnode_handle_put(child);
+                       return dev_err_probe(dev, ret,
+                                            "Could not register LED %s\n",
+                                            chip->leds[i].ldev.name);
+               }
+       }
+
+       /* Disable LED all-call address and set normal mode */
+       ret = regmap_write(chip->regmap, PCA995X_MODE1, PCA995X_MODE1_CFG);
+       if (ret)
+               return ret;
+
+       /* IREF Output current value for all LEDn outputs */
+       return regmap_write(chip->regmap,
+                           btype ? PCA9955B_IREFALL : PCA9952_IREFALL,
+                           PCA995X_IREFALL_HALF_CFG);
+}
+
+static const struct i2c_device_id pca995x_id[] = {
+       { "pca9952", .driver_data = (kernel_ulong_t)PCA995X_TYPE_NON_B },
+       { "pca9955b", .driver_data = (kernel_ulong_t)PCA995X_TYPE_B },
+       {}
+};
+MODULE_DEVICE_TABLE(i2c, pca995x_id);
+
+static const struct of_device_id pca995x_of_match[] = {
+       { .compatible = "nxp,pca9952",  .data = (void *)PCA995X_TYPE_NON_B },
+       { .compatible = "nxp,pca9955b", .data = (void *)PCA995X_TYPE_B },
+       {},
+};
+MODULE_DEVICE_TABLE(of, pca995x_of_match);
+
+static struct i2c_driver pca995x_driver = {
+       .driver = {
+               .name = "leds-pca995x",
+               .of_match_table = pca995x_of_match,
+       },
+       .probe = pca995x_probe,
+       .id_table = pca995x_id,
+};
+module_i2c_driver(pca995x_driver);
+
+MODULE_AUTHOR("Isai Gaspar <isaiezequiel.gaspar@nxp.com>");
+MODULE_DESCRIPTION("PCA995x LED driver");
+MODULE_LICENSE("GPL");
index b9233f1..3f49a51 100644 (file)
@@ -4,7 +4,6 @@
 #include <linux/leds.h>
 #include <linux/module.h>
 #include <linux/of.h>
-#include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/pm.h>
 #include <linux/regmap.h>
index 29194cc..419b710 100644 (file)
@@ -12,7 +12,7 @@
 #include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/platform_device.h>
-#include <linux/of_platform.h>
+#include <linux/of.h>
 #include <linux/leds.h>
 #include <linux/err.h>
 #include <linux/pwm.h>
@@ -146,7 +146,7 @@ static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv)
                        led.name = to_of_node(fwnode)->name;
 
                if (!led.name) {
-                       ret = EINVAL;
+                       ret = -EINVAL;
                        goto err_child_out;
                }
 
index 2c7ffc3..9d91f21 100644 (file)
@@ -30,7 +30,7 @@
 
 #include <linux/leds.h>
 #include <linux/module.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
 #include <linux/spi/spi.h>
 #include <linux/mutex.h>
 #include <uapi/linux/uleds.h>
index e38abb5..360a376 100644 (file)
@@ -7,8 +7,7 @@
  */
 #include <linux/io.h>
 #include <linux/init.h>
-#include <linux/of_device.h>
-#include <linux/of_address.h>
+#include <linux/of.h>
 #include <linux/platform_device.h>
 #include <linux/stat.h>
 #include <linux/slab.h>
index d7f10ad..b249166 100644 (file)
@@ -7,7 +7,7 @@
 
 #include <linux/bitops.h>
 #include <linux/err.h>
-#include <linux/of_device.h>
+#include <linux/property.h>
 
 #include <linux/leds-ti-lmu-common.h>
 
index dfc6fb2..945e831 100644 (file)
@@ -8,7 +8,6 @@
 #include <linux/leds.h>
 #include <linux/module.h>
 #include <linux/of.h>
-#include <linux/of_device.h>
 #include <linux/regmap.h>
 #include <linux/slab.h>
 
index 64b2d7b..b8a95a9 100644 (file)
@@ -156,24 +156,20 @@ static ssize_t brightness_show(struct device *dev, struct device_attribute *a,
                               char *buf)
 {
        struct i2c_client *client = to_i2c_client(dev);
-       struct omnia_leds *leds = i2c_get_clientdata(client);
        int ret;
 
-       mutex_lock(&leds->lock);
        ret = i2c_smbus_read_byte_data(client, CMD_LED_GET_BRIGHTNESS);
-       mutex_unlock(&leds->lock);
 
        if (ret < 0)
                return ret;
 
-       return sprintf(buf, "%d\n", ret);
+       return sysfs_emit(buf, "%d\n", ret);
 }
 
 static ssize_t brightness_store(struct device *dev, struct device_attribute *a,
                                const char *buf, size_t count)
 {
        struct i2c_client *client = to_i2c_client(dev);
-       struct omnia_leds *leds = i2c_get_clientdata(client);
        unsigned long brightness;
        int ret;
 
@@ -183,15 +179,10 @@ static ssize_t brightness_store(struct device *dev, struct device_attribute *a,
        if (brightness > 100)
                return -EINVAL;
 
-       mutex_lock(&leds->lock);
        ret = i2c_smbus_write_byte_data(client, CMD_LED_SET_BRIGHTNESS,
                                        (u8)brightness);
-       mutex_unlock(&leds->lock);
-
-       if (ret < 0)
-               return ret;
 
-       return count;
+       return ret < 0 ? ret : count;
 }
 static DEVICE_ATTR_RW(brightness);
 
index 360c867..183bccc 100644 (file)
@@ -2,6 +2,18 @@
 
 if LEDS_CLASS_MULTICOLOR
 
+config LEDS_GROUP_MULTICOLOR
+       tristate "LEDs group multi-color support"
+       depends on OF || COMPILE_TEST
+       help
+         This option enables support for monochrome LEDs that are grouped
+         into multicolor LEDs which is useful in the case where LEDs of
+         different colors are physically grouped in a single multi-color LED
+         and driven by a controller that doesn't have multi-color support.
+
+         To compile this driver as a module, choose M here: the module
+         will be called leds-group-multicolor.
+
 config LEDS_PWM_MULTICOLOR
        tristate "PWM driven multi-color LED Support"
        depends on PWM
index 8c01daf..c11cc56 100644 (file)
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 
+obj-$(CONFIG_LEDS_GROUP_MULTICOLOR)    += leds-group-multicolor.o
 obj-$(CONFIG_LEDS_PWM_MULTICOLOR)      += leds-pwm-multicolor.o
 obj-$(CONFIG_LEDS_QCOM_LPG)            += leds-qcom-lpg.o
 obj-$(CONFIG_LEDS_MT6370_RGB)          += leds-mt6370-rgb.o
diff --git a/drivers/leds/rgb/leds-group-multicolor.c b/drivers/leds/rgb/leds-group-multicolor.c
new file mode 100644 (file)
index 0000000..39f58be
--- /dev/null
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Multi-color LED built with monochromatic LED devices
+ *
+ * This driver groups several monochromatic LED devices in a single multicolor LED device.
+ *
+ * Compared to handling this grouping in user-space, the benefits are:
+ * - The state of the monochromatic LED relative to each other is always consistent.
+ * - The sysfs interface of the LEDs can be used for the group as a whole.
+ *
+ * Copyright 2023 Jean-Jacques Hiblot <jjhiblot@traphandler.com>
+ */
+
+#include <linux/err.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+struct leds_multicolor {
+       struct led_classdev_mc mc_cdev;
+       struct led_classdev **monochromatics;
+};
+
+static int leds_gmc_set(struct led_classdev *cdev, enum led_brightness brightness)
+{
+       struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+       struct leds_multicolor *priv = container_of(mc_cdev, struct leds_multicolor, mc_cdev);
+       const unsigned int group_max_brightness = mc_cdev->led_cdev.max_brightness;
+       int i;
+
+       for (i = 0; i < mc_cdev->num_colors; i++) {
+               struct led_classdev *mono = priv->monochromatics[i];
+               const unsigned int mono_max_brightness = mono->max_brightness;
+               unsigned int intensity = mc_cdev->subled_info[i].intensity;
+               int mono_brightness;
+
+               /*
+                * Scale the brightness according to relative intensity of the
+                * color AND the max brightness of the monochromatic LED.
+                */
+               mono_brightness = DIV_ROUND_CLOSEST(brightness * intensity * mono_max_brightness,
+                                                   group_max_brightness * group_max_brightness);
+
+               led_set_brightness(mono, mono_brightness);
+       }
+
+       return 0;
+}
+
+static void restore_sysfs_write_access(void *data)
+{
+       struct led_classdev *led_cdev = data;
+
+       /* Restore the write acccess to the LED */
+       mutex_lock(&led_cdev->led_access);
+       led_sysfs_enable(led_cdev);
+       mutex_unlock(&led_cdev->led_access);
+}
+
+static int leds_gmc_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct led_init_data init_data = {};
+       struct led_classdev *cdev;
+       struct mc_subled *subled;
+       struct leds_multicolor *priv;
+       unsigned int max_brightness = 0;
+       int i, ret, count = 0;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       for (;;) {
+               struct led_classdev *led_cdev;
+
+               led_cdev = devm_of_led_get_optional(dev, count);
+               if (IS_ERR(led_cdev))
+                       return dev_err_probe(dev, PTR_ERR(led_cdev), "Unable to get LED #%d",
+                                            count);
+               if (!led_cdev)
+                       break;
+
+               priv->monochromatics = devm_krealloc_array(dev, priv->monochromatics,
+                                       count + 1, sizeof(*priv->monochromatics),
+                                       GFP_KERNEL);
+               if (!priv->monochromatics)
+                       return -ENOMEM;
+
+               priv->monochromatics[count] = led_cdev;
+
+               max_brightness = max(max_brightness, led_cdev->max_brightness);
+
+               count++;
+       }
+
+       subled = devm_kcalloc(dev, count, sizeof(*subled), GFP_KERNEL);
+       if (!subled)
+               return -ENOMEM;
+       priv->mc_cdev.subled_info = subled;
+
+       for (i = 0; i < count; i++) {
+               struct led_classdev *led_cdev = priv->monochromatics[i];
+
+               subled[i].color_index = led_cdev->color;
+
+               /* Configure the LED intensity to its maximum */
+               subled[i].intensity = max_brightness;
+       }
+
+       /* Initialise the multicolor's LED class device */
+       cdev = &priv->mc_cdev.led_cdev;
+       cdev->flags = LED_CORE_SUSPENDRESUME;
+       cdev->brightness_set_blocking = leds_gmc_set;
+       cdev->max_brightness = max_brightness;
+       cdev->color = LED_COLOR_ID_MULTI;
+       priv->mc_cdev.num_colors = count;
+
+       init_data.fwnode = dev_fwnode(dev);
+       ret = devm_led_classdev_multicolor_register_ext(dev, &priv->mc_cdev, &init_data);
+       if (ret)
+               return dev_err_probe(dev, ret, "failed to register multicolor LED for %s.\n",
+                                    cdev->name);
+
+       ret = leds_gmc_set(cdev, cdev->brightness);
+       if (ret)
+               return dev_err_probe(dev, ret, "failed to set LED value for %s.", cdev->name);
+
+       for (i = 0; i < count; i++) {
+               struct led_classdev *led_cdev = priv->monochromatics[i];
+
+               /*
+                * Make the individual LED sysfs interface read-only to prevent the user
+                * to change the brightness of the individual LEDs of the group.
+                */
+               mutex_lock(&led_cdev->led_access);
+               led_sysfs_disable(led_cdev);
+               mutex_unlock(&led_cdev->led_access);
+
+               /* Restore the write access to the LED sysfs when the group is destroyed */
+               devm_add_action_or_reset(dev, restore_sysfs_write_access, led_cdev);
+       }
+
+       return 0;
+}
+
+static const struct of_device_id of_leds_group_multicolor_match[] = {
+       { .compatible = "leds-group-multicolor" },
+       {}
+};
+MODULE_DEVICE_TABLE(of, of_leds_group_multicolor_match);
+
+static struct platform_driver leds_group_multicolor_driver = {
+       .probe          = leds_gmc_probe,
+       .driver         = {
+               .name   = "leds_group_multicolor",
+               .of_match_table = of_leds_group_multicolor_match,
+       }
+};
+module_platform_driver(leds_group_multicolor_driver);
+
+MODULE_AUTHOR("Jean-Jacques Hiblot <jjhiblot@traphandler.com>");
+MODULE_DESCRIPTION("LEDs group multicolor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:leds-group-multicolor");
index 59581b3..df469aa 100644 (file)
@@ -9,7 +9,6 @@
 #include <linux/led-class-multicolor.h>
 #include <linux/module.h>
 #include <linux/of.h>
-#include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/pwm.h>
 #include <linux/regmap.h>
@@ -1093,7 +1092,6 @@ static int lpg_add_pwm(struct lpg *lpg)
 {
        int ret;
 
-       lpg->pwm.base = -1;
        lpg->pwm.dev = lpg->dev;
        lpg->pwm.npwm = lpg->num_channels;
        lpg->pwm.ops = &lpg_pwm_ops;
index 609e438..e616cc6 100644 (file)
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config LEDS_SIEMENS_SIMATIC_IPC
        tristate "LED driver for Siemens Simatic IPCs"
+       depends on LEDS_CLASS
        depends on SIEMENS_SIMATIC_IPC
        default y
        help
@@ -35,3 +36,16 @@ config LEDS_SIEMENS_SIMATIC_IPC_F7188X
 
          To compile this driver as a module, choose M here: the module
          will be called simatic-ipc-leds-gpio-f7188x.
+
+config LEDS_SIEMENS_SIMATIC_IPC_ELKHARTLAKE
+       tristate "LED driver for Siemens Simatic IPCs based on Intel Elkhart Lake GPIO"
+       depends on LEDS_GPIO
+       depends on PINCTRL_ELKHARTLAKE
+       depends on SIEMENS_SIMATIC_IPC
+       default LEDS_SIEMENS_SIMATIC_IPC
+       help
+         This option enables support for the LEDs of several Industrial PCs
+         from Siemens based on Elkhart Lake GPIO i.e. BX-21A.
+
+         To compile this driver as a module, choose M here: the module
+         will be called simatic-ipc-leds-gpio-elkhartlake.
index e3e840c..783578f 100644 (file)
@@ -2,3 +2,4 @@
 obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC)                 += simatic-ipc-leds.o
 obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_APOLLOLAKE)      += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-apollolake.o
 obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_F7188X)          += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-f7188x.o
+obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_ELKHARTLAKE)     += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-elkhartlake.o
index 2a21b66..c552ea7 100644 (file)
@@ -57,6 +57,7 @@ int simatic_ipc_leds_gpio_probe(struct platform_device *pdev,
        switch (plat->devmode) {
        case SIMATIC_IPC_DEVICE_127E:
        case SIMATIC_IPC_DEVICE_227G:
+       case SIMATIC_IPC_DEVICE_BX_21A:
                break;
        default:
                return -ENODEV;
@@ -72,6 +73,9 @@ int simatic_ipc_leds_gpio_probe(struct platform_device *pdev,
                goto out;
        }
 
+       if (!table_extra)
+               return 0;
+
        table_extra->dev_id = dev_name(dev);
        gpiod_add_lookup_table(table_extra);
 
diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-elkhartlake.c b/drivers/leds/simple/simatic-ipc-leds-gpio-elkhartlake.c
new file mode 100644 (file)
index 0000000..6ba21db
--- /dev/null
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Siemens SIMATIC IPC driver for GPIO based LEDs
+ *
+ * Copyright (c) Siemens AG, 2023
+ *
+ * Author:
+ *  Henning Schild <henning.schild@siemens.com>
+ */
+
+#include <linux/gpio/machine.h>
+#include <linux/gpio/consumer.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/x86/simatic-ipc-base.h>
+
+#include "simatic-ipc-leds-gpio.h"
+
+static struct gpiod_lookup_table simatic_ipc_led_gpio_table = {
+       .dev_id = "leds-gpio",
+       .table = {
+               GPIO_LOOKUP_IDX("INTC1020:04", 72, NULL, 0, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("INTC1020:04", 77, NULL, 1, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("INTC1020:04", 78, NULL, 2, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("INTC1020:04", 58, NULL, 3, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("INTC1020:04", 60, NULL, 4, GPIO_ACTIVE_HIGH),
+               GPIO_LOOKUP_IDX("INTC1020:04", 62, NULL, 5, GPIO_ACTIVE_HIGH),
+               {} /* Terminating entry */
+       },
+};
+
+static int simatic_ipc_leds_gpio_elkhartlake_probe(struct platform_device *pdev)
+{
+       return simatic_ipc_leds_gpio_probe(pdev, &simatic_ipc_led_gpio_table,
+                                          NULL);
+}
+
+static int simatic_ipc_leds_gpio_elkhartlake_remove(struct platform_device *pdev)
+{
+       return simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table,
+                                           NULL);
+}
+
+static struct platform_driver simatic_ipc_led_gpio_elkhartlake_driver = {
+       .probe = simatic_ipc_leds_gpio_elkhartlake_probe,
+       .remove = simatic_ipc_leds_gpio_elkhartlake_remove,
+       .driver = {
+               .name = KBUILD_MODNAME,
+       },
+};
+module_platform_driver(simatic_ipc_led_gpio_elkhartlake_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" KBUILD_MODNAME);
+MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core platform:elkhartlake-pinctrl");
+MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
index bf258c3..3d4877a 100644 (file)
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+/* SPDX-License-Identifier: GPL-2.0 */
 /*
  * Siemens SIMATIC IPC driver for GPIO based LEDs
  *
index cc32615..58f3352 100644 (file)
@@ -609,18 +609,7 @@ static struct led_trigger netdev_led_trigger = {
        .groups = netdev_trig_groups,
 };
 
-static int __init netdev_trig_init(void)
-{
-       return led_trigger_register(&netdev_led_trigger);
-}
-
-static void __exit netdev_trig_exit(void)
-{
-       led_trigger_unregister(&netdev_led_trigger);
-}
-
-module_init(netdev_trig_init);
-module_exit(netdev_trig_exit);
+module_led_trigger(netdev_led_trigger);
 
 MODULE_AUTHOR("Ben Whitten <ben.whitten@gmail.com>");
 MODULE_AUTHOR("Oliver Jowett <oliver@opencloud.com>");
index f62db7e..8ae0d2d 100644 (file)
@@ -7,6 +7,8 @@
 #include <linux/tty.h>
 #include <uapi/linux/serial.h>
 
+#define LEDTRIG_TTY_INTERVAL   50
+
 struct ledtrig_tty_data {
        struct led_classdev *led_cdev;
        struct delayed_work dwork;
@@ -122,17 +124,19 @@ static void ledtrig_tty_work(struct work_struct *work)
 
        if (icount.rx != trigger_data->rx ||
            icount.tx != trigger_data->tx) {
-               led_set_brightness_sync(trigger_data->led_cdev, LED_ON);
+               unsigned long interval = LEDTRIG_TTY_INTERVAL;
+
+               led_blink_set_oneshot(trigger_data->led_cdev, &interval,
+                                     &interval, 0);
 
                trigger_data->rx = icount.rx;
                trigger_data->tx = icount.tx;
-       } else {
-               led_set_brightness_sync(trigger_data->led_cdev, LED_OFF);
        }
 
 out:
        mutex_unlock(&trigger_data->mutex);
-       schedule_delayed_work(&trigger_data->dwork, msecs_to_jiffies(100));
+       schedule_delayed_work(&trigger_data->dwork,
+                             msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2));
 }
 
 static struct attribute *ledtrig_tty_attrs[] = {
index 7320337..3d361c9 100644 (file)
@@ -209,17 +209,7 @@ static struct miscdevice uleds_misc = {
        .name           = ULEDS_NAME,
 };
 
-static int __init uleds_init(void)
-{
-       return misc_register(&uleds_misc);
-}
-module_init(uleds_init);
-
-static void __exit uleds_exit(void)
-{
-       misc_deregister(&uleds_misc);
-}
-module_exit(uleds_exit);
+module_misc_device(uleds_misc);
 
 MODULE_AUTHOR("David Lechner <david@lechnology.com>");
 MODULE_DESCRIPTION("Userspace driver for the LED subsystem");
index 7d42810..aa16dc2 100644 (file)
@@ -100,6 +100,7 @@ struct led_classdev {
        const char              *name;
        unsigned int brightness;
        unsigned int max_brightness;
+       unsigned int color;
        int                      flags;
 
        /* Lower 16 bits reflect status */
@@ -313,6 +314,8 @@ extern struct led_classdev *of_led_get(struct device_node *np, int index);
 extern void led_put(struct led_classdev *led_cdev);
 struct led_classdev *__must_check devm_of_led_get(struct device *dev,
                                                  int index);
+struct led_classdev *__must_check devm_of_led_get_optional(struct device *dev,
+                                                 int index);
 
 /**
  * led_blink_set - set blinking with software fallback