Merge tag 'leds-next-6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 17 Jan 2024 23:25:27 +0000 (15:25 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 17 Jan 2024 23:25:27 +0000 (15:25 -0800)
Pull LED updates from Lee Jones:
 "New Drivers:
   - Add support for Allwinner A100 RGB LED controller
   - Add support for Maxim 5970 Dual Hot-swap controller

  New Device Support:
   - Add support for AW20108 to Awinic LED driver

  New Functionality:
   - Extend support for Net speeds to include; 2.5G, 5G and 10G
   - Allow tx/rx and cts/dsr/dcd/rng TTY LEDS to be turned on and off
     via sysfs if required
   - Add support for hardware control in AW200xx

  Fix-ups:
   - Use safer methods for string handling
   - Improve error handling; return proper error values, simplify,
     avoid duplicates, etc
   - Replace Mutex use with the Completion mechanism
   - Fix include lists; alphabetise, remove unused, explicitly add used
   - Use generic platform device properties
   - Use/convert to new/better APIs/helpers/MACROs instead of
     hand-rolling implementations
   - Device Tree binding adaptions/conversions/creation
   - Continue work to remove superfluous platform .remove() call-backs
   - Remove superfluous/defunct code
   - Trivial; whitespace, unused variables, spelling, clean-ups, etc
   - Avoid unnecessary duplicate locks

  Bug Fixes:
   - Repair Kconfig based dependency lists
   - Ensure unused dynamically allocated data is freed after use
   - Fix support for brightness control
   - Add missing sufficient delays during reset to ensure correct
     operation
   - Avoid division-by-zero issues"

* tag 'leds-next-6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (45 commits)
  leds: trigger: netdev: Add core support for hw not supporting fallback to LED sw control
  leds: trigger: panic: Don't register panic notifier if creating the trigger failed
  leds: sun50i-a100: Convert to be agnostic to property provider
  leds: max5970: Add missing headers
  leds: max5970: Make use of dev_err_probe()
  leds: max5970: Make use of device properties
  leds: max5970: Remove unused variable
  leds: rgb: Drop obsolete dependency on COMPILE_TEST
  leds: sun50i-a100: Avoid division-by-zero warning
  leds: trigger: Remove unused function led_trigger_rename_static()
  leds: qcom-lpg: Introduce a wrapper for getting driver data from a pwm chip
  leds: gpio: Add kernel log if devm_fwnode_gpiod_get() fails
  dt-bindings: leds: qcom,spmi-flash-led: Fix example node name
  dt-bindings: leds: aw200xx: Fix led pattern and add reg constraints
  dt-bindings: leds: awinic,aw200xx: Add AW20108 device
  leds: aw200xx: Add support for aw20108 device
  leds: aw200xx: Improve autodim calculation method
  leds: aw200xx: Enable disable_locking flag in regmap config
  leds: aw200xx: Add delay after software reset
  dt-bindings: leds: aw200xx: Remove property "awinic,display-rows"
  ...

24 files changed:
Documentation/ABI/testing/sysfs-class-led-trigger-netdev
Documentation/ABI/testing/sysfs-class-led-trigger-tty
Documentation/devicetree/bindings/leds/allwinner,sun50i-a100-ledc.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/leds/awinic,aw200xx.yaml
Documentation/devicetree/bindings/leds/common.yaml
Documentation/devicetree/bindings/leds/qcom,spmi-flash-led.yaml
drivers/leds/Kconfig
drivers/leds/Makefile
drivers/leds/led-triggers.c
drivers/leds/leds-aw200xx.c
drivers/leds/leds-gpio.c
drivers/leds/leds-max5970.c [new file with mode: 0644]
drivers/leds/leds-sun50i-a100.c [new file with mode: 0644]
drivers/leds/leds-syscon.c
drivers/leds/leds-tca6507.c
drivers/leds/rgb/Kconfig
drivers/leds/rgb/leds-qcom-lpg.c
drivers/leds/trigger/ledtrig-gpio.c
drivers/leds/trigger/ledtrig-netdev.c
drivers/leds/trigger/ledtrig-panic.c
drivers/leds/trigger/ledtrig-tty.c
drivers/tty/tty_io.c
include/linux/leds.h
include/linux/tty.h

index f6d9d72..a6c307c 100644 (file)
@@ -114,6 +114,45 @@ Description:
                speed of 1000Mbps of the named network device.
                Setting this value also immediately changes the LED state.
 
+What:          /sys/class/leds/<led>/link_2500
+Date:          Nov 2023
+KernelVersion: 6.8
+Contact:       linux-leds@vger.kernel.org
+Description:
+               Signal the link speed state of 2500Mbps of the named network device.
+
+               If set to 0 (default), the LED's normal state is off.
+
+               If set to 1, the LED's normal state reflects the link state
+               speed of 2500Mbps of the named network device.
+               Setting this value also immediately changes the LED state.
+
+What:          /sys/class/leds/<led>/link_5000
+Date:          Nov 2023
+KernelVersion: 6.8
+Contact:       linux-leds@vger.kernel.org
+Description:
+               Signal the link speed state of 5000Mbps of the named network device.
+
+               If set to 0 (default), the LED's normal state is off.
+
+               If set to 1, the LED's normal state reflects the link state
+               speed of 5000Mbps of the named network device.
+               Setting this value also immediately changes the LED state.
+
+What:          /sys/class/leds/<led>/link_10000
+Date:          Nov 2023
+KernelVersion: 6.8
+Contact:       linux-leds@vger.kernel.org
+Description:
+               Signal the link speed state of 10000Mbps of the named network device.
+
+               If set to 0 (default), the LED's normal state is off.
+
+               If set to 1, the LED's normal state reflects the link state
+               speed of 10000Mbps of the named network device.
+               Setting this value also immediately changes the LED state.
+
 What:          /sys/class/leds/<led>/half_duplex
 Date:          Jun 2023
 KernelVersion: 6.5
index 2bf6b24..30cef9a 100644 (file)
@@ -4,3 +4,59 @@ KernelVersion: 5.10
 Contact:       linux-leds@vger.kernel.org
 Description:
                Specifies the tty device name of the triggering tty
+
+What:          /sys/class/leds/<led>/rx
+Date:          February 2024
+KernelVersion: 6.8
+Description:
+               Signal reception (rx) of data on the named tty device.
+               If set to 0, the LED will not blink on reception.
+               If set to 1 (default), the LED will blink on reception.
+
+What:          /sys/class/leds/<led>/tx
+Date:          February 2024
+KernelVersion: 6.8
+Description:
+               Signal transmission (tx) of data on the named tty device.
+               If set to 0, the LED will not blink on transmission.
+               If set to 1 (default), the LED will blink on transmission.
+
+What:          /sys/class/leds/<led>/cts
+Date:          February 2024
+KernelVersion: 6.8
+Description:
+               CTS = Clear To Send
+               DCE is ready to accept data from the DTE.
+               If the line state is detected, the LED is switched on.
+               If set to 0 (default), the LED will not evaluate CTS.
+               If set to 1, the LED will evaluate CTS.
+
+What:          /sys/class/leds/<led>/dsr
+Date:          February 2024
+KernelVersion: 6.8
+Description:
+               DSR = Data Set Ready
+               DCE is ready to receive and send data.
+               If the line state is detected, the LED is switched on.
+               If set to 0 (default), the LED will not evaluate DSR.
+               If set to 1, the LED will evaluate DSR.
+
+What:          /sys/class/leds/<led>/dcd
+Date:          February 2024
+KernelVersion: 6.8
+Description:
+               DCD = Data Carrier Detect
+               DTE is receiving a carrier from the DCE.
+               If the line state is detected, the LED is switched on.
+               If set to 0 (default), the LED will not evaluate CAR (DCD).
+               If set to 1, the LED will evaluate CAR (DCD).
+
+What:          /sys/class/leds/<led>/rng
+Date:          February 2024
+KernelVersion: 6.8
+Description:
+               RNG = Ring Indicator
+               DCE has detected an incoming ring signal on the telephone
+               line. If the line state is detected, the LED is switched on.
+               If set to 0 (default), the LED will not evaluate RNG.
+               If set to 1, the LED will evaluate RNG.
diff --git a/Documentation/devicetree/bindings/leds/allwinner,sun50i-a100-ledc.yaml b/Documentation/devicetree/bindings/leds/allwinner,sun50i-a100-ledc.yaml
new file mode 100644 (file)
index 0000000..760cb33
--- /dev/null
@@ -0,0 +1,137 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/allwinner,sun50i-a100-ledc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Allwinner A100 LED Controller
+
+maintainers:
+  - Samuel Holland <samuel@sholland.org>
+
+description:
+  The LED controller found in Allwinner sunxi SoCs uses a one-wire serial
+  interface to drive up to 1024 RGB LEDs.
+
+properties:
+  compatible:
+    oneOf:
+      - const: allwinner,sun50i-a100-ledc
+      - items:
+          - enum:
+              - allwinner,sun20i-d1-ledc
+              - allwinner,sun50i-r329-ledc
+          - const: allwinner,sun50i-a100-ledc
+
+  reg:
+    maxItems: 1
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: Bus clock
+      - description: Module clock
+
+  clock-names:
+    items:
+      - const: bus
+      - const: mod
+
+  resets:
+    maxItems: 1
+
+  dmas:
+    maxItems: 1
+    description: TX DMA channel
+
+  dma-names:
+    const: tx
+
+  allwinner,pixel-format:
+    description: Pixel format (subpixel transmission order), default is "grb"
+    enum:
+      - bgr
+      - brg
+      - gbr
+      - grb
+      - rbg
+      - rgb
+
+  allwinner,t0h-ns:
+    default: 336
+    description: Length of high pulse when transmitting a "0" bit
+
+  allwinner,t0l-ns:
+    default: 840
+    description: Length of low pulse when transmitting a "0" bit
+
+  allwinner,t1h-ns:
+    default: 882
+    description: Length of high pulse when transmitting a "1" bit
+
+  allwinner,t1l-ns:
+    default: 294
+    description: Length of low pulse when transmitting a "1" bit
+
+  allwinner,treset-ns:
+    default: 300000
+    description: Minimum delay between transmission frames
+
+patternProperties:
+  "^multi-led@[0-9a-f]+$":
+    type: object
+    $ref: leds-class-multicolor.yaml#
+    unevaluatedProperties: false
+    properties:
+      reg:
+        minimum: 0
+        maximum: 1023
+        description: Index of the LED in the series (must be contiguous)
+
+    required:
+      - reg
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - clock-names
+  - resets
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/leds/common.h>
+
+    ledc: led-controller@2008000 {
+      compatible = "allwinner,sun20i-d1-ledc",
+                   "allwinner,sun50i-a100-ledc";
+      reg = <0x2008000 0x400>;
+      interrupts = <36 IRQ_TYPE_LEVEL_HIGH>;
+      clocks = <&ccu 12>, <&ccu 34>;
+      clock-names = "bus", "mod";
+      resets = <&ccu 12>;
+      dmas = <&dma 42>;
+      dma-names = "tx";
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      multi-led@0 {
+        reg = <0x0>;
+        color = <LED_COLOR_ID_RGB>;
+        function = LED_FUNCTION_INDICATOR;
+      };
+    };
+
+...
index feb5feb..54d6d1f 100644 (file)
@@ -10,15 +10,19 @@ maintainers:
   - Martin Kurbanov <mmkurbanov@sberdevices.ru>
 
 description: |
