2018-01-10 22:30:57

by Enric Balletbo i Serra

[permalink] [raw]
Subject: [PATCH 0/4] backlight: pwm_bl: support linear interpolation and brightness to human eye

Dear all,

This series is a first patchset based on the discussion of two previous RFCs
[1] and [2].

The first and second patch what tries to solve is the problem of granularity
for high resolution PWMs. The idea is simple interpolate between 2
brightness values so we can have a high PWM duty cycle (a 16 bits PWM is up
to 65535 possible steps) without having to list out every possible value in
the dts. I think that this patch is required to not break backward
compability, to be more flexible and also extend the functionality to be
able to use high resolution PWM with enough steps to have a good UI
experience in userspace.

The thirth and fourth patch is a bit more ambicious, the idea is let decide
the driver the brightness-levels required in function of the PWM resolution.
To do this create a brightness-levels table filled with the CIE 1931
algorithm values to convert brightness to PWM duty cycle.

More detailed info is available in the commit message of every patch.

Both functionalities were tested on a Samsung Chromebook Plus (that has a
16 bits PWM) and a SL50 device (with a 8 bits PWM)

Waiting for your feedback.

[1] http://www.spinics.net/lists/devicetree/msg193262.html
[2] https://lkml.org/lkml/2017/11/16/301

Best regards,

Enric Balletbo i Serra (4):
backlight: pwm_bl: linear interpolation between brightness-levels
dt-bindings: pwm-backlight: add a num-interpolation-steps property.
backlight: pwm_bl: compute brightness of LED linearly to human eye.
dt-bindings: pwm-backlight: move brightness-levels to optional.

.../bindings/leds/backlight/pwm-backlight.txt | 36 +++-
drivers/video/backlight/pwm_bl.c | 237 +++++++++++++++++++--
2 files changed, 253 insertions(+), 20 deletions(-)

--
2.15.1


2018-01-10 22:31:03

by Enric Balletbo i Serra

[permalink] [raw]
Subject: [PATCH 3/4] backlight: pwm_bl: compute brightness of LED linearly to human eye.

When you want to change the brightness using a PWM signal, one thing you
need to consider is how human perceive the brightness. Human perceive
the brightness change non-linearly, we have better sensitivity at low
luminance than high luminance, so to achieve perceived linear dimming,
the brightness must be matches to the way our eyes behave. The CIE 1931
lightness formula is what actually describes how we perceive light.

This patch computes a default table with the brightness levels filled
with the numbers provided by the CIE 1931 algorithm, the number of the
brightness levels is calculated based on the PWM resolution.

The calculation of the table using the CIE 1931 algorithm is enabled by
default when you do not define the 'brightness-levels' propriety in your
device tree.

Signed-off-by: Enric Balletbo i Serra <[email protected]>
---
Changes since RFCv2:
- Pre-compute the table at boot using the cie 1931 algorithm, this
introduced again the fixed point calculations that needs to be
reviewed.
- Calculate the number of needed steps based on the number of bits of
the PWM.
- Improve some code documentation.

Changes since RFCv1:
- Get rid of fixed point calculations and use a table instead.

drivers/video/backlight/pwm_bl.c | 149 +++++++++++++++++++++++++++++++++++----
1 file changed, 136 insertions(+), 13 deletions(-)

diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c
index eabe0a4462af..9398516db0ce 100644
--- a/drivers/video/backlight/pwm_bl.c
+++ b/drivers/video/backlight/pwm_bl.c
@@ -143,6 +143,107 @@ static const struct backlight_ops pwm_backlight_ops = {
};

