Leaderboard
Popular Content
Showing content with the highest reputation on 12/26/20 in all areas
-
Heya! After a lot of playing around with ASUS acpi tables I have finally come up with a SSDT that will work nearly on all ASUS laptops (Haswell or above) for reading FAN RPM, CPU TEMP and also Custom Controlling FAN. I do not have hardware below Haswell so can't test. If someone get's it working using this SSDT on machines prior to haswell. Let me and others know. Background Info: The system FAN is generally controlled by the embedded controller (EC) but there are methods in ACPI that can let you read and control your system FAN. As you might know, ASUS machines are the best for hackintosh as they have the best written ACPI code. Like, if it was HP instead of ASUS you would have to acquire a mutex object. Write a value to the EC and then release the object. But in ASUS machines there is a method that takes arguments and does this job automatically. Still. There are multiple methods in our DSDT that allow us to read and control system FAN. For example to read FAN speed, there are two ways. If you observe, the FAN speed is at offset 0x93 of EC (Use RWEverything to find out) So, when we search for Offset 0x93 in the DSDT we get a result like this: Offset (0x93), TAH0, 16, // TAH0 stands for FAN1 TAH1, 16, // TAH1 stands for FAN2 (in case your laptop has 2 fans) TSTP, 16, // TSTP stores current fan value in some bytes Note: Instead of using RWEverything if you have a look at method TACH you can see that it stores the values in TAH0 and TAH1 depending upon the Args supplied. So, searching for TAH0 or TAH1 we can see that they are located at offset 0x93 So, now we can read the value from these registers in some units and then we will have to use some formula to convert that unit to RPM. In order to find that formula, if you have a look at Method TACH in dsdt, you will see: Method (TACH, 1, Serialized) { Name (_T_0, Zero) If (ECAV ()) { While (One) { _T_0 = Arg0 If ((_T_0 == Zero)) { Local0 = TAH0 Break } ElseIf ((_T_0 == One)) { Local0 = TAH1 Break } Else { Return (Ones) } Break } Local0 *= 0x02 If ((Local0 != Zero)) { Divide (0x0041CDB4, Local0, Local1, Local0) Return (Local0) } Else { Return (Ones) } } Else { Return (Ones) } } So, we know that we have to store the value from TAH0 or TAH1 (depends on Arg0) and then we have to multiply it by 2 and then we have to divide it by 0x0041CDB4 (4312500) to get the value in RPMs. Once we know that, we could write a simple ACPI code to return the value in RPMs like: // GRPM means get RPM Method (GRPM, 0) { // Store value in Local0 Local0 = \_SB.PCI0.LPCB.EC0.TAH0 // If local0 is not equal to 0 If (Local0 != 0){ // Multiply by 2 Local0 = Local0 * 2 // Divide by 4312500 Divide (0x0041CDB4, Local0, Local1, Local0) } // Return the value Return (Local0) } As you can see this is very expensive method. But what we learned here is, If we execute method TACH with Arg0 as 0 (Zero) it will give us the speed in RPM of FAN 1. Similarly, if we use Arg0 as 1 (One) it will return the value in RPM for FAN 2 My laptop only has one FAN so, I can execute method TACH like, \SB.PCI0.LPCB.EC0.TACH(0) // Using Arg0 as Zero for FAN 1 And it will give me FANs RPM, so, instead of reading from EC and converting bits to RPM this is more preferable. Also, EC bytes may change while patching DSDT so it is a good idea to use dynamic methods so that a single SSDT could work for all machines. One such example is, If you use ACPIBatteryManager then you will have to convert 16bits registers to 8bits. And then, to use that "reading RPM from EC method you will have to create a new method to combine 2 8 bits registers to 1 16 bit like, // JEBR = Join 8 bit registers Method (JEBR, 2) { Return ((Arg0 | (Arg1 << 8))) // Arg0 and Arg1 will be 8Bit register 1 and 2 respectively (AH00, AH01) } Now moving to FAN control, If you have a look at method QMOD in DSDT: Method (QMOD, 1, NotSerialized) { If ((Arg0 == Zero)) { Return (Zero) // If arg0 is Zero. Terminate by returning 0 } If ((Arg0 == One)) { ^^PCI0.LPCB.EC0.ST98 (QFAN) // Hmm, ST98 is somewhat related to FAN control } If ((Arg0 == 0x02)) { ^^PCI0.LPCB.EC0.ST98 (0xFF) // Okay, ST98 again.. Something is interesting } Return (One) } Now, if you look at ST98 you can see: Method (ST98, 1, Serialized) { If (ECAV ()) // Checked if EC is available, will you write to it? { Acquire (MU4T, 0xFFFF) // Oh! So you are acquiring a mutex object CMD = 0xFF EDA1 = 0x98 EDA2 = Arg0 // Oh, so you did write Arg0 in EDA2 (Located in EC01 OperationRegion) ECAC () Release (MU4T) // Released it here, you did write something to the EC for sure Return (Zero) } Return (Ones) } Now, we can understand that, method QMOD (Quiet Mode?) Takes 1 argument that could be (0, 1 or 2). We can eliminate 0 as when we pass Arg0 as 0 it simply returns. Interesting are args 1 and 2 When using 1 it passes Arg0 to ST98 as QFAN's value (Hmm.. What is this QFAN) When using 2 it passes Arg0 to ST98 as 0xFF (255) (Oh wait! according to ACPI spec, 255 is max allowed FAN value.) Gotcha! We can use ST98 and pass it an arg ranging between 0x0 (0) to 0xFF (255) where 0 is for FAN off and 255 is for Max allowed or auto. But, why is there a QMOD method then? There might be some good reason for that. So, we will not invoke ST98 directly but will use the modus operandi of method QMOD Like, we will first store that max allowed FAN value in QFAN Then, we will invoke QMOD with Arg0 as 1 (One). Which will invoke ST98 for us hence, setting the max allowed speed for FAN. So, the ACPI code for this would be: Method (SETR, 0) { QFAN = 200 // Suppose we want to use max allowed value as 200 // Now we can call QMOD with Arg0 as 1 QMOD (1) } Now we know what to do. We now just need to write a method that can calculate the CPU temperature and then set FAN RPM accordingly. This is when you will use my SSDT-FAN How to implement: Requirements: FakeSMC kext along with sensors installed at /L/E or /S/L/E (DO NOT INJECT USING CLOVER) ACPIPoller.kext HWMonitor for monitoring CPU Temp and FANS My SSDT-FAN.aml Installation: Place SSDT-FAN.aml to /EFI/CLOVER/ACPI/patched (If using sorted order make sure you add SSDT-FAN to it) Install ACPIPoller to /L/E or /S/L/E (not both and definitely do not inject using CLOVER) Configuration: You can set "Name (UCFC, One)" to "Name (UCFC, Zero)" in my SSDT-FAN in case you want to use default FAN control method as provided by your OEM. My SSDT will only provide FAN RPM reading and CPU Temp reading.Achievements: Default scaling that ASUS provided was from 2200RPM to 2900RPM (Fan spinning fast without use) I managed to bring scale it from 255RPM to 5026RPM (Fan turns off if temp Temp rarely goes above 53˚C (Went up to 68 earlier) Technicalities: As we know know how to implement custom fan control using various methods in our DSDT we can't really use them as is in real life scenarios as FAN will literally be dancing. You might have observed that temperature keeps fluctuating a few degrees every second while you are working on something. Moreover, we need an automated method that can read and set FAN RPM. So, my SSDT calculates average temperature and then also waits for 2 seconds (to handle fluctuation) before increasing the RPM and waits for 5 seconds (to let CPU cool) before lowering RPM. You can edit this timeout by editing "Name (FCTU, 2)" for FanControlTimeoutUp and "Name (FCTD, 5)" for FanControlTimeoutDown. Method to calculate average accredited to RehabMan as he wrote the code first. There is only one known way to calculate average in maths. LOL. If you want to know how it is implemented, read this code: Note: This code is a part of my single optimizer SSDT project for ASUS laptops (Means, I am working on a single SSDT that you could place in your CLOVER/ACPI/patched and will have everything working without patching DSDT. You can customize SSDT using Device ANKD (A Nick's Device) like you can configure this SSDT to use custom FAN control or not). You can have a look at WIP code here // SSDT for FAN readings and custom FAN control for ASUS laptops // Copyright, black.dragon74 <www.osxlatitude.com> // Please configure the options in Device ANKD before compiling this SSDT DefinitionBlock("SSDT-FAN", "SSDT", 2, "Nick", "AsusFan", 0) { // Declare externals External (\_SB.QFAN, FieldUnitObj) External (\_SB.ATKD.QMOD, MethodObj) External (\_SB.PCI0.LPCB.EC0.ECAV, MethodObj) External (\_SB.PCI0.LPCB.EC0.ECPU, FieldUnitObj) External (\_SB.PCI0.LPCB.EC0.ST83, MethodObj) External (\_SB.PCI0.LPCB.EC0.ST98, MethodObj) External (\_SB.PCI0.LPCB.EC0.TACH, MethodObj) // Create a Nick's device to take care of this SSDT's configurations Device (ANKD) { Name (_HID, "ANKD0000") // Required. DO NOT change Name (UCFC, 1) // Set this to 0 if you don't wanna use my custom FAN control } // Create devices required by FakeSMC_ACPISensors Device (SMCD) { Name (_HID, "FAN0000") // Required, DO NOT change // Add tachometer Name (TACH, Package() { "System FAN", "FAN0" }) // Add CPU heatsink Name (TEMP, Package() { "CPU Heatsink", "TCPU" }) // Method to read FAN RPM (tachometer) Method (FAN0, 0) { // Check is EC is ready If (\_SB.PCI0.LPCB.EC0.ECAV()) { // Continue Local0 = \_SB.PCI0.LPCB.EC0.ST83(0) // Method ST83 acquires mutex and writes value to EC. O stands for FAN 1, Use 1 for FAN 2 If (Local0 == 255) { // If ST83 is 0xFF (Max fan speed) terminate by returning FAN RPM Return (Local0) } // Else, Get RPM and store it in Local0 Local0 = \_SB.PCI0.LPCB.EC0.TACH(0) // Method TACH in DSDT returns current FAN RPM in 100s, Arg0 as 0 is for FAN 1, for FAN 2, use Arg0 as 1 } Else { // Terminate, return Zero Local0 = 0 } // Return 255, 0 or Fan RPM based on conditionals above Return (Local0) } // Method to read CPU temp (CPU Heatsink) Method (TCPU, 0) { // Check if EC is ready If (\_SB.PCI0.LPCB.EC0.ECAV()) { // Then Local0 = \_SB.PCI0.LPCB.EC0.ECPU // EC Field storing current CPU temp Local1 = 60 // From DSDT If (Local0 < 128) { Local1 = Local0 } } Else { // Terminate, return Zero Local1 = 0 } // Return final CPU temp. ACPISensors take care of unit conversion. Return (Local1) } // Custom FAN table by black.dragon74 for ASUS laptops based on RehabMan's idea // Quietest fan operation yet coolest CPU. // Scaling from values as low as 255 RPM to values as high as 5026 RPM (That's great!) // Scaling that ASUS provided was from 2200 RPM to 2900 RPM (Duh!) // Temperatures. 0xFF means if temp is above 52C, let bios take control of things(auto). Name(FTA1, Package() { 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 0xFF, }) // Fan speeds. 255(0xFF) is max/auto, 0(0x00) is for fan off Name(FTA2, Package() { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 160, 185, 205, 225, 245, 250, 255 }) // Time out values Name (FCTU, 2) // RPM Up Name (FCTD, 5) // RPM Down // Table to keep track of past temperatures (to track average) Name (FHST, Buffer() { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }) // Size should match the count of above FTA1 and FTA2 package Name (FIDX, 0) // current index in buffer above Name (FNUM, 0) // number of entries in above buffer to count in avg Name (FSUM, 0) // current sum of entries in buffer // Keeps track of last fan speed set, and counter to set new one Name (FLST, 0xFF) // last index for fan control Name (FCNT, 0) // count of times it has been "wrong", 0 means no counter // Method to control FAN wrt TEMP // Name in ACPIPoller.kext's Info.plist should be FCPU with HID FAN0000 Method (FCPU, 0) { // If UCFC is set to 0, terminate If (\ANKD.UCFC == 0) { Return (0) } // If EC is not ready, terminate If (!\_SB.PCI0.LPCB.EC0.ECAV()) { Return (0) } Local5 = \_SB.PCI0.LPCB.EC0.ECPU // Current temperature of the CPU Heatsink If (Local5 < 128) { Local0 = Local5 // Store temperature in Local0 } Else { Local0 = 60 // As per BIOS } // calculate average temperature Local1 = Local0 + FSUM Local2 = FIDX Local1 -= DerefOf(FHST[Local2]) FHST[Local2] = Local0 FSUM = Local1 // Local1 is new sum // adjust current index into temperature history table Local2++ if (Local2 >= SizeOf(FHST)) { Local2 = 0 } FIDX = Local2 // adjust total items collected in temp table Local2 = FNUM if (Local2 != SizeOf(FHST)) { Local2++ FNUM = Local2 } // Local1 is new sum, Local2 is number of entries in sum Local0 = Local1 / Local2 // Local0 is now average temp // table based search (use avg temperature to search) if (Local0 > 255) { Local0 = 255 } Local2 = Match(FTA1, MGE, Local0, MTR, 0, 0) // calculate difference between current and found index if (Local2 > FLST) { Local1 = Local2 - FLST Local4 = FCTU } else { Local1 = FLST - Local2 Local4 = FCTD } // set new fan speed, if necessary if (!Local1) { // no difference, so leave current fan speed and reset count FCNT = 0 } else { // there is a difference, start/continue process of changing fan Local3 = FCNT FCNT++ // how long to wait depends on how big the difference // 20 secs if diff is 2, 5 secs if diff is 4, etc. Local1 = Local4 / Local1 if (Local3 >= Local1) { // timeout expired, so start setting new fan speed FLST = Local2 // Method 1 (Recommended) // Store custom fan value from table in Local5 Local5 = DerefOf(FTA2[Local2]) // Set QFAN value to that of Local5 \_SB.QFAN = Local5 // Execute QMOD with Arg0 as 1(One) to set FAN's max allowed speed to that of \_SB.QFAN \_SB.ATKD.QMOD(1) // End Method 1 // Method 2 (Works but not recommended) Uncomment the line below to use this (remember to comment lines in method 1) // \_SB.PCI0.LPCB.EC0.ST98 (DerefOf(FTA2[Local2])) // End Method 2 // Reset FAN count (Required in either methods) FCNT = 0 } } Return (1) // Return something as this is a requirement of a ACPI Method } } } Moment of joy: Problem Reporting: Attach proper problem reporting files. See How to generate proper problem reporting files Note: If you see FAN RPM = 255 and hear a lot of noise from FAN it means your FAN is running at it's maximum speed. Regards ACPIPoller.zip SSDT-FAN.zip1 point