-  This controller is present on AW20036/AW20054/AW20072.
-  It is a 3x12/6x9/6x12 matrix LED programmed via
-  an I2C interface, up to 36/54/72 LEDs or 12/18/24 RGBs,
-  3 pattern controllers for auto breathing or group dimming control.
+  It is a matrix LED driver programmed via an I2C interface. Devices have
+  a set of individually controlled leds and support 3 pattern controllers
+  for auto breathing or group dimming control. Supported devices:
+    - AW20036 (3x12) 36 LEDs
+    - AW20054 (6x9)  54 LEDs
+    - AW20072 (6x12) 72 LEDs
+    - AW20108 (9x12) 108 LEDs
 
   For more product information please see the link below:
   aw20036 - https://www.awinic.com/en/productDetail/AW20036QNR#tech-docs
   aw20054 - https://www.awinic.com/en/productDetail/AW20054QNR#tech-docs
   aw20072 - https://www.awinic.com/en/productDetail/AW20072QNR#tech-docs
+  aw20108 - https://www.awinic.com/en/productDetail/AW20108QNR#tech-docs
 
 properties:
   compatible:
@@ -26,6 +30,7 @@ properties:
       - awinic,aw20036
       - awinic,aw20054
       - awinic,aw20072
+      - awinic,aw20108
 
   reg:
     maxItems: 1
@@ -36,13 +41,11 @@ properties:
   "#size-cells":
     const: 0
 
-  awinic,display-rows:
-    $ref: /schemas/types.yaml#/definitions/uint32
-    description:
-      Leds matrix size
+  enable-gpios:
+    maxItems: 1
 
 patternProperties:
-  "^led@[0-9a-f]$":
+  "^led@[0-9a-f]+$":
     type: object
     $ref: common.yaml#
     unevaluatedProperties: false
@@ -60,16 +63,11 @@ patternProperties:
           since the chip has a single global setting.
           The maximum output current of each LED is calculated by the
           following formula:
-            IMAXled = 160000 * (592 / 600.5) * (1 / display-rows)
+            IMAXled = 160000 * (592 / 600.5) * (1 / max-current-switch-number)
           And the minimum output current formula:
-            IMINled = 3300 * (592 / 600.5) * (1 / display-rows)
-
-required:
-  - compatible
-  - reg
-  - "#address-cells"
-  - "#size-cells"
-  - awinic,display-rows
+            IMINled = 3300 * (592 / 600.5) * (1 / max-current-switch-number)
+          where max-current-switch-number is determinated by led configuration
+          and depends on how leds are physically connected to the led driver.
 
 allOf:
   - if:
@@ -78,18 +76,67 @@ allOf:
           contains:
             const: awinic,aw20036
     then:
+      patternProperties:
+        "^led@[0-9a-f]+$":
+          properties:
+            reg:
+              items:
+                minimum: 0
+                maximum: 36
+
+  - if:
       properties:
-        awinic,display-rows:
-          enum: [1, 2, 3]
-    else:
+        compatible:
+          contains:
+            const: awinic,aw20054
+    then:
+      patternProperties:
+        "^led@[0-9a-f]+$":
+          properties:
+            reg:
+              items:
+                minimum: 0
+                maximum: 54
+
+  - if:
       properties:
-        awinic,display-rows:
-          enum: [1, 2, 3, 4, 5, 6, 7]
+        compatible:
+          contains:
+            const: awinic,aw20072
+    then:
+      patternProperties:
+        "^led@[0-9a-f]+$":
+          properties:
+            reg:
+              items:
+                minimum: 0
+                maximum: 72
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: awinic,aw20108
+    then:
+      patternProperties:
+        "^led@[0-9a-f]+$":
+          properties:
+            reg:
+              items:
+                minimum: 0
+                maximum: 108
+
+required:
+  - compatible
+  - reg
+  - "#address-cells"
+  - "#size-cells"
 
 additionalProperties: false
 
 examples:
   - |
+    #include <dt-bindings/gpio/gpio.h>
     #include <dt-bindings/leds/common.h>
 
     i2c {
@@ -101,7 +148,7 @@ examples:
             reg = <0x3a>;
             #address-cells = <1>;
             #size-cells = <0>;
-            awinic,display-rows = <3>;
+            enable-gpios = <&gpio 3 GPIO_ACTIVE_HIGH>;
 
             led@0 {
                 reg = <0x0>;
index c8d0ba5..55a8d13 100644 (file)
@@ -167,7 +167,7 @@ properties:
       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
+    $ref: /schemas/types.yaml#/definitions/uint32
 
   panic-indicator:
     description:
index a8736fd..1ba6076 100644 (file)
@@ -89,9 +89,11 @@ additionalProperties: false
 examples:
   - |
     #include <dt-bindings/leds/common.h>
-    spmi {
+
+    pmic {
         #address-cells = <1>;
         #size-cells = <0>;
+
         led-controller@ee00 {
             compatible = "qcom,pm8350c-flash-led", "qcom,spmi-flash-led";
             reg = <0xee00>;
index 6292fdd..d721b25 100644 (file)
@@ -95,14 +95,18 @@ config LEDS_ARIEL
          Say Y to if your machine is a Dell Wyse 3020 thin client.
 
 config LEDS_AW200XX
-       tristate "LED support for Awinic AW20036/AW20054/AW20072"
+       tristate "LED support for Awinic AW20036/AW20054/AW20072/AW20108"
        depends on LEDS_CLASS
        depends on I2C
        help
-         This option enables support for the AW20036/AW20054/AW20072 LED driver.
-         It is a 3x12/6x9/6x12 matrix LED driver programmed via
-         an I2C interface, up to 36/54/72 LEDs or 12/18/24 RGBs,
-         3 pattern controllers for auto breathing or group dimming control.
+         This option enables support for the Awinic AW200XX LED controllers.
+         It is a matrix LED driver programmed via an I2C interface. Devices have
+         a set of individually controlled LEDs and support 3 pattern controllers
+         for auto breathing or group dimming control. Supported devices:
+           - AW20036 (3x12) 36 LEDs
+           - AW20054 (6x9)  54 LEDs
+           - AW20072 (6x12) 72 LEDs
+           - AW20108 (9x12) 108 LEDs
 
          To compile this driver as a module, choose M here: the module
          will be called leds-aw200xx.
@@ -110,6 +114,7 @@ config LEDS_AW200XX
 config LEDS_AW2013
        tristate "LED support for Awinic AW2013"
        depends on LEDS_CLASS && I2C && OF
+       select REGMAP_I2C
        help
          This option enables support for the AW2013 3-channel
          LED driver.
@@ -298,6 +303,15 @@ config LEDS_COBALT_RAQ
        help
          This option enables support for the Cobalt Raq series LEDs.
 
+config LEDS_SUN50I_A100
+       tristate "LED support for Allwinner A100 RGB LED controller"
+       depends on LEDS_CLASS_MULTICOLOR
+       depends on ARCH_SUNXI || COMPILE_TEST
+       help
+         This option enables support for the RGB LED controller found
+         in some Allwinner sunxi SoCs, including A100, R329, and D1.
+         It uses a one-wire interface to control up to 1024 LEDs.
+
 config LEDS_SUNFIRE
        tristate "LED support for SunFire servers."
        depends on LEDS_CLASS
@@ -638,6 +652,17 @@ config LEDS_ADP5520
          To compile this driver as a module, choose M here: the module will
          be called leds-adp5520.
 
+config LEDS_MAX5970
+       tristate "LED Support for Maxim 5970"
+       depends on LEDS_CLASS
+       depends on MFD_MAX5970
+       help
+         This option enables support for the Maxim MAX5970 & MAX5978 smart
+         switch indication LEDs via the I2C bus.
+
+         To compile this driver as a module, choose M here: the module will
+         be called leds-max5970.
+
 config LEDS_MC13783
        tristate "LED Support for MC13XXX PMIC"
        depends on LEDS_CLASS
index d7348e8..ce07dc2 100644 (file)
@@ -56,6 +56,7 @@ obj-$(CONFIG_LEDS_LP8501)             += leds-lp8501.o
 obj-$(CONFIG_LEDS_LP8788)              += leds-lp8788.o
 obj-$(CONFIG_LEDS_LP8860)              += leds-lp8860.o
 obj-$(CONFIG_LEDS_LT3593)              += leds-lt3593.o
+obj-$(CONFIG_LEDS_MAX5970)             += leds-max5970.o
 obj-$(CONFIG_LEDS_MAX77650)            += leds-max77650.o
 obj-$(CONFIG_LEDS_MAX8997)             += leds-max8997.o
 obj-$(CONFIG_LEDS_MC13783)             += leds-mc13783.o
@@ -78,6 +79,7 @@ obj-$(CONFIG_LEDS_POWERNV)            += leds-powernv.o
 obj-$(CONFIG_LEDS_PWM)                 += leds-pwm.o
 obj-$(CONFIG_LEDS_REGULATOR)           += leds-regulator.o
 obj-$(CONFIG_LEDS_SC27XX_BLTC)         += leds-sc27xx-bltc.o
+obj-$(CONFIG_LEDS_SUN50I_A100)         += leds-sun50i-a100.o
 obj-$(CONFIG_LEDS_SUNFIRE)             += leds-sunfire.o
 obj-$(CONFIG_LEDS_SYSCON)              += leds-syscon.o
 obj-$(CONFIG_LEDS_TCA6507)             += leds-tca6507.o
index 6a5e1f4..bd59a14 100644 (file)
@@ -269,19 +269,6 @@ void led_trigger_set_default(struct led_classdev *led_cdev)
 }
 EXPORT_SYMBOL_GPL(led_trigger_set_default);
 
-void led_trigger_rename_static(const char *name, struct led_trigger *trig)
-{
-       /* new name must be on a temporary string to prevent races */
-       BUG_ON(name == trig->name);
-
-       down_write(&triggers_list_lock);
-       /* this assumes that trig->name was originaly allocated to
-        * non constant storage */
-       strcpy((char *)trig->name, name);
-       up_write(&triggers_list_lock);
-}
-EXPORT_SYMBOL_GPL(led_trigger_rename_static);
-
 /* LED Trigger Interface */
 
 int led_trigger_register(struct led_trigger *trig)
index 14ca236..f584a7f 100644 (file)
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * Awinic AW20036/AW20054/AW20072 LED driver
+ * Awinic AW20036/AW20054/AW20072/AW20108 LED driver
  *
  * Copyright (c) 2023, SberDevices. All Rights Reserved.
  *
@@ -10,6 +10,7 @@
 #include <linux/bitfield.h>
 #include <linux/bits.h>
 #include <linux/container_of.h>
+#include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
 #include <linux/leds.h>
 #include <linux/mod_devicetable.h>
 #define AW200XX_LED2REG(x, columns) \
        ((x) + (((x) / (columns)) * (AW200XX_DSIZE_COLUMNS_MAX - (columns))))
 
+/* DIM current configuration register on page 1 */
+#define AW200XX_REG_DIM_PAGE1(x, columns) \
+       AW200XX_REG(AW200XX_PAGE1, AW200XX_LED2REG(x, columns))
+
 /*
  * DIM current configuration register (page 4).
  * The even address for current DIM configuration.
@@ -82,6 +87,8 @@
 #define AW200XX_REG_DIM(x, columns) \
        AW200XX_REG(AW200XX_PAGE4, AW200XX_LED2REG(x, columns) * 2)
 #define AW200XX_REG_DIM2FADE(x) ((x) + 1)
+#define AW200XX_REG_FADE2DIM(fade) \
+       DIV_ROUND_UP((fade) * AW200XX_DIM_MAX, AW200XX_FADE_MAX)
 
 /*
  * Duty ratio of display scan (see p.15 of datasheet for formula):
@@ -112,6 +119,7 @@ struct aw200xx {
        struct mutex mutex;
        u32 num_leds;
        u32 display_rows;
+       struct gpio_desc *hwen;
        struct aw200xx_led leds[] __counted_by(num_leds);
 };
 
@@ -153,7 +161,8 @@ static ssize_t dim_store(struct device *dev, struct device_attribute *devattr,
 
        if (dim >= 0) {
                ret = regmap_write(chip->regmap,
-                                  AW200XX_REG_DIM(led->num, columns), dim);
+                                  AW200XX_REG_DIM_PAGE1(led->num, columns),
+                                  dim);
                if (ret)
                        goto out_unlock;
        }
@@ -188,9 +197,7 @@ static int aw200xx_brightness_set(struct led_classdev *cdev,
 
        dim = led->dim;
        if (dim < 0)
-               dim = max_t(int,
-                           brightness / (AW200XX_FADE_MAX / AW200XX_DIM_MAX),
-                           1);
+               dim = AW200XX_REG_FADE2DIM(brightness);
 
        ret = regmap_write(chip->regmap, reg, dim);
        if (ret)
@@ -314,6 +321,9 @@ static int aw200xx_chip_reset(const struct aw200xx *const chip)
        if (ret)
                return ret;
 
+       /* According to the datasheet software reset takes at least 1ms */
+       fsleep(1000);
+
        regcache_mark_dirty(chip->regmap);
        return regmap_write(chip->regmap, AW200XX_REG_FCD, AW200XX_FCD_CLEAR);
 }
@@ -353,6 +363,50 @@ static int aw200xx_chip_check(const struct aw200xx *const chip)
        return 0;
 }
 
+static void aw200xx_enable(const struct aw200xx *const chip)
+{
+       gpiod_set_value_cansleep(chip->hwen, 1);
+
+       /*
+        * After HWEN pin set high the chip begins to load the OTP information,
+        * which takes 200us to complete. About 200us wait time is needed for
+        * internal oscillator startup and display SRAM initialization. After
+        * display SRAM initialization, the registers in page1 to page5 can be
+        * configured via i2c interface.
+        */
+       fsleep(400);
+}
+
+static void aw200xx_disable(const struct aw200xx *const chip)
+{
+       return gpiod_set_value_cansleep(chip->hwen, 0);
+}
+
+static int aw200xx_probe_get_display_rows(struct device *dev,
+                                         struct aw200xx *chip)
+{
+       struct fwnode_handle *child;
+       u32 max_source = 0;
+
+       device_for_each_child_node(dev, child) {
+               u32 source;
+               int ret;
+
+               ret = fwnode_property_read_u32(child, "reg", &source);
+               if (ret || source >= chip->cdef->channels)
+                       continue;
+
+               max_source = max(max_source, source);
+       }
+
+       if (max_source == 0)
+               return -EINVAL;
+
+       chip->display_rows = max_source / chip->cdef->display_size_columns + 1;
+
+       return 0;
+}
+
 static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
 {
        struct fwnode_handle *child;
@@ -360,18 +414,10 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
        int ret;
        int i;
 
-       ret = device_property_read_u32(dev, "awinic,display-rows",
-                                      &chip->display_rows);
+       ret = aw200xx_probe_get_display_rows(dev, chip);
        if (ret)
                return dev_err_probe(dev, ret,
-                                    "Failed to read 'display-rows' property\n");
-
-       if (!chip->display_rows ||
-           chip->display_rows > chip->cdef->display_size_rows_max) {
-               return dev_err_probe(dev, -EINVAL,
-                                    "Invalid leds display size %u\n",
-                                    chip->display_rows);
-       }
+                                    "No valid led definitions found\n");
 
        current_max = aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA);
        current_min = aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA);
