Merge tag 'leds-5.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/pavel...
authorLinus Torvalds <torvalds@linux-foundation.org>
Mon, 8 Aug 2022 18:36:21 +0000 (11:36 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 8 Aug 2022 18:36:21 +0000 (11:36 -0700)
Pull LED updates from Pavel Machek:
 "A new driver for bcm63138, is31fl319x updates, fixups for multicolor.

  The clevo-mail driver got disabled, it needs an API fix"

* tag 'leds-5.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds: (23 commits)
  leds: is31fl319x: use simple i2c probe function
  leds: is31fl319x: Fix devm vs. non-devm ordering
  leds: is31fl319x: Make use of dev_err_probe()
  leds: is31fl319x: Make use of device properties
  leds: is31fl319x: Cleanup formatting and dev_dbg calls
  leds: is31fl319x: Add support for is31fl319{0,1,3} chips
  leds: is31fl319x: Move chipset-specific values in chipdef struct
  leds: is31fl319x: Use non-wildcard names for vars, structs and defines
  leds: is31fl319x: Add missing si-en compatibles
  dt-bindings: leds: pwm-multicolor: document max-brigthness
  leds: turris-omnia: convert to use dev_groups
  leds: leds-bcm63138: get rid of LED_OFF
  leds: add help info about BCM63138 module name
  dt-bindings: leds: leds-bcm63138: unify full stops in descriptions
  dt-bindings: leds: lp50xx: fix LED children names
  dt-bindings: leds: class-multicolor: reference class directly in multi-led node
  leds: bcm63138: add support for BCM63138 controller
  dt-bindings: leds: add Broadcom's BCM63138 controller
  leds: clevo-mail: Mark as broken pending interface fix
  leds: pwm-multicolor: Support active-low LEDs
  ...

13 files changed:
Documentation/devicetree/bindings/leds/cznic,turris-omnia-leds.yaml
Documentation/devicetree/bindings/leds/leds-bcm63138.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/leds/leds-class-multicolor.yaml
Documentation/devicetree/bindings/leds/leds-lp50xx.yaml
Documentation/devicetree/bindings/leds/leds-pwm-multicolor.yaml
Documentation/devicetree/bindings/leds/leds-qcom-lpg.yaml
drivers/leds/Kconfig
drivers/leds/blink/Kconfig
drivers/leds/blink/Makefile
drivers/leds/blink/leds-bcm63138.c [new file with mode: 0644]
drivers/leds/leds-is31fl319x.c
drivers/leds/leds-turris-omnia.c
drivers/leds/rgb/leds-pwm-multicolor.c

index 9362b1e..14bebe1 100644 (file)
@@ -33,6 +33,8 @@ patternProperties:
   "^multi-led@[0-9a-b]$":
     type: object
     $ref: leds-class-multicolor.yaml#
+    unevaluatedProperties: false
+
     description:
       This node represents one of the RGB LED devices on Turris Omnia.
       No subnodes need to be added for subchannels since this controller only
diff --git a/Documentation/devicetree/bindings/leds/leds-bcm63138.yaml b/Documentation/devicetree/bindings/leds/leds-bcm63138.yaml
new file mode 100644 (file)
index 0000000..52252fb
--- /dev/null
@@ -0,0 +1,95 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/leds-bcm63138.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Broadcom's BCM63138 LEDs controller
+
+maintainers:
+  - Rafał Miłecki <rafal@milecki.pl>
+
+description: |
+  This LEDs controller was first used on BCM63138 and later reused on BCM4908,
+  BCM6848, BCM6858, BCM63138, BCM63148, BCM63381 and BCM68360 SoCs.
+
+  It supports up to 32 LEDs that can be connected parallelly or serially. It
+  also includes limited support for hardware blinking.
+
+  Binding serially connected LEDs isn't documented yet.
+
+properties:
+  compatible:
+    oneOf:
+      - items:
+          - enum:
+              - brcm,bcm4908-leds
+              - brcm,bcm6848-leds
+              - brcm,bcm6858-leds
+              - brcm,bcm63148-leds
+              - brcm,bcm63381-leds
+              - brcm,bcm68360-leds
+          - const: brcm,bcm63138-leds
+      - const: brcm,bcm63138-leds
+
+  reg:
+    maxItems: 1
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+patternProperties:
+  "^led@[a-f0-9]+$":
+    type: object
+
+    $ref: common.yaml#
+
+    properties:
+      reg:
+        maxItems: 1
+        description: LED pin number
+
+      active-low:
+        type: boolean
+        description: Makes LED active low
+
+    required:
+      - reg
+
+    unevaluatedProperties: false
+
+required:
+  - reg
+  - "#address-cells"
+  - "#size-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/leds/common.h>
+
+    leds@ff800800 {
+        compatible = "brcm,bcm4908-leds", "brcm,bcm63138-leds";
+        reg = <0xff800800 0xdc>;
+
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        led@0 {
+            reg = <0x0>;
+            function = LED_FUNCTION_POWER;
+            color = <LED_COLOR_ID_GREEN>;
+            default-state = "on";
+        };
+
+        led@3 {
+            reg = <0x3>;
+            function = LED_FUNCTION_STATUS;
+            color = <LED_COLOR_ID_GREEN>;
+            active-low;
+        };
+    };
index f41d021..1269348 100644 (file)
@@ -19,22 +19,22 @@ description: |
   LED class.  Common LED nodes and properties are inherited from the common.yaml
   within this documentation directory.
 
-patternProperties:
-  "^multi-led(@[0-9a-f])?$":
-    type: object
-    description: Represents the LEDs that are to be grouped.
-    properties:
-      color:
-        description: |
-          For multicolor LED support this property should be defined as either
-          LED_COLOR_ID_RGB or LED_COLOR_ID_MULTI which can be found in
-          include/linux/leds/common.h.
-        enum: [ 8, 9 ]
-
-    $ref: "common.yaml#"
-
-    required:
-      - color
+properties:
+  $nodename:
+    pattern: "^multi-led(@[0-9a-f])?$"
+
+  color:
+    description: |
+      For multicolor LED support this property should be defined as either
+      LED_COLOR_ID_RGB or LED_COLOR_ID_MULTI which can be found in
+      include/linux/leds/common.h.
+    enum: [ 8, 9 ]
+
+required:
+  - color
+
+allOf:
+  - $ref: "common.yaml#"
 
 additionalProperties: true
 
index d118985..e0b658f 100644 (file)
@@ -56,6 +56,8 @@ patternProperties:
   '^multi-led@[0-9a-f]$':
     type: object
     $ref: leds-class-multicolor.yaml#
+    unevaluatedProperties: false
+
     properties:
       reg:
         minItems: 1
@@ -65,8 +67,14 @@ patternProperties:
           for the child node.  The LED modules can either be used stand alone
           or grouped into a module bank.
 
+      '#address-cells':
+        const: 1
+
+      '#size-cells':
+        const: 0
+
     patternProperties:
-      "(^led-[0-9a-f]$|led)":
+      "^led@[0-9a-f]+$":
         type: object
         $ref: common.yaml#
 
index fdaf04e..bd6ec04 100644 (file)
@@ -19,6 +19,14 @@ properties:
 
   multi-led:
     type: object
+    $ref: leds-class-multicolor.yaml#
+    unevaluatedProperties: false
+
+    properties:
+      max-brightness:
+        description:
+          Maximum brightness possible for the LED
+        $ref: /schemas/types.yaml#/definitions/uint32
 
     patternProperties:
       "^led-[0-9a-z]+$":
@@ -33,6 +41,10 @@ properties:
 
           pwm-names: true
 
+          active-low:
+            description: For PWMs where the LED is wired to supply rather than ground.
+            type: boolean
+
           color: true
 
         required:
@@ -42,9 +54,6 @@ properties:
 required:
   - compatible
 
-allOf:
-  - $ref: leds-class-multicolor.yaml#
-
 additionalProperties: false
 
 examples:
index cd02811..497db28 100644 (file)
@@ -59,6 +59,8 @@ properties:
   multi-led:
     type: object
     $ref: leds-class-multicolor.yaml#
+    unevaluatedProperties: false
+
     properties:
       "#address-cells":
         const: 1
index a49979f..499d0f2 100644 (file)
@@ -447,16 +447,16 @@ config LEDS_LP8860
 
 config LEDS_CLEVO_MAIL
        tristate "Mail LED on Clevo notebook"
-       depends on LEDS_CLASS
+       depends on LEDS_CLASS && BROKEN
        depends on X86 && SERIO_I8042 && DMI
        help
          This driver makes the mail LED accessible from userspace
-         programs through the leds subsystem. This LED have three
-         known mode: off, blink at 0.5Hz and blink at 1Hz.
+         programs through the LEDs subsystem. This LED has three
+         known modes: off, blink at 0.5Hz and blink at 1Hz.
 
          The driver supports two kinds of interface: using ledtrig-timer
          or through /sys/class/leds/clevo::mail/brightness. As this LED
-         cannot change it's brightness it blinks instead. The brightness
+         cannot change its brightness it blinks instead. The brightness
          value 0 means off, 1..127 means blink at 0.5Hz and 128..255 means
          blink at 1Hz.
 
@@ -697,7 +697,7 @@ config LEDS_MENF21BMC
 
 config LEDS_IS31FL319X
        tristate "LED Support for ISSI IS31FL319x I2C LED controller family"
-       depends on LEDS_CLASS && I2C && OF
+       depends on LEDS_CLASS && I2C
        select REGMAP_I2C
        help
          This option enables support for LEDs connected to ISSI IS31FL319x
index 59ba81e..945c842 100644 (file)
@@ -1,3 +1,17 @@
+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 HAS_IOMEM
+       depends on OF
+       default ARCH_BCM4908
+       help
+         This option enables support for LED controller that is part of
+         BCM63138 SoC. The same hardware block is known to be also used
+         in BCM4908, BCM6848, BCM6858, BCM63148, BCM63381 and BCM68360.
+
+         If compiled as module it will be called leds-bcm63138.
+
 config LEDS_LGM
        tristate "LED support for LGM SoC series"
        depends on X86 || COMPILE_TEST
index fa5d04d..447029f 100644 (file)
@@ -1,2 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_LEDS_BCM63138)    += leds-bcm63138.o
 obj-$(CONFIG_LEDS_LGM) += leds-lgm-sso.o
