Message ID | 20250330-wip-obbardc-qcom-t14s-oled-panel-brightness-v6-1-84ad1cd1078a@linaro.org |
---|---|
State | Superseded |
Headers | show |
Series | [v6] drm/dp: clamp PWM bit count to advertised MIN and MAX capabilities | expand |
On Sun, Mar 30, 2025 at 08:31:07PM +0100, Christopher Obbard wrote: > According to the eDP specification (VESA Embedded DisplayPort Standard > v1.4b, Section 3.3.10.2), if the value of DP_EDP_PWMGEN_BIT_COUNT is > less than DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, the sink is required to use > the MIN value as the effective PWM bit count. > > This commit updates the logic to clamp the reported > DP_EDP_PWMGEN_BIT_COUNT to the range defined by _CAP_MIN and _CAP_MAX. > > As part of this change, the behavior is modified such that reading both > _CAP_MIN and _CAP_MAX registers is now required to succeed, otherwise > bl->max value could end up being not set although > drm_edp_backlight_probe_max() returned success. > > This ensures correct handling of eDP panels that report a zero PWM > bit count but still provide valid non-zero MIN and MAX capability > values. Without this clamping, brightness values may be interpreted > incorrectly, leading to a dim or non-functional backlight. > > For example, the Samsung ATNA40YK20 OLED panel used in the Lenovo > ThinkPad T14s Gen6 (Snapdragon) reports a PWM bit count of 0, but > supports AUX backlight control and declares a valid 11-bit range. > Clamping ensures brightness scaling works as intended on such panels. > > Co-developed-by: Rui Miguel Silva <rui.silva@linaro.org> > Signed-off-by: Rui Miguel Silva <rui.silva@linaro.org> > Signed-off-by: Christopher Obbard <christopher.obbard@linaro.org> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
On Sun, Mar 30, 2025 at 08:31:07PM +0100, Christopher Obbard wrote: > According to the eDP specification (VESA Embedded DisplayPort Standard > v1.4b, Section 3.3.10.2), if the value of DP_EDP_PWMGEN_BIT_COUNT is > less than DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, the sink is required to use > the MIN value as the effective PWM bit count. > > This commit updates the logic to clamp the reported > DP_EDP_PWMGEN_BIT_COUNT to the range defined by _CAP_MIN and _CAP_MAX. > > As part of this change, the behavior is modified such that reading both > _CAP_MIN and _CAP_MAX registers is now required to succeed, otherwise > bl->max value could end up being not set although > drm_edp_backlight_probe_max() returned success. > > This ensures correct handling of eDP panels that report a zero PWM > bit count but still provide valid non-zero MIN and MAX capability > values. Without this clamping, brightness values may be interpreted > incorrectly, leading to a dim or non-functional backlight. > > For example, the Samsung ATNA40YK20 OLED panel used in the Lenovo > ThinkPad T14s Gen6 (Snapdragon) reports a PWM bit count of 0, but > supports AUX backlight control and declares a valid 11-bit range. > Clamping ensures brightness scaling works as intended on such panels. > > Co-developed-by: Rui Miguel Silva <rui.silva@linaro.org> > Signed-off-by: Rui Miguel Silva <rui.silva@linaro.org> > Signed-off-by: Christopher Obbard <christopher.obbard@linaro.org> > @@ -4035,6 +4036,32 @@ drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_inf > } > > pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK; > + > + ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min); > + if (ret < 0) { > + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n", > + aux->name, ret); > + return -ENODEV; > + } > + pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; > + > + ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max); > + if (ret < 0) { > + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n", > + aux->name, ret); > + return -ENODEV; > + } > + pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; > + > + /* > + * Per VESA eDP Spec v1.4b, section 3.3.10.2: > + * If DP_EDP_PWMGEN_BIT_COUNT is less than DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, > + * the sink must use the MIN value as the effective PWM bit count. > + * Clamp the reported value to the [MIN, MAX] capability range to ensure > + * correct brightness scaling on compliant eDP panels. > + */ > + pn = clamp(pn, pn_min, pn_max); You never make sure that pn_min <= pn_max so you could end up with pn < pn_min on broken hardware here. Not sure if it's something you need to worry about at this point. > + > bl->max = (1 << pn) - 1; > if (!driver_pwm_freq_hz) > return 0; Otherwise this looks correct to me and does not break backlight control on the X1E reference design: Reviewed-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Johan Hovold <johan+linaro@kernel.org> Johan
Hi Johan, On Mon, 31 Mar 2025 at 09:33, Johan Hovold <johan@kernel.org> wrote: > > On Sun, Mar 30, 2025 at 08:31:07PM +0100, Christopher Obbard wrote: > > According to the eDP specification (VESA Embedded DisplayPort Standard > > v1.4b, Section 3.3.10.2), if the value of DP_EDP_PWMGEN_BIT_COUNT is > > less than DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, the sink is required to use > > the MIN value as the effective PWM bit count. > > > > This commit updates the logic to clamp the reported > > DP_EDP_PWMGEN_BIT_COUNT to the range defined by _CAP_MIN and _CAP_MAX. > > > > As part of this change, the behavior is modified such that reading both > > _CAP_MIN and _CAP_MAX registers is now required to succeed, otherwise > > bl->max value could end up being not set although > > drm_edp_backlight_probe_max() returned success. > > > > This ensures correct handling of eDP panels that report a zero PWM > > bit count but still provide valid non-zero MIN and MAX capability > > values. Without this clamping, brightness values may be interpreted > > incorrectly, leading to a dim or non-functional backlight. > > > > For example, the Samsung ATNA40YK20 OLED panel used in the Lenovo > > ThinkPad T14s Gen6 (Snapdragon) reports a PWM bit count of 0, but > > supports AUX backlight control and declares a valid 11-bit range. > > Clamping ensures brightness scaling works as intended on such panels. > > > > Co-developed-by: Rui Miguel Silva <rui.silva@linaro.org> > > Signed-off-by: Rui Miguel Silva <rui.silva@linaro.org> > > Signed-off-by: Christopher Obbard <christopher.obbard@linaro.org> > > > @@ -4035,6 +4036,32 @@ drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_inf > > } > > > > pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK; > > + > > + ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min); > > + if (ret < 0) { > > + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n", > > + aux->name, ret); > > + return -ENODEV; > > + } > > + pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; > > + > > + ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max); > > + if (ret < 0) { > > + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n", > > + aux->name, ret); > > + return -ENODEV; > > + } > > + pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; > > + > > + /* > > + * Per VESA eDP Spec v1.4b, section 3.3.10.2: > > + * If DP_EDP_PWMGEN_BIT_COUNT is less than DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, > > + * the sink must use the MIN value as the effective PWM bit count. > > + * Clamp the reported value to the [MIN, MAX] capability range to ensure > > + * correct brightness scaling on compliant eDP panels. > > + */ > > + pn = clamp(pn, pn_min, pn_max); > > You never make sure that pn_min <= pn_max so you could end up with > pn < pn_min on broken hardware here. Not sure if it's something you need > to worry about at this point. I am honestly not sure. I would hope that devices follow the spec and there is no need to be too paranoid, but then again we do live in the real world where things are... not so simple ;-). I will wait for further feedback from someone who has more experience with eDP panels than I have. > > + > > bl->max = (1 << pn) - 1; > > if (!driver_pwm_freq_hz) > > return 0; > > Otherwise this looks correct to me and does not break backlight control > on the X1E reference design: > > Reviewed-by: Johan Hovold <johan+linaro@kernel.org> > Tested-by: Johan Hovold <johan+linaro@kernel.org> Thanks for testing! Chris
On Fri, Apr 04, 2025 at 08:54:29AM +0100, Christopher Obbard wrote: > On Mon, 31 Mar 2025 at 09:33, Johan Hovold <johan@kernel.org> wrote: > > > @@ -4035,6 +4036,32 @@ drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_inf > > > } > > > > > > pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK; > > > + > > > + ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min); > > > + if (ret < 0) { > > > + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n", > > > + aux->name, ret); > > > + return -ENODEV; > > > + } > > > + pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; > > > + > > > + ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max); > > > + if (ret < 0) { > > > + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n", > > > + aux->name, ret); > > > + return -ENODEV; > > > + } > > > + pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; > > > + > > > + /* > > > + * Per VESA eDP Spec v1.4b, section 3.3.10.2: > > > + * If DP_EDP_PWMGEN_BIT_COUNT is less than DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, > > > + * the sink must use the MIN value as the effective PWM bit count. > > > + * Clamp the reported value to the [MIN, MAX] capability range to ensure > > > + * correct brightness scaling on compliant eDP panels. > > > + */ > > > + pn = clamp(pn, pn_min, pn_max); > > > > You never make sure that pn_min <= pn_max so you could end up with > > pn < pn_min on broken hardware here. Not sure if it's something you need > > to worry about at this point. > > I am honestly not sure. I would hope that devices follow the spec and > there is no need to be too paranoid, but then again we do live in the > real world where things are... not so simple ;-). > I will wait for further feedback from someone who has more experience > with eDP panels than I have. There's always going to be buggy devices and input should always be sanitised so I suggest adding that check before calling clamp() (which expects min <= max) so that the result here is well-defined. Johan
Johan, On Fri, 4 Apr 2025 at 09:54, Johan Hovold <johan@kernel.org> wrote: > > On Fri, Apr 04, 2025 at 08:54:29AM +0100, Christopher Obbard wrote: > > On Mon, 31 Mar 2025 at 09:33, Johan Hovold <johan@kernel.org> wrote: > > > > > @@ -4035,6 +4036,32 @@ drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_inf > > > > } > > > > > > > > pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK; > > > > + > > > > + ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min); > > > > + if (ret < 0) { > > > > + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n", > > > > + aux->name, ret); > > > > + return -ENODEV; > > > > + } > > > > + pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; > > > > + > > > > + ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max); > > > > + if (ret < 0) { > > > > + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n", > > > > + aux->name, ret); > > > > + return -ENODEV; > > > > + } > > > > + pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; > > > > + > > > > + /* > > > > + * Per VESA eDP Spec v1.4b, section 3.3.10.2: > > > > + * If DP_EDP_PWMGEN_BIT_COUNT is less than DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, > > > > + * the sink must use the MIN value as the effective PWM bit count. > > > > + * Clamp the reported value to the [MIN, MAX] capability range to ensure > > > > + * correct brightness scaling on compliant eDP panels. > > > > + */ > > > > + pn = clamp(pn, pn_min, pn_max); > > > > > > You never make sure that pn_min <= pn_max so you could end up with > > > pn < pn_min on broken hardware here. Not sure if it's something you need > > > to worry about at this point. > > > > I am honestly not sure. I would hope that devices follow the spec and > > there is no need to be too paranoid, but then again we do live in the > > real world where things are... not so simple ;-). > > I will wait for further feedback from someone who has more experience > > with eDP panels than I have. > > There's always going to be buggy devices and input should always be > sanitised so I suggest adding that check before calling clamp() (which > expects min <= max) so that the result here is well-defined. Makes sense, I will do so in the next revision. Thanks. Chris
diff --git a/drivers/gpu/drm/display/drm_dp_helper.c b/drivers/gpu/drm/display/drm_dp_helper.c index e2439c8a7fefe116b04aaa689b557e2387b05540..5550c40310c55ee275b3ebec08a7500cab38ae78 100644 --- a/drivers/gpu/drm/display/drm_dp_helper.c +++ b/drivers/gpu/drm/display/drm_dp_helper.c @@ -28,6 +28,7 @@ #include <linux/init.h> #include <linux/iopoll.h> #include <linux/kernel.h> +#include <linux/minmax.h> #include <linux/module.h> #include <linux/sched.h> #include <linux/seq_file.h> @@ -4035,6 +4036,32 @@ drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_inf } pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK; + + ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min); + if (ret < 0) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n", + aux->name, ret); + return -ENODEV; + } + pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; + + ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max); + if (ret < 0) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n", + aux->name, ret); + return -ENODEV; + } + pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; + + /* + * Per VESA eDP Spec v1.4b, section 3.3.10.2: + * If DP_EDP_PWMGEN_BIT_COUNT is less than DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, + * the sink must use the MIN value as the effective PWM bit count. + * Clamp the reported value to the [MIN, MAX] capability range to ensure + * correct brightness scaling on compliant eDP panels. + */ + pn = clamp(pn, pn_min, pn_max); + bl->max = (1 << pn) - 1; if (!driver_pwm_freq_hz) return 0; @@ -4061,21 +4088,6 @@ drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_inf * - FxP is within 25% of desired value. * Note: 25% is arbitrary value and may need some tweak. */ - ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min); - if (ret < 0) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n", - aux->name, ret); - return 0; - } - ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max); - if (ret < 0) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n", - aux->name, ret); - return 0; - } - pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; - pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; - /* Ensure frequency is within 25% of desired value */ fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4); fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4);