@@ -416,6 +462,7 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
                led->num = source;
                led->chip = chip;
                led->cdev.brightness_set_blocking = aw200xx_brightness_set;
+               led->cdev.max_brightness = AW200XX_FADE_MAX;
                led->cdev.groups = dim_groups;
                init_data.fwnode = child;
 
@@ -480,6 +527,7 @@ static const struct regmap_config aw200xx_regmap_config = {
        .rd_table = &aw200xx_readable_table,
        .wr_table = &aw200xx_writeable_table,
        .cache_type = REGCACHE_MAPLE,
+       .disable_locking = true,
 };
 
 static int aw200xx_probe(struct i2c_client *client)
@@ -512,6 +560,14 @@ static int aw200xx_probe(struct i2c_client *client)
        if (IS_ERR(chip->regmap))
                return PTR_ERR(chip->regmap);
 
+       chip->hwen = devm_gpiod_get_optional(&client->dev, "enable",
+                                            GPIOD_OUT_HIGH);
+       if (IS_ERR(chip->hwen))
+               return dev_err_probe(&client->dev, PTR_ERR(chip->hwen),
+                                    "Cannot get enable GPIO");
+
+       aw200xx_enable(chip);
+
        ret = aw200xx_chip_check(chip);
        if (ret)
                return ret;
@@ -532,6 +588,9 @@ static int aw200xx_probe(struct i2c_client *client)
        ret = aw200xx_chip_init(chip);
 
 out_unlock:
+       if (ret)
+               aw200xx_disable(chip);
+
        mutex_unlock(&chip->mutex);
        return ret;
 }
@@ -541,6 +600,7 @@ static void aw200xx_remove(struct i2c_client *client)
        struct aw200xx *chip = i2c_get_clientdata(client);
 
        aw200xx_chip_reset(chip);
+       aw200xx_disable(chip);
        mutex_destroy(&chip->mutex);
 }
 
@@ -562,10 +622,17 @@ static const struct aw200xx_chipdef aw20072_cdef = {
        .display_size_columns = 12,
 };
 
+static const struct aw200xx_chipdef aw20108_cdef = {
+       .channels = 108,
+       .display_size_rows_max = 9,
+       .display_size_columns = 12,
+};
+
 static const struct i2c_device_id aw200xx_id[] = {
        { "aw20036" },
        { "aw20054" },
        { "aw20072" },
+       { "aw20108" },
        {}
 };
 MODULE_DEVICE_TABLE(i2c, aw200xx_id);
@@ -574,6 +641,7 @@ static const struct of_device_id aw200xx_match_table[] = {
        { .compatible = "awinic,aw20036", .data = &aw20036_cdef, },
        { .compatible = "awinic,aw20054", .data = &aw20054_cdef, },
        { .compatible = "awinic,aw20072", .data = &aw20072_cdef, },
+       { .compatible = "awinic,aw20108", .data = &aw20108_cdef, },
        {}
 };
 MODULE_DEVICE_TABLE(of, aw200xx_match_table);
index 710c319..83fcd7b 100644 (file)
@@ -172,6 +172,8 @@ static struct gpio_leds_priv *gpio_leds_create(struct device *dev)
                led.gpiod = devm_fwnode_gpiod_get(dev, child, NULL, GPIOD_ASIS,
                                                  NULL);
                if (IS_ERR(led.gpiod)) {
+                       dev_err_probe(dev, PTR_ERR(led.gpiod), "Failed to get GPIO '%pfw'\n",
+                                     child);
                        fwnode_handle_put(child);
                        return ERR_CAST(led.gpiod);
                }