diff --git a/drivers/leds/blink/leds-bcm63138.c b/drivers/leds/blink/leds-bcm63138.c
new file mode 100644 (file)
index 0000000..2cf2761
--- /dev/null
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl>
+ */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BCM63138_MAX_LEDS                              32
+#define BCM63138_MAX_BRIGHTNESS                                9
+
+#define BCM63138_LED_BITS                              4                               /* how many bits control a single LED */
+#define BCM63138_LED_MASK                              ((1 << BCM63138_LED_BITS) - 1)  /* 0xf */
+#define BCM63138_LEDS_PER_REG                          (32 / BCM63138_LED_BITS)        /* 8 */
+
+#define BCM63138_GLB_CTRL                              0x00
+#define  BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL                0x00000002
+#define  BCM63138_GLB_CTRL_SERIAL_LED_EN_POL           0x00000008
+#define BCM63138_MASK                                  0x04
+#define BCM63138_HW_LED_EN                             0x08
+#define BCM63138_SERIAL_LED_SHIFT_SEL                  0x0c
+#define BCM63138_FLASH_RATE_CTRL1                      0x10
+#define BCM63138_FLASH_RATE_CTRL2                      0x14
+#define BCM63138_FLASH_RATE_CTRL3                      0x18
+#define BCM63138_FLASH_RATE_CTRL4                      0x1c
+#define BCM63138_BRIGHT_CTRL1                          0x20
+#define BCM63138_BRIGHT_CTRL2                          0x24
+#define BCM63138_BRIGHT_CTRL3                          0x28
+#define BCM63138_BRIGHT_CTRL4                          0x2c
+#define BCM63138_POWER_LED_CFG                         0x30
+#define BCM63138_HW_POLARITY                           0xb4
+#define BCM63138_SW_DATA                               0xb8
+#define BCM63138_SW_POLARITY                           0xbc
+#define BCM63138_PARALLEL_LED_POLARITY                 0xc0
+#define BCM63138_SERIAL_LED_POLARITY                   0xc4
+#define BCM63138_HW_LED_STATUS                         0xc8
+#define BCM63138_FLASH_CTRL_STATUS                     0xcc
+#define BCM63138_FLASH_BRT_CTRL                                0xd0
+#define BCM63138_FLASH_P_LED_OUT_STATUS                        0xd4
+#define BCM63138_FLASH_S_LED_OUT_STATUS                        0xd8
+
+struct bcm63138_leds {
+       struct device *dev;
+       void __iomem *base;
+       spinlock_t lock;
+};
+
+struct bcm63138_led {
+       struct bcm63138_leds *leds;
+       struct led_classdev cdev;
+       u32 pin;
+       bool active_low;
+};
+
+/*
+ * I/O access
+ */
+
+static void bcm63138_leds_write(struct bcm63138_leds *leds, unsigned int reg,
+                               u32 data)
+{
+       writel(data, leds->base + reg);
+}
+
+static unsigned long bcm63138_leds_read(struct bcm63138_leds *leds,
+                                       unsigned int reg)
+{
+       return readl(leds->base + reg);
+}
+
+static void bcm63138_leds_update_bits(struct bcm63138_leds *leds,
+                                     unsigned int reg, u32 mask, u32 val)
+{
+       WARN_ON(val & ~mask);
+
+       bcm63138_leds_write(leds, reg, (bcm63138_leds_read(leds, reg) & ~mask) | (val & mask));
+}
+
+/*
+ * Helpers
+ */
+
+static void bcm63138_leds_set_flash_rate(struct bcm63138_leds *leds,
+                                        struct bcm63138_led *led,
+                                        u8 value)
+{
+       int reg_offset = (led->pin >> fls((BCM63138_LEDS_PER_REG - 1))) * 4;
+       int shift = (led->pin & (BCM63138_LEDS_PER_REG - 1)) * BCM63138_LED_BITS;
+
+       bcm63138_leds_update_bits(leds, BCM63138_FLASH_RATE_CTRL1 + reg_offset,
+                                 BCM63138_LED_MASK << shift, value << shift);
+}
+
+static void bcm63138_leds_set_bright(struct bcm63138_leds *leds,
+                                    struct bcm63138_led *led,
+                                    u8 value)
+{
+       int reg_offset = (led->pin >> fls((BCM63138_LEDS_PER_REG - 1))) * 4;
+       int shift = (led->pin & (BCM63138_LEDS_PER_REG - 1)) * BCM63138_LED_BITS;
+
+       bcm63138_leds_update_bits(leds, BCM63138_BRIGHT_CTRL1 + reg_offset,
+                                 BCM63138_LED_MASK << shift, value << shift);
+}
+
+static void bcm63138_leds_enable_led(struct bcm63138_leds *leds,
+                                    struct bcm63138_led *led,
+                                    enum led_brightness value)
+{
+       u32 bit = BIT(led->pin);
+
+       bcm63138_leds_update_bits(leds, BCM63138_SW_DATA, bit, value ? bit : 0);
+}
+
+/*
+ * API callbacks
+ */
+
+static void bcm63138_leds_brightness_set(struct led_classdev *led_cdev,
+                                        enum led_brightness value)
+{
+       struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev);
+       struct bcm63138_leds *leds = led->leds;
+       unsigned long flags;
+
+       spin_lock_irqsave(&leds->lock, flags);
+
+       bcm63138_leds_enable_led(leds, led, value);
+       if (!value)
+               bcm63138_leds_set_flash_rate(leds, led, 0);
+       else
+               bcm63138_leds_set_bright(leds, led, value);
+
+       spin_unlock_irqrestore(&leds->lock, flags);
+}
+
+static int bcm63138_leds_blink_set(struct led_classdev *led_cdev,
+                                  unsigned long *delay_on,
+                                  unsigned long *delay_off)
+{
+       struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev);
+       struct bcm63138_leds *leds = led->leds;
+       unsigned long flags;
+       u8 value;
+
+       if (!*delay_on && !*delay_off) {
+               *delay_on = 640;
+               *delay_off = 640;
+       }
+
+       if (*delay_on != *delay_off) {
+               dev_dbg(led_cdev->dev, "Blinking at unequal delays is not supported\n");
+               return -EINVAL;
+       }
+
+       switch (*delay_on) {
+       case 1152 ... 1408: /* 1280 ms ± 10% */
+               value = 0x7;
+               break;
+       case 576 ... 704: /* 640 ms ± 10% */
+               value = 0x6;
+               break;
+       case 288 ... 352: /* 320 ms ± 10% */
+               value = 0x5;
+               break;
+       case 126 ... 154: /* 140 ms ± 10% */
+               value = 0x4;
+               break;
+       case 59 ... 72: /* 65 ms ± 10% */
+               value = 0x3;
+               break;
+       default:
+               dev_dbg(led_cdev->dev, "Blinking delay value %lu is unsupported\n",
+                       *delay_on);
+               return -EINVAL;
+       }
+
+       spin_lock_irqsave(&leds->lock, flags);
+
+       bcm63138_leds_enable_led(leds, led, BCM63138_MAX_BRIGHTNESS);
+       bcm63138_leds_set_flash_rate(leds, led, value);
+
+       spin_unlock_irqrestore(&leds->lock, flags);
+
+       return 0;
+}
+
+/*
+ * LED driver
+ */
+
+static void bcm63138_leds_create_led(struct bcm63138_leds *leds,
+                                    struct device_node *np)
+{
+       struct led_init_data init_data = {
+               .fwnode = of_fwnode_handle(np),
+       };
+       struct device *dev = leds->dev;
+       struct bcm63138_led *led;
+       struct pinctrl *pinctrl;
+       u32 bit;
+       int err;
+
+       led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+       if (!led) {
+               dev_err(dev, "Failed to alloc LED\n");
+               return;
+       }
+
+       led->leds = leds;
+
+       if (of_property_read_u32(np, "reg", &led->pin)) {
+               dev_err(dev, "Missing \"reg\" property in %pOF\n", np);
+               goto err_free;
+       }
+
+       if (led->pin >= BCM63138_MAX_LEDS) {
+               dev_err(dev, "Invalid \"reg\" value %d\n", led->pin);
+               goto err_free;
+       }
+
+       led->active_low = of_property_read_bool(np, "active-low");
+
+       led->cdev.max_brightness = BCM63138_MAX_BRIGHTNESS;
+       led->cdev.brightness_set = bcm63138_leds_brightness_set;
+       led->cdev.blink_set = bcm63138_leds_blink_set;
+
+       err = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
+       if (err) {
+               dev_err(dev, "Failed to register LED %pOF: %d\n", np, err);
+               goto err_free;
+       }
+
+       pinctrl = devm_pinctrl_get_select_default(led->cdev.dev);
+       if (IS_ERR(pinctrl) && PTR_ERR(pinctrl) != -ENODEV) {
+               dev_warn(led->cdev.dev, "Failed to select %pOF pinctrl: %ld\n",
+                        np, PTR_ERR(pinctrl));
+       }
+
+       bit = BIT(led->pin);
+       bcm63138_leds_update_bits(leds, BCM63138_PARALLEL_LED_POLARITY, bit,
+                                 led->active_low ? 0 : bit);
+       bcm63138_leds_update_bits(leds, BCM63138_HW_LED_EN, bit, 0);
+       bcm63138_leds_set_flash_rate(leds, led, 0);
+       bcm63138_leds_enable_led(leds, led, led->cdev.brightness);
+
+       return;
+
+err_free:
+       devm_kfree(dev, led);
+}
+
+static int bcm63138_leds_probe(struct platform_device *pdev)
+{
+       struct device_node *np = dev_of_node(&pdev->dev);
+       struct device *dev = &pdev->dev;
+       struct bcm63138_leds *leds;
+       struct device_node *child;
+
+       leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
+       if (!leds)
+               return -ENOMEM;
+
+       leds->dev = dev;
+
+       leds->base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(leds->base))
+               return PTR_ERR(leds->base);
+
+       spin_lock_init(&leds->lock);
+
+       bcm63138_leds_write(leds, BCM63138_GLB_CTRL,
+                           BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL |
+                           BCM63138_GLB_CTRL_SERIAL_LED_EN_POL);
+       bcm63138_leds_write(leds, BCM63138_HW_LED_EN, 0);
+       bcm63138_leds_write(leds, BCM63138_SERIAL_LED_POLARITY, 0);
+       bcm63138_leds_write(leds, BCM63138_PARALLEL_LED_POLARITY, 0);
+
+       for_each_available_child_of_node(np, child) {
+               bcm63138_leds_create_led(leds, child);
+       }
+
+       return 0;
+}
+
+static const struct of_device_id bcm63138_leds_of_match_table[] = {
+       { .compatible = "brcm,bcm63138-leds", },
+       { },
+};
+
+static struct platform_driver bcm63138_leds_driver = {
+       .probe = bcm63138_leds_probe,
+       .driver = {
+               .name = "leds-bcm63xxx",
+               .of_match_table = bcm63138_leds_of_match_table,
+       },
+};
+
+module_platform_driver(bcm63138_leds_driver);
+
+MODULE_AUTHOR("Rafał Miłecki");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(of, bcm63138_leds_of_match_table);
index 4161b9d..52b59b6 100644 (file)
@@ -11,9 +11,9 @@
 #include <linux/err.h>
 #include <linux/i2c.h>
 #include <linux/leds.h>
