zhen-zen / YogaSMC

ACPI driver for OEM hardware.
GNU General Public License v2.0
225 stars 24 forks source link

PSC function support for DYTC 4.2+ #110

Open Someone52 opened 3 years ago

Someone52 commented 3 years ago

Lenovo Thinkpad E590 When trying to set the modes, I get the following:

YSMC - Info: ThinkVPC::KHEY DYTCMode command 0x12b001 result: 0x0000000a (invalid argument) YSMC - Info: ThinkVPC::HKEY DYTCMode toggle failed

After a bit of investigation, i've decompiled the dytc method on my laptop, which is as follows:

            Local0 = Arg0
            Local1 = Zero
            ADBG (Concatenate ("DYTC STT=", ToHexString (Local0)))
            If ((WNTF && TATC))
            {
                Switch (ToInteger ((Local0 & 0x01FF)))
                {
                    Case (Zero)
                    {
                        Local1 = 0x0100
                        Local1 |= 0x50000000
                        Local1 |= Zero
                        Local1 |= One
                    }
                    Case (One)
                    {
                        Local2 = ((Local0 >> 0x0C) & 0x0F)
                        Local3 = ((Local0 >> 0x10) & 0x0F)
                        Local4 = ((Local0 >> 0x14) & One)
                        ADBG ("DYTC_CMD_SET")
                        ADBG (Concatenate ("ICFunc=", ToHexString (Local2)))
                        ADBG (Concatenate ("ICMode=", ToHexString (Local3)))
                        ADBG (Concatenate ("ValidF=", ToHexString (Local4)))
                        Local5 = (One << Local2)
                        If ((FCAP & Local5)){}
                        Else
                        {
                            Local1 = 0x06
                            ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                            Return (Local1)
                        }

                        Switch (Local2)
                        {
                            Case (One)
                            {
                                If ((Local3 != 0x0F))
                                {
                                    Local1 = 0x0A
                                    ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                    Return (Local1)
                                }

                                If ((Local4 == Zero))
                                {
                                    VCQL = Zero
                                }
                                Else
                                {
                                    VCQL = One
                                }
                            }
                            Case (0x04)
                            {
                                If ((Local3 != 0x0F))
                                {
                                    Local1 = 0x0A
                                    ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                    Return (Local1)
                                }

                                If ((Local4 == Zero))
                                {
                                    VSTP = Zero
                                }
                                Else
                                {
                                    VSTP = One
                                }
                            }
                            Case (0x0B)
                            {
                                Switch (Local3)
                                {
                                    Case (0x0F)
                                    {
                                        If ((Local4 != Zero))
                                        {
                                            Local1 = 0x0A
                                            ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                            Return (Local1)
                                        }
                                    }
                                    Default
                                    {
                                        Local1 = 0x0A
                                        ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                        Return (Local1)
                                    }

                                }

                                If ((Local4 == Zero))
                                {
                                    VMMC = Zero
                                    SMMC = Zero
                                }
                                Else
                                {
                                    VMMC = One
                                    SMMC = Local3
                                }
                            }
                            Case (0x0D)
                            {
                                If (((Local3 <= 0x08) && (Local3 >= One)))
                                {
                                    If ((Local4 != One))
                                    {
                                        Local1 = 0x0A
                                        ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                        Return (Local1)
                                    }
                                }
                                ElseIf ((Local3 == 0x0F))
                                {
                                    If ((Local4 != Zero))
                                    {
                                        Local1 = 0x0A
                                        ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                        Return (Local1)
                                    }
                                }
                                Else
                                {
                                    Local1 = 0x0A
                                    ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                    Return (Local1)
                                }

                                If ((Local4 == Zero))
                                {
                                    VPSC = Zero
                                    SPSC = Zero
                                }
                                Else
                                {
                                    VPSC = One
                                    SPSC = Local3
                                }
                            }
                            Case (Zero)
                            {
                                If ((Local3 != 0x0F))
                                {
                                    Local1 = 0x0A
                                    ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                    Return (Local1)
                                }
                            }
                            Default
                            {
                                Local1 = 0x02
                                ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                Return (Local1)
                            }

                        }

                        ADBG (" Set ODM Variable")
                        If (CondRefOf (\_SB.IETM.DPTE))
                        {
                            If ((^^^^^IETM.DPTE & One))
                            {
                                ODV0 = STDV /* \STDV */
                                ODV1 = VCQL /* \VCQL */
                                ODV2 = VTIO /* \VTIO */
                                If (((VMYH == One) && (SMYH == Zero)))
                                {
                                    ODV3 = One
                                }
                                Else
                                {
                                    ODV3 = Zero
                                }

                                If (((VMYH == One) && (SMYH == One)))
                                {
                                    ODV4 = One
                                }
                                Else
                                {
                                    ODV4 = Zero
                                }

                                If (((VMYH == One) && (SMYH == 0x02)))
                                {
                                    ODV5 = One
                                }
                                Else
                                {
                                    ODV5 = Zero
                                }

                                ODV6 = VSTP /* \VSTP */
                                ODV7 = VCQH /* \VCQH */
                                ODV8 = VDCC /* \VDCC */
                                ODV9 = VSFN /* \VSFN */
                                ODVA = VDMC /* \VDMC */
                                ODVB = VFHP /* \VFHP */
                                ODVC = VIFC /* \VIFC */
                                If (((VMMC == One) && (SMMC == Zero)))
                                {
                                    ODVD = One
                                }
                                Else
                                {
                                    ODVD = Zero
                                }

                                If (((VMMC == One) && (SMMC == One)))
                                {
                                    ODVE = One
                                }
                                Else
                                {
                                    ODVE = Zero
                                }

                                If (((VMMC == One) && (SMMC == 0x02)))
                                {
                                    ODVF = One
                                }
                                Else
                                {
                                    ODVF = Zero
                                }

                                If (((VMMC == One) && (SMMC == 0x03)))
                                {
                                    ODVH = One
                                }
                                Else
                                {
                                    ODVH = Zero
                                }

                                ODVG = VMSC /* \VMSC */
                                If ((VPSC == One))
                                {
                                    ODVI = SPSC /* \SPSC */
                                }
                                Else
                                {
                                    ODVI = Zero
                                }

                                ODVJ = VCSC /* \VCSC */
                                Notify (IETM, 0x88) // Device-Specific
                            }
                        }

                        If ((VSTP == One))
                        {
                            SCPF (0x04, DPST, One, Zero)
                        }
                        ElseIf ((VMMC == One))
                        {
                            If ((SMMC == Zero))
                            {
                                DMMC = MMOP /* \MMOP */
                                SCPF (0x0B, DMMC, Zero, Zero)
                            }
                            ElseIf ((SMMC == One))
                            {
                                DMMC = MMCO /* \MMCO */
                                SCPF (0x0B, DMMC, Zero, Zero)
                            }
                            ElseIf ((SMMC == 0x02))
                            {
                                DMMC = MMPE /* \MMPE */
                                SCPF (0x0B, DMMC, Zero, Zero)
                            }
                            ElseIf ((SMMC == 0x03))
                            {
                                DMMC = MMQU /* \MMQU */
                                SCPF (0x0B, DMMC, Zero, Zero)
                            }
                        }
                        ElseIf ((VCQL == One))
                        {
                            SCPF (One, DCQL, Zero, Zero)
                        }
                        ElseIf ((VPSC == One))
                        {
                            If ((SPSC == 0x08))
                            {
                                DPSC = PSM8 /* \PSM8 */
                            }
                            ElseIf ((SPSC == 0x07))
                            {
                                DPSC = PSM7 /* \PSM7 */
                            }
                            ElseIf ((SPSC == 0x06))
                            {
                                DPSC = PSM6 /* \PSM6 */
                            }
                            ElseIf ((SPSC == 0x05))
                            {
                                DPSC = PSM5 /* \PSM5 */
                            }
                            ElseIf ((SPSC == 0x04))
                            {
                                DPSC = PSM4 /* \PSM4 */
                            }
                            ElseIf ((SPSC == 0x03))
                            {
                                DPSC = PSM3 /* \PSM3 */
                            }
                            ElseIf ((SPSC == 0x02))
                            {
                                DPSC = PSM2 /* \PSM2 */
                            }
                            ElseIf ((SPSC == One))
                            {
                                DPSC = PSM1 /* \PSM1 */
                            }
                            Else
                            {
                                DPSC = PSM7 /* \PSM7 */
                            }

                            If ((SPSC <= 0x04))
                            {
                                SCPF (0x0D, DPSC, 0x05, Zero)
                            }
                            ElseIf (((SPSC <= 0x06) && (SPSC >= 0x05)))
                            {
                                SCPF (0x0D, DPSC, Zero, Zero)
                            }
                            ElseIf (((SPSC <= 0x08) && (SPSC >= 0x07)))
                            {
                                SCPF (0x0D, DPSC, 0x04, Zero)
                            }
                        }
                        Else
                        {
                            SCPF (Zero, DSTD, Zero, Zero)
                        }

                        Local5 = VSTD /* \VSTD */
                        Local5 |= (VCQL << One)
                        Local5 |= (VTIO << 0x02)
                        Local5 |= (VMYH << 0x03)
                        Local5 |= (VSTP << 0x04)
                        Local5 |= (VCQH << 0x05)
                        Local5 |= (VDCC << 0x06)
                        Local5 |= (VSFN << 0x07)
                        Local5 |= (VDMC << 0x08)
                        Local5 |= (VFHP << 0x09)
                        Local5 |= (VIFC << 0x0A)
                        Local5 |= (VMMC << 0x0B)
                        Local5 |= (VMSC << 0x0C)
                        Local5 |= (VPSC << 0x0D)
                        Local5 |= (VCSC << 0x0E)
                        Local1 = (CICF << 0x08)
                        If ((CICF == 0x03))
                        {
                            CICM = SMYH /* \SMYH */
                        }
                        ElseIf ((CICF == 0x0B))
                        {
                            CICM = SMMC /* \SMMC */
                        }
                        ElseIf ((CICF == 0x0D))
                        {
                            CICM = SPSC /* \SPSC */
                        }
                        Else
                        {
                            CICM = 0x0F
                        }

                        Local1 |= (CICM << 0x0C)
                        Local1 |= (Local5 << 0x10)
                        Local1 |= One
                        If (DHKC)
                        {
                            MHKQ (0x6032)
                        }
                    }
                    Case (0x02)
                    {
                        Local5 = VSTD /* \VSTD */
                        Local5 |= (VCQL << One)
                        Local5 |= (VTIO << 0x02)
                        Local5 |= (VMYH << 0x03)
                        Local5 |= (VSTP << 0x04)
                        Local5 |= (VCQH << 0x05)
                        Local5 |= (VDCC << 0x06)
                        Local5 |= (VSFN << 0x07)
                        Local5 |= (VDMC << 0x08)
                        Local5 |= (VFHP << 0x09)
                        Local5 |= (VIFC << 0x0A)
                        Local5 |= (VMMC << 0x0B)
                        Local5 |= (VMSC << 0x0C)
                        Local5 |= (VPSC << 0x0D)
                        Local5 |= (VCSC << 0x0E)
                        Local1 = (CICF << 0x08)
                        If ((CICF == 0x03))
                        {
                            CICM = SMYH /* \SMYH */
                        }
                        ElseIf ((CICF == 0x0B))
                        {
                            CICM = SMMC /* \SMMC */
                        }
                        ElseIf ((CICF == 0x0D))
                        {
                            CICM = SPSC /* \SPSC */
                        }
                        Else
                        {
                            CICM = 0x0F
                        }

                        Local1 |= (CICM << 0x0C)
                        Local1 |= (Local5 << 0x10)
                        Local1 |= One
                    }
                    Case (0x03)
                    {
                        Local1 = (FCAP << 0x10)
                        Local1 |= One
                    }
                    Case (0x04)
                    {
                        Local1 = (MYHC << 0x10)
                        Local1 |= One
                    }
                    Case (0x06)
                    {
                        Local2 = ((Local0 >> 0x09) & 0x0F)
                        If ((Local2 != One))
                        {
                            Local1 = (MMCC << 0x10)
                        }
                        Else
                        {
                            Local1 = 0x0200
                        }

                        Local1 |= One
                    }
                    Case (0x05)
                    {
                        If (Ones)
                        {
                            Local1 = 0x0500
                            Local1 |= 0x10E00000
                        }

                        Local1 |= One
                    }
                    Case (0x0100)
                    {
                        Local1 = 0x10010000
                        Local1 |= One
                    }
                    Case (0x01FF)
                    {
                        ADBG (" DYTC_CMD_RESET")
                        VCQL = Zero
                        VTIO = Zero
                        VMYH = Zero
                        VSTP = Zero
                        VCQH = Zero
                        VDCC = Zero
                        VSFN = Zero
                        VDMC = Zero
                        VFHP = Zero
                        VIFC = Zero
                        VMMC = Zero
                        VMSC = Zero
                        VPSC = Zero
                        VCSC = Zero
                        SMYH = Zero
                        SMMC = Zero
                        SPSC = Zero
                        SCPF (Zero, DSTD, Zero, Zero)
                        CICM = 0x0F
                        Local5 = VSTD /* \VSTD */
                        Local5 |= (VCQL << One)
                        Local5 |= (VTIO << 0x02)
                        Local5 |= (VMYH << 0x03)
                        Local5 |= (VSTP << 0x04)
                        Local5 |= (VCQH << 0x05)
                        Local5 |= (VDCC << 0x06)
                        Local5 |= (VSFN << 0x07)
                        Local5 |= (VDMC << 0x08)
                        Local5 |= (VFHP << 0x09)
                        Local5 |= (VIFC << 0x0A)
                        Local5 |= (VMMC << 0x0B)
                        Local5 |= (VMSC << 0x0C)
                        Local5 |= (VPSC << 0x0D)
                        Local5 |= (VCSC << 0x0E)
                        Local1 = (CICF << 0x08)
                        Local1 |= (CICM << 0x0C)
                        Local1 |= (Local5 << 0x10)
                        If (CondRefOf (\_SB.IETM.DPTE))
                        {
                            If ((^^^^^IETM.DPTE & One))
                            {
                                ODV0 = STDV /* \STDV */
                                ODV1 = Zero
                                ODV2 = Zero
                                ODV3 = Zero
                                ODV4 = Zero
                                ODV5 = Zero
                                ODV6 = Zero
                                ODV7 = Zero
                                ODV8 = Zero
                                ODV9 = Zero
                                ODVA = Zero
                                ODVB = Zero
                                ODVC = Zero
                                ODVD = Zero
                                ODVE = Zero
                                ODVF = Zero
                                ODVG = Zero
                                ODVH = Zero
                                ODVI = Zero
                                ODVJ = Zero
                                Notify (IETM, 0x88) // Device-Specific
                            }
                        }

                        Local1 |= One
                    }
                    Default
                    {
                        Local1 = 0x04
                    }

                }
            }
            Else
            {
                Local1 = 0x08
            }

            ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
            Return (Local1)
        }