diff --git a/drivers/leds/leds-max5970.c b/drivers/leds/leds-max5970.c
new file mode 100644 (file)
index 0000000..56a5843
--- /dev/null
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device driver for leds in MAX5970 and MAX5978 IC
+ *
+ * Copyright (c) 2022 9elements GmbH
+ *
+ * Author: Patrick Rudolph <patrick.rudolph@9elements.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/leds.h>
+#include <linux/mfd/max5970.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#define ldev_to_maxled(c)       container_of(c, struct max5970_led, cdev)
+
+struct max5970_led {
+       struct device *dev;
+       struct regmap *regmap;
+       struct led_classdev cdev;
+       unsigned int index;
+};
+
+static int max5970_led_set_brightness(struct led_classdev *cdev,
+                                     enum led_brightness brightness)
+{
+       struct max5970_led *ddata = ldev_to_maxled(cdev);
+       int ret, val;
+
+       /* Set/clear corresponding bit for given led index */
+       val = !brightness ? BIT(ddata->index) : 0;
+
+       ret = regmap_update_bits(ddata->regmap, MAX5970_REG_LED_FLASH, BIT(ddata->index), val);
+       if (ret < 0)
+               dev_err(cdev->dev, "failed to set brightness %d", ret);
+
+       return ret;
+}
+
+static int max5970_led_probe(struct platform_device *pdev)
+{
+       struct fwnode_handle *led_node, *child;
+       struct device *dev = &pdev->dev;
+       struct regmap *regmap;
+       struct max5970_led *ddata;
+       int ret = -ENODEV;
+
+       regmap = dev_get_regmap(dev->parent, NULL);
+       if (!regmap)
+               return -ENODEV;
+
+       led_node = device_get_named_child_node(dev->parent, "leds");
+       if (!led_node)
+               return -ENODEV;
+
+       fwnode_for_each_available_child_node(led_node, child) {
+               u32 reg;
+
+               if (fwnode_property_read_u32(child, "reg", &reg))
+                       continue;
+
+               if (reg >= MAX5970_NUM_LEDS) {
+                       dev_err_probe(dev, -EINVAL, "invalid LED (%u >= %d)\n", reg, MAX5970_NUM_LEDS);
+                       continue;
+               }
+
+               ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+               if (!ddata) {
+                       fwnode_handle_put(child);
+                       return -ENOMEM;
+               }
+
+               ddata->index = reg;
+               ddata->regmap = regmap;
+               ddata->dev = dev;
+
+               if (fwnode_property_read_string(child, "label", &ddata->cdev.name))
+                       ddata->cdev.name = fwnode_get_name(child);
+
+               ddata->cdev.max_brightness = 1;
+               ddata->cdev.brightness_set_blocking = max5970_led_set_brightness;
+               ddata->cdev.default_trigger = "none";
+
+               ret = devm_led_classdev_register(dev, &ddata->cdev);
+               if (ret < 0) {
+                       fwnode_handle_put(child);
+                       return dev_err_probe(dev, ret, "Failed to initialize LED %u\n", reg);
+               }
+       }
+
+       return ret;
+}
+
+static struct platform_driver max5970_led_driver = {
+       .driver = {
+               .name = "max5970-led",
+       },
+       .probe = max5970_led_probe,
+};
+module_platform_driver(max5970_led_driver);
+
+MODULE_AUTHOR("Patrick Rudolph <patrick.rudolph@9elements.com>");
+MODULE_AUTHOR("Naresh Solanki <Naresh.Solanki@9elements.com>");
+MODULE_DESCRIPTION("MAX5970_hot-swap controller LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-sun50i-a100.c b/drivers/leds/leds-sun50i-a100.c
new file mode 100644 (file)
index 0000000..62d21c3
--- /dev/null
@@ -0,0 +1,584 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021-2023 Samuel Holland <samuel@sholland.org>
+ *
+ * Partly based on drivers/leds/leds-turris-omnia.c, which is:
+ *     Copyright (c) 2020 by Marek Behún <kabel@kernel.org>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/property.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+
+#define LEDC_CTRL_REG                  0x0000
+#define LEDC_CTRL_REG_DATA_LENGTH              GENMASK(28, 16)
+#define LEDC_CTRL_REG_RGB_MODE                 GENMASK(8, 6)
+#define LEDC_CTRL_REG_LEDC_EN                  BIT(0)
+#define LEDC_T01_TIMING_CTRL_REG       0x0004
+#define LEDC_T01_TIMING_CTRL_REG_T1H           GENMASK(26, 21)
+#define LEDC_T01_TIMING_CTRL_REG_T1L           GENMASK(20, 16)
+#define LEDC_T01_TIMING_CTRL_REG_T0H           GENMASK(10, 6)
+#define LEDC_T01_TIMING_CTRL_REG_T0L           GENMASK(5, 0)
+#define LEDC_RESET_TIMING_CTRL_REG     0x000c
+#define LEDC_RESET_TIMING_CTRL_REG_TR          GENMASK(28, 16)
+#define LEDC_RESET_TIMING_CTRL_REG_LED_NUM     GENMASK(9, 0)
+#define LEDC_DATA_REG                  0x0014
+#define LEDC_DMA_CTRL_REG              0x0018
+#define LEDC_DMA_CTRL_REG_DMA_EN               BIT(5)
+#define LEDC_DMA_CTRL_REG_FIFO_TRIG_LEVEL      GENMASK(4, 0)
+#define LEDC_INT_CTRL_REG              0x001c
+#define LEDC_INT_CTRL_REG_GLOBAL_INT_EN                BIT(5)
+#define LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN   BIT(1)
+#define LEDC_INT_CTRL_REG_TRANS_FINISH_INT_EN  BIT(0)
+#define LEDC_INT_STS_REG               0x0020
+#define LEDC_INT_STS_REG_FIFO_WLW              GENMASK(15, 10)
+#define LEDC_INT_STS_REG_FIFO_CPUREQ_INT       BIT(1)
+#define LEDC_INT_STS_REG_TRANS_FINISH_INT      BIT(0)
+
+#define LEDC_FIFO_DEPTH                        32U
+#define LEDC_MAX_LEDS                  1024
+#define LEDC_CHANNELS_PER_LED          3 /* RGB */
+
+#define LEDS_TO_BYTES(n)               ((n) * sizeof(u32))
+
+struct sun50i_a100_ledc_led {
+       struct led_classdev_mc mc_cdev;
+       struct mc_subled subled_info[LEDC_CHANNELS_PER_LED];
+       u32 addr;
+};
+
+#define to_ledc_led(mc) container_of(mc, struct sun50i_a100_ledc_led, mc_cdev)
+
+struct sun50i_a100_ledc_timing {
+       u32 t0h_ns;
+       u32 t0l_ns;
+       u32 t1h_ns;
+       u32 t1l_ns;
+       u32 treset_ns;
+};
+
+struct sun50i_a100_ledc {
+       struct device *dev;
+       void __iomem *base;
+       struct clk *bus_clk;
+       struct clk *mod_clk;
+       struct reset_control *reset;
+
+       u32 *buffer;
+       struct dma_chan *dma_chan;
+       dma_addr_t dma_handle;
+       unsigned int pio_length;
+       unsigned int pio_offset;
+
+       spinlock_t lock;
+       unsigned int next_length;
+       bool xfer_active;
+
+       u32 format;
+       struct sun50i_a100_ledc_timing timing;
+
+       u32 max_addr;
+       u32 num_leds;
+       struct sun50i_a100_ledc_led leds[] __counted_by(num_leds);
+};
+
+static int sun50i_a100_ledc_dma_xfer(struct sun50i_a100_ledc *priv, unsigned int length)
+{
+       struct dma_async_tx_descriptor *desc;
+       dma_cookie_t cookie;
+
+       desc = dmaengine_prep_slave_single(priv->dma_chan, priv->dma_handle,
+                                          LEDS_TO_BYTES(length), DMA_MEM_TO_DEV, 0);
+       if (!desc)
+               return -ENOMEM;
+
+       cookie = dmaengine_submit(desc);
+       if (dma_submit_error(cookie))
+               return -EIO;
+
+       dma_async_issue_pending(priv->dma_chan);
+
+       return 0;
+}
+
+static void sun50i_a100_ledc_pio_xfer(struct sun50i_a100_ledc *priv, unsigned int fifo_used)
+{
+       unsigned int burst, length, offset;
+       u32 control;
+
+       length = priv->pio_length;
+       offset = priv->pio_offset;
+       burst  = min(length, LEDC_FIFO_DEPTH - fifo_used);
+
+       iowrite32_rep(priv->base + LEDC_DATA_REG, priv->buffer + offset, burst);
+
+       if (burst < length) {
+               priv->pio_length = length - burst;
+               priv->pio_offset = offset + burst;
+
+               if (!offset) {
+                       control = readl(priv->base + LEDC_INT_CTRL_REG);
+                       control |= LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN;
+                       writel(control, priv->base + LEDC_INT_CTRL_REG);
+               }
+       } else {
+               /* Disable the request IRQ once all data is written. */
+               control = readl(priv->base + LEDC_INT_CTRL_REG);
+               control &= ~LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN;
+               writel(control, priv->base + LEDC_INT_CTRL_REG);
+       }
+}
+
+static void sun50i_a100_ledc_start_xfer(struct sun50i_a100_ledc *priv, unsigned int length)
+{
+       bool use_dma = false;
+       u32 control;
+
+       if (priv->dma_chan && length > LEDC_FIFO_DEPTH) {
+               int ret;
+
+               ret = sun50i_a100_ledc_dma_xfer(priv, length);
+               if (ret)
+                       dev_warn(priv->dev, "Failed to set up DMA (%d), using PIO\n", ret);
+               else
+                       use_dma = true;
+       }
+
+       /* The DMA trigger level must be at least the burst length. */
+       control = FIELD_PREP(LEDC_DMA_CTRL_REG_DMA_EN, use_dma) |
+                 FIELD_PREP_CONST(LEDC_DMA_CTRL_REG_FIFO_TRIG_LEVEL, LEDC_FIFO_DEPTH / 2);
+       writel(control, priv->base + LEDC_DMA_CTRL_REG);
+
+       control = readl(priv->base + LEDC_CTRL_REG);
+       control &= ~LEDC_CTRL_REG_DATA_LENGTH;
+       control |= FIELD_PREP(LEDC_CTRL_REG_DATA_LENGTH, length) | LEDC_CTRL_REG_LEDC_EN;
+       writel(control, priv->base + LEDC_CTRL_REG);
+
+       if (!use_dma) {
+               /* The FIFO is empty when starting a new transfer. */
+               unsigned int fifo_used = 0;
+
+               priv->pio_length = length;
+               priv->pio_offset = 0;
+
+               sun50i_a100_ledc_pio_xfer(priv, fifo_used);
+       }
+}
+
+static irqreturn_t sun50i_a100_ledc_irq(int irq, void *data)
+{
+       struct sun50i_a100_ledc *priv = data;
+       u32 status;
+
+       status = readl(priv->base + LEDC_INT_STS_REG);
+
+       if (status & LEDC_INT_STS_REG_TRANS_FINISH_INT) {
+               unsigned int next_length;
+
+               spin_lock(&priv->lock);
+
+               /* If another transfer is queued, dequeue and start it. */
+               next_length = priv->next_length;
+               if (next_length)
+                       priv->next_length = 0;
+               else
+                       priv->xfer_active = false;
+
+               spin_unlock(&priv->lock);
+
+               if (next_length)
+                       sun50i_a100_ledc_start_xfer(priv, next_length);
+       } else if (status & LEDC_INT_STS_REG_FIFO_CPUREQ_INT) {
+               /* Continue the current transfer. */
+               sun50i_a100_ledc_pio_xfer(priv, FIELD_GET(LEDC_INT_STS_REG_FIFO_WLW, status));
+       }
+
+       /* Clear the W1C status bits. */
+       writel(status, priv->base + LEDC_INT_STS_REG);
+
+       return IRQ_HANDLED;
+}
+
+static void sun50i_a100_ledc_brightness_set(struct led_classdev *cdev,
+                                           enum led_brightness brightness)
+{
+       struct sun50i_a100_ledc *priv = dev_get_drvdata(cdev->dev->parent);
+       struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+       struct sun50i_a100_ledc_led *led = to_ledc_led(mc_cdev);
+       unsigned int next_length;
+       unsigned long flags;
+       bool xfer_active;
+
+       led_mc_calc_color_components(mc_cdev, brightness);
+
+       priv->buffer[led->addr] = led->subled_info[0].brightness << 16 |
+                                 led->subled_info[1].brightness <<  8 |
+                                 led->subled_info[2].brightness;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       /* Start, enqueue, or extend an enqueued transfer, as appropriate. */
+       next_length = max(priv->next_length, led->addr + 1);
+       xfer_active = priv->xfer_active;
+       if (xfer_active)
+               priv->next_length = next_length;
+       else
+               priv->xfer_active = true;
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       if (!xfer_active)
+               sun50i_a100_ledc_start_xfer(priv, next_length);
+}
+
+static const char *const sun50i_a100_ledc_formats[] = {
+       "rgb", "rbg", "grb", "gbr", "brg", "bgr",
+};
+
+static int sun50i_a100_ledc_parse_format(struct device *dev,
+                                        struct sun50i_a100_ledc *priv)
+{
+       const char *format = "grb";
+       u32 i;
+
+       device_property_read_string(dev, "allwinner,pixel-format", &format);
+
+       for (i = 0; i < ARRAY_SIZE(sun50i_a100_ledc_formats); i++) {
+               if (!strcmp(format, sun50i_a100_ledc_formats[i])) {
+                       priv->format = i;
+                       return 0;
+               }
+       }
+
+       return dev_err_probe(dev, -EINVAL, "Bad pixel format '%s'\n", format);
+}
+
+static void sun50i_a100_ledc_set_format(struct sun50i_a100_ledc *priv)
+{
+       u32 control;
+
+       control = readl(priv->base + LEDC_CTRL_REG);
+       control &= ~LEDC_CTRL_REG_RGB_MODE;
+       control |= FIELD_PREP(LEDC_CTRL_REG_RGB_MODE, priv->format);
+       writel(control, priv->base + LEDC_CTRL_REG);
+}
+
+static const struct sun50i_a100_ledc_timing sun50i_a100_ledc_default_timing = {
+       .t0h_ns = 336,
+       .t0l_ns = 840,
+       .t1h_ns = 882,
+       .t1l_ns = 294,
+       .treset_ns = 300000,
+};
+
+static int sun50i_a100_ledc_parse_timing(struct device *dev,
+                                        struct sun50i_a100_ledc *priv)
+{
+       struct sun50i_a100_ledc_timing *timing = &priv->timing;
+
+       *timing = sun50i_a100_ledc_default_timing;
+
+       device_property_read_u32(dev, "allwinner,t0h-ns", &timing->t0h_ns);
+       device_property_read_u32(dev, "allwinner,t0l-ns", &timing->t0l_ns);
+       device_property_read_u32(dev, "allwinner,t1h-ns", &timing->t1h_ns);
+       device_property_read_u32(dev, "allwinner,t1l-ns", &timing->t1l_ns);
+       device_property_read_u32(dev, "allwinner,treset-ns", &timing->treset_ns);
+
+       return 0;
+}
+
+static void sun50i_a100_ledc_set_timing(struct sun50i_a100_ledc *priv)
+{
+       const struct sun50i_a100_ledc_timing *timing = &priv->timing;
+       unsigned long mod_freq = clk_get_rate(priv->mod_clk);
+       u32 cycle_ns;
+       u32 control;
+
+       if (!mod_freq)
+               return;
+
+       cycle_ns = NSEC_PER_SEC / mod_freq;
+       control = FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T1H, timing->t1h_ns / cycle_ns) |
+                 FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T1L, timing->t1l_ns / cycle_ns) |
+                 FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T0H, timing->t0h_ns / cycle_ns) |
+                 FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T0L, timing->t0l_ns / cycle_ns);
+       writel(control, priv->base + LEDC_T01_TIMING_CTRL_REG);
+
+       control = FIELD_PREP(LEDC_RESET_TIMING_CTRL_REG_TR, timing->treset_ns / cycle_ns) |
+                 FIELD_PREP(LEDC_RESET_TIMING_CTRL_REG_LED_NUM, priv->max_addr);
+       writel(control, priv->base + LEDC_RESET_TIMING_CTRL_REG);
+}
+
+static int sun50i_a100_ledc_resume(struct device *dev)
+{
+       struct sun50i_a100_ledc *priv = dev_get_drvdata(dev);
+       int ret;
+
+       ret = reset_control_deassert(priv->reset);
+       if (ret)
+               return ret;
+
+       ret = clk_prepare_enable(priv->bus_clk);
+       if (ret)
+               goto err_assert_reset;
+
+       ret = clk_prepare_enable(priv->mod_clk);
+       if (ret)
+               goto err_disable_bus_clk;
+
+       sun50i_a100_ledc_set_format(priv);
+       sun50i_a100_ledc_set_timing(priv);
+
+       writel(LEDC_INT_CTRL_REG_GLOBAL_INT_EN | LEDC_INT_CTRL_REG_TRANS_FINISH_INT_EN,
+              priv->base + LEDC_INT_CTRL_REG);
+
+       return 0;
+
+err_disable_bus_clk:
+       clk_disable_unprepare(priv->bus_clk);
+err_assert_reset:
+       reset_control_assert(priv->reset);
+
+       return ret;
+}
+
+static int sun50i_a100_ledc_suspend(struct device *dev)
+{
+       struct sun50i_a100_ledc *priv = dev_get_drvdata(dev);
+
+       /* Wait for all transfers to complete. */
+       for (;;) {
+               unsigned long flags;
+               bool xfer_active;
+
+               spin_lock_irqsave(&priv->lock, flags);
+               xfer_active = priv->xfer_active;
+               spin_unlock_irqrestore(&priv->lock, flags);
+               if (!xfer_active)
+                       break;
+
+               msleep(1);
+       }
+
+       clk_disable_unprepare(priv->mod_clk);
+       clk_disable_unprepare(priv->bus_clk);
+       reset_control_assert(priv->reset);
+
+       return 0;
+}
+
+static void sun50i_a100_ledc_dma_cleanup(void *data)
+{
+       struct sun50i_a100_ledc *priv = data;
+
+       dma_release_channel(priv->dma_chan);
+}
+
+static int sun50i_a100_ledc_probe(struct platform_device *pdev)
+{
+       struct dma_slave_config dma_cfg = {};
+       struct led_init_data init_data = {};
+       struct sun50i_a100_ledc_led *led;
+       struct device *dev = &pdev->dev;
+       struct sun50i_a100_ledc *priv;
+       struct fwnode_handle *child;
+       struct resource *mem;
+       u32 max_addr = 0;
+       u32 num_leds = 0;
+       int irq, ret;
+
+       /*
+        * The maximum LED address must be known in sun50i_a100_ledc_resume() before
+        * class device registration, so parse and validate the subnodes up front.
+        */
+       device_for_each_child_node(dev, child) {
+               u32 addr, color;
+
+               ret = fwnode_property_read_u32(child, "reg", &addr);
+               if (ret || addr >= LEDC_MAX_LEDS) {
+                       fwnode_handle_put(child);
+                       return dev_err_probe(dev, -EINVAL, "'reg' must be between 0 and %d\n",
+                                            LEDC_MAX_LEDS - 1);
+               }
+
+               ret = fwnode_property_read_u32(child, "color", &color);
+               if (ret || color != LED_COLOR_ID_RGB) {
+                       fwnode_handle_put(child);
+                       return dev_err_probe(dev, -EINVAL, "'color' must be LED_COLOR_ID_RGB\n");
+               }
+
+               max_addr = max(max_addr, addr);
+               num_leds++;
+       }
+
+       if (!num_leds)
+               return -ENODEV;
+
+       priv = devm_kzalloc(dev, struct_size(priv, leds, num_leds), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->dev = dev;
+       priv->max_addr = max_addr;
+       priv->num_leds = num_leds;
+       spin_lock_init(&priv->lock);
+       dev_set_drvdata(dev, priv);
+
+       ret = sun50i_a100_ledc_parse_format(dev, priv);
+       if (ret)
+               return ret;
+
+       ret = sun50i_a100_ledc_parse_timing(dev, priv);
+       if (ret)
+               return ret;
+
+       priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
+       if (IS_ERR(priv->base))
+               return PTR_ERR(priv->base);
+
+       priv->bus_clk = devm_clk_get(dev, "bus");
+       if (IS_ERR(priv->bus_clk))
+               return PTR_ERR(priv->bus_clk);
+
+       priv->mod_clk = devm_clk_get(dev, "mod");
+       if (IS_ERR(priv->mod_clk))
+               return PTR_ERR(priv->mod_clk);
+
+       priv->reset = devm_reset_control_get_exclusive(dev, NULL);
+       if (IS_ERR(priv->reset))
+               return PTR_ERR(priv->reset);
+
+       priv->dma_chan = dma_request_chan(dev, "tx");
+       if (IS_ERR(priv->dma_chan)) {
+               if (PTR_ERR(priv->dma_chan) != -ENODEV)
+                       return PTR_ERR(priv->dma_chan);
+
+               priv->dma_chan = NULL;
+
+               priv->buffer = devm_kzalloc(dev, LEDS_TO_BYTES(LEDC_MAX_LEDS), GFP_KERNEL);
+               if (!priv->buffer)
+                       return -ENOMEM;
+       } else {
+               ret = devm_add_action_or_reset(dev, sun50i_a100_ledc_dma_cleanup, priv);
+               if (ret)
+                       return ret;
+
+               dma_cfg.dst_addr        = mem->start + LEDC_DATA_REG;
+               dma_cfg.dst_addr_width  = DMA_SLAVE_BUSWIDTH_4_BYTES;
+               dma_cfg.dst_maxburst    = LEDC_FIFO_DEPTH / 2;
+
+               ret = dmaengine_slave_config(priv->dma_chan, &dma_cfg);
+               if (ret)
+                       return ret;
+
+               priv->buffer = dmam_alloc_attrs(dmaengine_get_dma_device(priv->dma_chan),
+                                               LEDS_TO_BYTES(LEDC_MAX_LEDS), &priv->dma_handle,
+                                               GFP_KERNEL, DMA_ATTR_WRITE_COMBINE);
+               if (!priv->buffer)
+                       return -ENOMEM;
+       }
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0)
+               return irq;
+
+       ret = devm_request_irq(dev, irq, sun50i_a100_ledc_irq, 0, dev_name(dev), priv);
+       if (ret)
+               return ret;
+
+       ret = sun50i_a100_ledc_resume(dev);
+       if (ret)
+               return ret;
+
+       led = priv->leds;
+       device_for_each_child_node(dev, child) {
+               struct led_classdev *cdev;
+
+               /* The node was already validated above. */
+               fwnode_property_read_u32(child, "reg", &led->addr);
+
+               led->subled_info[0].color_index = LED_COLOR_ID_RED;
+               led->subled_info[0].channel = 0;
+               led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+               led->subled_info[1].channel = 1;
+               led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+               led->subled_info[2].channel = 2;
+
+               led->mc_cdev.num_colors = ARRAY_SIZE(led->subled_info);
+               led->mc_cdev.subled_info = led->subled_info;
+
+               cdev = &led->mc_cdev.led_cdev;
+               cdev->max_brightness = U8_MAX;
+               cdev->brightness_set = sun50i_a100_ledc_brightness_set;
+
+               init_data.fwnode = child;
+
+               ret = led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data);
+               if (ret) {
+                       dev_err_probe(dev, ret, "Failed to register multicolor LED %u", led->addr);
+                       goto err_put_child;
+               }
+
+               led++;
+       }
+
+       dev_info(dev, "Registered %u LEDs\n", num_leds);
+
+       return 0;
+
+err_put_child:
+       fwnode_handle_put(child);
+       while (led-- > priv->leds)
+               led_classdev_multicolor_unregister(&led->mc_cdev);
+       sun50i_a100_ledc_suspend(&pdev->dev);
+
+       return ret;
+}
+
+static void sun50i_a100_ledc_remove(struct platform_device *pdev)
+{
+       struct sun50i_a100_ledc *priv = platform_get_drvdata(pdev);
+
+       for (u32 i = 0; i < priv->num_leds; i++)
+               led_classdev_multicolor_unregister(&priv->leds[i].mc_cdev);
+       sun50i_a100_ledc_suspend(&pdev->dev);
+}
+
+static const struct of_device_id sun50i_a100_ledc_of_match[] = {
+       { .compatible = "allwinner,sun50i-a100-ledc" },
+       {}
+};
+MODULE_DEVICE_TABLE(of, sun50i_a100_ledc_of_match);
+
+static DEFINE_SIMPLE_DEV_PM_OPS(sun50i_a100_ledc_pm,
+                               sun50i_a100_ledc_suspend,
+                               sun50i_a100_ledc_resume);
+
+static struct platform_driver sun50i_a100_ledc_driver = {
+       .probe          = sun50i_a100_ledc_probe,
+       .remove_new     = sun50i_a100_ledc_remove,
+       .shutdown       = sun50i_a100_ledc_remove,
+       .driver         = {
+               .name           = "sun50i-a100-ledc",
+               .of_match_table = sun50i_a100_ledc_of_match,
+               .pm             = pm_ptr(&sun50i_a100_ledc_pm),
+       },
+};
+module_platform_driver(sun50i_a100_ledc_driver);
+
+MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
+MODULE_DESCRIPTION("Allwinner A100 LED controller driver");
+MODULE_LICENSE("GPL");
index 360a376..d633ad5 100644 (file)
@@ -81,7 +81,8 @@ static int syscon_led_probe(struct platform_device *pdev)
 
        sled->map = map;
 
