741 lines · 20.9 KB · C
RawDownload
// SPDX-License-Identifier: MIT/* * Copyright 2026 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Authors: AMD */ #include "dc.h"#include "dc/dc_dmub_srv.h"#include "dc/dc_state.h"#include "dc/dc_stat.h" #include "amdgpu.h"#include "amdgpu_display.h"#include "amdgpu_dm.h"#include "amdgpu_dm_backlight.h"#include "amdgpu_dm_psr.h"#include "amdgpu_dm_replay.h"#include "amdgpu_atombios.h" #include "modules/inc/mod_power.h" #include <linux/backlight.h>#include <linux/power_supply.h>#include <drm/drm_edid.h>#include <drm/drm_utils.h> #include <acpi/video.h> #include "amdgpu_dm_trace.h"#include "amd_shared.h"#include "dm_helpers.h" void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm,				     int bl_idx){	struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[bl_idx]; 	if (caps->caps_valid)		return; #if defined(CONFIG_ACPI)	amdgpu_acpi_get_backlight_caps(caps); 	/* validate the firmware value is sane */	if (caps->caps_valid) {		int spread = caps->max_input_signal - caps->min_input_signal; 		if (caps->max_input_signal > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT ||		    caps->min_input_signal < 0 ||		    spread > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT ||		    spread < AMDGPU_DM_MIN_SPREAD) {			drm_dbg_kms(adev_to_drm(dm->adev), "DM: Invalid backlight caps: min=%d, max=%d\n",				      caps->min_input_signal, caps->max_input_signal);			caps->caps_valid = false;		}	}#else	if (caps->aux_support)		return;#endif	if (!caps->caps_valid) {		caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;		caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;		caps->ac_level = caps->dc_level = 50;		caps->caps_valid = true;	}}EXPORT_IF_KUNIT(amdgpu_dm_update_backlight_caps); STATIC_IFN_KUNITint get_brightness_range(const struct amdgpu_dm_backlight_caps *caps,			 unsigned int *min, unsigned int *max){	if (!caps)		return 0; 	if (caps->aux_support) {		/* Firmware limits are in nits, DC API wants millinits. */		*max = 1000 * caps->aux_max_input_signal;		*min = 1000 * caps->aux_min_input_signal;	} else {		/* Firmware limits are 8-bit, PWM control is 16-bit. */		*max = 0x101 * caps->max_input_signal;		*min = 0x101 * caps->min_input_signal;	}	return 1;}EXPORT_IF_KUNIT(get_brightness_range); /* Rescale userspace [0..max] to the firmware curve's [0..255]. */static inline u32 scale_input_to_fw(int max, u64 input){	return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max);} /* Rescale from [0..AMDGPU_MAX_BL_LEVEL] to [min..max] */static inline u32 scale_fw_to_input(int min, int max, u64 input){	return min + DIV_ROUND_CLOSEST_ULL(input * (max - min), AMDGPU_MAX_BL_LEVEL);} STATIC_IFN_KUNITvoid convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,			       unsigned int min, unsigned int max,			       uint32_t *user_brightness){	u32 brightness = scale_input_to_fw(max, *user_brightness);	u8 lower_signal, upper_signal, upper_lum, lower_lum, lum;	int left, right; 	if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)		return; 	if (!caps->data_points)		return; 	/*	 * Handle the case where brightness is below the first data point	 * Interpolate between (0,0) and (first_signal, first_lum)	 */	if (brightness < caps->luminance_data[0].input_signal) {		lum = DIV_ROUND_CLOSEST(caps->luminance_data[0].luminance * brightness,					caps->luminance_data[0].input_signal);		goto scale;	} 	left = 0;	right = caps->data_points - 1;	while (left <= right) {		int mid = left + (right - left) / 2;		u8 signal = caps->luminance_data[mid].input_signal; 		/* Exact match found */		if (signal == brightness) {			lum = caps->luminance_data[mid].luminance;			goto scale;		} 		if (signal < brightness)			left = mid + 1;		else			right = mid - 1;	} 	/* verify bound */	if (left >= caps->data_points)		left = caps->data_points - 1; 	/* At this point, left > right */	lower_signal = caps->luminance_data[right].input_signal;	upper_signal = caps->luminance_data[left].input_signal;	lower_lum = caps->luminance_data[right].luminance;	upper_lum = caps->luminance_data[left].luminance; 	/* interpolate */	if (right == left || !lower_lum)		lum = upper_lum;	else		lum = lower_lum + DIV_ROUND_CLOSEST((upper_lum - lower_lum) *						    (brightness - lower_signal),						    upper_signal - lower_signal);scale:	*user_brightness = scale_fw_to_input(min, max,					     DIV_ROUND_CLOSEST(lum * brightness, 101));} EXPORT_IF_KUNIT(convert_custom_brightness); STATIC_IFN_KUNITu32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,				uint32_t brightness){	unsigned int min, max; 	if (!get_brightness_range(caps, &min, &max))		return brightness; 	convert_custom_brightness(caps, min, max, &brightness); 	/* Rescale 0..max to min..max */	return min + DIV_ROUND_CLOSEST_ULL((u64)(max - min) * brightness, max);} EXPORT_IF_KUNIT(convert_brightness_from_user); STATIC_IFN_KUNITu32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps,			      uint32_t brightness){	unsigned int min, max; 	if (!get_brightness_range(caps, &min, &max))		return brightness; 	if (brightness < min)		return 0;	/* Rescale min..max to 0..max */	return DIV_ROUND_CLOSEST_ULL((u64)max * (brightness - min),				 max - min);}EXPORT_IF_KUNIT(convert_brightness_to_user); STATIC_IFN_KUNITstruct dc_stream_state *dm_find_stream_with_link(	struct amdgpu_display_manager *dm,	struct dc_link *link){	struct dc_state *cur_dc_state = dm->dc->current_state;	struct dc_stream_state *stream = NULL;	int i; 	for (i = 0; i < cur_dc_state->stream_count; i++) {		stream = cur_dc_state->streams[i];		if (stream->link == link)			return stream;	} 	return NULL;}EXPORT_IF_KUNIT(dm_find_stream_with_link); STATIC_IFN_KUNITint amdgpu_dm_backlight_get_device_index(struct amdgpu_display_manager *dm,					 struct backlight_device *bd){	int i; 	for (i = 0; i < dm->num_of_edps; i++) {		if (bd == dm->backlight_dev[i])			return i;	} 	return 0;}EXPORT_IF_KUNIT(amdgpu_dm_backlight_get_device_index); void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,				   int bl_idx,				   u32 user_brightness){	struct amdgpu_dm_backlight_caps *caps;	struct dc_link *link;	u32 brightness = 0;	bool rc = false, reallow_idle = false;	struct drm_connector *connector;	struct dc_stream_state *stream;	unsigned int min, max; 	list_for_each_entry(connector, &dm->ddev->mode_config.connector_list, head) {		struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); 		if (aconnector->bl_idx != bl_idx)			continue; 		/* if connector is off, save the brightness for next time it's on */		if (!aconnector->base.encoder) {			dm->brightness[bl_idx] = user_brightness;			dm->actual_brightness[bl_idx] = 0;			return;		}	} 	amdgpu_dm_update_backlight_caps(dm, bl_idx);	caps = &dm->backlight_caps[bl_idx]; 	dm->brightness[bl_idx] = user_brightness;	/* update scratch register */	if (bl_idx == 0)		amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]);	brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]);	link = (struct dc_link *)dm->backlight_link[bl_idx]; 	/* Apply brightness quirk */	if (caps->brightness_mask)		brightness |= caps->brightness_mask; 	if (trace_amdgpu_dm_brightness_enabled()) {		trace_amdgpu_dm_brightness(__builtin_return_address(0),					   user_brightness,					   brightness,					   caps->aux_support,					   power_supply_is_system_supplied() > 0);	} 	stream = dm_find_stream_with_link(dm, link);	if (!stream)		return; 	mutex_lock(&dm->dc_lock);	if (dm->dc->caps.ips_support && dm->dc->ctx->dmub_srv->idle_allowed) {		dc_allow_idle_optimizations(dm->dc, false);		reallow_idle = true;	} 	if (caps->aux_support) {		rc = mod_power_set_backlight_nits(dm->power_module, stream, brightness,			AUX_BL_DEFAULT_TRANSITION_TIME_MS, false, true);	} else {		/* power module uses millipercent */		get_brightness_range(caps, &min, &max);		brightness = DIV_ROUND_CLOSEST(brightness * 100, (max - min)) * 1000;		rc = mod_power_set_backlight_percent(dm->power_module, stream,						     brightness, 0, false);	} 	/*	 * Some kms clients create a ramped backlight transition effect	 * by rapidly changing the backlight. Yet we must wait on dmcub	 * fw to exit psr/replay before programming backlight. To	 * prevent lag, keep disable psr/replay and let the next atomic	 * flip clear the event.	 *	 * ToDo: use ISM to handle rapidly backlight change	 *	 * Rapidly backlight change is similar to rapidly cursor events,	 * which is now handled by ISM. ISM can delay the event until system	 * is really idle, so we may use ISM to handle backlight change as well.	 */	amdgpu_dm_psr_set_event(dm, stream, true,		psr_event_hw_programming, true);	amdgpu_dm_replay_set_event(dm, stream, true,		replay_event_hw_programming, true); 	if (dm->dc->caps.ips_support && reallow_idle)		dc_allow_idle_optimizations(dm->dc, true); 	mutex_unlock(&dm->dc_lock); 	if (rc)		dm->actual_brightness[bl_idx] = user_brightness;}EXPORT_IF_KUNIT(amdgpu_dm_backlight_set_level); STATIC_IFN_KUNITint amdgpu_dm_backlight_update_status(struct backlight_device *bd){	struct amdgpu_display_manager *dm = bl_get_data(bd);	int i = amdgpu_dm_backlight_get_device_index(dm, bd); 	amdgpu_dm_backlight_set_level(dm, i, bd->props.brightness); 	return 0;}EXPORT_IF_KUNIT(amdgpu_dm_backlight_update_status); STATIC_IFN_KUNITu32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, int bl_idx){	int ret;	struct amdgpu_dm_backlight_caps caps;	struct dc_link *link = (struct dc_link *)dm->backlight_link[bl_idx]; 	amdgpu_dm_update_backlight_caps(dm, bl_idx);	caps = dm->backlight_caps[bl_idx]; 	if (caps.aux_support) {		u32 avg, peak; 		if (!dc_link_get_backlight_level_nits(link, &avg, &peak))			return dm->brightness[bl_idx];		return convert_brightness_to_user(&caps, avg);	} 	ret = dc_link_get_backlight_level(link); 	if (ret == DC_ERROR_UNEXPECTED)		return dm->brightness[bl_idx]; 	return convert_brightness_to_user(&caps, ret);}EXPORT_IF_KUNIT(amdgpu_dm_backlight_get_level); STATIC_IFN_KUNITint amdgpu_dm_backlight_get_brightness(struct backlight_device *bd){	struct amdgpu_display_manager *dm = bl_get_data(bd);	int i = amdgpu_dm_backlight_get_device_index(dm, bd); 	return amdgpu_dm_backlight_get_level(dm, i);}EXPORT_IF_KUNIT(amdgpu_dm_backlight_get_brightness); static const struct backlight_ops amdgpu_dm_backlight_ops = {	.options = BL_CORE_SUSPENDRESUME,	.get_brightness = amdgpu_dm_backlight_get_brightness,	.update_status	= amdgpu_dm_backlight_update_status,}; STATIC_IFN_KUNITvoid amdgpu_dm_backlight_fill_props(const struct amdgpu_dm_backlight_caps *caps,				    bool is_system_supplied,				    bool custom_curve_enabled,				    struct backlight_properties *props){	unsigned int min, max; 	if (get_brightness_range(caps, &min, &max)) {		if (is_system_supplied)			props->brightness = DIV_ROUND_CLOSEST(max * caps->ac_level,							       100);		else			props->brightness = DIV_ROUND_CLOSEST(max * caps->dc_level,							       100);		props->max_brightness = max;	} else {		props->brightness = MAX_BACKLIGHT_LEVEL;		props->max_brightness = MAX_BACKLIGHT_LEVEL;	} 	if (caps && caps->data_points && custom_curve_enabled)		props->scale = BACKLIGHT_SCALE_NON_LINEAR;	else		props->scale = BACKLIGHT_SCALE_LINEAR;	props->type = BACKLIGHT_RAW;}EXPORT_IF_KUNIT(amdgpu_dm_backlight_fill_props); voidamdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector){	struct drm_device *drm = aconnector->base.dev;	struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm;	struct backlight_properties props = { 0 };	struct amdgpu_dm_backlight_caps *caps;	char bl_name[16];	int real_brightness;	int init_brightness; 	if (aconnector->bl_idx == -1)		return; 	if (!acpi_video_backlight_use_native()) {		drm_info(drm, "Skipping amdgpu DM backlight registration\n");		/* Try registering an ACPI video backlight device instead. */		acpi_video_register_backlight();		return;	} 	caps = &dm->backlight_caps[aconnector->bl_idx];	amdgpu_dm_backlight_fill_props(caps, power_supply_is_system_supplied() > 0,				       !(amdgpu_dc_debug_mask &					 DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE),				       &props);	drm_dbg(drm, "Backlight caps: max_brightness: %d, ac %d, dc %d\n",		props.max_brightness, caps->ac_level, caps->dc_level); 	init_brightness = props.brightness; 	if (props.scale == BACKLIGHT_SCALE_NON_LINEAR)		drm_info(drm, "Using custom brightness curve\n"); 	snprintf(bl_name, sizeof(bl_name), "amdgpu_bl%d",		 drm->primary->index + aconnector->bl_idx); 	dm->backlight_dev[aconnector->bl_idx] =		backlight_device_register(bl_name, aconnector->base.kdev, dm,					  &amdgpu_dm_backlight_ops, &props);	dm->brightness[aconnector->bl_idx] = props.brightness; 	if (IS_ERR(dm->backlight_dev[aconnector->bl_idx])) {		drm_err(drm, "DM: Backlight registration failed!\n");		dm->backlight_dev[aconnector->bl_idx] = NULL;	} else {		/*		 * dm->brightness[x] can be inconsistent just after startup until		 * ops.get_brightness is called.		 */		real_brightness =			amdgpu_dm_backlight_ops.get_brightness(dm->backlight_dev[aconnector->bl_idx]); 		if (real_brightness != init_brightness) {			dm->actual_brightness[aconnector->bl_idx] = real_brightness;			dm->brightness[aconnector->bl_idx] = real_brightness;		}		drm_dbg_driver(drm, "DM: Registered Backlight device: %s\n", bl_name);	}}EXPORT_IF_KUNIT(amdgpu_dm_register_backlight_device); void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector){

Lines 1–500 of 741.