diff mbox

[v2] platform: x86: asus-wmi: add fan control

Message ID 1381236524-19633-1-git-send-email-felipe.contreras@gmail.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Felipe Contreras Oct. 8, 2013, 12:48 p.m. UTC
Simple driver to enable control of the fan in ASUS laptops. So far this
has only been tested in ASUS Zenbook Prime UX31A, but according to some
online reference [1], it should work in other models as well.

The implementation is very straight-forward, the only caveat is that the
fan speed needs to be saved after it has been manually changed because
it won't be reported properly until it goes back to 'auto' mode.

[1] http://forum.notebookreview.com/asus/705656-fan-control-asus-prime-ux31-ux31a-ux32a-ux32vd.html

Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---
 drivers/platform/x86/asus-wmi.c | 105 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 105 insertions(+)

Comments

Felipe Contreras Oct. 8, 2013, 12:57 p.m. UTC | #1
On Tue, Oct 8, 2013 at 7:48 AM, Felipe Contreras
<felipe.contreras@gmail.com> wrote:
> Simple driver to enable control of the fan in ASUS laptops. So far this
> has only been tested in ASUS Zenbook Prime UX31A, but according to some
> online reference [1], it should work in other models as well.
>
> The implementation is very straight-forward, the only caveat is that the
> fan speed needs to be saved after it has been manually changed because
> it won't be reported properly until it goes back to 'auto' mode.
>
> [1] http://forum.notebookreview.com/asus/705656-fan-control-asus-prime-ux31-ux31a-ux32a-ux32vd.html
>
> Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>

> +       r = asus_wmi_evaluate_method(ASUS_WMI_METHODID_AGFN, virt_to_phys(&args), 0, &value);