-       if (of_property_read_u32(np, "offset", &sled->offset))
+       if (of_property_read_u32(np, "reg", &sled->offset) &&
+           of_property_read_u32(np, "offset", &sled->offset))
                return -EINVAL;
        if (of_property_read_u32(np, "mask", &sled->mask))
                return -EINVAL;
index e190746..4f22f42 100644 (file)
@@ -638,19 +638,13 @@ static int tca6507_probe_gpios(struct device *dev,
        tca->gpio.direction_output = tca6507_gpio_direction_output;
        tca->gpio.set = tca6507_gpio_set_value;
        tca->gpio.parent = dev;
-       err = gpiochip_add_data(&tca->gpio, tca);
+       err = devm_gpiochip_add_data(dev, &tca->gpio, tca);
        if (err) {
                tca->gpio.ngpio = 0;
                return err;
        }
        return 0;
 }
-
-static void tca6507_remove_gpio(struct tca6507_chip *tca)
-{
-       if (tca->gpio.ngpio)
-               gpiochip_remove(&tca->gpio);
-}
 #else /* CONFIG_GPIOLIB */
 static int tca6507_probe_gpios(struct device *dev,
                               struct tca6507_chip *tca,
@@ -658,9 +652,6 @@ static int tca6507_probe_gpios(struct device *dev,
 {
        return 0;
 }
-static void tca6507_remove_gpio(struct tca6507_chip *tca)
-{
-}
 #endif /* CONFIG_GPIOLIB */
 
 static struct tca6507_platform_data *
@@ -762,38 +753,25 @@ static int tca6507_probe(struct i2c_client *client)
                        l->led_cdev.brightness_set = tca6507_brightness_set;
                        l->led_cdev.blink_set = tca6507_blink_set;
                        l->bank = -1;
-                       err = led_classdev_register(dev, &l->led_cdev);
+                       err = devm_led_classdev_register(dev, &l->led_cdev);
                        if (err < 0)
-                               goto exit;
+                               return err;
                }
        }
        err = tca6507_probe_gpios(dev, tca, pdata);
        if (err)