+#include <linux/mod_devicetable.h>
 #include <linux/module.h>
-#include <linux/of.h>
-#include <linux/of_device.h>
+#include <linux/property.h>
 #include <linux/regmap.h>
 #include <linux/slab.h>
 #include <linux/delay.h>
 
 /* register numbers */
 #define IS31FL319X_SHUTDOWN            0x00
-#define IS31FL319X_CTRL1               0x01
-#define IS31FL319X_CTRL2               0x02
-#define IS31FL319X_CONFIG1             0x03
-#define IS31FL319X_CONFIG2             0x04
-#define IS31FL319X_RAMP_MODE           0x05
-#define IS31FL319X_BREATH_MASK         0x06
-#define IS31FL319X_PWM(channel)                (0x07 + channel)
-#define IS31FL319X_DATA_UPDATE         0x10
-#define IS31FL319X_T0(channel)         (0x11 + channel)
-#define IS31FL319X_T123_1              0x1a
-#define IS31FL319X_T123_2              0x1b
-#define IS31FL319X_T123_3              0x1c
-#define IS31FL319X_T4(channel)         (0x1d + channel)
-#define IS31FL319X_TIME_UPDATE         0x26
-#define IS31FL319X_RESET               0xff
-
-#define IS31FL319X_REG_CNT             (IS31FL319X_RESET + 1)
+
+/* registers for 3190, 3191 and 3193 */
+#define IS31FL3190_BREATHING           0x01
+#define IS31FL3190_LEDMODE             0x02
+#define IS31FL3190_CURRENT             0x03
+#define IS31FL3190_PWM(channel)                (0x04 + channel)
+#define IS31FL3190_DATA_UPDATE         0x07
+#define IS31FL3190_T0(channel)         (0x0a + channel)
+#define IS31FL3190_T1T2(channel)       (0x10 + channel)
+#define IS31FL3190_T3T4(channel)       (0x16 + channel)
+#define IS31FL3190_TIME_UPDATE         0x1c
+#define IS31FL3190_LEDCONTROL          0x1d
+#define IS31FL3190_RESET               0x2f
+
+#define IS31FL3190_CURRENT_uA_MIN      5000
+#define IS31FL3190_CURRENT_uA_DEFAULT  42000
+#define IS31FL3190_CURRENT_uA_MAX      42000
+#define IS31FL3190_CURRENT_MASK                GENMASK(4, 2)
+#define IS31FL3190_CURRENT_5_mA                0x02
+#define IS31FL3190_CURRENT_10_mA       0x01
+#define IS31FL3190_CURRENT_17dot5_mA   0x04
+#define IS31FL3190_CURRENT_30_mA       0x03
+#define IS31FL3190_CURRENT_42_mA       0x00
+
+/* registers for 3196 and 3199 */
+#define IS31FL3196_CTRL1               0x01
+#define IS31FL3196_CTRL2               0x02
+#define IS31FL3196_CONFIG1             0x03
+#define IS31FL3196_CONFIG2             0x04
+#define IS31FL3196_RAMP_MODE           0x05
+#define IS31FL3196_BREATH_MARK         0x06
+#define IS31FL3196_PWM(channel)                (0x07 + channel)
+#define IS31FL3196_DATA_UPDATE         0x10
+#define IS31FL3196_T0(channel)         (0x11 + channel)
+#define IS31FL3196_T123_1              0x1a
+#define IS31FL3196_T123_2              0x1b
+#define IS31FL3196_T123_3              0x1c
+#define IS31FL3196_T4(channel)         (0x1d + channel)
+#define IS31FL3196_TIME_UPDATE         0x26
+#define IS31FL3196_RESET               0xff
+
+#define IS31FL3196_REG_CNT             (IS31FL3196_RESET + 1)
 
 #define IS31FL319X_MAX_LEDS            9
 
 /* CS (Current Setting) in CONFIG2 register */