#ifdef CONFIG_OF
+#define PWM_LUMINANCE_SCALE 10000 /* luminance scale */
+
+/* An integer based power function */
+static u64 int_pow(u64 base, int exp)
+{
+ u64 result = 1;
+
+ while (exp) {
+ if (exp & 1)
+ result *= base;
+ exp >>= 1;
+ base *= base;
+ }
+
+ return result;
+}
+
+/*
+ * CIE lightness to PWM conversion.
+ *
+ * The CIE 1931 lightness formula is what actually describes how we perceive
+ * light:
+ * Y = (L* / 902.3) if L* ≤ 0.08856
+ * Y = ((L* + 16) / 116)^3 if L* > 0.08856
+ *
+ * Where Y is the luminance, the amount of light coming out of the screen, and
+ * is a number between 0.0 and 1.0; and L* is the lightness, how bright a human
+ * perceives the screen to be, and is a number between 0 and 100.
+ *
+ * The following function does the fixed point maths needed to implement the
+ * above formula.
+ */
+static u64 cie1931(unsigned int lightness, unsigned int scale)
+{
+ u64 retval;
+
+ lightness *= 100;
+ if (lightness <= (8 * scale)) {
+ retval = DIV_ROUND_CLOSEST_ULL(lightness * 10, 9023);
+ } else {
+ retval = int_pow((lightness + (16 * scale)) / 116, 3);
+ retval = DIV_ROUND_CLOSEST_ULL(retval, (scale * scale));
+ }
+
+ return retval;
+}
+
+/*
+ * Create a default correction table for PWM values to create linear brightness
+ * for LED based backlights using the CIE1931 algorithm.
+ */
+static
+int pwm_backlight_brightness_default(struct device *dev,
+ struct platform_pwm_backlight_data *data,
+ unsigned int period)
+{
+ unsigned int counter = 0;
+ unsigned int i, n;
+ u64 retval;
+
+ /*
+ * Count the number of bits needed to represent the period number. The
+ * number of bits is used to calculate the number of levels used for the
+ * brightness-levels table, the purpose of this calculation is have a
+ * pre-computed table with enough levels to get linear brightness
+ * perception. The period is divided by the number of bits so for a
+ * 8-bit PWM we have 255 / 8 = 32 brightness levels or for a 16-bit PWM
+ * we have 65535 / 16 = 4096 brightness levels.
+ *
+ * Note that this method is based on empirical testing on different
+ * devices with PWM of 8 and 16 bits of resolution.
+ */
+ n = period;
+ while (n) {
+ counter += n % 2;
+ n >>= 1;
+ }
+
+ data->max_brightness = DIV_ROUND_UP(period, counter);
+ data->levels = devm_kcalloc(dev, data->max_brightness,
+ sizeof(*data->levels), GFP_KERNEL);
+ if (!data->levels)
+ return -ENOMEM;
+
+ /* Fill the table using the cie1931 algorithm */
+ for (i = 0; i < data->max_brightness; i++) {
+ retval = cie1931((i * PWM_LUMINANCE_SCALE) /
+ data->max_brightness, PWM_LUMINANCE_SCALE) *
+ period;
+ retval = DIV_ROUND_CLOSEST_ULL(retval, PWM_LUMINANCE_SCALE);
+ if (retval > UINT_MAX)
+ return -EINVAL;
+ data->levels[i] = (unsigned int)retval;
+ }
+
+ data->dft_brightness = data->max_brightness / 2;
+ data->max_brightness--;
+
+ return 0;
+}
+
static int pwm_backlight_parse_dt(struct device *dev,
struct platform_pwm_backlight_data *data)
{
@@ -161,10 +262,13 @@ static int pwm_backlight_parse_dt(struct device *dev,

memset(data, 0, sizeof(*data));

- /* determine the number of brightness levels */
+ /*
+ * Determine the number of brightness levels, if this property is not
+ * set a default table of brightness levels will be used.
+ */
prop = of_find_property(node, "brightness-levels", &length);
if (!prop)
- return -EINVAL;
+ return 0;

data->max_brightness = length / sizeof(u32);

@@ -299,6 +403,14 @@ static int pwm_backlight_parse_dt(struct device *dev,
{
return -ENODEV;
}
+
+static
+int pwm_backlight_brightness_default(struct device *dev,
+ struct platform_pwm_backlight_data *data,
+ unsigned int period)
+{
+ return -ENODEV;
+}
#endif

static int pwm_backlight_initial_power_state(const struct pwm_bl_data *pb)
@@ -339,7 +451,9 @@ static int pwm_backlight_probe(struct platform_device *pdev)
struct backlight_device *bl;
struct device_node *node = pdev->dev.of_node;
struct pwm_bl_data *pb;
+ struct pwm_state state;
struct pwm_args pargs;
+ unsigned int i;
int ret;

if (!data) {
@@ -364,17 +478,6 @@ static int pwm_backlight_probe(struct platform_device *pdev)
goto err_alloc;
}

- if (data->levels) {
- unsigned int i;
-
- for (i = 0; i <= data->max_brightness; i++)
- if (data->levels[i] > pb->scale)
- pb->scale = data->levels[i];
-
- pb->levels = data->levels;
- } else
- pb->scale = data->max_brightness;
-
pb->notify = data->notify;
pb->notify_after = data->notify_after;
pb->check_fb = data->check_fb;
@@ -441,6 +544,26 @@ static int pwm_backlight_probe(struct platform_device *pdev)

dev_dbg(&pdev->dev, "got pwm for backlight\n");

+ if (!data->levels) {
+ /* Get the PWM period (in nanoseconds) */
+ pwm_get_state(pb->pwm, &state);
+
+ ret = pwm_backlight_brightness_default(&pdev->dev, data,
+ state.period);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "failed to setup default brightness table\n");
+ goto err_alloc;
+ }
+ }
+
+ for (i = 0; i <= data->max_brightness; i++)
+ if (data->levels[i] > pb->scale)
+ pb->scale = data->levels[i];
+
+ pb->levels = data->levels;
+ }
+
/*
* FIXME: pwm_apply_args() should be removed when switching to
* the atomic PWM API.
--
2.15.1

2018-01-10 22:31:01

by Enric Balletbo i Serra

[permalink] [raw]
Subject: [PATCH 4/4] dt-bindings: pwm-backlight: move brightness-levels to optional.

The patch 'backlight: pwm_bl: compute brightness of LED linearly to
human eye' introduced a default brightness-levels table that is used
when brightness-levels is not availablel in the dts so move move
brightness-levels and default-brightness-level to be optional.

Signed-off-by: Enric Balletbo i Serra <[email protected]>
---
.../devicetree/bindings/leds/backlight/pwm-backlight.txt | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt b/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
index 605432c910c5..3d5a148bd66d 100644
--- a/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
+++ b/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
@@ -3,13 +3,6 @@ pwm-backlight bindings
Required properties:
- compatible: "pwm-backlight"
- pwms: OF device-tree PWM specification (see PWM binding[0])
- - brightness-levels: Array of distinct brightness levels. Typically these
- are in the range from 0 to 255, but any range starting at 0 will do.
- The actual brightness level (PWM duty cycle) will be interpolated
- from these values. 0 means a 0% duty cycle (darkest/off), while the
- last value in the array represents a 100% duty cycle (brightest).
- - default-brightness-level: the default brightness level (index into the
- array defined by the "brightness-levels" property)
- power-supply: regulator for supply voltage

Optional properties:
@@ -21,6 +14,14 @@ Optional properties:
and enabling the backlight using GPIO.
- pwm-off-delay-ms: Delay in ms between disabling the backlight using GPIO
and setting PWM value to 0.
+ - brightness-levels: Array of distinct brightness levels. Typically these
+ are in the range from 0 to 255, but any range starting at
+ 0 will do. The actual brightness level (PWM duty cycle)
+ will be interpolated from these values. 0 means a 0% duty
+ cycle (darkest/off), while the last value in the array
+ represents a 100% duty cycle (brightest).
+ - default-brightness-level: The default brightness level (index into the
+ array defined by the "brightness-levels" property).
- num-interpolated-steps: Number of interpolated steps between each value
of brightness-levels table. This way a high
resolution pwm duty cycle can be used without
--
2.15.1

2018-01-10 22:31:47

by Enric Balletbo i Serra

[permalink] [raw]
Subject: [PATCH 2/4] dt-bindings: pwm-backlight: add a num-interpolation-steps property.

The num-interpolated-steps property specifies the number of
interpolated steps between each value of brightness-level table. This is
useful for high resolution PWMs to not have to list out every possible
value in the brightness-level array.

Signed-off-by: Enric Balletbo i Serra <[email protected]>
---
.../bindings/leds/backlight/pwm-backlight.txt | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)

diff --git a/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt b/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
index 310810906613..605432c910c5 100644
--- a/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
+++ b/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
@@ -21,6 +21,11 @@ Optional properties:
and enabling the backlight using GPIO.
- pwm-off-delay-ms: Delay in ms between disabling the backlight using GPIO
and setting PWM value to 0.
+ - num-interpolated-steps: Number of interpolated steps between each value
+ of brightness-levels table. This way a high
+ resolution pwm duty cycle can be used without
+ having to list out every possible value in the
+ brightness-level array.

[0]: Documentation/devicetree/bindings/pwm/pwm.txt
[1]: Documentation/devicetree/bindings/gpio/gpio.txt
@@ -39,3 +44,19 @@ Example:
post-pwm-on-delay-ms = <10>;
pwm-off-delay-ms = <10>;
};
+
+Example using num-interpolation-steps:
+
+ backlight {
+ compatible = "pwm-backlight";
+ pwms = <&pwm 0 5000000>;
+
+ brightness-levels = <0 65535>;
+ num-interpolated-steps = <4096>;
+ default-brightness-level = <6>;
+
+ power-supply = <&vdd_bl_reg>;
+ enable-gpios = <&gpio 58 0>;
+ post-pwm-on-delay-ms = <10>;
+ pwm-off-delay-ms = <10>;
+ };
--
2.15.1

2018-01-10 22:32:09

by Enric Balletbo i Serra

[permalink] [raw]
Subject: [PATCH 1/4] backlight: pwm_bl: linear interpolation between brightness-levels

Setting num-interpolated-steps in the dts will allow you to have linear
interpolation between values of brightness-levels. This way a high
resolution pwm duty cycle can be used without having to list out every
possible value in the dts. This system also allows for gamma corrected
values.

The most simple example is interpolate between two brightness values a
number of steps, this can be done setting the following in the dts:

brightness-levels = <0 65535>;
num-interpolated-steps = <1024>;
default-brightness-level = <512>;

This will create a brightness-level table with the following values:

<0 63 126 189 252 315 378 441 ... 64260 64323 64386 64449 65535>

Another use case can be describe a gamma corrected curve, as we have
better sensitivity at low luminance than high luminance we probably
want have smaller steps for low brightness levels values and bigger
steps for high brightness levels values. This can be achieved with
the following in the dts:

brightness-levels = <0 4096 65535>;
num-interpolated-steps = <1024>;
default-brightness-level = <512>;

This will create a brightness-levels table with the following values:

<0 4 8 12 16 20 ... 4096 4156 4216 4276 ... 65535>

Signed-off-by: Enric Balletbo i Serra <[email protected]>
---
Changes since RFCv2:
- Replace use-linear-interpolation for num-interpolated-steps so it's
more flexible to specify any kind of curve to the user.
- Reworked the code to interpolate between two points.
- Pre-compute the brightness-levels table at boot.
Changes since RFCv1:
- Add linear interpolation for high resolution PWM.

drivers/video/backlight/pwm_bl.c | 88 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 88 insertions(+)

diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c
index 8e3f1245f5c5..eabe0a4462af 100644
--- a/drivers/video/backlight/pwm_bl.c
+++ b/drivers/video/backlight/pwm_bl.c
@@ -147,7 +147,11 @@ static int pwm_backlight_parse_dt(struct device *dev,
struct platform_pwm_backlight_data *data)
{
struct device_node *node = dev->of_node;
+ unsigned int num_levels = 0;
+ unsigned int levels_count;
+ unsigned int num_steps;
struct property *prop;
+ unsigned int *table;
int length;
u32 value;
int ret;
@@ -167,6 +171,7 @@ static int pwm_backlight_parse_dt(struct device *dev,
/* read brightness levels from DT property */
if (data->max_brightness > 0) {
size_t size = sizeof(*data->levels) * data->max_brightness;
+ unsigned int i, j, n = 0;

data->levels = devm_kzalloc(dev, size, GFP_KERNEL);
if (!data->levels)
@@ -184,6 +189,89 @@ static int pwm_backlight_parse_dt(struct device *dev,
return ret;

data->dft_brightness = value;
+
+ /*
+ * This property is optional, if is set enables linear
+ * interpolation between each of the values of brightness levels
+ * and creates a new pre-computed table.
+ */
+ ret = of_property_read_u32(node, "num-interpolated-steps",
+ &num_steps);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Make sure that there is at least two entries in the
+ * brightness-levels table, otherwise we can't interpolate
+ * between two points.
+ */
+ if (num_steps) {
+ if (data->max_brightness < 2) {
+ dev_err(dev, "can't interpolate\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Recalculate the number of brightness levels, now
+ * taking in consideration the number of interpolated
+ * steps between two levels.
+ */
+ for (i = 0; i < data->max_brightness - 1; i++) {
+ if ((data->levels[i + 1] - data->levels[i]) /
+ num_steps)
+ num_levels += num_steps;
+ else
+ num_levels++;
+ }
+ num_levels++;
+ dev_dbg(dev, "new number of brightness levels: %d\n",
+ num_levels);
+
+ /*
+ * Create a new table of brightness levels with all the
+ * interpolated steps.
+ */
+ table = kcalloc(num_levels, sizeof(*table), GFP_KERNEL);
+ if (!table)
+ return -ENOMEM;
+
+ /* Fill the interpolated table. */
+ levels_count = 0;
+ for (i = 0; i < data->max_brightness - 1; i++) {
+ value = data->levels[i];
+ n = (data->levels[i + 1] - value) / num_steps;
+ if (n > 0) {
+ for (j = 0; j < num_steps; j++) {
+ table[levels_count] = value;
+ value += n;
+ levels_count++;
+ }
+ } else {
+ table[levels_count] = data->levels[i];
+ levels_count++;
+ }
+ }
+ table[levels_count] = data->levels[i];
+
+ /*
+ * As we use interpolation lets remove current
+ * brightness levels table for the new interpolated
+ * table.
+ */
+ devm_kfree(dev, data->levels);
+ data->levels = devm_kcalloc(dev, num_levels,
+ sizeof(*data->levels),
+ GFP_KERNEL);
+ memcpy(data->levels, table,
+ num_levels * sizeof(*table));
+ kfree(table);
+ /*
+ * Reassign max_brightness value to the new total number
+ * of brightness levels.
+ */
+ data->max_brightness = num_levels;
+ }
+
data->max_brightness--;
}

--
2.15.1

2018-01-12 10:15:00

by Enric Balletbo Serra

[permalink] [raw]
Subject: Re: [PATCH 3/4] backlight: pwm_bl: compute brightness of LED linearly to human eye.

2018-01-10 23:30 GMT+01:00 Enric Balletbo i Serra
<[email protected]>:
> When you want to change the brightness using a PWM signal, one thing you
> need to consider is how human perceive the brightness. Human perceive
> the brightness change non-linearly, we have better sensitivity at low
> luminance than high luminance, so to achieve perceived linear dimming,
> the brightness must be matches to the way our eyes behave. The CIE 1931
> lightness formula is what actually describes how we perceive light.
>
> This patch computes a default table with the brightness levels filled
> with the numbers provided by the CIE 1931 algorithm, the number of the
> brightness levels is calculated based on the PWM resolution.
>
> The calculation of the table using the CIE 1931 algorithm is enabled by
> default when you do not define the 'brightness-levels' propriety in your
> device tree.
>
> Signed-off-by: Enric Balletbo i Serra <[email protected]>
> ---
> Changes since RFCv2:
> - Pre-compute the table at boot using the cie 1931 algorithm, this
> introduced again the fixed point calculations that needs to be
> reviewed.
> - Calculate the number of needed steps based on the number of bits of
> the PWM.
> - Improve some code documentation.
>
> Changes since RFCv1:
> - Get rid of fixed point calculations and use a table instead.
>
> drivers/video/backlight/pwm_bl.c | 149 +++++++++++++++++++++++++++++++++++----
> 1 file changed, 136 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c
> index eabe0a4462af..9398516db0ce 100644
> --- a/drivers/video/backlight/pwm_bl.c
> +++ b/drivers/video/backlight/pwm_bl.c
> @@ -143,6 +143,107 @@ static const struct backlight_ops pwm_backlight_ops = {
> };
>
> #ifdef CONFIG_OF
> +#define PWM_LUMINANCE_SCALE 10000 /* luminance scale */
> +
> +/* An integer based power function */
> +static u64 int_pow(u64 base, int exp)
> +{
> + u64 result = 1;
> +
> + while (exp) {
> + if (exp & 1)
> + result *= base;
> + exp >>= 1;
> + base *= base;
> + }
> +
> + return result;
> +}
> +
> +/*
> + * CIE lightness to PWM conversion.
> + *
> + * The CIE 1931 lightness formula is what actually describes how we perceive
> + * light:
> + * Y = (L* / 902.3) if L* ≤ 0.08856
> + * Y = ((L* + 16) / 116)^3 if L* > 0.08856
> + *
> + * Where Y is the luminance, the amount of light coming out of the screen, and
> + * is a number between 0.0 and 1.0; and L* is the lightness, how bright a human
> + * perceives the screen to be, and is a number between 0 and 100.
> + *
> + * The following function does the fixed point maths needed to implement the
> + * above formula.
> + */
> +static u64 cie1931(unsigned int lightness, unsigned int scale)
> +{
> + u64 retval;
> +
> + lightness *= 100;
> + if (lightness <= (8 * scale)) {
> + retval = DIV_ROUND_CLOSEST_ULL(lightness * 10, 9023);
> + } else {
> + retval = int_pow((lightness + (16 * scale)) / 116, 3);
> + retval = DIV_ROUND_CLOSEST_ULL(retval, (scale * scale));
> + }
> +
> + return retval;
> +}
> +
> +/*
> + * Create a default correction table for PWM values to create linear brightness
> + * for LED based backlights using the CIE1931 algorithm.
> + */
> +static
> +int pwm_backlight_brightness_default(struct device *dev,
> + struct platform_pwm_backlight_data *data,
> + unsigned int period)
> +{
> + unsigned int counter = 0;
> + unsigned int i, n;
> + u64 retval;
> +
> + /*
> + * Count the number of bits needed to represent the period number. The
> + * number of bits is used to calculate the number of levels used for the
> + * brightness-levels table, the purpose of this calculation is have a
> + * pre-computed table with enough levels to get linear brightness
> + * perception. The period is divided by the number of bits so for a
> + * 8-bit PWM we have 255 / 8 = 32 brightness levels or for a 16-bit PWM
> + * we have 65535 / 16 = 4096 brightness levels.
> + *
> + * Note that this method is based on empirical testing on different
> + * devices with PWM of 8 and 16 bits of resolution.
> + */
> + n = period;
> + while (n) {
> + counter += n % 2;
> + n >>= 1;
> + }
> +
> + data->max_brightness = DIV_ROUND_UP(period, counter);
> + data->levels = devm_kcalloc(dev, data->max_brightness,
> + sizeof(*data->levels), GFP_KERNEL);
> + if (!data->levels)
> + return -ENOMEM;
> +
> + /* Fill the table using the cie1931 algorithm */
> + for (i = 0; i < data->max_brightness; i++) {
> + retval = cie1931((i * PWM_LUMINANCE_SCALE) /
> + data->max_brightness, PWM_LUMINANCE_SCALE) *
> + period;
> + retval = DIV_ROUND_CLOSEST_ULL(retval, PWM_LUMINANCE_SCALE);
> + if (retval > UINT_MAX)
> + return -EINVAL;
> + data->levels[i] = (unsigned int)retval;
> + }
> +
> + data->dft_brightness = data->max_brightness / 2;
> + data->max_brightness--;
> +
> + return 0;
> +}
> +
> static int pwm_backlight_parse_dt(struct device *dev,
> struct platform_pwm_backlight_data *data)
> {
> @@ -161,10 +262,13 @@ static int pwm_backlight_parse_dt(struct device *dev,
>
> memset(data, 0, sizeof(*data));
>
> - /* determine the number of brightness levels */
> + /*
> + * Determine the number of brightness levels, if this property is not
> + * set a default table of brightness levels will be used.
> + */
> prop = of_find_property(node, "brightness-levels", &length);
> if (!prop)
> - return -EINVAL;
> + return 0;
>
> data->max_brightness = length / sizeof(u32);
>
> @@ -299,6 +403,14 @@ static int pwm_backlight_parse_dt(struct device *dev,
> {
> return -ENODEV;
> }
> +
> +static
> +int pwm_backlight_brightness_default(struct device *dev,
> + struct platform_pwm_backlight_data *data,
> + unsigned int period)
> +{
> + return -ENODEV;
> +}
> #endif
>
> static int pwm_backlight_initial_power_state(const struct pwm_bl_data *pb)
> @@ -339,7 +451,9 @@ static int pwm_backlight_probe(struct platform_device *pdev)
> struct backlight_device *bl;
> struct device_node *node = pdev->dev.of_node;
> struct pwm_bl_data *pb;
> + struct pwm_state state;
> struct pwm_args pargs;
> + unsigned int i;
> int ret;
>
> if (!data) {
> @@ -364,17 +478,6 @@ static int pwm_backlight_probe(struct platform_device *pdev)
> goto err_alloc;
> }
>
> - if (data->levels) {
> - unsigned int i;
> -
> - for (i = 0; i <= data->max_brightness; i++)
> - if (data->levels[i] > pb->scale)
> - pb->scale = data->levels[i];
> -
> - pb->levels = data->levels;
> - } else
> - pb->scale = data->max_brightness;
> -
> pb->notify = data->notify;
> pb->notify_after = data->notify_after;
> pb->check_fb = data->check_fb;
> @@ -441,6 +544,26 @@ static int pwm_backlight_probe(struct platform_device *pdev)
>
> dev_dbg(&pdev->dev, "got pwm for backlight\n");
>
> + if (!data->levels) {
> + /* Get the PWM period (in nanoseconds) */
> + pwm_get_state(pb->pwm, &state);
> +
> + ret = pwm_backlight_brightness_default(&pdev->dev, data,
> + state.period);
> + if (ret < 0) {
> + dev_err(&pdev->dev,
> + "failed to setup default brightness table\n");
> + goto err_alloc;
> + }
> + }
> +
> + for (i = 0; i <= data->max_brightness; i++)

Oops, horrible and unjustifiable mistake, missing { here :/

> + if (data->levels[i] > pb->scale)
> + pb->scale = data->levels[i];
> +
> + pb->levels = data->levels;
> + }
> +
> /*
> * FIXME: pwm_apply_args() should be removed when switching to
> * the atomic PWM API.
> --
> 2.15.1
>

2018-01-12 11:50:59

by Daniel Thompson

[permalink] [raw]
Subject: Re: [PATCH 3/4] backlight: pwm_bl: compute brightness of LED linearly to human eye.

On Fri, Jan 12, 2018 at 11:14:54AM +0100, Enric Balletbo Serra wrote:
> > @@ -441,6 +544,26 @@ static int pwm_backlight_probe(struct platform_device *pdev)
> >
> > dev_dbg(&pdev->dev, "got pwm for backlight\n");
> >
> > + if (!data->levels) {
> > + /* Get the PWM period (in nanoseconds) */
> > + pwm_get_state(pb->pwm, &state);
> > +
> > + ret = pwm_backlight_brightness_default(&pdev->dev, data,
> > + state.period);
> > + if (ret < 0) {
> > + dev_err(&pdev->dev,
> > + "failed to setup default brightness table\n");
> > + goto err_alloc;
> > + }
> > + }
> > +
> > + for (i = 0; i <= data->max_brightness; i++)
>
> Oops, horrible and unjustifiable mistake, missing { here :/

Well, at least you found it rather than us :-)


Daniel.

2018-01-19 20:54:19

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH 2/4] dt-bindings: pwm-backlight: add a num-interpolation-steps property.

On Wed, Jan 10, 2018 at 11:30:44PM +0100, Enric Balletbo i Serra wrote:
> The num-interpolated-steps property specifies the number of
> interpolated steps between each value of brightness-level table. This is
> useful for high resolution PWMs to not have to list out every possible
> value in the brightness-level array.
>
> Signed-off-by: Enric Balletbo i Serra <[email protected]>
> ---
> .../bindings/leds/backlight/pwm-backlight.txt | 21 +++++++++++++++++++++
> 1 file changed, 21 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt b/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
> index 310810906613..605432c910c5 100644
> --- a/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
> +++ b/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
> @@ -21,6 +21,11 @@ Optional properties:
> and enabling the backlight using GPIO.
> - pwm-off-delay-ms: Delay in ms between disabling the backlight using GPIO
> and setting PWM value to 0.
> + - num-interpolated-steps: Number of interpolated steps between each value
> + of brightness-levels table. This way a high
> + resolution pwm duty cycle can be used without
> + having to list out every possible value in the
> + brightness-level array.
>
> [0]: Documentation/devicetree/bindings/pwm/pwm.txt
> [1]: Documentation/devicetree/bindings/gpio/gpio.txt
> @@ -39,3 +44,19 @@ Example:
> post-pwm-on-delay-ms = <10>;
> pwm-off-delay-ms = <10>;
> };
> +
> +Example using num-interpolation-steps:
> +
> + backlight {
> + compatible = "pwm-backlight";
> + pwms = <&pwm 0 5000000>;
> +
> + brightness-levels = <0 65535>;
> + num-interpolated-steps = <4096>;

How does this make sense with only 2 defined levels other than having
fewer steps? I thought the purpose of this was to have a piecewise
linear curve.

> + default-brightness-level = <6>;

Aren't valid values 0, 16, 32, 48, etc.?

Rob

2018-01-19 20:58:14

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH 4/4] dt-bindings: pwm-backlight: move brightness-levels to optional.

On Wed, Jan 10, 2018 at 11:30:46PM +0100, Enric Balletbo i Serra wrote:
> The patch 'backlight: pwm_bl: compute brightness of LED linearly to
> human eye' introduced a default brightness-levels table that is used
> when brightness-levels is not availablel in the dts so move move

s/availablel/available/

Perhaps "so move..." should be a new sentence.

> brightness-levels and default-brightness-level to be optional.
>
> Signed-off-by: Enric Balletbo i Serra <[email protected]>
> ---
> .../devicetree/bindings/leds/backlight/pwm-backlight.txt | 15 ++++++++-------
> 1 file changed, 8 insertions(+), 7 deletions(-)

With that,

Reviewed-by: Rob Herring <[email protected]>

2018-01-22 09:16:58

by Daniel Thompson

[permalink] [raw]
Subject: Re: [PATCH 2/4] dt-bindings: pwm-backlight: add a num-interpolation-steps property.

On Fri, Jan 19, 2018 at 02:52:49PM -0600, Rob Herring wrote:
> On Wed, Jan 10, 2018 at 11:30:44PM +0100, Enric Balletbo i Serra wrote:
> > The num-interpolated-steps property specifies the number of
> > interpolated steps between each value of brightness-level table. This is
> > useful for high resolution PWMs to not have to list out every possible
> > value in the brightness-level array.
> >
> > Signed-off-by: Enric Balletbo i Serra <[email protected]>
> > ---
> > .../bindings/leds/backlight/pwm-backlight.txt | 21 +++++++++++++++++++++
> > 1 file changed, 21 insertions(+)
> >
> > diff --git a/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt b/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
> > index 310810906613..605432c910c5 100644
> > --- a/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
> > +++ b/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
> > @@ -21,6 +21,11 @@ Optional properties:
> > and enabling the backlight using GPIO.
> > - pwm-off-delay-ms: Delay in ms between disabling the backlight using GPIO
> > and setting PWM value to 0.
> > + - num-interpolated-steps: Number of interpolated steps between each value
> > + of brightness-levels table. This way a high
> > + resolution pwm duty cycle can be used without
> > + having to list out every possible value in the
> > + brightness-level array.
> >
> > [0]: Documentation/devicetree/bindings/pwm/pwm.txt
> > [1]: Documentation/devicetree/bindings/gpio/gpio.txt
> > @@ -39,3 +44,19 @@ Example:
> > post-pwm-on-delay-ms = <10>;
> > pwm-off-delay-ms = <10>;
> > };
> > +
> > +Example using num-interpolation-steps:
> > +
> > + backlight {
> > + compatible = "pwm-backlight";
> > + pwms = <&pwm 0 5000000>;
> > +
> > + brightness-levels = <0 65535>;
> > + num-interpolated-steps = <4096>;
>
> How does this make sense with only 2 defined levels other than having
> fewer steps? I thought the purpose of this was to have a piecewise
> linear curve.

It's not wrong as such, this is how a device with a linear (or nearly
linear) response could be compactly described.

Nevertheless I agree! An example with a small but realistic curve
would be better... we know that copy 'n paste exists so I'd rather
see a simple curve than no curve.


Daniel.


> > + default-brightness-level = <6>;
>
> Aren't valid values 0, 16, 32, 48, etc.?
>
> Rob

2018-01-23 10:22:42

by Enric Balletbo Serra

[permalink] [raw]
Subject: Re: [PATCH 2/4] dt-bindings: pwm-backlight: add a num-interpolation-steps property.

2018-01-22 10:16 GMT+01:00 Daniel Thompson <[email protected]>:
> On Fri, Jan 19, 2018 at 02:52:49PM -0600, Rob Herring wrote:
>> On Wed, Jan 10, 2018 at 11:30:44PM +0100, Enric Balletbo i Serra wrote:
>> > The num-interpolated-steps property specifies the number of
>> > interpolated steps between each value of brightness-level table. This is
>> > useful for high resolution PWMs to not have to list out every possible
>> > value in the brightness-level array.
>> >
>> > Signed-off-by: Enric Balletbo i Serra <[email protected]>
>> > ---
>> > .../bindings/leds/backlight/pwm-backlight.txt | 21 +++++++++++++++++++++
>> > 1 file changed, 21 insertions(+)
>> >
>> > diff --git a/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt b/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
>> > index 310810906613..605432c910c5 100644
>> > --- a/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
>> > +++ b/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
>> > @@ -21,6 +21,11 @@ Optional properties:
>> > and enabling the backlight using GPIO.
>> > - pwm-off-delay-ms: Delay in ms between disabling the backlight using GPIO
>> > and setting PWM value to 0.
>> > + - num-interpolated-steps: Number of interpolated steps between each value
>> > + of brightness-levels table. This way a high
>> > + resolution pwm duty cycle can be used without
>> > + having to list out every possible value in the
>> > + brightness-level array.
>> >
>> > [0]: Documentation/devicetree/bindings/pwm/pwm.txt
>> > [1]: Documentation/devicetree/bindings/gpio/gpio.txt
>> > @@ -39,3 +44,19 @@ Example:
>> > post-pwm-on-delay-ms = <10>;
>> > pwm-off-delay-ms = <10>;
>> > };
>> > +
>> > +Example using num-interpolation-steps:
>> > +
>> > + backlight {
>> > + compatible = "pwm-backlight";
>> > + pwms = <&pwm 0 5000000>;
>> > +
>> > + brightness-levels = <0 65535>;
>> > + num-interpolated-steps = <4096>;
>>
>> How does this make sense with only 2 defined levels other than having
>> fewer steps? I thought the purpose of this was to have a piecewise
>> linear curve.
>
> It's not wrong as such, this is how a device with a linear (or nearly
> linear) response could be compactly described.
>
> Nevertheless I agree! An example with a small but realistic curve
> would be better... we know that copy 'n paste exists so I'd rather
> see a simple curve than no curve.
>

Makes sense, I'll put a better example on next series. Thanks for the review.

- Enric

>
> Daniel.
>
>
>> > + default-brightness-level = <6>;
>>
>> Aren't valid values 0, 16, 32, 48, etc.?
>>
>> Rob