-               goto exit;
+               return err;
        /* set all registers to known state - zero */
        tca->reg_set = 0x7f;
        schedule_work(&tca->work);
 
        return 0;
-exit:
-       while (i--) {
-               if (tca->leds[i].led_cdev.name)
-                       led_classdev_unregister(&tca->leds[i].led_cdev);
-       }
-       return err;
 }
 
 static void tca6507_remove(struct i2c_client *client)
 {
-       int i;
        struct tca6507_chip *tca = i2c_get_clientdata(client);
-       struct tca6507_led *tca_leds = tca->leds;
 
-       for (i = 0; i < NUM_LEDS; i++) {
-               if (tca_leds[i].led_cdev.name)
-                       led_classdev_unregister(&tca_leds[i].led_cdev);
-       }
-       tca6507_remove_gpio(tca);
        cancel_work_sync(&tca->work);
 }
 
index a6a21f5..e66bd21 100644 (file)
@@ -4,7 +4,7 @@ if LEDS_CLASS_MULTICOLOR
 
 config LEDS_GROUP_MULTICOLOR
        tristate "LEDs group multi-color support"
-       depends on OF || COMPILE_TEST
+       depends on OF
        help
          This option enables support for monochrome LEDs that are grouped
          into multicolor LEDs which is useful in the case where LEDs of
index 68d82a6..156b73d 100644 (file)
@@ -552,9 +552,9 @@ static int lpg_parse_dtest(struct lpg *lpg)
                ret = count;
                goto err_malformed;
        } else if (count != lpg->data->num_channels * 2) {
-               dev_err(lpg->dev, "qcom,dtest needs to be %d items\n",
-                       lpg->data->num_channels * 2);
-               return -EINVAL;
+               return dev_err_probe(lpg->dev, -EINVAL,
+                                    "qcom,dtest needs to be %d items\n",
+                                    lpg->data->num_channels * 2);
        }
 
        for (i = 0; i < lpg->data->num_channels; i++) {
@@ -574,8 +574,7 @@ static int lpg_parse_dtest(struct lpg *lpg)
        return 0;
 
 err_malformed:
-       dev_err(lpg->dev, "malformed qcom,dtest\n");
-       return ret;
+       return dev_err_probe(lpg->dev, ret, "malformed qcom,dtest\n");
 }
 
 static void lpg_apply_dtest(struct lpg_channel *chan)
@@ -977,9 +976,14 @@ static int lpg_pattern_mc_clear(struct led_classdev *cdev)
        return lpg_pattern_clear(led);
 }
 
+static inline struct lpg *lpg_pwm_from_chip(struct pwm_chip *chip)
+{
+       return container_of(chip, struct lpg, pwm);
+}
+
 static int lpg_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
 {
-       struct lpg *lpg = container_of(chip, struct lpg, pwm);
+       struct lpg *lpg = lpg_pwm_from_chip(chip);
        struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
 
        return chan->in_use ? -EBUSY : 0;
@@ -995,7 +999,7 @@ static int lpg_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
 static int lpg_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
                         const struct pwm_state *state)
 {
-       struct lpg *lpg = container_of(chip, struct lpg, pwm);
+       struct lpg *lpg = lpg_pwm_from_chip(chip);
        struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
        int ret = 0;
 
@@ -1026,7 +1030,7 @@ out_unlock:
 static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
                             struct pwm_state *state)
 {
-       struct lpg *lpg = container_of(chip, struct lpg, pwm);
+       struct lpg *lpg = lpg_pwm_from_chip(chip);
        struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
        unsigned int resolution;
        unsigned int pre_div;
@@ -1095,9 +1099,9 @@ static int lpg_add_pwm(struct lpg *lpg)
        lpg->pwm.npwm = lpg->num_channels;
        lpg->pwm.ops = &lpg_pwm_ops;
 
-       ret = pwmchip_add(&lpg->pwm);
+       ret = devm_pwmchip_add(lpg->dev, &lpg->pwm);
        if (ret)
-               dev_err(lpg->dev, "failed to add PWM chip: ret %d\n", ret);
+               dev_err_probe(lpg->dev, ret, "failed to add PWM chip\n");
 
        return ret;
 }
@@ -1111,19 +1115,16 @@ static int lpg_parse_channel(struct lpg *lpg, struct device_node *np,
        int ret;
 
        ret = of_property_read_u32(np, "reg", &reg);
-       if (ret || !reg || reg > lpg->num_channels) {
-               dev_err(lpg->dev, "invalid \"reg\" of %pOFn\n", np);
-               return -EINVAL;
-       }
+       if (ret || !reg || reg > lpg->num_channels)
+               return dev_err_probe(lpg->dev, -EINVAL, "invalid \"reg\" of %pOFn\n", np);
 
        chan = &lpg->channels[reg - 1];
        chan->in_use = true;
 
        ret = of_property_read_u32(np, "color", &color);
-       if (ret < 0 && ret != -EINVAL) {
-               dev_err(lpg->dev, "failed to parse \"color\" of %pOF\n", np);
-               return ret;
-       }
+       if (ret < 0 && ret != -EINVAL)
+               return dev_err_probe(lpg->dev, ret,
+                                    "failed to parse \"color\" of %pOF\n", np);
 
        chan->color = color;
 
@@ -1146,10 +1147,9 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
        int i;
 
        ret = of_property_read_u32(np, "color", &color);
-       if (ret < 0 && ret != -EINVAL) {
-               dev_err(lpg->dev, "failed to parse \"color\" of %pOF\n", np);
-               return ret;
-       }
+       if (ret < 0 && ret != -EINVAL)
+               return dev_err_probe(lpg->dev, ret,
+                             "failed to parse \"color\" of %pOF\n", np);
 
        if (color == LED_COLOR_ID_RGB)
                num_channels = of_get_available_child_count(np);
@@ -1226,7 +1226,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
        else
                ret = devm_led_classdev_register_ext(lpg->dev, &led->cdev, &init_data);
        if (ret)
-               dev_err(lpg->dev, "unable to register %s\n", cdev->name);
+               dev_err_probe(lpg->dev, ret, "unable to register %s\n", cdev->name);
 
        return ret;
 }
@@ -1272,10 +1272,9 @@ static int lpg_init_triled(struct lpg *lpg)
 
        if (lpg->triled_has_src_sel) {
                ret = of_property_read_u32(np, "qcom,power-source", &lpg->triled_src);
-               if (ret || lpg->triled_src == 2 || lpg->triled_src > 3) {
-                       dev_err(lpg->dev, "invalid power source\n");
-                       return -EINVAL;
-               }
+               if (ret || lpg->triled_src == 2 || lpg->triled_src > 3)
+                       return dev_err_probe(lpg->dev, -EINVAL,
+                                            "invalid power source\n");
        }
 
        /* Disable automatic trickle charge LED */
@@ -1324,8 +1323,6 @@ static int lpg_probe(struct platform_device *pdev)
        if (!lpg->data)
                return -EINVAL;
 
-       platform_set_drvdata(pdev, lpg);
-
        lpg->dev = &pdev->dev;
        mutex_init(&lpg->lock);
 
@@ -1363,13 +1360,6 @@ static int lpg_probe(struct platform_device *pdev)
        return lpg_add_pwm(lpg);
 }
 