-#define IS31FL319X_CONFIG2_CS_SHIFT    4
-#define IS31FL319X_CONFIG2_CS_MASK     0x7
-#define IS31FL319X_CONFIG2_CS_STEP_REF 12
+#define IS31FL3196_CONFIG2_CS_SHIFT    4
+#define IS31FL3196_CONFIG2_CS_MASK     GENMASK(2, 0)
+#define IS31FL3196_CONFIG2_CS_STEP_REF 12
 
-#define IS31FL319X_CURRENT_MIN         ((u32)5000)
-#define IS31FL319X_CURRENT_MAX         ((u32)40000)
-#define IS31FL319X_CURRENT_STEP                ((u32)5000)
-#define IS31FL319X_CURRENT_DEFAULT     ((u32)20000)
+#define IS31FL3196_CURRENT_uA_MIN      5000
+#define IS31FL3196_CURRENT_uA_MAX      40000
+#define IS31FL3196_CURRENT_uA_STEP     5000
+#define IS31FL3196_CURRENT_uA_DEFAULT  20000
 
 /* Audio gain in CONFIG2 register */
-#define IS31FL319X_AUDIO_GAIN_DB_MAX   ((u32)21)
-#define IS31FL319X_AUDIO_GAIN_DB_STEP  ((u32)3)
+#define IS31FL3196_AUDIO_GAIN_DB_MAX   ((u32)21)
+#define IS31FL3196_AUDIO_GAIN_DB_STEP  3
 
 /*
  * regmap is used as a cache of chip's register space,
@@ -78,52 +103,161 @@ struct is31fl319x_chip {
 
 struct is31fl319x_chipdef {
        int num_leds;
+       u8 reset_reg;
+       const struct regmap_config *is31fl319x_regmap_config;
+       int (*brightness_set)(struct led_classdev *cdev, enum led_brightness brightness);
+       u32 current_default;
+       u32 current_min;
+       u32 current_max;
+       bool is_3196or3199;
 };
 
-static const struct is31fl319x_chipdef is31fl3190_cdef = {
-       .num_leds = 1,
-};
+static bool is31fl319x_readable_reg(struct device *dev, unsigned int reg)
+{
+       /* we have no readable registers */
+       return false;
+}
 
