Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/dp: clamp PWM bit count to advertised MIN and MAX capabilities

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>
Tested-by: Christopher Obbard <christopher.obbard@linaro.org>
Reviewed-by: Christopher Obbard <christopher.obbard@linaro.org>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Link: https://lore.kernel.org/r/20250814-topic-x1e80100-t14s-oled-dp-brightness-v7-1-b3d7b4dfe8c5@linaro.org

authored by

Christopher Obbard and committed by
Neil Armstrong
68a7c52f 36842189

+49 -19
+49 -19
drivers/gpu/drm/display/drm_dp_helper.c
··· 29 29 #include <linux/init.h> 30 30 #include <linux/iopoll.h> 31 31 #include <linux/kernel.h> 32 + #include <linux/minmax.h> 32 33 #include <linux/module.h> 33 34 #include <linux/sched.h> 34 35 #include <linux/seq_file.h> ··· 4137 4136 { 4138 4137 int fxp, fxp_min, fxp_max, fxp_actual, f = 1; 4139 4138 int ret; 4140 - u8 pn, pn_min, pn_max; 4139 + u8 pn, pn_min, pn_max, bit_count; 4141 4140 4142 4141 if (!bl->aux_set) 4143 4142 return 0; 4144 4143 4145 - ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT, &pn); 4144 + ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT, &bit_count); 4146 4145 if (ret < 0) { 4147 4146 drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap: %d\n", 4148 4147 aux->name, ret); 4149 4148 return -ENODEV; 4150 4149 } 4151 4150 4152 - pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK; 4151 + bit_count &= DP_EDP_PWMGEN_BIT_COUNT_MASK; 4152 + 4153 + ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min); 4154 + if (ret < 0) { 4155 + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n", 4156 + aux->name, ret); 4157 + return -ENODEV; 4158 + } 4159 + pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; 4160 + 4161 + ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max); 4162 + if (ret < 0) { 4163 + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n", 4164 + aux->name, ret); 4165 + return -ENODEV; 4166 + } 4167 + pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; 4168 + 4169 + if (unlikely(pn_min > pn_max)) { 4170 + drm_dbg_kms(aux->drm_dev, "%s: Invalid pwmgen bit count cap min/max returned: %d %d\n", 4171 + aux->name, pn_min, pn_max); 4172 + return -EINVAL; 4173 + } 4174 + 4175 + /* 4176 + * Per VESA eDP Spec v1.4b, section 3.3.10.2: 4177 + * If DP_EDP_PWMGEN_BIT_COUNT is less than DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, 4178 + * the sink must use the MIN value as the effective PWM bit count. 4179 + * Clamp the reported value to the [MIN, MAX] capability range to ensure 4180 + * correct brightness scaling on compliant eDP panels. 4181 + * Only enable this logic if the [MIN, MAX] range is valid in regard to Spec. 4182 + */ 4183 + pn = bit_count; 4184 + if (bit_count < pn_min) 4185 + pn = clamp(bit_count, pn_min, pn_max); 4186 + 4153 4187 bl->max = (1 << pn) - 1; 4154 - if (!driver_pwm_freq_hz) 4188 + if (!driver_pwm_freq_hz) { 4189 + if (pn != bit_count) 4190 + goto bit_count_write_back; 4191 + 4155 4192 return 0; 4193 + } 4156 4194 4157 4195 /* 4158 4196 * Set PWM Frequency divider to match desired frequency provided by the driver. ··· 4215 4175 * - FxP is within 25% of desired value. 4216 4176 * Note: 25% is arbitrary value and may need some tweak. 4217 4177 */ 4218 - ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min); 4219 - if (ret < 0) { 4220 - drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n", 4221 - aux->name, ret); 4222 - return 0; 4223 - } 4224 - ret = drm_dp_dpcd_read_byte(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max); 4225 - if (ret < 0) { 4226 - drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n", 4227 - aux->name, ret); 4228 - return 0; 4229 - } 4230 - pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; 4231 - pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; 4232 - 4233 4178 /* Ensure frequency is within 25% of desired value */ 4234 4179 fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4); 4235 4180 fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4); ··· 4232 4207 break; 4233 4208 } 4234 4209 4210 + bit_count_write_back: 4235 4211 ret = drm_dp_dpcd_write_byte(aux, DP_EDP_PWMGEN_BIT_COUNT, pn); 4236 4212 if (ret < 0) { 4237 4213 drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n", 4238 4214 aux->name, ret); 4239 4215 return 0; 4240 4216 } 4217 + 4218 + if (!driver_pwm_freq_hz) 4219 + return 0; 4220 + 4241 4221 bl->pwmgen_bit_count = pn; 4242 4222 bl->max = (1 << pn) - 1; 4243 4223