-static void lpg_remove(struct platform_device *pdev)
-{
-       struct lpg *lpg = platform_get_drvdata(pdev);
-
-       pwmchip_remove(&lpg->pwm);
-}
-
 static const struct lpg_data pm8916_pwm_data = {
        .num_channels = 1,
        .channels = (const struct lpg_channel_data[]) {
@@ -1529,7 +1519,6 @@ MODULE_DEVICE_TABLE(of, lpg_of_table);
 
 static struct platform_driver lpg_driver = {
        .probe = lpg_probe,
-       .remove_new = lpg_remove,
        .driver = {
                .name = "qcom-spmi-lpg",
                .of_match_table = lpg_of_table,
index 9b7fe5d..7f6a235 100644 (file)
@@ -41,33 +41,30 @@ static irqreturn_t gpio_trig_irq(int irq, void *_led)
        return IRQ_HANDLED;
 }
 
-static ssize_t gpio_trig_brightness_show(struct device *dev,
+static ssize_t desired_brightness_show(struct device *dev,
                struct device_attribute *attr, char *buf)
 {
        struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev);
 
-       return sprintf(buf, "%u\n", gpio_data->desired_brightness);
+       return sysfs_emit(buf, "%u\n", gpio_data->desired_brightness);
 }
 
-static ssize_t gpio_trig_brightness_store(struct device *dev,
+static ssize_t desired_brightness_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t n)
 {
        struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev);
-       unsigned desired_brightness;
+       u8 desired_brightness;
        int ret;
 
-       ret = sscanf(buf, "%u", &desired_brightness);
-       if (ret < 1 || desired_brightness > 255) {
-               dev_err(dev, "invalid value\n");
-               return -EINVAL;
-       }
+       ret = kstrtou8(buf, 10, &desired_brightness);
+       if (ret)
+               return ret;
 
        gpio_data->desired_brightness = desired_brightness;
 
        return n;
 }
-static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show,
-               gpio_trig_brightness_store);
+static DEVICE_ATTR_RW(desired_brightness);
 
 static struct attribute *gpio_trig_attrs[] = {
        &dev_attr_desired_brightness.attr,
@@ -89,10 +86,7 @@ static int gpio_trig_activate(struct led_classdev *led)
         * The generic property "trigger-sources" is followed,
         * and we hope that this is a GPIO.
         */
-       gpio_data->gpiod = fwnode_gpiod_get_index(dev->fwnode,
-                                                 "trigger-sources",
-                                                 0, GPIOD_IN,
-                                                 "led-trigger");
+       gpio_data->gpiod = gpiod_get_optional(dev, "trigger-sources", GPIOD_IN);
        if (IS_ERR(gpio_data->gpiod)) {
                ret = PTR_ERR(gpio_data->gpiod);
                kfree(gpio_data);
@@ -104,6 +98,8 @@ static int gpio_trig_activate(struct led_classdev *led)
                return -EINVAL;
        }
 
+       gpiod_set_consumer_name(gpio_data->gpiod, "led-trigger");
+
        gpio_data->led = led;
        led_set_trigger_data(led, gpio_data);
 
index d76214f..8e54758 100644 (file)
  * tx -  LED blinks on transmitted data
  * rx -  LED blinks on receive data
  *
+ * Note: If the user selects a mode that is not supported by hw, default
+ * behavior is to fall back to software control of the LED. However not every
+ * hw supports software control. LED callbacks brightness_set() and
+ * brightness_set_blocking() are NULL in this case. hw_control_is_supported()
+ * should use available means supported by hw to inform the user that selected
+ * mode isn't supported by hw. This could be switching off the LED or any
+ * hw blink mode. If software control fallback isn't possible, we return
+ * -EOPNOTSUPP to the user, but still store the selected mode. This is needed
+ * in case an intermediate unsupported mode is necessary to switch from one
+ * supported mode to another.
  */
 
 struct led_netdev_data {
@@ -99,6 +109,18 @@ static void set_baseline_state(struct led_netdev_data *trigger_data)
                    trigger_data->link_speed == SPEED_1000)
                        blink_on = true;
 
+               if (test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) &&
+                   trigger_data->link_speed == SPEED_2500)
+                       blink_on = true;
+
+               if (test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) &&
+                   trigger_data->link_speed == SPEED_5000)
+                       blink_on = true;
+
+               if (test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) &&
+                   trigger_data->link_speed == SPEED_10000)
+                       blink_on = true;
+
                if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) &&
                    trigger_data->duplex == DUPLEX_HALF)
                        blink_on = true;
@@ -289,6 +311,9 @@ static ssize_t netdev_led_attr_show(struct device *dev, char *buf,
        case TRIGGER_NETDEV_LINK_10:
        case TRIGGER_NETDEV_LINK_100:
        case TRIGGER_NETDEV_LINK_1000:
+       case TRIGGER_NETDEV_LINK_2500:
+       case TRIGGER_NETDEV_LINK_5000:
+       case TRIGGER_NETDEV_LINK_10000:
        case TRIGGER_NETDEV_HALF_DUPLEX:
        case TRIGGER_NETDEV_FULL_DUPLEX:
        case TRIGGER_NETDEV_TX:
@@ -306,6 +331,7 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
                                     size_t size, enum led_trigger_netdev_modes attr)
 {
        struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+       struct led_classdev *led_cdev = trigger_data->led_cdev;
        unsigned long state, mode = trigger_data->mode;
        int ret;
        int bit;
@@ -319,6 +345,9 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
        case TRIGGER_NETDEV_LINK_10:
        case TRIGGER_NETDEV_LINK_100:
        case TRIGGER_NETDEV_LINK_1000:
+       case TRIGGER_NETDEV_LINK_2500:
+       case TRIGGER_NETDEV_LINK_5000:
+       case TRIGGER_NETDEV_LINK_10000:
        case TRIGGER_NETDEV_HALF_DUPLEX:
        case TRIGGER_NETDEV_FULL_DUPLEX:
        case TRIGGER_NETDEV_TX:
@@ -337,7 +366,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
        if (test_bit(TRIGGER_NETDEV_LINK, &mode) &&
            (test_bit(TRIGGER_NETDEV_LINK_10, &mode) ||
             test_bit(TRIGGER_NETDEV_LINK_100, &mode) ||
-            test_bit(TRIGGER_NETDEV_LINK_1000, &mode)))
+            test_bit(TRIGGER_NETDEV_LINK_1000, &mode) ||
+            test_bit(TRIGGER_NETDEV_LINK_2500, &mode) ||
+            test_bit(TRIGGER_NETDEV_LINK_5000, &mode) ||
+            test_bit(TRIGGER_NETDEV_LINK_10000, &mode)))
                return -EINVAL;
 
        cancel_delayed_work_sync(&trigger_data->work);
@@ -345,6 +377,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
        trigger_data->mode = mode;
        trigger_data->hw_control = can_hw_control(trigger_data);
 
+       if (!led_cdev->brightness_set && !led_cdev->brightness_set_blocking &&
+           !trigger_data->hw_control)
+               return -EOPNOTSUPP;
+
        set_baseline_state(trigger_data);
 
        return size;
@@ -367,6 +403,9 @@ DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK);
 DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10);
 DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100);
 DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000);
+DEFINE_NETDEV_TRIGGER(link_2500, TRIGGER_NETDEV_LINK_2500);
+DEFINE_NETDEV_TRIGGER(link_5000, TRIGGER_NETDEV_LINK_5000);
+DEFINE_NETDEV_TRIGGER(link_10000, TRIGGER_NETDEV_LINK_10000);
 DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX);
 DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX);
 DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
@@ -425,6 +464,9 @@ static struct attribute *netdev_trig_attrs[] = {
        &dev_attr_link_10.attr,
        &dev_attr_link_100.attr,
        &dev_attr_link_1000.attr,
+       &dev_attr_link_2500.attr,
+       &dev_attr_link_5000.attr,
+       &dev_attr_link_10000.attr,
        &dev_attr_full_duplex.attr,
        &dev_attr_half_duplex.attr,
        &dev_attr_rx.attr,
@@ -522,6 +564,9 @@ static void netdev_trig_work(struct work_struct *work)
                         test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) ||
                         test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) ||
                         test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) ||