-static const struct is31fl319x_chipdef is31fl3193_cdef = {
-       .num_leds = 3,
+static bool is31fl3190_volatile_reg(struct device *dev, unsigned int reg)
+{
+       /* volatile registers are not cached */
+       switch (reg) {
+       case IS31FL3190_DATA_UPDATE:
+       case IS31FL3190_TIME_UPDATE:
+       case IS31FL3190_RESET:
+               return true; /* always write-through */
+       default:
+               return false;
+       }
+}
+
+static const struct reg_default is31fl3190_reg_defaults[] = {
+       { IS31FL3190_LEDMODE, 0x00 },
+       { IS31FL3190_CURRENT, 0x00 },
+       { IS31FL3190_PWM(0), 0x00 },
+       { IS31FL3190_PWM(1), 0x00 },
+       { IS31FL3190_PWM(2), 0x00 },
 };
 
-static const struct is31fl319x_chipdef is31fl3196_cdef = {
-       .num_leds = 6,
+static struct regmap_config is31fl3190_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .max_register = IS31FL3190_RESET,
+       .cache_type = REGCACHE_FLAT,
+       .readable_reg = is31fl319x_readable_reg,
+       .volatile_reg = is31fl3190_volatile_reg,
+       .reg_defaults = is31fl3190_reg_defaults,
+       .num_reg_defaults = ARRAY_SIZE(is31fl3190_reg_defaults),
 };
 
-static const struct is31fl319x_chipdef is31fl3199_cdef = {
-       .num_leds = 9,
+static bool is31fl3196_volatile_reg(struct device *dev, unsigned int reg)
+{
+       /* volatile registers are not cached */
+       switch (reg) {
+       case IS31FL3196_DATA_UPDATE:
+       case IS31FL3196_TIME_UPDATE:
+       case IS31FL3196_RESET:
+               return true; /* always write-through */
+       default:
+               return false;
+       }
+}
+
+static const struct reg_default is31fl3196_reg_defaults[] = {
+       { IS31FL3196_CONFIG1, 0x00 },
+       { IS31FL3196_CONFIG2, 0x00 },
+       { IS31FL3196_PWM(0), 0x00 },
+       { IS31FL3196_PWM(1), 0x00 },
+       { IS31FL3196_PWM(2), 0x00 },
+       { IS31FL3196_PWM(3), 0x00 },
+       { IS31FL3196_PWM(4), 0x00 },
+       { IS31FL3196_PWM(5), 0x00 },
+       { IS31FL3196_PWM(6), 0x00 },
+       { IS31FL3196_PWM(7), 0x00 },
+       { IS31FL3196_PWM(8), 0x00 },
 };
 
-static const struct of_device_id of_is31fl319x_match[] = {
-       { .compatible = "issi,is31fl3190", .data = &is31fl3190_cdef, },
-       { .compatible = "issi,is31fl3191", .data = &is31fl3190_cdef, },
-       { .compatible = "issi,is31fl3193", .data = &is31fl3193_cdef, },
-       { .compatible = "issi,is31fl3196", .data = &is31fl3196_cdef, },
-       { .compatible = "issi,is31fl3199", .data = &is31fl3199_cdef, },
-       { .compatible = "si-en,sn3199",    .data = &is31fl3199_cdef, },
-       { }
+static struct regmap_config is31fl3196_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .max_register = IS31FL3196_REG_CNT,
+       .cache_type = REGCACHE_FLAT,
+       .readable_reg = is31fl319x_readable_reg,
+       .volatile_reg = is31fl3196_volatile_reg,
+       .reg_defaults = is31fl3196_reg_defaults,
+       .num_reg_defaults = ARRAY_SIZE(is31fl3196_reg_defaults),
 };
-MODULE_DEVICE_TABLE(of, of_is31fl319x_match);
 
-static int is31fl319x_brightness_set(struct led_classdev *cdev,
+static int is31fl3190_brightness_set(struct led_classdev *cdev,
+                                    enum led_brightness brightness)
+{
+       struct is31fl319x_led *led = container_of(cdev, struct is31fl319x_led, cdev);
+       struct is31fl319x_chip *is31 = led->chip;
+       int chan = led - is31->leds;
+       int ret;
+       int i;
+       u8 ctrl = 0;
+
+       dev_dbg(&is31->client->dev, "channel %d: %d\n", chan, brightness);
+
+       mutex_lock(&is31->lock);
+
+       /* update PWM register */
+       ret = regmap_write(is31->regmap, IS31FL3190_PWM(chan), brightness);
+       if (ret < 0)
+               goto out;
+
+       /* read current brightness of all PWM channels */
+       for (i = 0; i < is31->cdef->num_leds; i++) {
+               unsigned int pwm_value;
+               bool on;
+
+               /*
+                * since neither cdev nor the chip can provide
+                * the current setting, we read from the regmap cache
+                */
+
+               ret = regmap_read(is31->regmap, IS31FL3190_PWM(i), &pwm_value);
+               on = ret >= 0 && pwm_value > LED_OFF;
+
+               ctrl |= on << i;
+       }
+
+       if (ctrl > 0) {
+               dev_dbg(&is31->client->dev, "power up %02x\n", ctrl);
+               regmap_write(is31->regmap, IS31FL3190_LEDCONTROL, ctrl);
+               /* update PWMs */
+               regmap_write(is31->regmap, IS31FL3190_DATA_UPDATE, 0x00);
+               /* enable chip from shut down and enable all channels */
+               ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x20);
+       } else {
+               dev_dbg(&is31->client->dev, "power down\n");
+               /* shut down (no need to clear LEDCONTROL) */
+               ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x01);
+       }
+
+out:
+       mutex_unlock(&is31->lock);
+
+       return ret;
+}
+
+static int is31fl3196_brightness_set(struct led_classdev *cdev,
                                     enum led_brightness brightness)
 {
-       struct is31fl319x_led *led = container_of(cdev, struct is31fl319x_led,
-                                                 cdev);
+       struct is31fl319x_led *led = container_of(cdev, struct is31fl319x_led, cdev);
        struct is31fl319x_chip *is31 = led->chip;
        int chan = led - is31->leds;
        int ret;
        int i;
        u8 ctrl1 = 0, ctrl2 = 0;
 
-       dev_dbg(&is31->client->dev, "%s %d: %d\n", __func__, chan, brightness);
+       dev_dbg(&is31->client->dev, "channel %d: %d\n", chan, brightness);
 
        mutex_lock(&is31->lock);
 
        /* update PWM register */
-       ret = regmap_write(is31->regmap, IS31FL319X_PWM(chan), brightness);
+       ret = regmap_write(is31->regmap, IS31FL3196_PWM(chan), brightness);
        if (ret < 0)
                goto out;
 
@@ -137,9 +271,7 @@ static int is31fl319x_brightness_set(struct led_classdev *cdev,
                 * the current setting, we read from the regmap cache
                 */
 
-               ret = regmap_read(is31->regmap, IS31FL319X_PWM(i), &pwm_value);
-               dev_dbg(&is31->client->dev, "%s read %d: ret=%d: %d\n",
-                       __func__, i, ret, pwm_value);
+               ret = regmap_read(is31->regmap, IS31FL3196_PWM(i), &pwm_value);
                on = ret >= 0 && pwm_value > LED_OFF;
 
                if (i < 3)
@@ -153,10 +285,10 @@ static int is31fl319x_brightness_set(struct led_classdev *cdev,
        if (ctrl1 > 0 || ctrl2 > 0) {
                dev_dbg(&is31->client->dev, "power up %02x %02x\n",
                        ctrl1, ctrl2);
-               regmap_write(is31->regmap, IS31FL319X_CTRL1, ctrl1);
-               regmap_write(is31->regmap, IS31FL319X_CTRL2, ctrl2);
+               regmap_write(is31->regmap, IS31FL3196_CTRL1, ctrl1);
+               regmap_write(is31->regmap, IS31FL3196_CTRL2, ctrl2);
                /* update PWMs */
-               regmap_write(is31->regmap, IS31FL319X_DATA_UPDATE, 0x00);
+               regmap_write(is31->regmap, IS31FL3196_DATA_UPDATE, 0x00);
                /* enable chip from shut down */
                ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x01);
        } else {
@@ -171,92 +303,141 @@ out:
        return ret;
 }
 
-static int is31fl319x_parse_child_dt(const struct device *dev,
-                                    const struct device_node *child,
-                                    struct is31fl319x_led *led)
+static const struct is31fl319x_chipdef is31fl3190_cdef = {
+       .num_leds = 1,
+       .reset_reg = IS31FL3190_RESET,
+       .is31fl319x_regmap_config = &is31fl3190_regmap_config,
+       .brightness_set = is31fl3190_brightness_set,
+       .current_default = IS31FL3190_CURRENT_uA_DEFAULT,
+       .current_min = IS31FL3190_CURRENT_uA_MIN,
+       .current_max = IS31FL3190_CURRENT_uA_MAX,
+       .is_3196or3199 = false,
+};
+
+static const struct is31fl319x_chipdef is31fl3193_cdef = {
+       .num_leds = 3,
+       .reset_reg = IS31FL3190_RESET,
+       .is31fl319x_regmap_config = &is31fl3190_regmap_config,
+       .brightness_set = is31fl3190_brightness_set,
+       .current_default = IS31FL3190_CURRENT_uA_DEFAULT,
+       .current_min = IS31FL3190_CURRENT_uA_MIN,
+       .current_max = IS31FL3190_CURRENT_uA_MAX,
+       .is_3196or3199 = false,
+};
+
+static const struct is31fl319x_chipdef is31fl3196_cdef = {
+       .num_leds = 6,
+       .reset_reg = IS31FL3196_RESET,
+       .is31fl319x_regmap_config = &is31fl3196_regmap_config,
+       .brightness_set = is31fl3196_brightness_set,
+       .current_default = IS31FL3196_CURRENT_uA_DEFAULT,
+       .current_min = IS31FL3196_CURRENT_uA_MIN,
+       .current_max = IS31FL3196_CURRENT_uA_MAX,
+       .is_3196or3199 = true,
+};
+
+static const struct is31fl319x_chipdef is31fl3199_cdef = {
+       .num_leds = 9,
+       .reset_reg = IS31FL3196_RESET,
+       .is31fl319x_regmap_config = &is31fl3196_regmap_config,
+       .brightness_set = is31fl3196_brightness_set,
+       .current_default = IS31FL3196_CURRENT_uA_DEFAULT,
+       .current_min = IS31FL3196_CURRENT_uA_MIN,
+       .current_max = IS31FL3196_CURRENT_uA_MAX,
+       .is_3196or3199 = true,
+};
+
+static const struct of_device_id of_is31fl319x_match[] = {
+       { .compatible = "issi,is31fl3190", .data = &is31fl3190_cdef, },
+       { .compatible = "issi,is31fl3191", .data = &is31fl3190_cdef, },
+       { .compatible = "issi,is31fl3193", .data = &is31fl3193_cdef, },
+       { .compatible = "issi,is31fl3196", .data = &is31fl3196_cdef, },
+       { .compatible = "issi,is31fl3199", .data = &is31fl3199_cdef, },
+       { .compatible = "si-en,sn3190",    .data = &is31fl3190_cdef, },
+       { .compatible = "si-en,sn3191",    .data = &is31fl3190_cdef, },
+       { .compatible = "si-en,sn3193",    .data = &is31fl3193_cdef, },
+       { .compatible = "si-en,sn3196",    .data = &is31fl3196_cdef, },
+       { .compatible = "si-en,sn3199",    .data = &is31fl3199_cdef, },
+       { }
+};
+MODULE_DEVICE_TABLE(of, of_is31fl319x_match);
+
+static int is31fl319x_parse_child_fw(const struct device *dev,
+                                    const struct fwnode_handle *child,
+                                    struct is31fl319x_led *led,
+                                    struct is31fl319x_chip *is31)
 {
        struct led_classdev *cdev = &led->cdev;
        int ret;
 
-       if (of_property_read_string(child, "label", &cdev->name))
-               cdev->name = child->name;
+       if (fwnode_property_read_string(child, "label", &cdev->name))
+               cdev->name = fwnode_get_name(child);
 
-       ret = of_property_read_string(child, "linux,default-trigger",
-                                     &cdev->default_trigger);
+       ret = fwnode_property_read_string(child, "linux,default-trigger", &cdev->default_trigger);
        if (ret < 0 && ret != -EINVAL) /* is optional */
                return ret;
 
-       led->max_microamp = IS31FL319X_CURRENT_DEFAULT;
-       ret = of_property_read_u32(child, "led-max-microamp",
-                                  &led->max_microamp);
+       led->max_microamp = is31->cdef->current_default;
+       ret = fwnode_property_read_u32(child, "led-max-microamp", &led->max_microamp);
        if (!ret) {
-               if (led->max_microamp < IS31FL319X_CURRENT_MIN)
+               if (led->max_microamp < is31->cdef->current_min)
                        return -EINVAL; /* not supported */
                led->max_microamp = min(led->max_microamp,
-                                         IS31FL319X_CURRENT_MAX);
+                                       is31->cdef->current_max);
        }
 
        return 0;
 }
 
-static int is31fl319x_parse_dt(struct device *dev,
-                              struct is31fl319x_chip *is31)
+static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31)
 {
-       struct device_node *np = dev_of_node(dev), *child;
+       struct fwnode_handle *fwnode = dev_fwnode(dev), *child;
        int count;
        int ret;
 
-       if (!np)
-               return -ENODEV;
-
-       is31->shutdown_gpio = devm_gpiod_get_optional(dev,
-                                               "shutdown",
-                                               GPIOD_OUT_HIGH);
-       if (IS_ERR(is31->shutdown_gpio)) {
-               ret = PTR_ERR(is31->shutdown_gpio);
-               dev_err(dev, "Failed to get shutdown gpio: %d\n", ret);
-               return ret;
-       }
+       is31->shutdown_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH);
+       if (IS_ERR(is31->shutdown_gpio))
+               return dev_err_probe(dev, PTR_ERR(is31->shutdown_gpio),
+                                    "Failed to get shutdown gpio\n");
 
        is31->cdef = device_get_match_data(dev);
 
-       count = of_get_available_child_count(np);
+       count = 0;
+       fwnode_for_each_available_child_node(fwnode, child)
+               count++;
 
        dev_dbg(dev, "probing with %d leds defined in DT\n", count);
 
-       if (!count || count > is31->cdef->num_leds) {
-               dev_err(dev, "Number of leds defined must be between 1 and %u\n",
-                       is31->cdef->num_leds);
-               return -ENODEV;
-       }
+       if (!count || count > is31->cdef->num_leds)
+               return dev_err_probe(dev, -ENODEV,
+                                    "Number of leds defined must be between 1 and %u\n",
+                                    is31->cdef->num_leds);
 
-       for_each_available_child_of_node(np, child) {
+       fwnode_for_each_available_child_node(fwnode, child) {
                struct is31fl319x_led *led;
                u32 reg;
 
-               ret = of_property_read_u32(child, "reg", &reg);
+               ret = fwnode_property_read_u32(child, "reg", &reg);
                if (ret) {
-                       dev_err(dev, "Failed to read led 'reg' property\n");
+                       ret = dev_err_probe(dev, ret, "Failed to read led 'reg' property\n");
                        goto put_child_node;
                }
 
                if (reg < 1 || reg > is31->cdef->num_leds) {
-                       dev_err(dev, "invalid led reg %u\n", reg);
-                       ret = -EINVAL;
+                       ret = dev_err_probe(dev, -EINVAL, "invalid led reg %u\n", reg);
                        goto put_child_node;
                }
 
                led = &is31->leds[reg - 1];
 
                if (led->configured) {
-                       dev_err(dev, "led %u is already configured\n", reg);
-                       ret = -EINVAL;
+                       ret = dev_err_probe(dev, -EINVAL, "led %u is already configured\n", reg);
                        goto put_child_node;
                }
 
-               ret = is31fl319x_parse_child_dt(dev, child, led);
+               ret = is31fl319x_parse_child_fw(dev, child, led, is31);
                if (ret) {
-                       dev_err(dev, "led %u DT parsing failed\n", reg);
+                       ret = dev_err_probe(dev, ret, "led %u DT parsing failed\n", reg);
                        goto put_child_node;
                }
 
@@ -264,82 +445,62 @@ static int is31fl319x_parse_dt(struct device *dev,
        }
 
        is31->audio_gain_db = 0;
-       ret = of_property_read_u32(np, "audio-gain-db", &is31->audio_gain_db);
-       if (!ret)
-               is31->audio_gain_db = min(is31->audio_gain_db,
-                                         IS31FL319X_AUDIO_GAIN_DB_MAX);
+       if (is31->cdef->is_3196or3199) {
+               ret = fwnode_property_read_u32(fwnode, "audio-gain-db", &is31->audio_gain_db);
+               if (!ret)
+                       is31->audio_gain_db = min(is31->audio_gain_db,
+                                                 IS31FL3196_AUDIO_GAIN_DB_MAX);
+       }
 
        return 0;
 
 put_child_node:
-       of_node_put(child);
+       fwnode_handle_put(child);
        return ret;
 }
 
-static bool is31fl319x_readable_reg(struct device *dev, unsigned int reg)
-{ /* we have no readable registers */
-       return false;
-}
-
-static bool is31fl319x_volatile_reg(struct device *dev, unsigned int reg)
-{ /* volatile registers are not cached */
-       switch (reg) {
-       case IS31FL319X_DATA_UPDATE:
-       case IS31FL319X_TIME_UPDATE:
-       case IS31FL319X_RESET:
-               return true; /* always write-through */
+static inline int is31fl3190_microamp_to_cs(struct device *dev, u32 microamp)
+{
+       switch (microamp) {
+       case 5000:
+               return IS31FL3190_CURRENT_5_mA;
+       case 10000:
+               return IS31FL3190_CURRENT_10_mA;
+       case 17500:
+               return IS31FL3190_CURRENT_17dot5_mA;
+       case 30000:
+               return IS31FL3190_CURRENT_30_mA;
+       case 42000:
+               return IS31FL3190_CURRENT_42_mA;
        default:
-               return false;
+               dev_warn(dev, "Unsupported current value: %d, using 5000 µA!\n", microamp);
+               return IS31FL3190_CURRENT_5_mA;
        }
 }
 
-static const struct reg_default is31fl319x_reg_defaults[] = {
-       { IS31FL319X_CONFIG1, 0x00},
-       { IS31FL319X_CONFIG2, 0x00},
-       { IS31FL319X_PWM(0), 0x00},
-       { IS31FL319X_PWM(1), 0x00},
-       { IS31FL319X_PWM(2), 0x00},
-       { IS31FL319X_PWM(3), 0x00},
-       { IS31FL319X_PWM(4), 0x00},
-       { IS31FL319X_PWM(5), 0x00},
-       { IS31FL319X_PWM(6), 0x00},
-       { IS31FL319X_PWM(7), 0x00},
-       { IS31FL319X_PWM(8), 0x00},
-};
-
-static struct regmap_config regmap_config = {
-       .reg_bits = 8,
-       .val_bits = 8,
-       .max_register = IS31FL319X_REG_CNT,
-       .cache_type = REGCACHE_FLAT,
-       .readable_reg = is31fl319x_readable_reg,
-       .volatile_reg = is31fl319x_volatile_reg,
-       .reg_defaults = is31fl319x_reg_defaults,
-       .num_reg_defaults = ARRAY_SIZE(is31fl319x_reg_defaults),
-};
-
-static inline int is31fl319x_microamp_to_cs(struct device *dev, u32 microamp)
-{ /* round down to nearest supported value (range check done by caller) */
-       u32 step = microamp / IS31FL319X_CURRENT_STEP;
+static inline int is31fl3196_microamp_to_cs(struct device *dev, u32 microamp)
+{
+       /* round down to nearest supported value (range check done by caller) */
+       u32 step = microamp / IS31FL3196_CURRENT_uA_STEP;
 
-       return ((IS31FL319X_CONFIG2_CS_STEP_REF - step) &
-               IS31FL319X_CONFIG2_CS_MASK) <<
-               IS31FL319X_CONFIG2_CS_SHIFT; /* CS encoding */
+       return ((IS31FL3196_CONFIG2_CS_STEP_REF - step) &
+               IS31FL3196_CONFIG2_CS_MASK) <<
+               IS31FL3196_CONFIG2_CS_SHIFT; /* CS encoding */
 }
 
-static inline int is31fl319x_db_to_gain(u32 dezibel)
-{ /* round down to nearest supported value (range check done by caller) */
-       return dezibel / IS31FL319X_AUDIO_GAIN_DB_STEP;
+static inline int is31fl3196_db_to_gain(u32 dezibel)
+{
+       /* round down to nearest supported value (range check done by caller) */
+       return dezibel / IS31FL3196_AUDIO_GAIN_DB_STEP;
 }
 
-static int is31fl319x_probe(struct i2c_client *client,
-                           const struct i2c_device_id *id)
+static int is31fl319x_probe(struct i2c_client *client)
 {
        struct is31fl319x_chip *is31;
        struct device *dev = &client->dev;
        int err;
        int i = 0;
-       u32 aggregated_led_microamp = IS31FL319X_CURRENT_MAX;
+       u32 aggregated_led_microamp;
 
        if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
                return -EIO;
@@ -349,10 +510,13 @@ static int is31fl319x_probe(struct i2c_client *client,
                return -ENOMEM;
 
        mutex_init(&is31->lock);
+       err = devm_add_action(dev, (void (*)(void *))mutex_destroy, &is31->lock);
+       if (err)
+               return err;
 
-       err = is31fl319x_parse_dt(&client->dev, is31);
+       err = is31fl319x_parse_fw(&client->dev, is31);
        if (err)
-               goto free_mutex;
+               return err;
 
        if (is31->shutdown_gpio) {
                gpiod_direction_output(is31->shutdown_gpio, 0);
@@ -361,37 +525,35 @@ static int is31fl319x_probe(struct i2c_client *client,
        }
 
        is31->client = client;
-       is31->regmap = devm_regmap_init_i2c(client, &regmap_config);
-       if (IS_ERR(is31->regmap)) {
-               dev_err(&client->dev, "failed to allocate register map\n");
-               err = PTR_ERR(is31->regmap);
-               goto free_mutex;
-       }
+       is31->regmap = devm_regmap_init_i2c(client, is31->cdef->is31fl319x_regmap_config);
+       if (IS_ERR(is31->regmap))
+               return dev_err_probe(dev, PTR_ERR(is31->regmap), "failed to allocate register map\n");
 
        i2c_set_clientdata(client, is31);
 
        /* check for write-reply from chip (we can't read any registers) */
-       err = regmap_write(is31->regmap, IS31FL319X_RESET, 0x00);
-       if (err < 0) {
-               dev_err(&client->dev, "no response from chip write: err = %d\n",
-                       err);
-               err = -EIO; /* does not answer */
-               goto free_mutex;
-       }
+       err = regmap_write(is31->regmap, is31->cdef->reset_reg, 0x00);
+       if (err < 0)
+               return dev_err_probe(dev, err, "no response from chip write\n");
 
        /*
         * Kernel conventions require per-LED led-max-microamp property.
         * But the chip does not allow to limit individual LEDs.
         * So we take minimum from all subnodes for safety of hardware.
         */
+       aggregated_led_microamp = is31->cdef->current_max;
        for (i = 0; i < is31->cdef->num_leds; i++)
                if (is31->leds[i].configured &&
                    is31->leds[i].max_microamp < aggregated_led_microamp)
                        aggregated_led_microamp = is31->leds[i].max_microamp;
 
-       regmap_write(is31->regmap, IS31FL319X_CONFIG2,
-                    is31fl319x_microamp_to_cs(dev, aggregated_led_microamp) |
-                    is31fl319x_db_to_gain(is31->audio_gain_db));
+       if (is31->cdef->is_3196or3199)
+               regmap_write(is31->regmap, IS31FL3196_CONFIG2,
+                            is31fl3196_microamp_to_cs(dev, aggregated_led_microamp) |
+                            is31fl3196_db_to_gain(is31->audio_gain_db));
+       else
+               regmap_update_bits(is31->regmap, IS31FL3190_CURRENT, IS31FL3190_CURRENT_MASK,
+                                  is31fl3190_microamp_to_cs(dev, aggregated_led_microamp));
 
        for (i = 0; i < is31->cdef->num_leds; i++) {
                struct is31fl319x_led *led = &is31->leds[i];
@@ -400,26 +562,14 @@ static int is31fl319x_probe(struct i2c_client *client,
                        continue;
 
                led->chip = is31;
-               led->cdev.brightness_set_blocking = is31fl319x_brightness_set;
+               led->cdev.brightness_set_blocking = is31->cdef->brightness_set;
 
                err = devm_led_classdev_register(&client->dev, &led->cdev);
                if (err < 0)
-                       goto free_mutex;
+                       return err;
        }
 
        return 0;
-
-free_mutex:
-       mutex_destroy(&is31->lock);
-       return err;
-}
-
-static int is31fl319x_remove(struct i2c_client *client)
-{
-       struct is31fl319x_chip *is31 = i2c_get_clientdata(client);
-
-       mutex_destroy(&is31->lock);
-       return 0;
 }
 
 /*
@@ -432,6 +582,10 @@ static const struct i2c_device_id is31fl319x_id[] = {
        { "is31fl3193" },
        { "is31fl3196" },
        { "is31fl3199" },
+       { "sn3190" },
+       { "sn3191" },
+       { "sn3193" },
+       { "sn3196" },
        { "sn3199" },
        {},
 };
@@ -440,10 +594,9 @@ MODULE_DEVICE_TABLE(i2c, is31fl319x_id);
 static struct i2c_driver is31fl319x_driver = {
        .driver   = {
                .name           = "leds-is31fl319x",
-               .of_match_table = of_match_ptr(of_is31fl319x_match),
+               .of_match_table = of_is31fl319x_match,
        },
-       .probe    = is31fl319x_probe,
-       .remove   = is31fl319x_remove,
+       .probe_new = is31fl319x_probe,
        .id_table = is31fl319x_id,
 };
 
index 1adfed1..eac6f4a 100644 (file)
@@ -239,9 +239,6 @@ static int omnia_leds_probe(struct i2c_client *client,
                led += ret;
        }
 
-       if (devm_device_add_groups(dev, omnia_led_controller_groups))
-               dev_warn(dev, "Could not add attribute group!\n");
-
        return 0;
 }
 
@@ -283,6 +280,7 @@ static struct i2c_driver omnia_leds_driver = {
        .driver         = {
                .name   = "leds-turris-omnia",
                .of_match_table = of_omnia_leds_match,
+               .dev_groups = omnia_led_controller_groups,
        },
 };
 
index 45e3870..da9d221 100644 (file)
@@ -19,6 +19,7 @@
 struct pwm_led {
        struct pwm_device *pwm;
        struct pwm_state state;
+       bool active_low;
 };
 
 struct pwm_mc_led {
@@ -45,6 +46,9 @@ static int led_pwm_mc_set(struct led_classdev *cdev,
                duty *= mc_cdev->subled_info[i].brightness;
                do_div(duty, cdev->max_brightness);
 
+               if (priv->leds[i].active_low)
+                       duty = priv->leds[i].state.period - duty;
+
                priv->leds[i].state.duty_cycle = duty;
                priv->leds[i].state.enabled = duty > 0;
                ret = pwm_apply_state(priv->leds[i].pwm,
@@ -72,11 +76,11 @@ static int iterate_subleds(struct device *dev, struct pwm_mc_led *priv,
                pwmled = &priv->leds[priv->mc_cdev.num_colors];
                pwmled->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL);
                if (IS_ERR(pwmled->pwm)) {
-                       ret = PTR_ERR(pwmled->pwm);
-                       dev_err(dev, "unable to request PWM: %d\n", ret);
+                       ret = dev_err_probe(dev, PTR_ERR(pwmled->pwm), "unable to request PWM\n");
                        goto release_fwnode;
                }
                pwm_init_state(pwmled->pwm, &pwmled->state);
+               pwmled->active_low = fwnode_property_read_bool(fwnode, "active-low");
 
                ret = fwnode_property_read_u32(fwnode, "color", &color);
                if (ret) {