I don't like using virt_to_phys() here, but it seems that's what the
ACPI code expects.

        Method (AGFN, 1, Serialized)
        {
            If (LEqual (Arg0, Zero))
            {
                Return (GNBF)
            }

            Store (Zero, Local0)
            OperationRegion (\PARM, SystemMemory, Arg0, 0x08)
            Field (PARM, DWordAcc, NoLock, Preserve)
            {
                MFUN,   16,
                SFUN,   16,
                LEN,    16,
                STAS,   8,
                EROR,   8
            }

Any suggestions?
Matthew Garrett Oct. 10, 2013, 4:06 p.m. UTC | #2
T24gVGh1LCAyMDEzLTEwLTEwIGF0IDE2OjU5ICswMTAwLCBDb3JlbnRpbiBDaGFyeSB3cm90ZToN
Cg0KPiANCj4gVGhpcyBkb2Vzbid0IHJlYWxseSBzZWVtcyB0byBiZSByZWxhdGVkIHRvIHdtaSwg
YW5kIGlzIGxpa2VseSB0byBiZQ0KPiBhdmFpbGFibGUgb25seSBvbiBhIHN1YnNldCBvZiBtb2Rl
bHMuIE1heWJlIGl0IHNob3VsZCBhIHNlcGFyYXRlDQo+IGRyaXZlciBpbnN0ZWFkID8NCg0KVGhp
cyB2ZXJzaW9uIHNlZW1zIHRvIGJlIGltcGxlbWVudGVkIGVudGlyZWx5IGluIFdNSSwgYW5kIGl0
J3MgdXNpbmcgdGhlDQpzYW1lIFdNSSBHVUlEIGFzIGFzdXMtd21pIC0gaW1wbGVtZW50aW5nIGl0
IGhlcmUgc2VlbXMgYXBwcm9wcmlhdGUuIEkgYW0NCmNvbmNlcm5lZCBhYm91dCB0aGUgcGh5cy92
aXJ0IHRoaW5nLCB0aG91Z2guIFRoZSBBQ1BJIGludGVycHJldGVyIGlzDQpydW5uaW5nIGluIHRo
ZSBrZXJuZWwsIG5vdCB0aGUgaGFyZHdhcmUgLSBhcmUgd2UgYXJ0aWZpY2lhbGx5IGxpbWl0aW5n
DQpTeXN0ZW1NZW1vcnkgb3ByZWdpb25zIHRvIHBoeXNpY2FsIGFkZHJlc3Nlcz8NCg0KLS0gDQpN
YXR0aGV3IEdhcnJldHQgPG1hdHRoZXcuZ2FycmV0dEBuZWJ1bGEuY29tPg0K
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Felipe Contreras Oct. 13, 2013, 8:49 a.m. UTC | #3
On Thu, Oct 10, 2013 at 10:59 AM, Corentin Chary
<corentin.chary@gmail.com> wrote:
>
>
>
> On Tue, Oct 8, 2013 at 1:48 PM, Felipe Contreras
> <felipe.contreras@gmail.com> wrote:
>>
>> Simple driver to enable control of the fan in ASUS laptops. So far this
>> has only been tested in ASUS Zenbook Prime UX31A, but according to some
>> online reference [1], it should work in other models as well.
>>
>> The implementation is very straight-forward, the only caveat is that the
>> fan speed needs to be saved after it has been manually changed because
>> it won't be reported properly until it goes back to 'auto' mode.
>
> This doesn't really seems to be related to wmi, and is likely to be
> available only on a subset of models. Maybe it should a separate driver
> instead ?

The first patch I sent was standalone, and you said it should be wmi,
and now I do it wmi, and you said it should be separate. Which is it?
Felipe Contreras Oct. 13, 2013, 9:29 a.m. UTC | #4
On Thu, Oct 10, 2013 at 11:06 AM, Matthew Garrett
<matthew.garrett@nebula.com> wrote:
> On Thu, 2013-10-10 at 16:59 +0100, Corentin Chary wrote:
>
>>
>> This doesn't really seems to be related to wmi, and is likely to be
>> available only on a subset of models. Maybe it should a separate
>> driver instead ?
>
> This version seems to be implemented entirely in WMI, and it's using the
> same WMI GUID as asus-wmi - implementing it here seems appropriate. I am
> concerned about the phys/virt thing, though. The ACPI interpreter is
> running in the kernel, not the hardware - are we artificially limiting
> SystemMemory opregions to physical addresses?

I don't see anything in acpi_ex_system_memory_space_handler() that
takes into consideration virtual addresses.
Matthew Garrett Oct. 13, 2013, 3:17 p.m. UTC | #5
On Sun, Oct 13, 2013 at 04:29:34AM -0500, Felipe Contreras wrote:

> I don't see anything in acpi_ex_system_memory_space_handler() that
> takes into consideration virtual addresses.

The spec doesn't seem to constrain it to physical addresses (it just 
refers to "Control Methods read and write data to locations in address 
spaces (for example, System memory and System I/O)", so I'd lean towards 
changing the behaviour of acpica rather than adding virt_to_phys().
Felipe Contreras Oct. 14, 2013, 2:31 a.m. UTC | #6
On Sun, Oct 13, 2013 at 10:17 AM, Matthew Garrett <mjg59@srcf.ucam.org> wrote:
> On Sun, Oct 13, 2013 at 04:29:34AM -0500, Felipe Contreras wrote:
>
>> I don't see anything in acpi_ex_system_memory_space_handler() that
>> takes into consideration virtual addresses.
>
> The spec doesn't seem to constrain it to physical addresses (it just
> refers to "Control Methods read and write data to locations in address
> spaces (for example, System memory and System I/O)", so I'd lean towards
> changing the behaviour of acpica rather than adding virt_to_phys().

And I assume you are not going to do that. Isn't acpica code outside
of the scope of Linux kernel development? If not, how do I go about
adding that capability.

Also, I find it weird that this the first driver that needs this.

Finally, I'm much more interested on what happens next, because I want
to link this driver to the thermal framework, so when temperature gets
too high, the fan gets an increased speed, because right now it seems
it's always on low speed, temperature gets high, and instead the CPU
gets throttled, which is not desirable.

Maybe this driver should be added to the staging area.
Matthew Garrett Oct. 14, 2013, 3:52 p.m. UTC | #7
On Sun, Oct 13, 2013 at 09:31:25PM -0500, Felipe Contreras wrote:
> On Sun, Oct 13, 2013 at 10:17 AM, Matthew Garrett <mjg59@srcf.ucam.org> wrote:
> > The spec doesn't seem to constrain it to physical addresses (it just
> > refers to "Control Methods read and write data to locations in address
> > spaces (for example, System memory and System I/O)", so I'd lean towards
> > changing the behaviour of acpica rather than adding virt_to_phys().
> 
> And I assume you are not going to do that. Isn't acpica code outside
> of the scope of Linux kernel development? If not, how do I go about
> adding that capability.

I wasn't planning on it, no. Just write the code and submit it to 
linux-acpi, and Cc: Bob Moore.

> Also, I find it weird that this the first driver that needs this.

The normal way to do this would be for the ASL to just define a buffer 
within the argument, rather than assigning it to an operation region. I 
haven't seen anyone take this approach before.

> Finally, I'm much more interested on what happens next, because I want
> to link this driver to the thermal framework, so when temperature gets
> too high, the fan gets an increased speed, because right now it seems
> it's always on low speed, temperature gets high, and instead the CPU
> gets throttled, which is not desirable.

It wouldn't be appropriate to alter the firmware behaviour by default, 
but yeah, that's the kind of thing that the thermal framework exists to 
do.

> Maybe this driver should be added to the staging area.

I don't think you can easily register multiple drivers for the same WMI 
device.
Felipe Contreras Oct. 14, 2013, 11:18 p.m. UTC | #8
On Mon, Oct 14, 2013 at 10:52 AM, Matthew Garrett <mjg59@srcf.ucam.org> wrote:
> On Sun, Oct 13, 2013 at 09:31:25PM -0500, Felipe Contreras wrote:
>> On Sun, Oct 13, 2013 at 10:17 AM, Matthew Garrett <mjg59@srcf.ucam.org> wrote:
>> > The spec doesn't seem to constrain it to physical addresses (it just
>> > refers to "Control Methods read and write data to locations in address
>> > spaces (for example, System memory and System I/O)", so I'd lean towards
>> > changing the behaviour of acpica rather than adding virt_to_phys().
>>
>> And I assume you are not going to do that. Isn't acpica code outside
>> of the scope of Linux kernel development? If not, how do I go about
>> adding that capability.
>
> I wasn't planning on it, no. Just write the code and submit it to
> linux-acpi, and Cc: Bob Moore.

I might give it a try.

>> Also, I find it weird that this the first driver that needs this.
>
> The normal way to do this would be for the ASL to just define a buffer
> within the argument, rather than assigning it to an operation region. I
> haven't seen anyone take this approach before.
>
>> Finally, I'm much more interested on what happens next, because I want
>> to link this driver to the thermal framework, so when temperature gets
>> too high, the fan gets an increased speed, because right now it seems
>> it's always on low speed, temperature gets high, and instead the CPU
>> gets throttled, which is not desirable.
>
> It wouldn't be appropriate to alter the firmware behaviour by default,
> but yeah, that's the kind of thing that the thermal framework exists to
> do.

Well, how do I do that? The driver is up and running, and I can
manually set different fan speeds, however nothing seems to happen
automatically when the temperature increases.

>> Maybe this driver should be added to the staging area.
>
> I don't think you can easily register multiple drivers for the same WMI
> device.

I don't mean this one, I mean the standalone one. Actually, the first
one I sent doesn't require all this system memory stuff.
Matthew Garrett Oct. 14, 2013, 11:22 p.m. UTC | #9
On Mon, Oct 14, 2013 at 06:18:36PM -0500, Felipe Contreras wrote:
> On Mon, Oct 14, 2013 at 10:52 AM, Matthew Garrett <mjg59@srcf.ucam.org> wrote:
> > It wouldn't be appropriate to alter the firmware behaviour by default,
> > but yeah, that's the kind of thing that the thermal framework exists to
> > do.
> 
> Well, how do I do that? The driver is up and running, and I can
> manually set different fan speeds, however nothing seems to happen
> automatically when the temperature increases.

The easiest is to just do it from userspace. I think Intel have some 
code for doing this, but I haven't looked at the thermal code for years.

> > I don't think you can easily register multiple drivers for the same WMI
> > device.
> 
> I don't mean this one, I mean the standalone one. Actually, the first
> one I sent doesn't require all this system memory stuff.

Banging EC registers directly is the wrong thing to do. Going via WMI is 
correct.
Felipe Contreras Oct. 14, 2013, 11:27 p.m. UTC | #10
On Mon, Oct 14, 2013 at 6:22 PM, Matthew Garrett <mjg59@srcf.ucam.org> wrote:
> On Mon, Oct 14, 2013 at 06:18:36PM -0500, Felipe Contreras wrote:
>> On Mon, Oct 14, 2013 at 10:52 AM, Matthew Garrett <mjg59@srcf.ucam.org> wrote:
>> > It wouldn't be appropriate to alter the firmware behaviour by default,
>> > but yeah, that's the kind of thing that the thermal framework exists to
>> > do.
>>
>> Well, how do I do that? The driver is up and running, and I can
>> manually set different fan speeds, however nothing seems to happen
>> automatically when the temperature increases.
>
> The easiest is to just do it from userspace. I think Intel have some
> code for doing this, but I haven't looked at the thermal code for years.

That defeats the purpose of the whole thermal binding infrastructure.

>> > I don't think you can easily register multiple drivers for the same WMI
>> > device.
>>
>> I don't mean this one, I mean the standalone one. Actually, the first
>> one I sent doesn't require all this system memory stuff.
>
> Banging EC registers directly is the wrong thing to do. Going via WMI is
> correct.

I'm not going to bother arguing against your absolutist rhetoric. The
fact is one patch can be applied, the other can't. Besides, nobody
said anything about banging EC registers directly.
Matthew Garrett Oct. 14, 2013, 11:34 p.m. UTC | #11
On Mon, Oct 14, 2013 at 06:27:33PM -0500, Felipe Contreras wrote:
> On Mon, Oct 14, 2013 at 6:22 PM, Matthew Garrett <mjg59@srcf.ucam.org> wrote:
> > The easiest is to just do it from userspace. I think Intel have some
> > code for doing this, but I haven't looked at the thermal code for years.
> 
> That defeats the purpose of the whole thermal binding infrastructure.

Not really, but I agree it's not ideal. If there's a mechanism to get 
the system temperature via WMI then you could easily construct your own 
thermal zone and associated cooling device, but otherwise you'd have to 
provide a mechanism for exporting either the CPU information from 
coretemp or the thermal zones from ACPI.

> >> > I don't think you can easily register multiple drivers for the same WMI
> >> > device.
> >>
> >> I don't mean this one, I mean the standalone one. Actually, the first
> >> one I sent doesn't require all this system memory stuff.
> >
> > Banging EC registers directly is the wrong thing to do. Going via WMI is
> > correct.
> 
> I'm not going to bother arguing against your absolutist rhetoric. The
> fact is one patch can be applied, the other can't. Besides, nobody
> said anything about banging EC registers directly.

I'm sorry, you're right - you're calling ACPI methods directly instead. 
This is still incorrect. The platform provides an exported interface, 
and you should use that exported interface.

As long as Corentin doesn't object, I'm happy to merge this driver in 
its current form (including virt_to_phys()) providing that it's wrapped 
in CONFIG_STAGING, and assuming that you'll do the supporting work in 
acpica. I'll pull it out again in 6 months or so if that hasn't been 
fixed up. Fair?
Felipe Contreras Oct. 15, 2013, 12:22 a.m. UTC | #12
On Mon, Oct 14, 2013 at 6:34 PM, Matthew Garrett <mjg59@srcf.ucam.org> wrote:
> On Mon, Oct 14, 2013 at 06:27:33PM -0500, Felipe Contreras wrote:
>> On Mon, Oct 14, 2013 at 6:22 PM, Matthew Garrett <mjg59@srcf.ucam.org> wrote:
>> > The easiest is to just do it from userspace. I think Intel have some
>> > code for doing this, but I haven't looked at the thermal code for years.
>>
>> That defeats the purpose of the whole thermal binding infrastructure.
>
> Not really, but I agree it's not ideal.

So what's the point of thermal_zone_bind_cooling_device?

>> >> > I don't think you can easily register multiple drivers for the same WMI
>> >> > device.
>> >>
>> >> I don't mean this one, I mean the standalone one. Actually, the first
>> >> one I sent doesn't require all this system memory stuff.
>> >
>> > Banging EC registers directly is the wrong thing to do. Going via WMI is
>> > correct.
>>
>> I'm not going to bother arguing against your absolutist rhetoric. The
>> fact is one patch can be applied, the other can't. Besides, nobody
>> said anything about banging EC registers directly.
>
> I'm sorry, you're right - you're calling ACPI methods directly instead.
> This is still incorrect. The platform provides an exported interface,
> and you should use that exported interface.

Maybe, but I can call those same methods outside asus-wmi.

> As long as Corentin doesn't object, I'm happy to merge this driver in
> its current form (including virt_to_phys()) providing that it's wrapped
> in CONFIG_STAGING, and assuming that you'll do the supporting work in
> acpica. I'll pull it out again in 6 months or so if that hasn't been
> fixed up. Fair?

That's fair, I'll give that a try soon.
diff mbox

Patch

diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 19c313b..5fdfed6 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -78,6 +78,7 @@  MODULE_LICENSE("GPL");
 #define ASUS_WMI_METHODID_GPID		0x44495047 /* Get Panel ID?? (Resol) */
 #define ASUS_WMI_METHODID_QMOD		0x444F4D51 /* Quiet MODe */
 #define ASUS_WMI_METHODID_SPLV		0x4C425053 /* Set Panel Light Value */
+#define ASUS_WMI_METHODID_AGFN		0x4E464741
 #define ASUS_WMI_METHODID_SFUN		0x4E554653 /* FUNCtionalities */
 #define ASUS_WMI_METHODID_SDSP		0x50534453 /* Set DiSPlay output */
 #define ASUS_WMI_METHODID_GDSP		0x50534447 /* Get DiSPlay output */
@@ -155,6 +156,16 @@  struct bios_args {
 	u32 arg1;
 } __packed;
 
+struct fan_args {
+	u16 mfun;
+	u16 sfun;
+	u16 len;
+	u8 stas;
+	u8 err;
+	u8 fan;
+	u32 speed;
+} __packed;
+
 /*
  * <platform>/    - debugfs root directory
  *   dev_id      - current dev_id
@@ -205,6 +216,9 @@  struct asus_wmi {
 	struct asus_rfkill gps;
 	struct asus_rfkill uwb;
 
+	struct thermal_cooling_device *fan;
+	int fan_speed;
+
 	struct hotplug_slot *hotplug_slot;
 	struct mutex hotplug_lock;
 	struct mutex wmi_lock;
@@ -1022,6 +1036,90 @@  exit:
 }
 
 /*
+ * Fan
+ */
+
+static int fan_speed(struct asus_wmi *asus, int write, int fan, unsigned long *speed)
+{
+	struct fan_args args = {
+		.len = sizeof(args),
+		.mfun = 0x13,
+		.sfun = write ? 0x07 : 0x06,
+		.fan = fan,
+		.speed = write ? *speed : 0,
+	};
+	int r;
+	u32 value;
+
+	if (!write && asus->fan_speed >= 0) {
+		*speed = asus->fan_speed;
+		return 0;
+	}
+
+	r = asus_wmi_evaluate_method(ASUS_WMI_METHODID_AGFN, virt_to_phys(&args), 0, &value);
+	if (r || value || args.err)
+		return -1;
+
+	if (write)
+		asus->fan_speed = fan > 0 ? *speed : -1;
+	else
+		*speed = args.speed;
+
+	return 0;
+}
+
+static int fan_set_auto(struct asus_wmi *asus)
+{
+	unsigned long speed = 0;
+	return fan_speed(asus, 1, 0, &speed);
+}
+
+static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state)
+{
+	*state = 0xff;
+	return 0;
+}
+
+static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state)
+{
+	return fan_speed(cdev->devdata, 0, 1, state);
+}
+
+static int fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
+{
+	return fan_speed(cdev->devdata, 1, 1, &state);
+}
+
+static const struct thermal_cooling_device_ops fan_cooling_ops = {
+	.get_max_state = fan_get_max_state,
+	.get_cur_state = fan_get_cur_state,
+	.set_cur_state = fan_set_cur_state,
+};
+
+static int asus_wmi_fan_init(struct asus_wmi *asus)
+{
+	struct thermal_cooling_device *cdev;
+
+	if (fan_set_auto(asus))
+		return 0;
+
+	cdev = thermal_cooling_device_register("Fan", asus, &fan_cooling_ops);
+	if (IS_ERR(cdev))
+		return PTR_ERR(cdev);
+	asus->fan = cdev;
+	return 0;
+}
+
+static void asus_wmi_fan_exit(struct asus_wmi *asus)
+{
+	if (!asus->fan)
+		return;
+	fan_set_auto(asus);
+	thermal_cooling_device_unregister(asus->fan);
+	asus->fan = NULL;
+}
+
+/*
  * Hwmon device
  */
 static ssize_t asus_hwmon_pwm1(struct device *dev,
@@ -1796,6 +1894,10 @@  static int asus_wmi_add(struct platform_device *pdev)
 	if (err)
 		goto fail_rfkill;
 
+	err = asus_wmi_fan_init(asus);
+	if (err)
+		goto fail_fan;
+
 	if (asus->driver->quirks->wmi_backlight_power)
 		acpi_video_dmi_promote_vendor();
 	if (!acpi_video_backlight_support()) {
@@ -1830,6 +1932,8 @@  fail_debugfs:
 fail_wmi_handler:
 	asus_wmi_backlight_exit(asus);
 fail_backlight:
+	asus_wmi_fan_exit(asus);
+fail_fan:
 	asus_wmi_rfkill_exit(asus);
 fail_rfkill:
 	asus_wmi_led_exit(asus);
@@ -1855,6 +1959,7 @@  static int asus_wmi_remove(struct platform_device *device)
 	asus_wmi_hwmon_exit(asus);
 	asus_wmi_led_exit(asus);
 	asus_wmi_rfkill_exit(asus);
+	asus_wmi_fan_exit(asus);
 	asus_wmi_debugfs_exit(asus);
 	asus_wmi_platform_exit(asus);