As evident by the code here:

                            Case (0x0B)
                            {
                                Switch (Local3)
                                {
                                    Case (0x0F)
                                    {
                                        If ((Local4 != Zero))
                                        {
                                            Local1 = 0x0A
                                            ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                            Return (Local1)
                                        }
                                    }
                                    Default
                                    {
                                        Local1 = 0x0A
                                        ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                        Return (Local1)
                                    }

                                }

                                If ((Local4 == Zero))
                                {
                                    VMMC = Zero
                                    SMMC = Zero
                                }
                                Else
                                {
                                    VMMC = One
                                    SMMC = Local3
                                }
                            }

The method 0x0B ( which is #define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode, priority 3 */ ) does seem to be disabled and rejects all arguments apart from ICMode=0xF / ValidF = 0x0

Method method 0x0D ( which is not documented) however does seem to do change something (most likely the same performance modes)


                      Case (0x0D)
                            {
                                If (((Local3 <= 0x08) && (Local3 >= One)))
                                {
                                    If ((Local4 != One))
                                    {
                                        Local1 = 0x0A
                                        ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                        Return (Local1)
                                    }
                                }
                                ElseIf ((Local3 == 0x0F))
                                {
                                    If ((Local4 != Zero))
                                    {
                                        Local1 = 0x0A
                                        ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                        Return (Local1)
                                    }
                                }
                                Else
                                {
                                    Local1 = 0x0A
                                    ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                    Return (Local1)
                                }

                                If ((Local4 == Zero))
                                {
                                    VPSC = Zero
                                    SPSC = Zero
                                }
                                Else
                                {
                                    VPSC = One
                                    SPSC = Local3
                                }
                            }
zhen-zen commented 3 years ago

Available DYTC modes may vary in different models. However, only documented modes in https://patchwork.kernel.org/project/linux-acpi/patch/20201211020630.305905-3-markpearson@lenovo.com/ is added. Other modes in DSDT are just made into unused macro.

If you are interested in further investigation, you can try following steps and maybe can find something useful.

  1. Reboot into windows / BIOS and change performance modes.
  2. Back into macOS and check raw DYTC status in the log (updated once during boot).
  3. Document the specific mode / function combinations. (Mine are just the same as the ones in the patch above)
  4. Try sending raw DYTC command, i.e. ioio -s ThinkVPC DYTCMode 0x12b001
Someone52 commented 3 years ago

After some more investigation, there seem to be 8 performance modes for the 0xD (13) command.

ioio -s ThinkVPC DYTCMode 0x11D001 ioio -s ThinkVPC DYTCMode 0x12D001 & etc

Modes 1-4 seem to be quiet, noticeably reducing the cooling fan speed (which is good because it seems like mode 4 is how it runs by default on windows) 1-3 seem to reduce cpu frequencies to 0,4ghz - 0,5ghz range which is definitely unusable and way too laggy. 4 reduces it to around 0,6ghz - 1,5 ghz, which seems to be optimal

5 and 6 seems to be how it runs by default, with fans blowing noticeably louder. 7 and 8 seem to let the cpu boost a little bit more and temperatures to climb higher, but need more testing just to be sure.

There is a catch though, it seems like the sequence of commands is very important. Before sending the mode command, a disable has to be set: ioio -s ThinkVPC DYTCMode 0x0FD001 And only then a performance mode can be set: ioio -s ThinkVPC DYTCMode 0x14D001

Otherwise, if not doing this with proper sequence the laptop sometimes end up glitching and cpu seems to lock up at a clock of 0,4ghz and stay at that until proper sequence (or at least two 0x1FF resets) are sent.

Other functions (0x1 - lapmode, 0x4 - ??) seem to have no noticeable effect.

Edit: after futher testing on my i7-8565u and monitoring speeds/temps/etc

Modes 1-4:

Modes 5-6:

Modes 7-8

Someone52 commented 3 years ago

I have made a pull request containing the implementation of my findings. https://github.com/zhen-zen/YogaSMC/pull/111

zhen-zen commented 3 years ago

Thank you for implementing support for this function. The PR looks good and maybe we can carry out preciser control based on DYTC version.

PSC function is also spotted on X1 Extreme and X1C6. Both machine has DYTC version 4.2, but the latter one has the following restriction:

If (((Local3 != 0x02) && ((Local3 != 0x07) && (Local3 != 0x08))))
{
    ADBG ("PSC InValid Mode, Clear the PSC State.")
    Local4 = 0x00
}

So I'm not sure if this function is ready on 4.2 version.

@1Revenger1 @tylernguyen @benbender Does current mode control work on your laptop? And how about the linked PR by @Someone52 ?

And prior to version 4.2, it seems that PSC mode is not supported. The 4.1 one from E580 even has few modes than mine (4.0). Maybe we can judge the availability of PSC mode by DYTC version?

The most complete DYTC method I have seen is obtained from Yoga C940 (15)

STD (0x0): 0xf, - // standard
CQL (0x1): 0xf, 0/1 // lap (check specific mode under desk/MMC)
TIO (0x2): 0xf, 0/1
MYH (0x3):
    TBL: 0x0, 1
    TNT: 0x1, 1
    LFT: 0x2, 1
    MASK: 0xf, 0
STP (0x4): 0xf, 0/1
CQH (0x5): 0xf, 0/1
DCC (0x6): 0xf, 0/1
SFN (0x7): 0xf, 0/1
DMC (0x8): 0xf, 0/1
FHP (0x9): 0xf, 0/1
IFC (0xa): 0xf, 0/1
MMC (0xb): // desk
    PFM: 0x2, 1 // performance
    QUT: 0x3, 1 // quiet
    MASK: 0xf, 1 // balance
MSC (0xc): 0xf, 0/1
PSC (0xd): 
    0x1-0x8, 1
    0xf, 0
CSC (0xe): 0xf, 0/1

Currently I don't have many samples on DYTC 5.0 and newer. But at least yours is covered by the list above.

1Revenger1 commented 3 years ago

Just tested on my device and it appears to function correctly. The changes are reflected in IORegistryExplorer and no errors are created when looking through log show --last boot | grep -i ysmc. There doesn't seem to really be any change on how my device uses the fans, though that was the case with the old implementation of DYTC as well. Additionally, I am able to select Quiet values (before this PR, if I set quiet, it would revert back to Normal). Here is a picture from IOReg: image

Edit: I just now realized that this should've gone under the PR, oops

zhen-zen commented 3 years ago

The normal mode is called MMC AUTO mode in Yoga C940 (15) example above. It seems that on X1E and X1C6, supported mode is 1/2/f instead of 3/2/f for quiet/performance/balance (aka AUTO). I don't know how to distinguish them, maybe some different command below may help that.

For version 5.0, only normal mode is supported for E590 and X1C7 don't have MMC function at all.

And it's quite confusing that they still stick to the MMC function in https://patchwork.kernel.org/project/platform-driver-x86/patch/20210111162237.3469-1-markpearson@lenovo.com/ where requires DYTC version 5 and newer.

tylernguyen commented 3 years ago

@zhen-zen

The current mode control doesn't work on my x1c6. It's limited to only Balance or Performance. The same thing goes for the new PR.

zhen-zen commented 3 years ago

@zhen-zen

The current mode control doesn't work on my x1c6. It's limited to only Balance or Performance. The same thing goes for the new PR.

I suppose at least 2/7/8 should work for your model. So it's a bit strange here. When you are available, can you check the initial DYTC result in debug log after switching to different thermal mode in Vantage / BIOS?

zhen-zen commented 3 years ago

Finally got some time for #116 . Please try that and I will merge both PR.