aboutsummaryrefslogtreecommitdiff
path: root/linux-legacy-oled-brightness.patch
diff options
context:
space:
mode:
authorGravatar Chris Xiong <chirs241097@gmail.com> 2025-05-04 01:10:58 -0400
committerGravatar Chris Xiong <chirs241097@gmail.com> 2025-05-04 01:12:00 -0400
commit397df5f4c5cb8d3e05ddd1c0e8da2386f61386f5 (patch)
treeb4d83b71fc79fdd53571dbbf3724b9adeb02399c /linux-legacy-oled-brightness.patch
downloadlinux-legacy-oled-brightness-dev.tar.xz
Initial patches for linux 6.14.4.HEADdev
Diffstat (limited to 'linux-legacy-oled-brightness.patch')
-rw-r--r--linux-legacy-oled-brightness.patch582
1 files changed, 582 insertions, 0 deletions
diff --git a/linux-legacy-oled-brightness.patch b/linux-legacy-oled-brightness.patch
new file mode 100644
index 0000000..c060432
--- /dev/null
+++ b/linux-legacy-oled-brightness.patch
@@ -0,0 +1,582 @@
+diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile
+index 3dda9f0eda82..ee413332a9ae 100644
+--- a/drivers/gpu/drm/i915/Makefile
++++ b/drivers/gpu/drm/i915/Makefile
+@@ -328,6 +328,7 @@ i915-y += \
+ display/intel_dp.o \
+ display/intel_dp_aux.o \
+ display/intel_dp_aux_backlight.o \
++ display/intel_dp_legacy_oled_brightness.o \
+ display/intel_dp_hdcp.o \
+ display/intel_dp_link_training.o \
+ display/intel_dp_mst.o \
+diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
+index 8271e50e3644..de512541628f 100644
+--- a/drivers/gpu/drm/i915/display/intel_display_types.h
++++ b/drivers/gpu/drm/i915/display/intel_display_types.h
+@@ -50,6 +50,7 @@
+ #include "intel_display_power.h"
+ #include "intel_dpll_mgr.h"
+ #include "intel_wm_types.h"
++#include "intel_dp_legacy_oled_brightness.h"
+
+ struct cec_notifier;
+ struct drm_printer;
+@@ -421,6 +422,9 @@ struct intel_panel {
+ bool supports_sdp_colorimetry;
+ bool supports_tone_mapping;
+ } intel_cap;
++ struct {
++ struct intel_legacy_panel_data panel_data;
++ } legacy;
+ } edp;
+
+ struct backlight_device *device;
+diff --git a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c
+index c846ef4acf5b..8a9eae55b362 100644
+--- a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c
++++ b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c
+@@ -39,6 +39,7 @@
+ #include "intel_display_core.h"
+ #include "intel_display_types.h"
+ #include "intel_dp.h"
++#include "intel_dp_legacy_oled_brightness.h"
+ #include "intel_dp_aux_backlight.h"
+
+ /*
+@@ -99,6 +100,7 @@ enum intel_dp_aux_backlight_modparam {
+ INTEL_DP_AUX_BACKLIGHT_ON = 1,
+ INTEL_DP_AUX_BACKLIGHT_FORCE_VESA = 2,
+ INTEL_DP_AUX_BACKLIGHT_FORCE_INTEL = 3,
++ INTEL_DP_AUX_BACKLIGHT_LEGACY = 99,
+ };
+
+ static bool is_intel_tcon_cap(const u8 tcon_cap[4])
+@@ -606,7 +608,7 @@ int intel_dp_aux_init_backlight_funcs(struct intel_connector *connector)
+ struct intel_display *display = to_intel_display(connector);
+ struct drm_device *dev = connector->base.dev;
+ struct intel_panel *panel = &connector->panel;
+- bool try_intel_interface = false, try_vesa_interface = false;
++ bool try_intel_interface = false, try_vesa_interface = false, try_legacy_interface = false;
+
+ /* Check the VBT and user's module parameters to figure out which
+ * interfaces to probe
+@@ -638,6 +640,9 @@ int intel_dp_aux_init_backlight_funcs(struct intel_connector *connector)
+ case INTEL_DP_AUX_BACKLIGHT_FORCE_INTEL:
+ try_intel_interface = true;
+ break;
++ case INTEL_DP_AUX_BACKLIGHT_LEGACY:
++ try_legacy_interface = true;
++ break;
+ }
+
+ /*
+@@ -667,5 +672,10 @@ int intel_dp_aux_init_backlight_funcs(struct intel_connector *connector)
+ return 0;
+ }
+
++ if (try_legacy_interface && intel_dp_aux_legacy_brightness_supported(connector)) {
++ panel->backlight.funcs = intel_dp_aux_legacy_get_bl_funcs();
++ return 0;
++ }
++
+ return -ENODEV;
+ }
+diff --git a/drivers/gpu/drm/i915/display/intel_dp_legacy_oled_brightness.c b/drivers/gpu/drm/i915/display/intel_dp_legacy_oled_brightness.c
+new file mode 100644
+index 000000000000..6959b34c621e
+--- /dev/null
++++ b/drivers/gpu/drm/i915/display/intel_dp_legacy_oled_brightness.c
+@@ -0,0 +1,446 @@
++/*
++ * Copyright © 2025 Chris Xiong
++ *
++ * 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 (including the next
++ * paragraph) 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 AUTHORS OR COPYRIGHT HOLDERS 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.
++ *
++ */
++
++#include "intel_backlight.h"
++#include "intel_display_core.h"
++#include "intel_display_types.h"
++#include "intel_dp.h"
++#include "intel_dp_legacy_oled_brightness.h"
++#include "linux/firmware.h"
++#include "linux/slab.h"
++#include "linux/stdarg.h"
++
++static bool intel_dp_aux_legacy_write_sequence(struct drm_dp_aux *aux, const u32 *address_seq, const u8 *value_seq, size_t length) {
++ drm_dbg_kms(aux->drm_dev, "intel_dp_aux_legacy_write_sequence: %*phN", (int)length, value_seq);
++ for (size_t p = 0; p < length; ++p) {
++ if (drm_dp_dpcd_writeb(aux, address_seq[p], value_seq[p]) < 1) {
++ return false;
++ }
++ }
++ return true;
++}
++
++static bool intel_dp_aux_legacy_write_sequence_parameterized(struct drm_dp_aux *aux, const u32 *address_seq, const u8 *value_seq, size_t length, size_t nparameters, ...) {
++ u8 *value_buf = NULL;
++ va_list args;
++ bool ret = false;
++
++ if (!nparameters)
++ return intel_dp_aux_legacy_write_sequence(aux, address_seq, value_seq, length);
++ value_buf = kmalloc(length, GFP_KERNEL);
++ memcpy(value_buf, value_seq, length);
++
++ va_start(args, nparameters);
++ for (size_t p = 0; p < nparameters; ++p) {
++ int pos = va_arg(args, int);
++ int val = va_arg(args, int);
++ if (pos >= 0 && pos < length)
++ value_buf[pos] = val;
++ }
++ va_end(args);
++ ret = intel_dp_aux_legacy_write_sequence(aux, address_seq, value_buf, length);
++
++ kfree(value_buf);
++ return ret;
++}
++
++static bool intel_dp_aux_legacy_handshake(struct intel_connector *connector) {
++ struct intel_display *display = to_intel_display(connector);
++ struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder);
++ struct drm_dp_aux *aux = &intel_dp->aux;
++ char *handshake_str = "PARAAUX-REG";
++ u8 v;
++ int ret;
++
++ ret = drm_dp_dpcd_readb(aux, 0x490, &v);
++ if (ret < 1)
++ drm_info(display->drm, "intel_dp_aux_legacy_handshake: read dpcd register 0x490 failed: %d", ret);
++ else {
++ if (v != 1) {
++ for (int retries = 0; retries < 20; ++retries) {
++ for (int p = 0; p < 11; ++p) {
++ ret = drm_dp_dpcd_writeb(aux, 0x490, (u8)handshake_str[p]);
++ if (ret < 1) break;
++ }
++ ret = drm_dp_dpcd_readb(aux, 0x490, &v);
++ if (ret == 1 && v == 1)
++ return true;
++ }
++ } else
++ return true;
++ }
++ drm_err(display->drm, "intel_dp_aux_legacy_handshake: handshake failed");
++ return false;
++}
++
++static void intel_dp_aux_legacy_get_elvss_max_offset(struct intel_connector *connector) {
++ struct intel_display *display = to_intel_display(connector);
++ struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder);
++ struct drm_dp_aux *aux = &intel_dp->aux;
++ struct intel_legacy_panel_data *panel_data = &connector->panel.backlight.edp.legacy.panel_data;
++ const u32 address_seq1[11] = {0x491, 0x492, 0x493, 0x491, 0x492, 0x493, 0x492, 0x493, 0x492, 0x493, 0x492};
++ const u8 value_seq1[11] = { 0x02, 0x7f, 0x01, 0x07, 0xff, 0x04, 0x03, 0x04, 0x06, 0x26, 0x0a};
++ if (intel_dp_aux_legacy_handshake(connector)) {
++ if (!intel_dp_aux_legacy_write_sequence(aux, address_seq1, value_seq1, 11)) {
++ drm_err(display->drm, "get_elvss_max_offset: write failed");
++ return;
++ }
++ if (drm_dp_dpcd_readb(aux, 0x493, &panel_data->elvss_max_offset) < 1) {
++ drm_err(display->drm, "get_elvss_max_offset: read failed");
++ return;
++ }
++ drm_dbg_kms(display->drm, "elvss_max_offset: %d", (int)panel_data->elvss_max_offset);
++ } else
++ drm_err(display->drm, "get_elvss_max_offset: handshake failed");
++}
++
++static bool intel_dp_aux_legacy_get_tcon_gamma(struct intel_connector *connector) {
++ struct intel_display *display = to_intel_display(connector);
++ struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder);
++ struct drm_dp_aux *aux = &intel_dp->aux;
++ struct intel_legacy_panel_data *panel_data = &connector->panel.backlight.edp.legacy.panel_data;
++ struct intel_legacy_panel_data_intermediate *interm = panel_data->intermediates;
++
++ const u32 address_seq1[6] = {0x491, 0x492, 0x493, 0x491, 0x492, 0x493};
++ const u8 value_seq1[6] = { 0x2, 0x7f, 0x1, 0x7, 0xff, 0x4};
++ const u32 address_seq2[8] = {0x492, 0x493, 0x492, 0x493, 0x492, 0x493, 0x491, 0x492};
++ const u8 value_seq2[8] = { 0x0, 0, 0x1, 0, 0x2, 0, 0x7, 0xc};
++
++ if (!intel_dp_aux_legacy_handshake(connector)) {
++ drm_err(display->drm, "get_tcon_gamma: handshake failed");
++ return false;
++ }
++ if (!intel_dp_aux_legacy_write_sequence(aux, address_seq1, value_seq1, 6)) {
++ drm_err(display->drm, "get_tcon_gamma: write failed");
++ return false;
++ }
++ if (!interm) {
++ drm_err(display->drm, "get_tcon_gamma: could not get hold of intermediate data");
++ return false;
++ }
++ for (u16 i = 0; i < 0x20; ++i) {
++ u16 k = i * 0xb;
++ for (u16 j = 0; j <= 0x20; ++j) {
++ intel_dp_aux_legacy_write_sequence_parameterized(aux, address_seq2, value_seq2, 8, 3,
++ 1, (j < 0xb ? 0x2 : (j < 0x16 ? 0x4 : 0x8)),
++ 3, (u8)(k << 7),
++ 5, (u8)(k >> 1));
++ if (i == 0 && j == 0) udelay(100);
++ drm_dp_dpcd_readb(aux, 0x493, &interm->tcon_gamma[i][j]);
++ if (++k >= (i + 1) * 0xb)
++ k = i * 0xb;
++ }
++ }
++ u16 *vp = &interm->gamma_values[0][0];
++ u8 *gp = &interm->tcon_gamma[0][0];
++ for (u16 i = 0; i < 0x20 ; ++i) {
++ for (u16 j = 0; j <= 0x20; ++j) {
++ switch (j) {
++ case 0x7: case 0x12: case 0x1d:
++ *vp = (u16)(*(gp + 2) & 0x80) * 2 | *gp;
++ break;
++ case 0x9: case 0x14: case 0x1f:
++ *vp = *gp & 0x7f;
++ break;
++ case 0xa: case 0x15: case 0x20:
++ *vp = *gp & 0xf;
++ break;
++ default:
++ *vp = *gp;
++ }
++ ++vp;
++ ++gp;
++ }
++ }
++ return true;
++}
++
++static void intel_dp_aux_legacy_write_brightness(struct intel_connector *connector, u8 brightness) {
++ struct intel_display *display = to_intel_display(connector);
++ struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder);
++ struct drm_dp_aux *aux = &intel_dp->aux;
++ struct intel_legacy_panel_data *panel_data = &connector->panel.backlight.edp.legacy.panel_data;
++ u8 *brightness_gamma = panel_data->interpolated_gamma[100 - brightness];
++ u32 *write_addr_buf = NULL;
++ u8 *write_val_buf = NULL;
++ const u32 address_seq1[7] = {0x491, 0x492, 0x493, 0x492, 0x493, 0x492, 0x493};
++ const u8 value_seq1[7] = { 0x0f, 0xff, 0x00, 0x9d, 0, 0x9b, 0};
++ const u32 address_seq2[7] = {0x491, 0x492, 0x493, 0x492, 0x493, 0x492, 0x493};
++ const u8 value_seq2[7] = { 0x0f, 0xff, 0x02, 0x30, 0, 0x33, 0};
++ const u32 address_seq3[7] = {0x491, 0x492, 0x493, 0x492, 0x493, 0x492, 0x493};
++ const u8 value_seq3[7] = { 0x0e, 0x80, 0, 0x90, 0, 0x91, 0};
++ const u32 address_seq4[6] = {0x491, 0x492, 0x493, 0x491, 0x492, 0x493};
++ const u8 value_seq4[6] = { 0x02, 0x7f, 0x01, 0x07, 0xff, 0x04};
++ const u32 address_seq5[14] = {0x492, 0x493, 0x491, 0x492, 0x493, 0x491, 0x492, 0x493, 0x492, 0x493, 0x491, 0x492, 0x493, 0x491};
++ const u8 value_seq5[14] = { 0x32, 0x01, 0x02, 0xeb, 0x91, 0x09, 0xf0, 0xef, 0xf9, 0, 0x02, 0xeb, 0x96, 0x0e};
++
++ if (!intel_dp_aux_legacy_handshake(connector)) {
++ drm_err(display->drm, "write_brightness: handshake failed");
++ return;
++ }
++ drm_dbg_kms(display->drm, "write_brightness %3d", brightness);
++
++ if (!intel_dp_aux_legacy_write_sequence_parameterized(aux, address_seq1, value_seq1, 7, 2,
++ 4, panel_data->aor_buf[brightness * 2], //LSB of AOR[brightness]
++ 6, panel_data->aor_buf[brightness * 2 + 1])) //MSB of AOR[brightness]
++ goto fail;
++
++ if (!intel_dp_aux_legacy_write_sequence_parameterized(aux, address_seq2, value_seq2, 7, 2,
++ 4, panel_data->delvss[brightness],
++ 6, panel_data->elvss_max_offset))
++ goto fail;
++
++ if (brightness > panel_data->brightness_for_acl) {
++ if (!intel_dp_aux_legacy_write_sequence_parameterized(aux, address_seq3, value_seq3, 7, 3,
++ 2, 0x90,
++ 4, panel_data->acl_cutoff_and_delta_buf[(brightness - panel_data->brightness_for_acl - 1) * 2], // LSB of ACLCutoffAndDelta[brightness - brightness_acl - 1]
++ 6, panel_data->acl_cutoff_and_delta_buf[(brightness - panel_data->brightness_for_acl - 1) * 2 + 1])) // MSB of ACLCutoffAndDelta[brightness - brightness_acl - 1]
++ goto fail;
++ } else {
++ if (!intel_dp_aux_legacy_write_sequence_parameterized(aux, address_seq3, value_seq3, 3, 1,
++ 2, 0x80))
++ goto fail;
++ }
++
++ if (!intel_dp_aux_legacy_write_sequence(aux, address_seq4, value_seq4, 6)) goto fail;
++ drm_dbg_kms(display->drm, "brightness_gamma @ %d: %*ph\n", 100 - brightness, 32, brightness_gamma);
++ write_addr_buf = kmalloc_array(0x21 * 2, 4, GFP_KERNEL);
++ write_val_buf = kmalloc_array(0x21 * 2, 1, GFP_KERNEL);
++ for (int i = 0; i <= 0x20; ++i) {
++ write_addr_buf[i * 2] = 0x492;
++ write_addr_buf[i * 2 + 1] = 0x493;
++ write_val_buf[i * 2] = i + 0x10;
++ if (drm_dp_dpcd_writeb(aux, 0x492, i + 0x10) < 1) goto fail;
++ if (i == 10 || i == 21 || i == 32)
++ write_val_buf[i * 2 + 1] = brightness_gamma[i] & 0xf;
++ else
++ write_val_buf[i * 2 + 1] = brightness_gamma[i];
++ }
++ if (!intel_dp_aux_legacy_write_sequence(aux, write_addr_buf, write_val_buf, 0x21 * 2))
++ goto fail;
++ udelay(1000);
++ if (!intel_dp_aux_legacy_write_sequence_parameterized(aux, address_seq5, value_seq5, 14, 1,
++ 9, panel_data->SP[brightness]))
++ goto fail;
++ for (int i = 0; i < 9; ++i) {
++ //reuse current data in write_addr_buf
++ //as well as values written to 0x492
++ write_val_buf[i * 2 + 1] = panel_data->IRC[brightness][i];
++ }
++ if (!intel_dp_aux_legacy_write_sequence(aux, write_addr_buf, write_val_buf, 9 * 2))
++ goto fail;
++ kfree(write_addr_buf);
++ kfree(write_val_buf);
++ return;
++fail:
++ if (write_addr_buf)
++ kfree(write_addr_buf);
++ if (write_val_buf)
++ kfree(write_val_buf);
++ drm_err(display->drm, "write_brightness: failed to write dpcd registers");
++}
++
++inline s16 sign(s16 v) { return v < 0 ? -1 : 1; }
++
++static void intel_dp_aux_legacy_calculate_interpolated_values(struct intel_connector *connector) {
++ struct intel_display *display = to_intel_display(connector);
++ struct intel_legacy_panel_data *panel_data = &connector->panel.backlight.edp.legacy.panel_data;
++ struct intel_legacy_panel_data_intermediate *interm = panel_data->intermediates;
++
++ s16 dv;
++ u8 old_d;
++ u8 base, delta, multiplier, bias, attenuation;
++ for (int i = 0; i <= 0x20; ++i) {
++ old_d = 0xff;
++ for (int br = 0; br <= 100; ++br) {
++ base = interm->interpolation[br][0];
++ delta = interm->interpolation[br][1];
++ multiplier = interm->interpolation[br][2];
++ bias = interm->interpolation[br][3];
++ attenuation = interm->interpolation[br][4];
++ if (old_d != delta) {
++ dv = interm->gamma_values[delta][i] - interm->gamma_values[delta + 1][i];
++ old_d = delta;
++ }
++ if (unlikely(attenuation == 0)) attenuation = 1;
++ interm->interpolated_values[br][i] = interm->gamma_values[base][i] + sign(dv) * (abs(dv * multiplier) + bias) / attenuation;
++ }
++ }
++
++ u8 *pint_gamma = &panel_data->interpolated_gamma[0][0];
++ u16 *pint_values = &interm->interpolated_values[0][0];
++
++ for (int i = 0; i < 101; ++i) {
++ for (int j = 0; j <= 32; ++j) {
++ switch (j) {
++ case 9: case 20: case 31:
++ *pint_gamma = (((u8)*(pint_values - 2) >> 1 ^ (u8)*pint_values) & 0x7f) ^ (u8)(*(pint_values - 2) >> 1);
++ break;
++ case 10: case 21: case 32:
++ *pint_gamma = (u8)*pint_values & 0xf;
++ break;
++ default:
++ *pint_gamma = (u8)*pint_values;
++ }
++ ++pint_gamma;
++ ++pint_values;
++ }
++ }
++ for (int i = 0; i < 101; ++i) {
++ drm_dbg_kms(display->drm, "interpolated gamma %3d %*phN", i, 33, panel_data->interpolated_gamma[i]);
++ }
++}
++
++static int intel_dp_aux_legacy_load_panel_properties(struct intel_connector *connector)
++{
++ struct intel_display *display = to_intel_display(connector);
++ struct device *dev = connector->encoder->base.dev->dev;
++ struct intel_legacy_panel_data *panel_data = &connector->panel.backlight.edp.legacy.panel_data;
++ struct intel_legacy_panel_data_intermediate *interm = panel_data->intermediates;
++ int ret;
++ const struct firmware *fw;
++ const u8 *pdata;
++ u16 len;
++
++ ret = request_firmware(&fw, "intel_legacy_panel_data.bin", dev);
++ if (ret < 0)
++ return ret;
++ ret = 0;
++ pdata = fw->data;
++
++#define load_array(arr, maxlen) \
++ if (fw->size - (pdata - fw->data) < 2) goto fail_invalid_data; \
++ memcpy(&len, pdata, 2); \
++ pdata += 2; \
++ len = __le16_to_cpu(len); \
++ if (len > maxlen) goto fail_invalid_data; \
++ if (fw->size - (pdata - fw->data) < len) goto fail_invalid_data; \
++ memcpy(arr, pdata, len); \
++ pdata += len;
++
++ load_array(panel_data->aor_buf, 202);
++ load_array(panel_data->delvss, 101);
++
++ if (fw->size - (pdata - fw->data) < 2) goto fail_invalid_data;
++ memcpy(&panel_data->brightness_for_acl, pdata, 2);
++ pdata += 2;
++ panel_data->brightness_for_acl = __le16_to_cpu(panel_data->brightness_for_acl);
++
++ load_array(panel_data->acl_cutoff_and_delta_buf, 200);
++ load_array(panel_data->IRC, 909);
++ load_array(panel_data->SP, 101);
++ load_array(interm->interpolation, 505);
++
++ release_firmware(fw);
++ return ret;
++fail_invalid_data:
++ release_firmware(fw);
++ drm_err(display->drm, "invalid intel_legacy_panel_data.bin");
++ return -EINVAL;
++}
++
++static int intel_dp_aux_legacy_setup_backlight(struct intel_connector *connector, enum pipe unused)
++{
++ struct intel_panel *panel = &connector->panel;
++ struct intel_legacy_panel_data *panel_data = &connector->panel.backlight.edp.legacy.panel_data;
++ int ret;
++
++ panel_data->intermediates = kzalloc(sizeof(struct intel_legacy_panel_data_intermediate), GFP_KERNEL);
++ ret = intel_dp_aux_legacy_load_panel_properties(connector);
++ if (ret < 0)
++ goto fail;
++ panel->backlight.max = 100;
++ panel->backlight.min = 0;
++ panel->backlight.level = 100;
++ if (!intel_dp_aux_legacy_handshake(connector)) {
++ ret = -ENODEV;
++ goto fail;
++ }
++ intel_dp_aux_legacy_get_elvss_max_offset(connector);
++ if (!intel_dp_aux_legacy_get_tcon_gamma(connector)) {
++ ret = -ENODEV;
++ goto fail;
++ }
++ intel_dp_aux_legacy_calculate_interpolated_values(connector);
++ panel_data->values_initialized = true;
++ ret = 0;
++fail:
++ kfree(panel_data->intermediates);
++ panel_data->intermediates = NULL;
++ return ret;
++}
++static void intel_dp_aux_legacy_disable_backlight(const struct drm_connector_state *old_conn_state,
++ u32 level)
++{
++ struct intel_connector *connector = to_intel_connector(old_conn_state->connector);
++ struct intel_legacy_panel_data *panel_data = &connector->panel.backlight.edp.legacy.panel_data;
++ if (!panel_data->values_initialized)
++ intel_dp_aux_legacy_setup_backlight(connector, PIPE_A);
++ intel_dp_aux_legacy_write_brightness(connector, 0);
++}
++static void
++intel_dp_aux_legacy_enable_backlight(const struct intel_crtc_state *crtc_state,
++ const struct drm_connector_state *conn_state, u32 level)
++{
++ struct intel_connector *connector = to_intel_connector(conn_state->connector);
++ struct intel_panel *panel = &connector->panel;
++ struct intel_legacy_panel_data *panel_data = &panel->backlight.edp.legacy.panel_data;
++ if (level > 100) level = 100;
++ panel->backlight.level = level;
++ if (!panel_data->values_initialized)
++ intel_dp_aux_legacy_setup_backlight(connector, PIPE_A);
++ intel_dp_aux_legacy_write_brightness(connector, level);
++}
++static u32 intel_dp_aux_legacy_get_backlight(struct intel_connector *connector, enum pipe unused)
++{
++ struct intel_panel *panel = &connector->panel;
++ return panel->backlight.level;
++}
++static void
++intel_dp_aux_legacy_set_backlight(const struct drm_connector_state *conn_state, u32 level)
++{
++ struct intel_connector *connector = to_intel_connector(conn_state->connector);
++ struct intel_panel *panel = &connector->panel;
++ struct intel_legacy_panel_data *panel_data = &panel->backlight.edp.legacy.panel_data;
++ if (level > 100) level = 100;
++ panel->backlight.level = level;
++ if (!panel_data->values_initialized)
++ intel_dp_aux_legacy_setup_backlight(connector, PIPE_A);
++ intel_dp_aux_legacy_write_brightness(connector, level);
++}
++
++static const struct intel_panel_bl_funcs intel_dp_aux_legacy_bl_funcs = {
++ .setup = intel_dp_aux_legacy_setup_backlight,
++ .enable = intel_dp_aux_legacy_enable_backlight,
++ .disable = intel_dp_aux_legacy_disable_backlight,
++ .set = intel_dp_aux_legacy_set_backlight,
++ .get = intel_dp_aux_legacy_get_backlight,
++};
++
++const struct intel_panel_bl_funcs* intel_dp_aux_legacy_get_bl_funcs(void) {
++ return &intel_dp_aux_legacy_bl_funcs;
++}
++bool intel_dp_aux_legacy_brightness_supported(struct intel_connector *connector) {
++ return intel_dp_aux_legacy_handshake(connector);
++}
+diff --git a/drivers/gpu/drm/i915/display/intel_dp_legacy_oled_brightness.h b/drivers/gpu/drm/i915/display/intel_dp_legacy_oled_brightness.h
+new file mode 100644
+index 000000000000..785509e608c8
+--- /dev/null
++++ b/drivers/gpu/drm/i915/display/intel_dp_legacy_oled_brightness.h
+@@ -0,0 +1,40 @@
++/* SPDX-License-Identifier: MIT */
++/*
++ * Copyright © 2025 Chris Xiong
++ */
++
++#ifndef __INTEL_DP_LEGACY_OLED_BRIGHTNESS_H__
++#define __INTEL_DP_LEGACY_OLED_BRIGHTNESS_H__
++
++#include <linux/types.h>
++
++struct intel_connector;
++struct intel_panel_bl_funcs;
++
++struct intel_legacy_panel_data_intermediate {
++ u8 interpolation[101][5];
++
++ u16 gamma_values[32][33];
++ u16 interpolated_values[101][33];
++ u8 tcon_gamma[32][33];
++};
++
++struct intel_legacy_panel_data {
++ //registry values
++ u8 aor_buf[202]; // u16[101]
++ u8 delvss[101];
++ u16 brightness_for_acl;
++ u8 acl_cutoff_and_delta_buf[200]; // u16[100]
++ u8 IRC[101][9];
++ u8 SP[101];
++
++ u8 interpolated_gamma[101][33];
++ u8 elvss_max_offset;
++ bool values_initialized;
++ struct intel_legacy_panel_data_intermediate* intermediates;
++};
++
++const struct intel_panel_bl_funcs* intel_dp_aux_legacy_get_bl_funcs(void);
++bool intel_dp_aux_legacy_brightness_supported(struct intel_connector *connector);
++
++#endif /* __INTEL_DP_LEGACY_OLED_BRIGHTNESS_H__ */