+                        test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) ||
+                        test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) ||
+                        test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) ||
                         test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) ||
                         test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode);
                interval = jiffies_to_msecs(
index 64abf2e..5a6b21b 100644 (file)
@@ -64,10 +64,13 @@ static long led_panic_blink(int state)
 
 static int __init ledtrig_panic_init(void)
 {
+       led_trigger_register_simple("panic", &trigger);
+       if (!trigger)
+               return -ENOMEM;
+
        atomic_notifier_chain_register(&panic_notifier_list,
                                       &led_trigger_panic_nb);
 
-       led_trigger_register_simple("panic", &trigger);
        panic_blink = led_panic_blink;
        return 0;
 }
index 8ae0d2d..8cf1485 100644 (file)
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
+#include <linux/completion.h>
 #include <linux/delay.h>
 #include <linux/leds.h>
 #include <linux/module.h>
 struct ledtrig_tty_data {
        struct led_classdev *led_cdev;
        struct delayed_work dwork;
-       struct mutex mutex;
+       struct completion sysfs;
        const char *ttyname;
        struct tty_struct *tty;
        int rx, tx;
+       bool mode_rx;
+       bool mode_tx;
+       bool mode_cts;
+       bool mode_dsr;
+       bool mode_dcd;
+       bool mode_rng;
 };
 
-static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data)
+/* Indicates which state the LED should now display */
+enum led_trigger_tty_state {
+       TTY_LED_BLINK,
+       TTY_LED_ENABLE,
+       TTY_LED_DISABLE,
+};
+
+enum led_trigger_tty_modes {
+       TRIGGER_TTY_RX = 0,
+       TRIGGER_TTY_TX,
+       TRIGGER_TTY_CTS,
+       TRIGGER_TTY_DSR,
+       TRIGGER_TTY_DCD,
+       TRIGGER_TTY_RNG,
+};
+
+static int ledtrig_tty_wait_for_completion(struct device *dev)
 {
-       schedule_delayed_work(&trigger_data->dwork, 0);
+       struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
+       int ret;
+
+       ret = wait_for_completion_timeout(&trigger_data->sysfs,
+                                         msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 20));
+       if (ret == 0)
+               return -ETIMEDOUT;
+
+       return ret;
 }
 
 static ssize_t ttyname_show(struct device *dev,
@@ -28,14 +59,16 @@ static ssize_t ttyname_show(struct device *dev,
 {
        struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
        ssize_t len = 0;
+       int completion;
 
-       mutex_lock(&trigger_data->mutex);
+       reinit_completion(&trigger_data->sysfs);
+       completion = ledtrig_tty_wait_for_completion(dev);
+       if (completion < 0)
+               return completion;
 
        if (trigger_data->ttyname)
                len = sprintf(buf, "%s\n", trigger_data->ttyname);
 
-       mutex_unlock(&trigger_data->mutex);
-
        return len;
 }
 
@@ -46,7 +79,7 @@ static ssize_t ttyname_store(struct device *dev,
        struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
        char *ttyname;
        ssize_t ret = size;
-       bool running;
+       int completion;
 
        if (size > 0 && buf[size - 1] == '\n')
                size -= 1;
@@ -59,9 +92,10 @@ static ssize_t ttyname_store(struct device *dev,
                ttyname = NULL;
        }
 
-       mutex_lock(&trigger_data->mutex);
-
-       running = trigger_data->ttyname != NULL;
+       reinit_completion(&trigger_data->sysfs);
+       completion = ledtrig_tty_wait_for_completion(dev);
+       if (completion < 0)
+               return completion;
 
        kfree(trigger_data->ttyname);
        tty_kref_put(trigger_data->tty);
@@ -69,29 +103,107 @@ static ssize_t ttyname_store(struct device *dev,
 
        trigger_data->ttyname = ttyname;
 
-       mutex_unlock(&trigger_data->mutex);
-
-       if (ttyname && !running)
-               ledtrig_tty_restart(trigger_data);
-
        return ret;
 }
 static DEVICE_ATTR_RW(ttyname);
 
+static ssize_t ledtrig_tty_attr_show(struct device *dev, char *buf,
+                                    enum led_trigger_tty_modes attr)
+{
+       struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
+       bool state;
+
+       switch (attr) {
+       case TRIGGER_TTY_RX:
+               state = trigger_data->mode_rx;
+               break;
+       case TRIGGER_TTY_TX:
+               state = trigger_data->mode_tx;
+               break;
+       case TRIGGER_TTY_CTS:
+               state = trigger_data->mode_cts;
+               break;
+       case TRIGGER_TTY_DSR:
+               state = trigger_data->mode_dsr;
+               break;
+       case TRIGGER_TTY_DCD:
+               state = trigger_data->mode_dcd;
+               break;
+       case TRIGGER_TTY_RNG:
+               state = trigger_data->mode_rng;
+               break;
+       }
+
+       return sysfs_emit(buf, "%u\n", state);
+}
+
+static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf,
+                                     size_t size, enum led_trigger_tty_modes attr)
+{
+       struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
+       bool state;
+       int ret;
+
+       ret = kstrtobool(buf, &state);
+       if (ret)
+               return ret;
+
+       switch (attr) {
+       case TRIGGER_TTY_RX:
+               trigger_data->mode_rx = state;
+               break;
+       case TRIGGER_TTY_TX:
+               trigger_data->mode_tx = state;
+               break;
+       case TRIGGER_TTY_CTS:
+               trigger_data->mode_cts = state;
+               break;
+       case TRIGGER_TTY_DSR:
+               trigger_data->mode_dsr = state;
+               break;
+       case TRIGGER_TTY_DCD:
+               trigger_data->mode_dcd = state;
+               break;
+       case TRIGGER_TTY_RNG:
+               trigger_data->mode_rng = state;
+               break;
+       }
+
+       return size;
+}
+
+#define DEFINE_TTY_TRIGGER(trigger_name, trigger) \
+       static ssize_t trigger_name##_show(struct device *dev, \
+               struct device_attribute *attr, char *buf) \
+       { \
+               return ledtrig_tty_attr_show(dev, buf, trigger); \
+       } \
+       static ssize_t trigger_name##_store(struct device *dev, \
+               struct device_attribute *attr, const char *buf, size_t size) \
+       { \
+               return ledtrig_tty_attr_store(dev, buf, size, trigger); \
+       } \
+       static DEVICE_ATTR_RW(trigger_name)
+
+DEFINE_TTY_TRIGGER(rx, TRIGGER_TTY_RX);
+DEFINE_TTY_TRIGGER(tx, TRIGGER_TTY_TX);
+DEFINE_TTY_TRIGGER(cts, TRIGGER_TTY_CTS);
+DEFINE_TTY_TRIGGER(dsr, TRIGGER_TTY_DSR);
+DEFINE_TTY_TRIGGER(dcd, TRIGGER_TTY_DCD);
+DEFINE_TTY_TRIGGER(rng, TRIGGER_TTY_RNG);
+
 static void ledtrig_tty_work(struct work_struct *work)
 {
        struct ledtrig_tty_data *trigger_data =
                container_of(work, struct ledtrig_tty_data, dwork.work);
-       struct serial_icounter_struct icount;
+       enum led_trigger_tty_state state = TTY_LED_DISABLE;
+       unsigned long interval = LEDTRIG_TTY_INTERVAL;
+       bool invert = false;
+       int status;
        int ret;
 
-       mutex_lock(&trigger_data->mutex);
-
-       if (!trigger_data->ttyname) {
-               /* exit without rescheduling */
-               mutex_unlock(&trigger_data->mutex);
-               return;
-       }
+       if (!trigger_data->ttyname)
+               goto out;
 
        /* try to get the tty corresponding to $ttyname */
        if (!trigger_data->tty) {
@@ -115,32 +227,83 @@ static void ledtrig_tty_work(struct work_struct *work)
                trigger_data->tty = tty;
        }
 
-       ret = tty_get_icount(trigger_data->tty, &icount);
-       if (ret) {
-               dev_info(trigger_data->tty->dev, "Failed to get icount, stopped polling\n");
-               mutex_unlock(&trigger_data->mutex);
-               return;
+       status = tty_get_tiocm(trigger_data->tty);
+       if (status > 0) {
+               if (trigger_data->mode_cts) {
+                       if (status & TIOCM_CTS)
+                               state = TTY_LED_ENABLE;
+               }
+
+               if (trigger_data->mode_dsr) {
+                       if (status & TIOCM_DSR)
+                               state = TTY_LED_ENABLE;
+               }
+
+               if (trigger_data->mode_dcd) {
+                       if (status & TIOCM_CAR)
+                               state = TTY_LED_ENABLE;
+               }
+
+               if (trigger_data->mode_rng) {
+                       if (status & TIOCM_RNG)
+                               state = TTY_LED_ENABLE;
+               }
        }
 
-       if (icount.rx != trigger_data->rx ||
-           icount.tx != trigger_data->tx) {
-               unsigned long interval = LEDTRIG_TTY_INTERVAL;
+       /*
+        * The evaluation of rx/tx must be done after the evaluation
+        * of TIOCM_*, because rx/tx has priority.
+        */
+       if (trigger_data->mode_rx || trigger_data->mode_tx) {
+               struct serial_icounter_struct icount;
 
-               led_blink_set_oneshot(trigger_data->led_cdev, &interval,
-                                     &interval, 0);
+               ret = tty_get_icount(trigger_data->tty, &icount);
+               if (ret)
+                       goto out;
 
-               trigger_data->rx = icount.rx;
-               trigger_data->tx = icount.tx;
+               if (trigger_data->mode_tx && (icount.tx != trigger_data->tx)) {
+                       trigger_data->tx = icount.tx;
+                       invert = state == TTY_LED_ENABLE;
+                       state = TTY_LED_BLINK;
+               }
+
+               if (trigger_data->mode_rx && (icount.rx != trigger_data->rx)) {
+                       trigger_data->rx = icount.rx;
+                       invert = state == TTY_LED_ENABLE;
+                       state = TTY_LED_BLINK;
+               }
        }
 
 out:
-       mutex_unlock(&trigger_data->mutex);
+       switch (state) {
+       case TTY_LED_BLINK:
+               led_blink_set_oneshot(trigger_data->led_cdev, &interval,
+                               &interval, invert);
+               break;
+       case TTY_LED_ENABLE:
+               led_set_brightness(trigger_data->led_cdev,
+                               trigger_data->led_cdev->blink_brightness);
+               break;
+       case TTY_LED_DISABLE:
+               fallthrough;
+       default:
+               led_set_brightness(trigger_data->led_cdev, LED_OFF);
+               break;
+       }
+
+       complete_all(&trigger_data->sysfs);
        schedule_delayed_work(&trigger_data->dwork,
                              msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2));
 }
 
 static struct attribute *ledtrig_tty_attrs[] = {
        &dev_attr_ttyname.attr,
+       &dev_attr_rx.attr,
+       &dev_attr_tx.attr,
+       &dev_attr_cts.attr,
+       &dev_attr_dsr.attr,
+       &dev_attr_dcd.attr,
+       &dev_attr_rng.attr,
        NULL
 };
 ATTRIBUTE_GROUPS(ledtrig_tty);
@@ -153,11 +316,17 @@ static int ledtrig_tty_activate(struct led_classdev *led_cdev)
        if (!trigger_data)
                return -ENOMEM;
 
+       /* Enable default rx/tx mode */
+       trigger_data->mode_rx = true;
+       trigger_data->mode_tx = true;
+
        led_set_trigger_data(led_cdev, trigger_data);
 
        INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work);
        trigger_data->led_cdev = led_cdev;
-       mutex_init(&trigger_data->mutex);
+       init_completion(&trigger_data->sysfs);
+
+       schedule_delayed_work(&trigger_data->dwork, 0);
 
        return 0;
 }
@@ -168,6 +337,10 @@ static void ledtrig_tty_deactivate(struct led_classdev *led_cdev)
 
        cancel_delayed_work_sync(&trigger_data->dwork);
 
+       kfree(trigger_data->ttyname);
+       tty_kref_put(trigger_data->tty);
+       trigger_data->tty = NULL;
+
        kfree(trigger_data);
 }
 
index 06414e4..e2e9340 100644 (file)
@@ -2498,6 +2498,24 @@ static int send_break(struct tty_struct *tty, unsigned int duration)
        return retval;
 }
 
+/**
+ * tty_get_tiocm - get tiocm status register
+ * @tty: tty device
+ *
+ * Obtain the modem status bits from the tty driver if the feature
+ * is supported.
+ */
+int tty_get_tiocm(struct tty_struct *tty)
+{
+       int retval = -ENOTTY;
+
+       if (tty->ops->tiocmget)
+               retval = tty->ops->tiocmget(tty);
+
+       return retval;
+}
+EXPORT_SYMBOL_GPL(tty_get_tiocm);
+
 /**
  * tty_tiocmget - get modem status
  * @tty: tty device
@@ -2510,14 +2528,12 @@ static int send_break(struct tty_struct *tty, unsigned int duration)
  */
 static int tty_tiocmget(struct tty_struct *tty, int __user *p)
 {
-       int retval = -ENOTTY;
+       int retval;
 
-       if (tty->ops->tiocmget) {
-               retval = tty->ops->tiocmget(tty);
+       retval = tty_get_tiocm(tty);
+       if (retval >= 0)
+               retval = put_user(retval, p);
 
-               if (retval >= 0)
-                       retval = put_user(retval, p);
-       }
        return retval;
 }
 
index aa16dc2..4754b02 100644 (file)
@@ -527,23 +527,6 @@ static inline void *led_get_trigger_data(struct led_classdev *led_cdev)
        return led_cdev->trigger_data;
 }
 
-/**
- * led_trigger_rename_static - rename a trigger
- * @name: the new trigger name
- * @trig: the LED trigger to rename
- *
- * Change a LED trigger name by copying the string passed in
- * name into current trigger name, which MUST be large
- * enough for the new string.
- *
- * Note that name must NOT point to the same string used
- * during LED registration, as that could lead to races.
- *
- * This is meant to be used on triggers with statically
- * allocated name.
- */
-void led_trigger_rename_static(const char *name, struct led_trigger *trig);
-
 #define module_led_trigger(__led_trigger) \
        module_driver(__led_trigger, led_trigger_register, \
                      led_trigger_unregister)
@@ -588,6 +571,9 @@ enum led_trigger_netdev_modes {
        TRIGGER_NETDEV_LINK_10,
        TRIGGER_NETDEV_LINK_100,
        TRIGGER_NETDEV_LINK_1000,
+       TRIGGER_NETDEV_LINK_2500,
+       TRIGGER_NETDEV_LINK_5000,
+       TRIGGER_NETDEV_LINK_10000,
        TRIGGER_NETDEV_HALF_DUPLEX,
        TRIGGER_NETDEV_FULL_DUPLEX,
        TRIGGER_NETDEV_TX,
index 4b6340a..d219a11 100644 (file)
@@ -419,6 +419,7 @@ bool tty_unthrottle_safe(struct tty_struct *tty);
 int tty_do_resize(struct tty_struct *tty, struct winsize *ws);
 int tty_get_icount(struct tty_struct *tty,
                struct serial_icounter_struct *icount);
+int tty_get_tiocm(struct tty_struct *tty);
 int is_current_pgrp_orphaned(void);
 void tty_hangup(struct tty_struct *tty);
 void tty_vhangup(struct tty_struct *tty);