leds: qcom-lpg: Require pattern to follow documentation
authorBjorn Andersson <bjorn.andersson@linaro.org>
Mon, 23 May 2022 23:37:19 +0000 (16:37 -0700)
committerPavel Machek <pavel@ucw.cz>
Tue, 24 May 2022 20:08:10 +0000 (22:08 +0200)
The leds-trigger-pattern documentation describes how the brightness of
the LED should transition linearly from one brightness value to the
next, over the given delta_t.

But the pattern engine in the Qualcomm LPG hardware only supports
holding the brightness for each entry for the period.
This subset of patterns can be represented in the leds-trigger-pattern
by injecting zero-time transitions after each entry in the pattern,
resulting in a pattern that pattern that can be rendered by the LPG.

Rework LPG pattern interface to require these zero-time transitions, to
make it comply with this subset of patterns and reject the patterns it
can't render.

Fixes: 24e2d05d1b68 ("leds: Add driver for Qualcomm LPG")
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
Signed-off-by: Pavel Machek <pavel@ucw.cz>
Documentation/leds/leds-qcom-lpg.rst
drivers/leds/rgb/leds-qcom-lpg.c

index f12416f02dd8a0ce7a025303bf3c6e9502b4cecd..de7ceead933715b60a6a2fe70b04a5e365b122d6 100644 (file)
@@ -35,11 +35,13 @@ Specify a hardware pattern for a Qualcomm LPG LED.
 The pattern is a series of brightness and hold-time pairs, with the hold-time
 expressed in milliseconds. The hold time is a property of the pattern and must
 therefor be identical for each element in the pattern (except for the pauses
-described below).
+described below). As the LPG hardware is not able to perform the linear
+transitions expected by the leds-trigger-pattern format, each entry in the
+pattern must be followed a zero-length entry of the same brightness.
 
 Simple pattern::
 
-    "255 500 0 500"
+    "255 500 255 0 0 500 0 0"
 
         ^
         |
@@ -54,7 +56,7 @@ in the pattern, the so called "low pause" and "high pause".
 
 Low-pause pattern::
 
-    "255 1000 0 500 255 500 0 500"
+    "255 1000 255 0 0 500 0 0 255 500 255 0 0 500 0 0"
 
         ^
         |
index cfa3362b2457f816e2384f249999bf73e38ec11c..02f51cc618376a49a63cf2771902aed8cda5c26f 100644 (file)
@@ -704,11 +704,12 @@ static int lpg_blink_mc_set(struct led_classdev *cdev,
        return ret;
 }
 
-static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *pattern,
+static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
                           u32 len, int repeat)
 {
        struct lpg_channel *chan;
        struct lpg *lpg = led->lpg;
+       struct led_pattern *pattern;
        unsigned int brightness_a;
        unsigned int brightness_b;
        unsigned int actual_len;
@@ -719,18 +720,48 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *pattern,
        unsigned int hi_idx;
        unsigned int i;
        bool ping_pong = true;
-       int ret;
+       int ret = -EINVAL;
 
        /* Hardware only support oneshot or indefinite loops */
        if (repeat != -1 && repeat != 1)
                return -EINVAL;
 
+       /*
+        * The standardized leds-trigger-pattern format defines that the
+        * brightness of the LED follows a linear transition from one entry
+        * in the pattern to the next, over the given delta_t time. It
+        * describes that the way to perform instant transitions a zero-length
+        * entry should be added following a pattern entry.
+        *
+        * The LPG hardware is only able to perform the latter (no linear
+        * transitions), so require each entry in the pattern to be followed by
+        * a zero-length transition.
+        */
+       if (len % 2)
+               return -EINVAL;
+
+       pattern = kcalloc(len / 2, sizeof(*pattern), GFP_KERNEL);
+       if (!pattern)
+               return -ENOMEM;
+
+       for (i = 0; i < len; i += 2) {
+               if (led_pattern[i].brightness != led_pattern[i + 1].brightness)
+                       goto out_free_pattern;
+               if (led_pattern[i + 1].delta_t != 0)
+                       goto out_free_pattern;
+
+               pattern[i / 2].brightness = led_pattern[i].brightness;
+               pattern[i / 2].delta_t = led_pattern[i].delta_t;
+       }
+
+       len /= 2;
+
        /*
         * Specifying a pattern of length 1 causes the hardware to iterate
         * through the entire LUT, so prohibit this.
         */
        if (len < 2)
-               return -EINVAL;
+               goto out_free_pattern;
 
        /*
         * The LPG plays patterns with at a fixed pace, a "low pause" can be
@@ -781,13 +812,13 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *pattern,
                         * specify hi pause. Reject other variations.
                         */
                        if (i != actual_len - 1)
-                               return -EINVAL;
+                               goto out_free_pattern;
                }
        }
 
        /* LPG_RAMP_DURATION_REG is a 9bit */
        if (delta_t >= BIT(9))
-               return -EINVAL;
+               goto out_free_pattern;
 
        /* Find "low pause" and "high pause" in the pattern */
        lo_pause = pattern[0].delta_t;
@@ -814,6 +845,8 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *pattern,
 
 out_unlock:
        mutex_unlock(&lpg->lock);
+out_free_pattern:
+       kfree(pattern);
 
        return ret;
 }