Received: by 10.213.65.68 with SMTP id h4csp786614imn; Fri, 6 Apr 2018 08:55:15 -0700 (PDT) X-Google-Smtp-Source: AIpwx4942KcpygPoB7YEGdR1BYat9oI7AuRtiVu2wjSJsGZoMeM8YZGA98ucGEwWQCAw/hz1VBhh X-Received: by 2002:a17:902:8505:: with SMTP id bj5-v6mr9475593plb.231.1523030115454; Fri, 06 Apr 2018 08:55:15 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1523030115; cv=none; d=google.com; s=arc-20160816; b=yPoWIdZ+K3r6pq8ZfCLgbK8JkoSGv/c9+RzMEFUINxLhXvtEiaTSldIO+Wp/35BIZa vnZw8gTKw2PfyLWwdDrtv1FLEglzBSK8wynxmFKRDmSu12ep8Z3Y0t80ZKRoZv3CQQsd nvC6UhcgtktqM2Jbtu2+xb25MmreJyzrnwR+bthYe0WZvoNvUdBHZtaCUnw24KHafGU9 uagogeJMhmg9WkBGKD/GZ2Ua/l0WaUYmfmGx2V3q3km34j6hvWPAAtXrVmN4TS55Wphh o3YWlPiRzG4C4tLDI59Mu98gMy7P53zR9T96x/COAn0phn9krC7W1A/kMnao9aXQxWfQ vlTA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:user-agent:in-reply-to :content-transfer-encoding:content-disposition:mime-version :references:message-id:subject:cc:to:from:date:dkim-signature :arc-authentication-results; bh=Eww/FqYHVP5tOno+Fh2tcDgy+SyqdRJdHieQvNcHP+0=; b=BtezRitxwYfLtlWWJH3cRUbFZxashIVJ5q2/0tb7BDtOLmoXFau9VN61nsqIPaUAd8 /w2dRwCaCd4DFnyfWonavqFRyCTI2c0aqK0H9/acXEQ9gudsgF5LZWoAkvOSn/oWDxjo qeXnzNBynvT7nnDm0peHbZ6pg3pqQ5VxDdLYzjZ6WaLi4bAFBHwFuQ3cd2kephNWGlHv NmWZrcEcc3A/VV9Gf+OowoxOjga3gBqEBGvI5VARqSApZhsZ9mCbIJJrFsNcXwSy8Ao4 SwpTPPtID8AZLX8PGCtjzQ0JrYalhsWTsFUCRgqTwDwGKdk/DIz+Mqm3+6zNP+GSO+9U vysw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=fsgT7pNF; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id 126si7175967pgd.375.2018.04.06.08.54.38; Fri, 06 Apr 2018 08:55:15 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=fsgT7pNF; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751967AbeDFPwA (ORCPT + 99 others); Fri, 6 Apr 2018 11:52:00 -0400 Received: from mail-wr0-f195.google.com ([209.85.128.195]:40216 "EHLO mail-wr0-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751417AbeDFPv6 (ORCPT ); Fri, 6 Apr 2018 11:51:58 -0400 Received: by mail-wr0-f195.google.com with SMTP id n2so2204071wrj.7 for ; Fri, 06 Apr 2018 08:51:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=date:from:to:cc:subject:message-id:references:mime-version :content-disposition:content-transfer-encoding:in-reply-to :user-agent; bh=Eww/FqYHVP5tOno+Fh2tcDgy+SyqdRJdHieQvNcHP+0=; b=fsgT7pNFZpgbJAvYCbWpmrN/+6WgadnjrV2d9X4YnpTCHYe3qfFIdAsomGmG6nGHOJ 3x+pMiynn83b5hbf77Q4HhX2Tpb+vZdGKTdjA0j0XgT6R/SySUTsNfZyUmyhFlpUDMZS h4Bf1dM7CivYgVLwelbGGplq9rJoyIcHgVYzw= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:content-transfer-encoding :in-reply-to:user-agent; bh=Eww/FqYHVP5tOno+Fh2tcDgy+SyqdRJdHieQvNcHP+0=; b=MW0y7KxomfIBCf7+3HQAN58g5+LdDXYyW4DpBVqEbOpe3A+8FWAcRYOV0s9FBo4O9k fVmSB8kTerfipYcvF6X2bAAkUCpgYTv6rsGKdhGMD5LUiIECz1Szk4vkOGvWSQrcp1sS lGuowjdzeku2+vFjDc93gU2IjIHwk9pvYCc3YLCAZU7Qo5DBX2dQPqxos8KWwl7Ijwz6 feAdAi6jMSHZtTnRBQNbfg+D1Pp5+Eb4lCL+Bd8MHi1ZBcPHBnI+GelXdlFOKfYB6cbW v/TtKZRr9O42nXYcDWrSG2bcDZuM0i1tTIYcIibzBUUkqIMINqSs4ynM3MOm8DTVF1Zm UyKw== X-Gm-Message-State: AElRT7FV5+Y+6vZDcGryZ5d6sLP7bTMgZJRkNLOFzOIbukgLbyCV23eo 2YozjhRrKibEQWea0vgdElwc0w== X-Received: by 10.223.209.66 with SMTP id b2mr20996438wri.178.1523029916816; Fri, 06 Apr 2018 08:51:56 -0700 (PDT) Received: from holly.lan (cpc141214-aztw34-2-0-cust773.18-1.cable.virginm.net. [86.9.19.6]) by smtp.gmail.com with ESMTPSA id w18sm9188911wra.25.2018.04.06.08.51.55 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Fri, 06 Apr 2018 08:51:55 -0700 (PDT) Date: Fri, 6 Apr 2018 16:51:53 +0100 From: Daniel Thompson To: Enric Balletbo i Serra Cc: Doug Anderson , Pavel Machek , Rob Herring , Jingoo Han , Richard Purdie , Jacek Anaszewski , Brian Norris , Guenter Roeck , Lee Jones , Alexandru Stan , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, kernel@collabora.com Subject: Re: [PATCH v3 3/4] backlight: pwm_bl: compute brightness of LED linearly to human eye. Message-ID: <20180406155153.d2urutl2ixfxjaud@holly.lan> References: <20180208113032.27810-1-enric.balletbo@collabora.com> <20180208113032.27810-4-enric.balletbo@collabora.com> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <20180208113032.27810-4-enric.balletbo@collabora.com> User-Agent: NeoMutt/20180223 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Thu, Feb 08, 2018 at 12:30:31PM +0100, Enric Balletbo i Serra wrote: > 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 > --- > Changes since v2: > - None > Changes since v1: > - Fix an horrible and unjustifiable mistake a missing { fortunately > catched very quick. > > 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 f0a108ab570a..d047d875f251 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. > -- > 2.15.1 >