diff options
author | 2025-05-04 01:10:58 -0400 | |
---|---|---|
committer | 2025-05-04 01:12:00 -0400 | |
commit | 397df5f4c5cb8d3e05ddd1c0e8da2386f61386f5 (patch) | |
tree | b4d83b71fc79fdd53571dbbf3724b9adeb02399c | |
download | linux-legacy-oled-brightness-397df5f4c5cb8d3e05ddd1c0e8da2386f61386f5.tar.xz |
-rw-r--r-- | README.rst | 32 | ||||
-rw-r--r-- | gen_panel_prop.py | 227 | ||||
-rw-r--r-- | linux-legacy-oled-brightness.patch | 582 |
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__ */ |