Bug 92391 - Asus Zenbook UX32LN: Backlight brightness controls (Fn+F5/Fn+F6) don't work
Summary: Asus Zenbook UX32LN: Backlight brightness controls (Fn+F5/Fn+F6) don't work
Status: CLOSED MOVED
Alias: None
Product: ACPI
Classification: Unclassified
Component: BIOS (show other bugs)
Hardware: Intel Linux
: P1 normal
Assignee: Aaron Lu
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2015-01-31 06:34 UTC by Alexey Loukianov
Modified: 2016-08-01 06:19 UTC (History)
2 users (show)

See Also:
Kernel Version: 3.13+ (and maybe earlier - hadn't tested)
Subsystem:
Regression: No
Bisected commit-id:


Attachments
Asus UX32LN acpidump for BIOS v.203 (150.51 KB, application/x-bzip)
2015-01-31 06:34 UTC, Alexey Loukianov
Details

Description Alexey Loukianov 2015-01-31 06:34:01 UTC
Created attachment 165341 [details]
Asus UX32LN acpidump for BIOS v.203

This bug seems to be somewhat alike bug #70241, i.e. it looks like to be caused by lack of proper interaction between ACPI and intel gfx driver.

Symptoms are exactly the same:
1) With default kernel cmdline - backlight keys do not work at all. At the same time I'm able to control backlight using mate-power-manager GUI or through sysfs. acpi_listen shows that there are no events emitted when I press Fn+F5 or Fn+F6.

2) Adding 'acpi_osi=' to the end of kernel cmdline works as workaround: it become possible to control backlight brightness but it seems that it is being controlled by two instances at once (ACPI AML and mate-power-manager) so each Fn+F5/F6 press leads to two separate changes in quick sequence to backlight brightness.

3) Disassembling DSDT code and digging into _Q0E/_Q0F reveals that for MSOS greater or equal than OSVT LCDD._DCS checks being made for GFX0 - if present - or for RP05.PEGP and if result is not equal to 0x1F then it just Return() effectively ignoring Fn+F5/F6 events. After spending some amount of time trying to understand logic behing CADL/DIDL/DDLx/et.al. manipulations looks like ACPI code expects LCD eDP panel to have ID with third octet equal to 4 and it stores its value to DIDX:

...
                        
            Device (DD01)
            {
                Method (_ADR, 0, Serialized)  // _ADR: Address
                {
                    If (LEqual (And (0x0F00, DID1), 0x0400))
                    {
                        Store (One, EDPV)
                        Store (NXD1, NXDX)
                        Store (DID1, DIDX)
                        Return (One)
                    }

...



Same repeats for all other DD0x. 
Device LCDD uses stored value as following:

...
            Device (LCDD)
            {
                Method (_ADR, 0, Serialized)  // _ADR: Address
                {
                    If (LEqual (EDPV, Zero))
                    {
                        Return (0x1F)
                    }
                    Else
                    {
                        Return (And (0xFFFF, DIDX))
                    }
                }

                Method (_DCS, 0, NotSerialized)  // _DCS: Display Current Status
                {
                    If (LEqual (EDPV, Zero))
                    {
                        Return (Zero)
                    }
                    Else
                    {
                        Return (CDDS (DIDX))
                    }
                }
...

So it looks like that SANV.DIDx are expected to contain some kind of "display ID", with eDP panel id determined as ((ID&0x0F00)==0x400) and stored into SANV.DIDX. 

IDs to be stored into SANV.DIDx are generated in SDDL using formula: (IGDM.DDLx & 0x0F0F) | 0x8000000. Actual population of SANV.DIDx happens in _DOD method like this:

...
                    Store (Zero, NDID)
                    If (LNotEqual (DIDL, Zero))
                    {
                        Store (SDDL (DDL2), DID1)
                    }
    
                    If (LNotEqual (DDL2, Zero))
                    {
                        Store (SDDL (DIDL), DID2)
                    }

                    If (LNotEqual (DDL3, Zero))
                    {
                        Store (SDDL (DDL3), DID3)
                    }
                    
                    If (LNotEqual (DDL4, Zero))
                    {
                        Store (SDDL (DDL4), DID4)
                    }
...

Each call to SDDL increments NDID which is used later on. Here there seems to be a minor bug in firmware with DIDL and DDL2 placed into wrong in Store() calls.

Next on the list is IGDM.CADL which seems to be expected to get initialized by gfx driver. Checking CADL..CAL8 contents on a live system using dd reveals that CADL is not being initialized at all. And this returns us to the very beginning - method LCDD._DCS, which in turn uses method CDDS to check if the specified device is in CADL and returns 0x1F if it is or 0x1D otherwise. _Q0E/_Q0F only process event in case 0x1F is returned and as they call _DCS for LCDD device which in turn uses expected to be initialized SANV.DIDX in a CDDS call. Thus having uninitialized CADL and non-populated DIDX end result is that brightness change keys are non-functional under linux.

To check and prove my findings I tried to hack a dirty workaround like this:

echo -ne '\x02\x00\x00\x80' | dd of=/dev/mem bs=1 seek=$(( $SANV_BASE + $DIDX_OFFSET)) count=4
dd if=/dev/mem skip=$(( $IGDM_BASE + $DIDL_OFFSET )) of=/dev/mem seek=$(( $IGDM_BASE + $CADL_OFFSET )) count=32 bs=1

Here I populate DIDX with 0x80000002 (display ID = 0x0002, this one was taken from the first entry in DIDL) and "initialize" CADL by copying there entire contents from DIDL (i.e. I trick ACPI into thinking that all attached displays are being "currently active"). Using this trick "fixes" the issue and brightness control start working as it should without any 'acpi_osi=' overrides.

I attach full acpidump output to this report.

P.S. There are some other ACPI-related bugs with this laptop which I suspect to be regressions introduced after 3.14, namely:

* Laptop fails to reboot or power itself off if it had been used for some time. Simply booting into systems and doing "shutdown -r now" or "shutdown -h now" works as expected but if I try to execute these some time after boot usual result is a kernel sitting at "Power down" or "Rebooting system" and doing nothing. I hadn't tried to bisect this bug due to its transient nature. It is not known for sure what amount of time should pass or what other conditions are to be met to trigger this bug.

* Laptop sometimes fails to resume properly from hibernate. Seems to be USB3-related and things got worse with recent hibernate-related changes.

* WiFi and Bluetooth control using Fn+F2 seems not to work unless I use 'acpi_osi=' at the end of kernel cmdline.

I'm going to file up separate reports for this issues after doing some tests with recently released 3.18.5. ATM I'm in process of recompiling 3.18.5 with custom DSDT I made to fix some obvious DTST/SSDT errors.

P.P.S. Bios v.203 is the latest available version from laptop vendor.
Comment 1 Aaron Lu 2015-02-12 08:39:19 UTC
For a proper solution to update CADL, please follow this bug:
https://bugs.freedesktop.org/show_bug.cgi?id=81762

Note You need to log in before you can comment on or make changes to this bug.