black.dragon74 Posted December 2, 2017 Share Posted December 2, 2017 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.zip 6 1 Link to comment Share on other sites More sharing options...
black.dragon74 Posted December 2, 2017 Author Share Posted December 2, 2017 Update 11 Dec 2017: Please note that in HWMonitor you will see two system fans namely: System Fan and System FAN (note the capitalization in the second name). It is not harmful but just in case you hate two system FANs in HWMonitor, you can fix this easily. To fix this, once you download the attached SSDT-FAN.aml you can open it and in Device (SMCD) scope you will see a name field like: Name (TACH, Package(){ "System FAN", "FAN0" } Change it to: Name (TACH, Package(){ "System Fan", "FAN0" } Regards Link to comment Share on other sites More sharing options...
Ig0r Posted December 6, 2017 Share Posted December 6, 2017 Very good coding, black.dragon74! Your method works on my Asus K501LX. And will probably work on any Asus laptop as QMOD Method is universal. I have only changed FTA1 & FTA2 to make my laptop even more quiet, and turn the FAN ON only when CPU temperature is above 50. Link to comment Share on other sites More sharing options...
black.dragon74 Posted December 10, 2017 Author Share Posted December 10, 2017 Yes it is universal and will work for all ASUS laptops as mentioned in post #1 Re: FTA1 & FTA2, it's bad implementation. You should keep the minimum threshold to somewhere around 45˚C Link to comment Share on other sites More sharing options...
Floriante1 Posted February 18, 2018 Share Posted February 18, 2018 Excellent article. However my laptop is a newer model and none of these actually work. As far as i see, my laptop has 4 fan levels. When offset 103 and 105 are 4, the fans almost dont spin. And when they are 01, they are spinning in the fastest mode. Laptop almost always runs in 04 and its almost silent. But it gets extremely loud during gaming cause the fans go to speed 01... However this is overkill. The noise in 02 mode is very acceptable and going to 01 drops temps about 5 degrees (75 to 65).. I checked my DSDT file, i have absolutely no programming experience and couldnt understand much. How could i find out the register which indicates "Max Fan RPM Value" and keep the fans from 01 no matter how hot it is? Link to comment Share on other sites More sharing options...
black.dragon74 Posted February 28, 2018 Author Share Posted February 28, 2018 If your vendor is ASUS, upload your native DSDT here, untouched. (Press F4 at CLOVER boot screen). P.S: If by newer you mean upto coffeelake(8th gen), it is working in my case (Vivobook S15). Regards 1 Link to comment Share on other sites More sharing options...
gheo Posted June 2, 2018 Share Posted June 2, 2018 There is a way to use Your method if my laptop ( Asus X542UN) run windows/linux? I don' have practical programming experience but I understood 35-40% of what you did here. Thank you for sharing this! Link to comment Share on other sites More sharing options...
gulios Posted June 7, 2018 Share Posted June 7, 2018 Hi, I have Asus UX430UA and fans speed is to fast(loud) with temp ~38/40' - i think fans should start ~45' I saw there is new BIOS v304 for that laptop which slow down fans but it breaks my hackintosh(do not have a time to generate all again so i stuck with BIOS v300) Can you help me with generate SSDT-FAN for that model ? Thank you. ux430ua.zip Link to comment Share on other sites More sharing options...
Allan Posted June 21, 2018 Share Posted June 21, 2018 What kind of problem do you have when you upgrade your BIOS? If you use a patched DSDT, yes, you'll need extract it again a re-patch it. Link to comment Share on other sites More sharing options...
Mohammadtaghi Farkhondekar Posted July 1, 2018 Share Posted July 1, 2018 HI . i have asus gl553vd when i using my system after open monitor app restart and sys because problem and we restart your mac .... Link to comment Share on other sites More sharing options...
Recommended Posts