From 573fe6d1c25c81b0af856ffafb516db7e8d978c5 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Mon, 9 Apr 2018 10:33:30 +0200 Subject: 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 Acked-by: Daniel Thompson Signed-off-by: Lee Jones --- drivers/video/backlight/pwm_bl.c | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) (limited to 'drivers') diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c index 44ac5bde4e9d..105f199a656d 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,84 @@ 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. + */ + of_property_read_u32(node, "num-interpolated-steps", + &num_steps); + + /* + * 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. + */ + size = sizeof(*table) * num_levels; + table = devm_kzalloc(dev, size, 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 and replace for the + * new interpolated table. + */ + devm_kfree(dev, data->levels); + data->levels = table; + + /* + * Reassign max_brightness value to the new total number + * of brightness levels. + */ + data->max_brightness = num_levels; + } + data->max_brightness--; } -- cgit v1.2.3 From 88ba95bedb7958a89ba107a11d9863ca58b64f22 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Mon, 9 Apr 2018 10:33:32 +0200 Subject: 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 Acked-by: Daniel Thompson Signed-off-by: Lee Jones --- drivers/video/backlight/pwm_bl.c | 149 +++++++++++++++++++++++++++++++++++---- 1 file changed, 136 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c index 105f199a656d..9ee4c1b735b2 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); @@ -294,6 +398,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) @@ -334,7 +446,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) { @@ -359,17 +473,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; @@ -436,6 +539,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. -- cgit v1.2.3 From 858c5dfc8c336ca26b8d4d9f639a9dc40c5daa80 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Mon, 9 Jul 2018 16:51:56 -0500 Subject: backlight: adp8860: Mark expected switch fall-through In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Signed-off-by: Gustavo A. R. Silva Acked-by: Michael Hennerich Reviewed-by: Daniel Thompson Signed-off-by: Lee Jones --- drivers/video/backlight/adp8860_bl.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/video/backlight/adp8860_bl.c b/drivers/video/backlight/adp8860_bl.c index 16119bde9750..f1dc41cf19e3 100644 --- a/drivers/video/backlight/adp8860_bl.c +++ b/drivers/video/backlight/adp8860_bl.c @@ -690,6 +690,7 @@ static int adp8860_probe(struct i2c_client *client, switch (ADP8860_MANID(reg_val)) { case ADP8863_MANUFID: data->gdwn_dis = !!pdata->gdwn_dis; + /* fall through */ case ADP8860_MANUFID: data->en_ambl_sens = !!pdata->en_ambl_sens; break; -- cgit v1.2.3 From 633786736ed53a53b8d0a630cc3ff57381027081 Mon Sep 17 00:00:00 2001 From: Daniel Thompson Date: Wed, 25 Jul 2018 08:38:30 +0100 Subject: backlight: pwm_bl: Fix uninitialized variable Currently, if the DT does not define num-interpolated-steps then num_steps is undefined and the interpolation code will deploy randomly. Fix with a simple initialize to zero. Fixes: 573fe6d1c25c ("backlight: pwm_bl: Linear interpolation between brightness-levels") Reported-by: Marcel Ziswiler Signed-off-by: Daniel Thompson Tested-by: Marcel Ziswiler Reviewed-by: Douglas Anderson Signed-off-by: Lee Jones --- drivers/video/backlight/pwm_bl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c index 9ee4c1b735b2..bdfcc0a71db1 100644 --- a/drivers/video/backlight/pwm_bl.c +++ b/drivers/video/backlight/pwm_bl.c @@ -250,7 +250,7 @@ static int pwm_backlight_parse_dt(struct device *dev, struct device_node *node = dev->of_node; unsigned int num_levels = 0; unsigned int levels_count; - unsigned int num_steps; + unsigned int num_steps = 0; struct property *prop; unsigned int *table; int length; -- cgit v1.2.3