aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.rst32
-rw-r--r--gen_panel_prop.py227
-rw-r--r--linux-legacy-oled-brightness.patch582
3 files changed, 841 insertions, 0 deletions
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..c6d15d2
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,32 @@
+Proper PWM brightness for legacy OLED pannels on an Intel GPU
+=============================================================
+
+Reverse engineered from the Windows Driver. Thanks Lenovo for including the debugging symbols in your driver
+package so that even a reverse engineering illiterate like me can do it.
+
+If your computer has an OLED screen and meets one of the following criteria, there's a chance that this
+driver might work for you:
+
+ - Your computer is released between 2015-2018, has an Intel processor and uses its integrated GPU.
+ - The ``.inf`` file in the Windows driver package for your computer's OLED panel contains the following text: ``AOR``, ``DELVSS``, ``BrightLevelForACL``, and ``ACLCutoffAndDelta``.
+
+This repository contains the required kernel patches and tools to generate the firmware used by the driver.
+
+ - ``linux-legacy-oled-brightness.patch``: The patch itself. Sorry I didn't bother writing a proper commit message.
+ - ``gen_panel_prop.py``: Generates ``intel_legacy_panel_data.bin``, a firmware file required by the driver.
+
+`Read this for more information, I guess. <https://chrisoft.org/blog/post/2025-04-28.html>`_
+
+Instructions for ThinkPad X1 Yoga 1st gen / 2nd gen:
+
+ 1. Run ``gen_panel_prop.py`` to generate ``intel_legacy_panel_data.bin``. Put the generated file in your
+ firmware directory (and if needed, your initramfs).
+ 2. Patch the sources for the kernel of your choice. Compile and install it.
+ 3. Boot your new kernel with an additional kernel parameter ``i915.enable_dpcd_backlight=99``. Check if it works.
+
+Instructions for other laptops:
+
+To be written
+
+This repo will be updated at the same pace as my local system updates (i.e. pretty much randomly) as long as I
+still have the laptop and haven't been frustrated with it.
diff --git a/gen_panel_prop.py b/gen_panel_prop.py
new file mode 100644
index 0000000..23dcd9f
--- /dev/null
+++ b/gen_panel_prop.py
@@ -0,0 +1,227 @@
+AOR = [ # 202 items expected
+ 0x0a,0xd8,0x0a,0xc8,0x0a,0xc0,0x0a,0xb0,0x0a,0xa8,0x0a,0x98,0x0a,0x90,0x0a,0x80,0x0a,0x78,0x0a,0x60,0x0a,0x48,0x0a,0x30,0x0a,
+ 0x18,0x09,0xe0,0x09,0xd0,0x09,0xb8,0x09,0xa0,0x09,0x80,0x09,0x68,0x09,0x50,0x09,0x20,0x09,0x00,0x08,0xe8,0x08,0xd0,0x08,0x98,
+ 0x08,0x80,0x08,0x68,0x08,0x48,0x08,0x38,0x08,0x28,0x07,0xe0,0x07,0xc0,0x07,0xa8,0x07,0x68,0x07,0x40,0x07,0x10,0x06,0xc0,0x06,
+ 0x70,0x06,0x58,0x06,0x28,0x05,0xf0,0x05,0xb8,0x05,0x88,0x05,0x50,0x04,0xe0,0x04,0x90,0x04,0x60,0x04,0x20,0x03,0xf0,0x03,0xa0,
+ 0x03,0x50,0x03,0x18,0x02,0xc0,0x02,0x60,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,
+ 0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,
+ 0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x58,0x02,0x70,0x02,0x30,0x01,0xe8,0x01,0xa8,0x01,
+ 0x70,0x01,0x28,0x00,0xe0,0x00,0x98,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,
+ 0x00,0x70
+]
+DELVSS = [ # 101 items expected
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x84,0x85,0x86,0x86,0x87,0x87,0x87,0x88,0x89,0x89,0x8a,0x8b,0x8b,0x8c,0x8c,0x8d,
+ 0x8d,0x8e,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8e,0x8d,0x8c,0x8b,
+ 0x8a,0x8a,0x8a,0x89,0x89,0x88,0x87,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x85,0x85,0x84,0x83,0x82,0x82,0x81,0x81,
+ 0x81
+]
+BrightLevelForACL = 79
+ACLCutoffAndDelta = [ # at least (100 - BrightLevelForACL) * 2 items expected
+ 0xe5,0x64,0xe5,0x64,0xe5,0x64,0xe5,0x64,0xd8,0x68,0xd8,0x68,0xd8,0x68,0xd8,0x68,0xd8,0x68,0xd8,0x68,0xd8,0x68,0xd8,0x68,0xd8,
+ 0x68,0xd8,0x68,0xcc,0x68,0xcc,0x68,0xcc,0x68,0xcc,0x68,0xcc,0x68,0xcc,0x68,0xcc,0x68
+]
+IRC = [ # 909 items expected
+ 0x03,0x03,0x03,0x00,0x01,0x00,0x01,0x01,0x01,0x03,0x03,0x03,0x01,0x01,0x00,0x00,0x01,0x01,0x03,0x04,0x03,0x01,0x00,0x01,0x01,
+ 0x01,0x00,0x04,0x04,0x03,0x00,0x00,0x01,0x01,0x01,0x00,0x04,0x04,0x03,0x00,0x00,0x01,0x01,0x02,0x00,0x04,0x04,0x03,0x00,0x01,
+ 0x01,0x01,0x01,0x01,0x04,0x04,0x03,0x00,0x01,0x01,0x02,0x01,0x01,0x04,0x05,0x04,0x01,0x00,0x00,0x01,0x01,0x01,0x04,0x05,0x04,
+ 0x01,0x00,0x01,0x01,0x02,0x00,0x05,0x05,0x04,0x00,0x01,0x01,0x01,0x01,0x01,0x05,0x05,0x04,0x01,0x01,0x01,0x01,0x02,0x01,0x05,
+ 0x06,
+ 0x05,0x01,0x00,0x01,0x01,0x02,0x00,0x06,0x06,0x05,0x00,0x01,0x01,0x02,0x02,0x01,0x06,0x07,0x05,0x01,0x01,0x02,0x02,0x02,0x01,
+ 0x07,0x07,0x06,0x00,0x01,0x01,0x02,0x02,0x01,0x07,0x08,0x06,0x01,0x00,0x01,0x02,0x03,0x01,0x07,0x08,0x06,0x01,0x01,0x02,0x02,
+ 0x02,0x01,0x08,0x08,0x07,0x01,0x01,0x01,0x02,0x03,0x01,0x08,0x09,0x07,0x01,0x01,0x01,0x02,0x02,0x02,0x08,0x09,0x07,0x01,0x01,
+ 0x02,0x03,0x03,0x01,0x09,0x0a,0x08,0x01,0x01,0x01,0x03,0x03,0x02,0x09,0x0a,0x08,0x01,0x01,0x02,0x03,0x03,0x01,0x0a,0x0a,0x08,
+ 0x01,
+ 0x02,0x02,0x02,0x03,0x02,0x0a,0x0b,0x09,0x01,0x01,0x02,0x03,0x03,0x01,0x0b,0x0c,0x09,0x01,0x01,0x02,0x03,0x03,0x02,0x0b,0x0c,
+ 0x09,0x01,0x01,0x03,0x03,0x04,0x01,0x0b,0x0c,0x0a,0x02,0x02,0x02,0x03,0x03,0x02,0x0c,0x0d,0x0a,0x01,0x01,0x02,0x03,0x04,0x02,
+ 0x0c,0x0d,0x0a,0x01,0x02,0x03,0x04,0x03,0x02,0x0c,0x0d,0x0a,0x02,0x02,0x03,0x03,0x04,0x02,0x0d,0x0e,0x0b,0x02,0x02,0x03,0x03,
+ 0x04,0x02,0x0d,0x0e,0x0b,0x02,0x02,0x03,0x04,0x04,0x02,0x0e,0x0f,0x0c,0x01,0x02,0x02,0x04,0x04,0x03,0x0f,0x10,0x0c,0x01,0x02,
+ 0x03,
+ 0x04,0x04,0x03,0x0f,0x10,0x0d,0x02,0x02,0x03,0x04,0x05,0x02,0x10,0x11,0x0d,0x02,0x02,0x03,0x04,0x05,0x03,0x11,0x12,0x0e,0x02,
+ 0x02,0x04,0x04,0x05,0x02,0x12,0x13,0x0f,0x02,0x02,0x04,0x05,0x06,0x02,0x12,0x13,0x0f,0x02,0x03,0x04,0x05,0x05,0x03,0x13,0x14,
+ 0x10,0x02,0x03,0x04,0x05,0x05,0x03,0x13,0x15,0x10,0x03,0x02,0x04,0x05,0x06,0x03,0x14,0x16,0x11,0x02,0x02,0x04,0x06,0x06,0x03,
+ 0x15,0x16,0x12,0x02,0x03,0x04,0x06,0x06,0x03,0x15,0x17,0x12,0x03,0x03,0x04,0x06,0x06,0x04,0x17,0x18,0x13,0x02,0x03,0x05,0x07,
+ 0x07,
+ 0x03,0x18,0x1a,0x14,0x03,0x03,0x05,0x06,0x07,0x04,0x19,0x1b,0x15,0x03,0x03,0x05,0x06,0x07,0x04,0x1a,0x1c,0x16,0x03,0x03,0x05,
+ 0x07,0x08,0x04,0x1a,0x1c,0x16,0x03,0x04,0x06,0x08,0x08,0x04,0x1b,0x1e,0x17,0x04,0x03,0x06,0x07,0x08,0x04,0x1c,0x1f,0x18,0x04,
+ 0x03,0x06,0x07,0x09,0x04,0x1d,0x1f,0x19,0x03,0x04,0x05,0x08,0x09,0x05,0x1e,0x20,0x1a,0x04,0x04,0x06,0x08,0x09,0x04,0x1f,0x21,
+ 0x1a,0x04,0x05,0x07,0x08,0x09,0x05,0x20,0x23,0x1b,0x04,0x04,0x07,0x08,0x09,0x05,0x21,0x23,0x1c,0x04,0x05,0x06,0x08,0x09,0x06,
+ 0x22,
+ 0x25,0x1d,0x04,0x04,0x07,0x09,0x0a,0x05,0x23,0x26,0x1e,0x04,0x04,0x07,0x0a,0x0b,0x05,0x24,0x27,0x1f,0x04,0x05,0x07,0x0a,0x0a,
+ 0x06,0x25,0x28,0x1f,0x04,0x05,0x08,0x0a,0x0b,0x06,0x26,0x29,0x21,0x05,0x05,0x07,0x0a,0x0c,0x06,0x28,0x2b,0x22,0x04,0x05,0x08,
+ 0x0b,0x0c,0x06,0x29,0x2c,0x23,0x05,0x06,0x08,0x0b,0x0c,0x07,0x2a,0x2d,0x24,0x05,0x06,0x08,0x0b,0x0c,0x07,0x2b,0x2f,0x25,0x06,
+ 0x05,0x09,0x0b,0x0d,0x06,0x2d,0x30,0x26,0x05,0x06,0x09,0x0c,0x0d,0x07,0x2e,0x32,0x27,0x06,0x06,0x09,0x0c,0x0d,0x08,0x30,0x33,
+ 0x29,
+ 0x05,0x07,0x09,0x0d,0x0e,0x08,0x31,0x35,0x29,0x06,0x06,0x0a,0x0d,0x0e,0x08,0x32,0x36,0x2a,0x06,0x06,0x0a,0x0d,0x0f,0x08,0x33,
+ 0x37,0x2b,0x06,0x06,0x0a,0x0d,0x10,0x08,0x34,0x39,0x2c,0x07,0x06,0x0b,0x0e,0x10,0x08,0x36,0x3a,0x2e,0x06,0x07,0x0b,0x0f,0x11,
+ 0x08,0x38,0x3c,0x30,0x07,0x08,0x0b,0x0f,0x11,0x09,0x39,0x3e,0x31,0x07,0x07,0x0b,0x10,0x12,0x09,0x3a,0x3f,0x32,0x07,0x08,0x0b,
+ 0x10,0x11,0x0a,0x3c,0x40,0x33,0x07,0x08,0x0c,0x10,0x12,0x09,0x3d,0x42,0x34,0x07,0x08,0x0c,0x11,0x12,0x0a,0x3e,0x43,0x35,0x08,
+ 0x08,
+ 0x0c,0x11,0x13,0x0a,0x40,0x45,0x36,0x07,0x08,0x0d,0x11,0x13,0x0a,0x41,0x46,0x37,0x08,0x09,0x0d,0x11,0x13,0x0b,0x43,0x48,0x39,
+ 0x08,0x09,0x0d,0x12,0x14,0x0b,0x45,0x4b,0x3b,0x08,0x08,0x0d,0x13,0x15,0x0b,0x46,0x4c,0x3c,0x09,0x09,0x0e,0x13,0x15,0x0b,0x48,
+ 0x4d,0x3d,0x08,0x0a,0x0e,0x14,0x15,0x0c,0x49,0x4f,0x3e,0x09,0x0a,0x0f,0x14,0x16,0x0c,0x4b,0x51,0x3f,0x09,0x09,0x0f,0x14,0x17,
+ 0x0c,0x4c,0x52,0x41,0x09,0x0a,0x0f,0x15,0x17,0x0c,0x4e,0x54,0x42,0x09,0x0a,0x10,0x15,0x18,0x0c,0x50,0x56,0x44,0x09,0x0a,0x10,
+ 0x16,
+ 0x18,0x0c,0x51,0x58,0x45,0x0a,0x0a,0x10,0x16,0x19,0x0d,0x53,0x5a,0x47,0x0a,0x0a,0x10,0x16,0x19,0x0d,0x55,0x5b,0x48,0x0a,0x0b,
+ 0x11,0x17,0x1a,0x0d,0x56,0x5d,0x49,0x0b,0x0b,0x12,0x17,0x1a,0x0d,0x58,0x5f,0x4b,0x0b,0x0c,0x12,0x18,0x1a,0x0e,0x5a,0x61,0x4d,
+ 0x0b,0x0c,0x12,0x18,0x1b,0x0e,0x5c,0x63,0x4e,0x0b,0x0c,0x12,0x18,0x1b,0x0f,0x5e,0x66,0x50,0x0b,0x0c,0x13,0x1a,0x1c,0x0f,0x60,
+ 0x67,0x51,0x0b,0x0d,0x13,0x1a,0x1c,0x10,0x61,0x69,0x53,0x0c,0x0d,0x13,0x1a,0x1d,0x10,0x64,0x6c,0x55,0x0c,0x0d,0x14,0x1b,0x1e,
+ 0x10
+]
+SP = [ # 101 items expected
+ 0x0f,0x0f,0x10,0x10,0x11,0x11,0x12,0x12,0x13,0x14,0x15,0x16,0x16,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1d,0x1f,0x1f,0x20,0x21,0x23,
+ 0x24,0x25,0x25,0x26,0x27,0x29,0x29,0x2a,0x2d,0x2d,0x2f,0x31,0x34,0x34,0x36,0x37,0x39,0x3b,0x3c,0x3f,0x42,0x44,0x46,0x47,0x4a,
+ 0x4c,0x4d,0x50,0x52,0x55,0x56,0x5a,0x5c,0x5f,0x62,0x65,0x69,0x6c,0x6f,0x73,0x76,0x7a,0x7e,0x81,0x84,0x86,0x8b,0x8f,0x94,0x98,
+ 0x9a,0x9e,0xa2,0xa5,0xa9,0xac,0xb1,0xb7,0xbb,0xbd,0xc1,0xc4,0xc8,0xcb,0xcf,0xd3,0xd6,0xdb,0xdf,0xe4,0xe9,0xed,0xf3,0xf7,0xfc,
+ 0xff
+]
+
+Interpolation = [
+ # entry, base, delta, multiplier, bias, attenuation
+ # entry must be 0..100 in increasing order, and the firmware generation code doesn't actually read or use it
+ # (it's for the person typing out these numbers only)
+ # base = 0..31
+ # delta = 0..30
+ # attenuation can't be 0
+ # formula used:
+ # delta_value = gamma[delta][i] - gamma[delta + 1][i]
+ # interpolated[entry][i] = gamma[base][i] + sgn(delta_value) * (abs(delta_value * multiplier) + bias) / attenuation
+ 0, 0, 0, 0, 0, 1,
+ 1, 1, 0, 8, 4, 9,
+ 2, 1, 0, 7, 4, 9,
+ 3, 1, 0, 2, 1, 3,
+ 4, 1, 0, 5, 4, 9,
+ 5, 1, 0, 4, 4, 9,
+ 6, 1, 0, 1, 1, 3,
+ 7, 1, 0, 2, 4, 9,
+ 8, 1, 0, 1, 4, 9,
+ 9, 1, 0, 0, 0, 1,
+ 10, 2, 1, 8, 4, 9,
+ 11, 2, 1, 7, 4, 9,
+ 12, 2, 1, 2, 1, 3,
+ 13, 2, 1, 5, 4, 9,
+ 14, 2, 1, 4, 4, 9,
+ 15, 2, 1, 1, 1, 3,
+ 16, 2, 1, 2, 4, 9,
+ 17, 2, 1, 1, 4, 9,
+ 18, 2, 1, 0, 0, 1,
+ 19, 3, 2, 7, 4, 8,
+ 20, 3, 2, 3, 2, 4,
+ 21, 3, 2, 5, 4, 8,
+ 22, 3, 2, 1, 1, 2,
+ 23, 3, 2, 3, 4, 8,
+ 24, 3, 2, 1, 2, 4,
+ 25, 3, 2, 1, 4, 8,
+ 26, 3, 2, 0, 0, 1,
+ 27, 4, 3, 8, 4, 9,
+ 28, 4, 3, 7, 4, 9,
+ 29, 4, 3, 2, 1, 3,
+ 30, 4, 3, 5, 4, 9,
+ 31, 4, 3, 4, 4, 9,
+ 32, 4, 3, 1, 1, 3,
+ 33, 4, 3, 2, 4, 9,
+ 34, 4, 3, 1, 4, 9,
+ 35, 4, 3, 0, 0, 1,
+ 36, 5, 4, 5, 3, 6,
+ 37, 5, 4, 2, 1, 3,
+ 38, 5, 4, 1, 1, 2,
+ 39, 5, 4, 1, 1, 3,
+ 40, 5, 4, 1, 3, 6,
+ 41, 5, 4, 0, 0, 1,
+ 42, 6, 5, 5, 3, 6,
+ 43, 6, 5, 2, 1, 3,
+ 44, 6, 5, 1, 1, 2,
+ 45, 6, 5, 1, 1, 3,
+ 46, 6, 5, 1, 3, 6,
+ 47, 6, 5, 0, 0, 1,
+ 48, 7, 6, 5, 3, 6,
+ 49, 7, 6, 2, 1, 3,
+ 50, 7, 6, 1, 1, 2,
+ 51, 7, 6, 1, 1, 3,
+ 52, 7, 6, 1, 3, 6,
+ 53, 7, 6, 0, 0, 1,
+ 54, 8, 7, 3, 2, 4,
+ 55, 8, 7, 1, 1, 2,
+ 56, 8, 7, 1, 2, 4,
+ 57, 8, 7, 0, 0, 1,
+ 58, 9, 8, 3, 2, 4,
+ 59, 9, 8, 1, 1, 2,
+ 60, 9, 8, 1, 2, 4,
+ 61, 9, 8, 0, 0, 1,
+ 62, 10, 9, 2, 1, 3,
+ 63, 10, 9, 1, 1, 3,
+ 64, 10, 9, 0, 0, 1,
+ 65, 11, 10, 2, 1, 3,
+ 66, 11, 10, 1, 1, 3,
+ 67, 11, 10, 0, 0, 1,
+ 68, 12, 11, 2, 1, 3,
+ 69, 12, 11, 1, 1, 3,
+ 70, 12, 11, 0, 0, 1,
+ 71, 13, 12, 3, 2, 4,
+ 72, 13, 12, 1, 1, 2,
+ 73, 13, 12, 1, 2, 4,
+ 74, 13, 12, 0, 0, 1,
+ 75, 14, 13, 2, 1, 3,
+ 76, 14, 13, 1, 1, 3,
+ 77, 14, 13, 0, 0, 1,
+ 78, 15, 14, 0, 0, 1,
+ 79, 16, 15, 1, 1, 2,
+ 80, 16, 15, 0, 0, 1,
+ 81, 17, 16, 0, 0, 1,
+ 82, 18, 17, 0, 0, 1,
+ 83, 19, 18, 1, 1, 2,
+ 84, 19, 18, 0, 0, 1,
+ 85, 20, 19, 0, 0, 1,
+ 86, 21, 20, 0, 0, 1,
+ 87, 22, 21, 0, 0, 1,
+ 88, 23, 22, 0, 0, 1,
+ 89, 24, 23, 0, 0, 1,
+ 90, 25, 24, 0, 0, 1,
+ 91, 26, 25, 0, 0, 1,
+ 92, 27, 26, 0, 0, 1,
+ 93, 28, 27, 1, 1, 2,
+ 94, 28, 27, 0, 0, 1,
+ 95, 29, 28, 1, 1, 2,
+ 96, 29, 28, 0, 0, 1,
+ 97, 30, 29, 1, 1, 2,
+ 98, 30, 29, 0, 0, 1,
+ 99, 31, 30, 1, 1, 2,
+ 100, 31, 30, 0, 0, 1,
+]
+
+if len(AOR) != 202:
+ print(f"Unexpected length of AOR: {len(AOR)} != 202")
+ exit(1)
+if len(DELVSS) != 101:
+ print(f"Unexpected length of DELVSS: {len(DELVSS)} != 101")
+ exit(1)
+if len(ACLCutoffAndDelta) < (100 - BrightLevelForACL) * 2:
+ print(f"Not enough items in ACLCutoffAndDelta: {len(ACLCutoffAndDelta)} < (100 - {BrightLevelForACL}) * 2")
+ exit(1)
+if len(ACLCutoffAndDelta) > 200:
+ print(f"Too many items in ACLCutoffAndDelta: {len(ACLCutoffAndDelta)}")
+ exit(1)
+if len(IRC) != 909:
+ print(f"Unexpected length of IRC: {len(IRC)} != 909")
+ exit(1)
+if len(SP) != 101:
+ print(f"Unexpected length of SP: {len(SP)} != 101")
+ exit(1)
+if len(Interpolation) != 606:
+ print(f"Invalid length for the interpolation array: {len(Interpolation)} != 606")
+output = bytes()
+output += len(AOR).to_bytes(2, "little") + bytes(AOR)
+output += len(DELVSS).to_bytes(2, "little") + bytes(DELVSS)
+output += BrightLevelForACL.to_bytes(2, "little")
+output += len(ACLCutoffAndDelta).to_bytes(2, "little") + bytes(ACLCutoffAndDelta)
+output += len(IRC).to_bytes(2, "little") + bytes(IRC)
+output += len(SP).to_bytes(2, "little") + bytes(SP)
+output += (101 * 5).to_bytes(2, "little")
+for i in range(0, 101):
+ [_, b, d, mult, bias, attn] = Interpolation[i * 6:(i + 1) * 6]
+ if b > 31 or d > 30 or b < 0 or d < 0 or attn == 0:
+ print(f"Interpolation has invalid values in row {i}")
+ exit(1)
+ output += bytes([b, d, mult, bias, attn])
+with open("intel_legacy_panel_data.bin", "wb") as f:
+ f.write(output)
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__ */