From d0a6825c9217cfc52d39b2b2bedd73bef8019f79 Mon Sep 17 00:00:00 2001 From: Corentin Chary Date: Fri, 28 Aug 2009 12:56:42 +0000 Subject: eeepc-laptop: document sysfs interface Signed-off-by: Corentin Chary Signed-off-by: Len Brown --- .../ABI/testing/sysfs-platform-eeepc-laptop | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-platform-eeepc-laptop (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-platform-eeepc-laptop b/Documentation/ABI/testing/sysfs-platform-eeepc-laptop new file mode 100644 index 000000000000..7445dfb321b5 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-eeepc-laptop @@ -0,0 +1,50 @@ +What: /sys/devices/platform/eeepc-laptop/disp +Date: May 2008 +KernelVersion: 2.6.26 +Contact: "Corentin Chary" +Description: + This file allows display switching. + - 1 = LCD + - 2 = CRT + - 3 = LCD+CRT + If you run X11, you should use xrandr instead. + +What: /sys/devices/platform/eeepc-laptop/camera +Date: May 2008 +KernelVersion: 2.6.26 +Contact: "Corentin Chary" +Description: + Control the camera. 1 means on, 0 means off. + +What: /sys/devices/platform/eeepc-laptop/cardr +Date: May 2008 +KernelVersion: 2.6.26 +Contact: "Corentin Chary" +Description: + Control the card reader. 1 means on, 0 means off. + +What: /sys/devices/platform/eeepc-laptop/cpufv +Date: Jun 2009 +KernelVersion: 2.6.31 +Contact: "Corentin Chary" +Description: + Change CPU clock configuration. + On the Eee PC 1000H there are three available clock configuration: + * 0 -> Super Performance Mode + * 1 -> High Performance Mode + * 2 -> Power Saving Mode + On Eee PC 701 there is only 2 available clock configurations. + Available configuration are listed in available_cpufv file. + Reading this file will show the raw hexadecimal value which + is defined as follow: + | 8 bit | 8 bit | + | `---- Current mode + `------------ Availables modes + For example, 0x301 means: mode 1 selected, 3 available modes. + +What: /sys/devices/platform/eeepc-laptop/available_cpufv +Date: Jun 2009 +KernelVersion: 2.6.31 +Contact: "Corentin Chary" +Description: + List available cpufv modes. -- cgit v1.2.3-70-g09d2 From 3c4c1b69a2d76ac9a1c716233fde956dba757d76 Mon Sep 17 00:00:00 2001 From: Corentin Chary Date: Fri, 28 Aug 2009 12:56:43 +0000 Subject: video/backlight: document sysfs interface Date and KernelVersion may be wrong because the backlight interface was introduced before git initial import. Cc:Richard Purdie Signed-off-by: Corentin Chary Signed-off-by: Len Brown --- Documentation/ABI/stable/sysfs-class-backlight | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Documentation/ABI/stable/sysfs-class-backlight (limited to 'Documentation') diff --git a/Documentation/ABI/stable/sysfs-class-backlight b/Documentation/ABI/stable/sysfs-class-backlight new file mode 100644 index 000000000000..4d637e1c4ff7 --- /dev/null +++ b/Documentation/ABI/stable/sysfs-class-backlight @@ -0,0 +1,36 @@ +What: /sys/class/backlight//bl_power +Date: April 2005 +KernelVersion: 2.6.12 +Contact: Richard Purdie +Description: + Control BACKLIGHT power, values are FB_BLANK_* from fb.h + - FB_BLANK_UNBLANK (0) : power on. + - FB_BLANK_POWERDOWN (4) : power off +Users: HAL + +What: /sys/class/backlight//brightness +Date: April 2005 +KernelVersion: 2.6.12 +Contact: Richard Purdie +Description: + Control the brightness for this . Values + are between 0 and max_brightness. This file will also + show the brightness level stored in the driver, which + may not be the actual brightness (see actual_brightness). +Users: HAL + +What: /sys/class/backlight//actual_brightness +Date: March 2006 +KernelVersion: 2.6.17 +Contact: Richard Purdie +Description: + Show the actual brightness by querying the hardware. +Users: HAL + +What: /sys/class/backlight//max_brightness +Date: April 2005 +KernelVersion: 2.6.12 +Contact: Richard Purdie +Description: + Maximum brightness for . +Users: HAL -- cgit v1.2.3-70-g09d2 From 243ca3e401bc62e704785d215931f1a51fd53bd7 Mon Sep 17 00:00:00 2001 From: Corentin Chary Date: Fri, 28 Aug 2009 12:56:44 +0000 Subject: video/lcd: document sysfs interface Date and KernelVersion may be wrong because the lcd interface was introduced before git initial import. Cc: Richard Purdie Signed-off-by: Corentin Chary Signed-off-by: Len Brown --- Documentation/ABI/testing/sysfs-class-lcd | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-lcd (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-class-lcd b/Documentation/ABI/testing/sysfs-class-lcd new file mode 100644 index 000000000000..35906bf7aa70 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-lcd @@ -0,0 +1,23 @@ +What: /sys/class/lcd//lcd_power +Date: April 2005 +KernelVersion: 2.6.12 +Contact: Richard Purdie +Description: + Control LCD power, values are FB_BLANK_* from fb.h + - FB_BLANK_UNBLANK (0) : power on. + - FB_BLANK_POWERDOWN (4) : power off + +What: /sys/class/lcd//contrast +Date: April 2005 +KernelVersion: 2.6.12 +Contact: Richard Purdie +Description: + Current contrast of this LCD device. Value is between 0 and + /sys/class/lcd//max_contrast. + +What: /sys/class/lcd//max_contrast +Date: April 2005 +KernelVersion: 2.6.12 +Contact: Richard Purdie +Description: + Maximum contrast for this LCD device. -- cgit v1.2.3-70-g09d2 From 5f634c6527249275df4199a294ee9cec2f3ff3b1 Mon Sep 17 00:00:00 2001 From: Corentin Chary Date: Fri, 28 Aug 2009 12:56:45 +0000 Subject: led: document sysfs interface Also fix Documentation/led-class.txt, the acceptable range of values for brightness is 0-max_brightness, not 0-255. Cc: Richard Purdie Signed-off-by: Corentin Chary Signed-off-by: Len Brown --- Documentation/ABI/testing/sysfs-class-led | 28 ++++++++++++++++++++++++++++ Documentation/leds-class.txt | 9 +++++---- 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-class-led (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-class-led b/Documentation/ABI/testing/sysfs-class-led new file mode 100644 index 000000000000..9e4541d71cb6 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-led @@ -0,0 +1,28 @@ +What: /sys/class/leds//brightness +Date: March 2006 +KernelVersion: 2.6.17 +Contact: Richard Purdie +Description: + Set the brightness of the LED. Most LEDs don't + have hardware brightness support so will just be turned on for + non-zero brightness settings. The value is between 0 and + /sys/class/leds//max_brightness. + +What: /sys/class/leds//max_brightness +Date: March 2006 +KernelVersion: 2.6.17 +Contact: Richard Purdie +Description: + Maximum brightness level for this led, default is 255 (LED_FULL). + +What: /sys/class/leds//trigger +Date: March 2006 +KernelVersion: 2.6.17 +Contact: Richard Purdie +Description: + Set the trigger for this LED. A trigger is a kernel based source + of led events. + You can change triggers in a similar manner to the way an IO + scheduler is chosen. Trigger specific parameters can appear in + /sys/class/leds/ once a given trigger is selected. + diff --git a/Documentation/leds-class.txt b/Documentation/leds-class.txt index 6399557cdab3..8fd5ca2ae32d 100644 --- a/Documentation/leds-class.txt +++ b/Documentation/leds-class.txt @@ -1,3 +1,4 @@ + LED handling under Linux ======================== @@ -5,10 +6,10 @@ If you're reading this and thinking about keyboard leds, these are handled by the input subsystem and the led class is *not* needed. In its simplest form, the LED class just allows control of LEDs from -userspace. LEDs appear in /sys/class/leds/. The brightness file will -set the brightness of the LED (taking a value 0-255). Most LEDs don't -have hardware brightness support so will just be turned on for non-zero -brightness settings. +userspace. LEDs appear in /sys/class/leds/. The maximum brightness of the +LED is defined in max_brightness file. The brightness file will set the brightness +of the LED (taking a value 0-max_brightness). Most LEDs don't have hardware +brightness support so will just be turned on for non-zero brightness settings. The class also introduces the optional concept of an LED trigger. A trigger is a kernel based source of led events. Triggers can either be simple or -- cgit v1.2.3-70-g09d2 From 6ce2c9d9a531e8753005a25a686dafab9a5d04bb Mon Sep 17 00:00:00 2001 From: Corentin Chary Date: Fri, 28 Aug 2009 12:56:54 +0000 Subject: asus-laptop: document the module Signed-off-by: Corentin Chary Signed-off-by: Len Brown --- Documentation/laptops/asus-laptop.txt | 258 ++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 Documentation/laptops/asus-laptop.txt (limited to 'Documentation') diff --git a/Documentation/laptops/asus-laptop.txt b/Documentation/laptops/asus-laptop.txt new file mode 100644 index 000000000000..c1c5be84e4b1 --- /dev/null +++ b/Documentation/laptops/asus-laptop.txt @@ -0,0 +1,258 @@ +Asus Laptop Extras + +Version 0.1 +August 6, 2009 + +Corentin Chary +http://acpi4asus.sf.net/ + + This driver provides support for extra features of ACPI-compatible ASUS laptops. + It may also support some MEDION, JVC or VICTOR laptops (such as MEDION 9675 or + VICTOR XP7210 for example). It makes all the extra buttons generate standard + ACPI events that go through /proc/acpi/events and input events (like keyboards). + On some models adds support for changing the display brightness and output, + switching the LCD backlight on and off, and most importantly, allows you to + blink those fancy LEDs intended for reporting mail and wireless status. + +This driver supercedes the old asus_acpi driver. + +Requirements +------------ + + Kernel 2.6.X sources, configured for your computer, with ACPI support. + You also need CONFIG_INPUT and CONFIG_ACPI. + +Status +------ + + The features currently supported are the following (see below for + detailed description): + + - Fn key combinations + - Bluetooth enable and disable + - Wlan enable and disable + - GPS enable and disable + - Video output switching + - Ambient Light Sensor on and off + - LED control + - LED Display control + - LCD brightness control + - LCD on and off + + A compatibility table by model and feature is maintained on the web + site, http://acpi4asus.sf.net/. + +Usage +----- + + Try "modprobe asus_acpi". Check your dmesg (simply type dmesg). You should + see some lines like this : + + Asus Laptop Extras version 0.42 + L2D model detected. + + If it is not the output you have on your laptop, send it (and the laptop's + DSDT) to me. + + That's all, now, all the events generated by the hotkeys of your laptop + should be reported in your /proc/acpi/event entry. You can check with + "acpi_listen". + + Hotkeys are also reported as input keys (like keyboards) you can check + which key are supported using "xev" under X11. + + You can get informations on the version of your DSDT table by reading the + /sys/devices/platform/asus-laptop/infos entry. If you have a question or a + bug report to do, please include the output of this entry. + +LEDs +---- + + You can modify LEDs be echoing values to /sys/class/leds/asus::*/brightness : + echo 1 > /sys/class/leds/asus::mail/brightness + will switch the mail LED on. + You can also know if they are on/off by reading their content and use + kernel triggers like ide-disk or heartbeat. + +Backlight +--------- + + You can control lcd backlight power and brightness with + /sys/class/backlight/asus-laptop/. Brightness Values are between 0 and 15. + +Wireless devices +--------------- + + You can turn the internal Bluetooth adapter on/off with the bluetooth entry + (only on models with Bluetooth). This usually controls the associated LED. + Same for Wlan adapter. + +Display switching +----------------- + + Note: the display switching code is currently considered EXPERIMENTAL. + + Switching works for the following models: + L3800C + A2500H + L5800C + M5200N + W1000N (albeit with some glitches) + M6700R + A6JC + F3J + + Switching doesn't work for the following: + M3700N + L2X00D (locks the laptop under certain conditions) + + To switch the displays, echo values from 0 to 15 to + /sys/devices/platform/asus-laptop/display. The significance of those values + is as follows: + + +-------+-----+-----+-----+-----+-----+ + | Bin | Val | DVI | TV | CRT | LCD | + +-------+-----+-----+-----+-----+-----+ + + 0000 + 0 + + + + + + +-------+-----+-----+-----+-----+-----+ + + 0001 + 1 + + + + X + + +-------+-----+-----+-----+-----+-----+ + + 0010 + 2 + + + X + + + +-------+-----+-----+-----+-----+-----+ + + 0011 + 3 + + + X + X + + +-------+-----+-----+-----+-----+-----+ + + 0100 + 4 + + X + + + + +-------+-----+-----+-----+-----+-----+ + + 0101 + 5 + + X + + X + + +-------+-----+-----+-----+-----+-----+ + + 0110 + 6 + + X + X + + + +-------+-----+-----+-----+-----+-----+ + + 0111 + 7 + + X + X + X + + +-------+-----+-----+-----+-----+-----+ + + 1000 + 8 + X + + + + + +-------+-----+-----+-----+-----+-----+ + + 1001 + 9 + X + + + X + + +-------+-----+-----+-----+-----+-----+ + + 1010 + 10 + X + + X + + + +-------+-----+-----+-----+-----+-----+ + + 1011 + 11 + X + + X + X + + +-------+-----+-----+-----+-----+-----+ + + 1100 + 12 + X + X + + + + +-------+-----+-----+-----+-----+-----+ + + 1101 + 13 + X + X + + X + + +-------+-----+-----+-----+-----+-----+ + + 1110 + 14 + X + X + X + + + +-------+-----+-----+-----+-----+-----+ + + 1111 + 15 + X + X + X + X + + +-------+-----+-----+-----+-----+-----+ + + In most cases, the appropriate displays must be plugged in for the above + combinations to work. TV-Out may need to be initialized at boot time. + + Debugging: + 1) Check whether the Fn+F8 key: + a) does not lock the laptop (try disabling CONFIG_X86_UP_APIC or boot with + noapic / nolapic if it does) + b) generates events (0x6n, where n is the value corresponding to the + configuration above) + c) actually works + Record the disp value at every configuration. + 2) Echo values from 0 to 15 to /sys/devices/platform/asus-laptop/display. + Record its value, note any change. If nothing changes, try a broader range, + up to 65535. + 3) Send ANY output (both positive and negative reports are needed, unless your + machine is already listed above) to the acpi4asus-user mailing list. + + Note: on some machines (e.g. L3C), after the module has been loaded, only 0x6n + events are generated and no actual switching occurs. In such a case, a line + like: + + echo $((10#$arg-60)) > /sys/devices/platform/asus-laptop/display + + will usually do the trick ($arg is the 0000006n-like event passed to acpid). + + Note: there is currently no reliable way to read display status on xxN + (Centrino) models. + +LED display +----------- + + Some models like the W1N have a LED display that can be used to display + several informations. + + LED display works for the following models: + W1000N + W1J + + To control the LED display, use the following : + + echo 0x0T000DDD > /sys/devices/platform/asus-laptop/ + + where T control the 3 letters display, and DDD the 3 digits display, + according to the tables below. + + DDD (digits) + 000 to 999 = display digits + AAA = --- + BBB to FFF = turn-off + + T (type) + 0 = off + 1 = dvd + 2 = vcd + 3 = mp3 + 4 = cd + 5 = tv + 6 = cpu + 7 = vol + + For example "echo 0x01000001 >/sys/devices/platform/asus-laptop/ledd" + would display "DVD001". + +Driver options: +--------------- + + Options can be passed to the asus-laptop driver using the standard + module argument syntax (= when passing the option to the + module or asus-laptop.= on the kernel boot line when + asus-laptop is statically linked into the kernel). + + wapf: WAPF defines the behavior of the Fn+Fx wlan key + The significance of values is yet to be found, but + most of the time: + - 0x0 should do nothing + - 0x1 should allow to control the device with Fn+Fx key. + - 0x4 should send an ACPI event (0x88) while pressing the Fn+Fx key + - 0x5 like 0x1 or 0x4 + + The default value is 0x1. + +Unsupported models +------------------ + + These models will never be supported by this module, as they use a completely + different mechanism to handle LEDs and extra stuff (meaning we have no clue + how it works): + + - ASUS A1300 (A1B), A1370D + - ASUS L7300G + - ASUS L8400 + +Patches, Errors, Questions: +-------------------------- + + I appreciate any success or failure + reports, especially if they add to or correct the compatibility table. + Please include the following information in your report: + + - Asus model name + - a copy of your ACPI tables, using the "acpidump" utility + - a copy of /sys/devices/platform/asus-laptop/infos + - which driver features work and which don't + - the observed behavior of non-working features + + Any other comments or patches are also more than welcome. + + acpi4asus-user@lists.sourceforge.net + http://sourceforge.net/projects/acpi4asus + -- cgit v1.2.3-70-g09d2 From b09f5fecf8b97c9de7add3e2eb0cfeb91ef28dbb Mon Sep 17 00:00:00 2001 From: Corentin Chary Date: Fri, 28 Aug 2009 12:56:55 +0000 Subject: asus-laptop: document sysfs interface Signed-off-by: Corentin Chary Signed-off-by: Len Brown --- .../ABI/testing/sysfs-platform-asus-laptop | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-platform-asus-laptop (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-platform-asus-laptop b/Documentation/ABI/testing/sysfs-platform-asus-laptop new file mode 100644 index 000000000000..a1cb660c50cf --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-asus-laptop @@ -0,0 +1,52 @@ +What: /sys/devices/platform/asus-laptop/display +Date: January 2007 +KernelVersion: 2.6.20 +Contact: "Corentin Chary" +Description: + This file allows display switching. The value + is composed by 4 bits and defined as follow: + 4321 + |||`- LCD + ||`-- CRT + |`--- TV + `---- DVI + Ex: - 0 (0000b) means no display + - 3 (0011b) CRT+LCD. + +What: /sys/devices/platform/asus-laptop/gps +Date: January 2007 +KernelVersion: 2.6.20 +Contact: "Corentin Chary" +Description: + Control the gps device. 1 means on, 0 means off. +Users: Lapsus + +What: /sys/devices/platform/asus-laptop/ledd +Date: January 2007 +KernelVersion: 2.6.20 +Contact: "Corentin Chary" +Description: + Some models like the W1N have a LED display that can be + used to display several informations. + To control the LED display, use the following : + echo 0x0T000DDD > /sys/devices/platform/asus-laptop/ + where T control the 3 letters display, and DDD the 3 digits display. + The DDD table can be found in Documentation/laptops/asus-laptop.txt + +What: /sys/devices/platform/asus-laptop/bluetooth +Date: January 2007 +KernelVersion: 2.6.20 +Contact: "Corentin Chary" +Description: + Control the bluetooth device. 1 means on, 0 means off. + This may control the led, the device or both. +Users: Lapsus + +What: /sys/devices/platform/asus-laptop/wlan +Date: January 2007 +KernelVersion: 2.6.20 +Contact: "Corentin Chary" +Description: + Control the bluetooth device. 1 means on, 0 means off. + This may control the led, the device or both. +Users: Lapsus -- cgit v1.2.3-70-g09d2 From 06777be6d8688ba93103fffbbe9e64a5e6fab3c8 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 12 Sep 2009 15:22:15 -0300 Subject: thinkpad-acpi: deprecate hotkey_bios_mask Some analysis of the ACPI DSDTs shows that the HKEY pre-enabled mask is always 0x80c (FN+F3,FN+F4 and FN+F12), which are the hotkeys that the second gen of HKEY firmware supported (the first gen didn't report any hotkeys, the second reported these tree hotkeys but had no mask support, and the third added mask support). So, this is probably some sort of backwards compatibility with older versions of the IBM ThinkVantage suite. We have no use for that, and I know of exactly ZERO users of that attribute, anyway. Start the process of getting rid of it. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/laptops/thinkpad-acpi.txt | 6 +++++- drivers/platform/x86/thinkpad_acpi.c | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index e2ddcdeb61b6..ab4b58eaa7f4 100644 --- a/Documentation/laptops/thinkpad-acpi.txt +++ b/Documentation/laptops/thinkpad-acpi.txt @@ -240,9 +240,13 @@ sysfs notes: Returns 0. hotkey_bios_mask: + DEPRECATED, DON'T USE, WILL BE REMOVED IN THE FUTURE. + Returns the hot keys mask when thinkpad-acpi was loaded. Upon module unload, the hot keys mask will be restored - to this value. + to this value. This is always 0x80c, because those are + the hotkeys that were supported by ancient firmware + without mask support. hotkey_enable: DEPRECATED, WILL BE REMOVED SOON. diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 679a73b43191..6b667891fc3b 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2544,6 +2544,8 @@ static ssize_t hotkey_bios_mask_show(struct device *dev, struct device_attribute *attr, char *buf) { + printk_deprecated_attribute("hotkey_bios_mask", + "This attribute is useless."); return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_orig_mask); } -- cgit v1.2.3-70-g09d2 From 20c9aa46f644b3ddb161a819d1b0c2b07097c4ee Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 12 Sep 2009 15:22:16 -0300 Subject: thinkpad-acpi: Fix procfs hotkey reset command echo "reset" > /proc/acpi/ibm/hotkey should do something non-useless, so instead of setting it to Fn+F2, Fn+F3, Fn+F5, set it to hotkey_recommended_mask. It is not like it will survive for much longer, anyway. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/laptops/thinkpad-acpi.txt | 2 +- drivers/platform/x86/thinkpad_acpi.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/laptops/thinkpad-acpi.txt b/Documentation/laptops/thinkpad-acpi.txt index ab4b58eaa7f4..6d03487ef1c7 100644 --- a/Documentation/laptops/thinkpad-acpi.txt +++ b/Documentation/laptops/thinkpad-acpi.txt @@ -219,7 +219,7 @@ The following commands can be written to the /proc/acpi/ibm/hotkey file: echo 0xffffffff > /proc/acpi/ibm/hotkey -- enable all hot keys echo 0 > /proc/acpi/ibm/hotkey -- disable all possible hot keys ... any other 8-hex-digit mask ... - echo reset > /proc/acpi/ibm/hotkey -- restore the original mask + echo reset > /proc/acpi/ibm/hotkey -- restore the recommended mask The following commands have been deprecated and will cause the kernel to log a warning: diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 6b667891fc3b..dd779e54894f 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3569,7 +3569,8 @@ static int hotkey_write(char *buf) hotkey_enabledisable_warn(0); res = -EPERM; } else if (strlencmp(cmd, "reset") == 0) { - mask = hotkey_orig_mask; + mask = (hotkey_all_mask | hotkey_source_mask) + & ~hotkey_reserved_mask; } else if (sscanf(cmd, "0x%x", &mask) == 1) { /* mask set */ } else if (sscanf(cmd, "%x", &mask) == 1) { -- cgit v1.2.3-70-g09d2 From de584afa5e188a2da484bb5373d449598cdb9f5e Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Fri, 18 Sep 2009 12:41:09 -0700 Subject: hwmon driver for ACPI 4.0 power meters This driver exposes ACPI 4.0 compliant power meters as hardware monitoring devices. This second revision of the driver also exports the ACPI string info as sysfs attributes, a list of the devices that the meter measures, and will send ACPI notifications over the ACPI netlink socket. This latest revision only enables the power capping controls if it can be confirmed that the power cap can be enforced by the hardware and explains how the notification interfaces work. [akpm@linux-foundation.org: remove default-y] [akpm@linux-foundation.org: build fix] Signed-off-by: Darrick J. Wong Cc: Zhang Rui Cc: Pavel Machek Signed-off-by: Andrew Morton Signed-off-by: Len Brown --- Documentation/hwmon/acpi_power_meter | 51 ++ drivers/acpi/Kconfig | 11 + drivers/acpi/Makefile | 1 + drivers/acpi/power_meter.c | 1018 ++++++++++++++++++++++++++++++++++ 4 files changed, 1081 insertions(+) create mode 100644 Documentation/hwmon/acpi_power_meter create mode 100644 drivers/acpi/power_meter.c (limited to 'Documentation') diff --git a/Documentation/hwmon/acpi_power_meter b/Documentation/hwmon/acpi_power_meter new file mode 100644 index 000000000000..c80399a00c50 --- /dev/null +++ b/Documentation/hwmon/acpi_power_meter @@ -0,0 +1,51 @@ +Kernel driver power_meter +========================= + +This driver talks to ACPI 4.0 power meters. + +Supported systems: + * Any recent system with ACPI 4.0. + Prefix: 'power_meter' + Datasheet: http://acpi.info/, section 10.4. + +Author: Darrick J. Wong + +Description +----------- + +This driver implements sensor reading support for the power meters exposed in +the ACPI 4.0 spec (Chapter 10.4). These devices have a simple set of +features--a power meter that returns average power use over a configurable +interval, an optional capping mechanism, and a couple of trip points. The +sysfs interface conforms with the specification outlined in the "Power" section +of Documentation/hwmon/sysfs-interface. + +Special Features +---------------- + +The power[1-*]_is_battery knob indicates if the power supply is a battery. +Both power[1-*]_average_{min,max} must be set before the trip points will work. +When both of them are set, an ACPI event will be broadcast on the ACPI netlink +socket and a poll notification will be sent to the appropriate +power[1-*]_average sysfs file. + +The power[1-*]_{model_number, serial_number, oem_info} fields display arbitrary +strings that ACPI provides with the meter. The measures/ directory contains +symlinks to the devices that this meter measures. + +Some computers have the ability to enforce a power cap in hardware. If this is +the case, the power[1-*]_cap and related sysfs files will appear. When the +average power consumption exceeds the cap, an ACPI event will be broadcast on +the netlink event socket and a poll notification will be sent to the +appropriate power[1-*]_alarm file to indicate that capping has begun, and the +hardware has taken action to reduce power consumption. Most likely this will +result in reduced performance. + +There are a few other ACPI notifications that can be sent by the firmware. In +all cases the ACPI event will be broadcast on the ACPI netlink event socket as +well as sent as a poll notification to a sysfs file. The events are as +follows: + +power[1-*]_cap will be notified if the firmware changes the power cap. +power[1-*]_interval will be notified if the firmware changes the averaging +interval. diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 7ec7d88c5999..2c1cab5642ff 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -82,6 +82,17 @@ config ACPI_PROCFS_POWER Say N to delete power /proc/acpi/ directories that have moved to /sys/ +config ACPI_POWER_METER + tristate "ACPI 4.0 power meter" + depends on HWMON + help + This driver exposes ACPI 4.0 power meters as hardware monitoring + devices. Say Y (or M) if you have a computer with ACPI 4.0 firmware + and a power meter. + + To compile this driver as a module, choose M here: + the module will be called power-meter. + config ACPI_SYSFS_POWER bool "Future power /sys interface" select POWER_SUPPLY diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 03a985be3fe3..82cd49dc603b 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -56,6 +56,7 @@ obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o obj-$(CONFIG_ACPI_BATTERY) += battery.o obj-$(CONFIG_ACPI_SBS) += sbshc.o obj-$(CONFIG_ACPI_SBS) += sbs.o +obj-$(CONFIG_ACPI_POWER_METER) += power_meter.o # processor has its own "processor." module_param namespace processor-y := processor_core.o processor_throttling.o diff --git a/drivers/acpi/power_meter.c b/drivers/acpi/power_meter.c new file mode 100644 index 000000000000..e6bfd77986b8 --- /dev/null +++ b/drivers/acpi/power_meter.c @@ -0,0 +1,1018 @@ +/* + * A hwmon driver for ACPI 4.0 power meters + * Copyright (C) 2009 IBM + * + * Author: Darrick J. Wong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ACPI_POWER_METER_NAME "power_meter" +ACPI_MODULE_NAME(ACPI_POWER_METER_NAME); +#define ACPI_POWER_METER_DEVICE_NAME "Power Meter" +#define ACPI_POWER_METER_CLASS "power_meter_resource" + +#define NUM_SENSORS 17 + +#define POWER_METER_CAN_MEASURE (1 << 0) +#define POWER_METER_CAN_TRIP (1 << 1) +#define POWER_METER_CAN_CAP (1 << 2) +#define POWER_METER_CAN_NOTIFY (1 << 3) +#define POWER_METER_IS_BATTERY (1 << 8) +#define UNKNOWN_HYSTERESIS 0xFFFFFFFF + +#define METER_NOTIFY_CONFIG 0x80 +#define METER_NOTIFY_TRIP 0x81 +#define METER_NOTIFY_CAP 0x82 +#define METER_NOTIFY_CAPPING 0x83 +#define METER_NOTIFY_INTERVAL 0x84 + +#define POWER_AVERAGE_NAME "power1_average" +#define POWER_CAP_NAME "power1_cap" +#define POWER_AVG_INTERVAL_NAME "power1_average_interval" +#define POWER_ALARM_NAME "power1_alarm" + +static int cap_in_hardware; +static int force_cap_on; + +static int can_cap_in_hardware(void) +{ + return force_cap_on || cap_in_hardware; +} + +static struct acpi_device_id power_meter_ids[] = { + {"ACPI000D", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, power_meter_ids); + +struct acpi_power_meter_capabilities { + acpi_integer flags; + acpi_integer units; + acpi_integer type; + acpi_integer accuracy; + acpi_integer sampling_time; + acpi_integer min_avg_interval; + acpi_integer max_avg_interval; + acpi_integer hysteresis; + acpi_integer configurable_cap; + acpi_integer min_cap; + acpi_integer max_cap; +}; + +struct acpi_power_meter_resource { + struct acpi_device *acpi_dev; + acpi_bus_id name; + struct mutex lock; + struct device *hwmon_dev; + struct acpi_power_meter_capabilities caps; + acpi_string model_number; + acpi_string serial_number; + acpi_string oem_info; + acpi_integer power; + acpi_integer cap; + acpi_integer avg_interval; + int sensors_valid; + unsigned long sensors_last_updated; + struct sensor_device_attribute sensors[NUM_SENSORS]; + int num_sensors; + int trip[2]; + int num_domain_devices; + struct acpi_device **domain_devices; + struct kobject *holders_dir; +}; + +struct ro_sensor_template { + char *label; + ssize_t (*show)(struct device *dev, + struct device_attribute *devattr, + char *buf); + int index; +}; + +struct rw_sensor_template { + char *label; + ssize_t (*show)(struct device *dev, + struct device_attribute *devattr, + char *buf); + ssize_t (*set)(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count); + int index; +}; + +/* Averaging interval */ +static int update_avg_interval(struct acpi_power_meter_resource *resource) +{ + unsigned long long data; + acpi_status status; + + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GAI", + NULL, &data); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GAI")); + return -ENODEV; + } + + resource->avg_interval = data; + return 0; +} + +static ssize_t show_avg_interval(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + + mutex_lock(&resource->lock); + update_avg_interval(resource); + mutex_unlock(&resource->lock); + + return sprintf(buf, "%llu\n", resource->avg_interval); +} + +static ssize_t set_avg_interval(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list args = { 1, &arg0 }; + int res; + unsigned long temp; + unsigned long long data; + acpi_status status; + + res = strict_strtoul(buf, 10, &temp); + if (res) + return res; + + if (temp > resource->caps.max_avg_interval || + temp < resource->caps.min_avg_interval) + return -EINVAL; + arg0.integer.value = temp; + + mutex_lock(&resource->lock); + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI", + &args, &data); + if (!ACPI_FAILURE(status)) + resource->avg_interval = temp; + mutex_unlock(&resource->lock); + + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI")); + return -EINVAL; + } + + /* _PAI returns 0 on success, nonzero otherwise */ + if (data) + return -EINVAL; + + return count; +} + +/* Cap functions */ +static int update_cap(struct acpi_power_meter_resource *resource) +{ + unsigned long long data; + acpi_status status; + + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GHL", + NULL, &data); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GHL")); + return -ENODEV; + } + + resource->cap = data; + return 0; +} + +static ssize_t show_cap(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + + mutex_lock(&resource->lock); + update_cap(resource); + mutex_unlock(&resource->lock); + + return sprintf(buf, "%llu\n", resource->cap * 1000); +} + +static ssize_t set_cap(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list args = { 1, &arg0 }; + int res; + unsigned long temp; + unsigned long long data; + acpi_status status; + + res = strict_strtoul(buf, 10, &temp); + if (res) + return res; + + temp /= 1000; + if (temp > resource->caps.max_cap || temp < resource->caps.min_cap) + return -EINVAL; + arg0.integer.value = temp; + + mutex_lock(&resource->lock); + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL", + &args, &data); + if (!ACPI_FAILURE(status)) + resource->cap = temp; + mutex_unlock(&resource->lock); + + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL")); + return -EINVAL; + } + + /* _SHL returns 0 on success, nonzero otherwise */ + if (data) + return -EINVAL; + + return count; +} + +/* Power meter trip points */ +static int set_acpi_trip(struct acpi_power_meter_resource *resource) +{ + union acpi_object arg_objs[] = { + {ACPI_TYPE_INTEGER}, + {ACPI_TYPE_INTEGER} + }; + struct acpi_object_list args = { 2, arg_objs }; + unsigned long long data; + acpi_status status; + + /* Both trip levels must be set */ + if (resource->trip[0] < 0 || resource->trip[1] < 0) + return 0; + + /* This driver stores min, max; ACPI wants max, min. */ + arg_objs[0].integer.value = resource->trip[1]; + arg_objs[1].integer.value = resource->trip[0]; + + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PTP", + &args, &data); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PTP")); + return -EINVAL; + } + + return data; +} + +static ssize_t set_trip(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + int res; + unsigned long temp; + + res = strict_strtoul(buf, 10, &temp); + if (res) + return res; + + temp /= 1000; + if (temp < 0) + return -EINVAL; + + mutex_lock(&resource->lock); + resource->trip[attr->index - 7] = temp; + res = set_acpi_trip(resource); + mutex_unlock(&resource->lock); + + if (res) + return res; + + return count; +} + +/* Power meter */ +static int update_meter(struct acpi_power_meter_resource *resource) +{ + unsigned long long data; + acpi_status status; + unsigned long local_jiffies = jiffies; + + if (time_before(local_jiffies, resource->sensors_last_updated + + msecs_to_jiffies(resource->caps.sampling_time)) && + resource->sensors_valid) + return 0; + + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM", + NULL, &data); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMM")); + return -ENODEV; + } + + resource->power = data; + resource->sensors_valid = 1; + resource->sensors_last_updated = jiffies; + return 0; +} + +static ssize_t show_power(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + + mutex_lock(&resource->lock); + update_meter(resource); + mutex_unlock(&resource->lock); + + return sprintf(buf, "%llu\n", resource->power * 1000); +} + +/* Miscellaneous */ +static ssize_t show_str(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + acpi_string val; + + switch (attr->index) { + case 0: + val = resource->model_number; + break; + case 1: + val = resource->serial_number; + break; + case 2: + val = resource->oem_info; + break; + default: + BUG(); + } + + return sprintf(buf, "%s\n", val); +} + +static ssize_t show_val(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + acpi_integer val = 0; + + switch (attr->index) { + case 0: + val = resource->caps.min_avg_interval; + break; + case 1: + val = resource->caps.max_avg_interval; + break; + case 2: + val = resource->caps.min_cap * 1000; + break; + case 3: + val = resource->caps.max_cap * 1000; + break; + case 4: + if (resource->caps.hysteresis == UNKNOWN_HYSTERESIS) + return sprintf(buf, "unknown\n"); + + val = resource->caps.hysteresis * 1000; + break; + case 5: + if (resource->caps.flags & POWER_METER_IS_BATTERY) + val = 1; + else + val = 0; + break; + case 6: + if (resource->power > resource->cap) + val = 1; + else + val = 0; + break; + case 7: + case 8: + if (resource->trip[attr->index - 7] < 0) + return sprintf(buf, "unknown\n"); + + val = resource->trip[attr->index - 7] * 1000; + break; + default: + BUG(); + } + + return sprintf(buf, "%llu\n", val); +} + +static ssize_t show_accuracy(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + unsigned int acc = resource->caps.accuracy; + + return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000); +} + +static ssize_t show_name(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME); +} + +/* Sensor descriptions. If you add a sensor, update NUM_SENSORS above! */ +static struct ro_sensor_template meter_ro_attrs[] = { +{POWER_AVERAGE_NAME, show_power, 0}, +{"power1_accuracy", show_accuracy, 0}, +{"power1_average_interval_min", show_val, 0}, +{"power1_average_interval_max", show_val, 1}, +{"power1_is_battery", show_val, 5}, +{NULL, NULL, 0}, +}; + +static struct rw_sensor_template meter_rw_attrs[] = { +{POWER_AVG_INTERVAL_NAME, show_avg_interval, set_avg_interval, 0}, +{NULL, NULL, NULL, 0}, +}; + +static struct ro_sensor_template misc_cap_attrs[] = { +{"power1_cap_min", show_val, 2}, +{"power1_cap_max", show_val, 3}, +{"power1_cap_hyst", show_val, 4}, +{POWER_ALARM_NAME, show_val, 6}, +{NULL, NULL, 0}, +}; + +static struct ro_sensor_template ro_cap_attrs[] = { +{POWER_CAP_NAME, show_cap, 0}, +{NULL, NULL, 0}, +}; + +static struct rw_sensor_template rw_cap_attrs[] = { +{POWER_CAP_NAME, show_cap, set_cap, 0}, +{NULL, NULL, NULL, 0}, +}; + +static struct rw_sensor_template trip_attrs[] = { +{"power1_average_min", show_val, set_trip, 7}, +{"power1_average_max", show_val, set_trip, 8}, +{NULL, NULL, NULL, 0}, +}; + +static struct ro_sensor_template misc_attrs[] = { +{"name", show_name, 0}, +{"power1_model_number", show_str, 0}, +{"power1_oem_info", show_str, 2}, +{"power1_serial_number", show_str, 1}, +{NULL, NULL, 0}, +}; + +/* Read power domain data */ +static void remove_domain_devices(struct acpi_power_meter_resource *resource) +{ + int i; + + if (!resource->num_domain_devices) + return; + + for (i = 0; i < resource->num_domain_devices; i++) { + struct acpi_device *obj = resource->domain_devices[i]; + if (!obj) + continue; + + sysfs_remove_link(resource->holders_dir, + kobject_name(&obj->dev.kobj)); + put_device(&obj->dev); + } + + kfree(resource->domain_devices); + kobject_put(resource->holders_dir); +} + +static int read_domain_devices(struct acpi_power_meter_resource *resource) +{ + int res = 0; + int i; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *pss; + acpi_status status; + + status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMD", NULL, + &buffer); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMD")); + return -ENODEV; + } + + pss = buffer.pointer; + if (!pss || + pss->type != ACPI_TYPE_PACKAGE) { + dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME + "Invalid _PMD data\n"); + res = -EFAULT; + goto end; + } + + if (!pss->package.count) + goto end; + + resource->domain_devices = kzalloc(sizeof(struct acpi_device *) * + pss->package.count, GFP_KERNEL); + if (!resource->domain_devices) { + res = -ENOMEM; + goto end; + } + + resource->holders_dir = kobject_create_and_add("measures", + &resource->acpi_dev->dev.kobj); + if (!resource->holders_dir) { + res = -ENOMEM; + goto exit_free; + } + + resource->num_domain_devices = pss->package.count; + + for (i = 0; i < pss->package.count; i++) { + struct acpi_device *obj; + union acpi_object *element = &(pss->package.elements[i]); + + /* Refuse non-references */ + if (element->type != ACPI_TYPE_LOCAL_REFERENCE) + continue; + + /* Create a symlink to domain objects */ + resource->domain_devices[i] = NULL; + status = acpi_bus_get_device(element->reference.handle, + &resource->domain_devices[i]); + if (ACPI_FAILURE(status)) + continue; + + obj = resource->domain_devices[i]; + get_device(&obj->dev); + + res = sysfs_create_link(resource->holders_dir, &obj->dev.kobj, + kobject_name(&obj->dev.kobj)); + if (res) { + put_device(&obj->dev); + resource->domain_devices[i] = NULL; + } + } + + res = 0; + goto end; + +exit_free: + kfree(resource->domain_devices); +end: + kfree(buffer.pointer); + return res; +} + +/* Registration and deregistration */ +static int register_ro_attrs(struct acpi_power_meter_resource *resource, + struct ro_sensor_template *ro) +{ + struct device *dev = &resource->acpi_dev->dev; + struct sensor_device_attribute *sensors = + &resource->sensors[resource->num_sensors]; + int res = 0; + + while (ro->label) { + sensors->dev_attr.attr.name = ro->label; + sensors->dev_attr.attr.mode = S_IRUGO; + sensors->dev_attr.show = ro->show; + sensors->index = ro->index; + + res = device_create_file(dev, &sensors->dev_attr); + if (res) { + sensors->dev_attr.attr.name = NULL; + goto error; + } + sensors++; + resource->num_sensors++; + ro++; + } + +error: + return res; +} + +static int register_rw_attrs(struct acpi_power_meter_resource *resource, + struct rw_sensor_template *rw) +{ + struct device *dev = &resource->acpi_dev->dev; + struct sensor_device_attribute *sensors = + &resource->sensors[resource->num_sensors]; + int res = 0; + + while (rw->label) { + sensors->dev_attr.attr.name = rw->label; + sensors->dev_attr.attr.mode = S_IRUGO | S_IWUSR; + sensors->dev_attr.show = rw->show; + sensors->dev_attr.store = rw->set; + sensors->index = rw->index; + + res = device_create_file(dev, &sensors->dev_attr); + if (res) { + sensors->dev_attr.attr.name = NULL; + goto error; + } + sensors++; + resource->num_sensors++; + rw++; + } + +error: + return res; +} + +static void remove_attrs(struct acpi_power_meter_resource *resource) +{ + int i; + + for (i = 0; i < resource->num_sensors; i++) { + if (!resource->sensors[i].dev_attr.attr.name) + continue; + device_remove_file(&resource->acpi_dev->dev, + &resource->sensors[i].dev_attr); + } + + remove_domain_devices(resource); + + resource->num_sensors = 0; +} + +static int setup_attrs(struct acpi_power_meter_resource *resource) +{ + int res = 0; + + res = read_domain_devices(resource); + if (res) + return res; + + if (resource->caps.flags & POWER_METER_CAN_MEASURE) { + res = register_ro_attrs(resource, meter_ro_attrs); + if (res) + goto error; + res = register_rw_attrs(resource, meter_rw_attrs); + if (res) + goto error; + } + + if (resource->caps.flags & POWER_METER_CAN_CAP) { + if (!can_cap_in_hardware()) { + dev_err(&resource->acpi_dev->dev, + "Ignoring unsafe software power cap!\n"); + goto skip_unsafe_cap; + } + + if (resource->caps.configurable_cap) { + res = register_rw_attrs(resource, rw_cap_attrs); + if (res) + goto error; + } else { + res = register_ro_attrs(resource, ro_cap_attrs); + if (res) + goto error; + } + res = register_ro_attrs(resource, misc_cap_attrs); + if (res) + goto error; + } +skip_unsafe_cap: + + if (resource->caps.flags & POWER_METER_CAN_TRIP) { + res = register_rw_attrs(resource, trip_attrs); + if (res) + goto error; + } + + res = register_ro_attrs(resource, misc_attrs); + if (res) + goto error; + + return res; +error: + remove_domain_devices(resource); + remove_attrs(resource); + return res; +} + +static void free_capabilities(struct acpi_power_meter_resource *resource) +{ + acpi_string *str; + int i; + + str = &resource->model_number; + for (i = 0; i < 3; i++, str++) + kfree(*str); +} + +static int read_capabilities(struct acpi_power_meter_resource *resource) +{ + int res = 0; + int i; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer state = { 0, NULL }; + struct acpi_buffer format = { sizeof("NNNNNNNNNNN"), "NNNNNNNNNNN" }; + union acpi_object *pss; + acpi_string *str; + acpi_status status; + + status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMC", NULL, + &buffer); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMC")); + return -ENODEV; + } + + pss = buffer.pointer; + if (!pss || + pss->type != ACPI_TYPE_PACKAGE || + pss->package.count != 14) { + dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME + "Invalid _PMC data\n"); + res = -EFAULT; + goto end; + } + + /* Grab all the integer data at once */ + state.length = sizeof(struct acpi_power_meter_capabilities); + state.pointer = &resource->caps; + + status = acpi_extract_package(pss, &format, &state); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Invalid data")); + res = -EFAULT; + goto end; + } + + if (resource->caps.units) { + dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME + "Unknown units %llu.\n", + resource->caps.units); + res = -EINVAL; + goto end; + } + + /* Grab the string data */ + str = &resource->model_number; + + for (i = 11; i < 14; i++) { + union acpi_object *element = &(pss->package.elements[i]); + + if (element->type != ACPI_TYPE_STRING) { + res = -EINVAL; + goto error; + } + + *str = kzalloc(sizeof(u8) * (element->string.length + 1), + GFP_KERNEL); + if (!*str) { + res = -ENOMEM; + goto error; + } + + strncpy(*str, element->string.pointer, element->string.length); + str++; + } + + dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n"); + goto end; +error: + str = &resource->model_number; + for (i = 0; i < 3; i++, str++) + kfree(*str); +end: + kfree(buffer.pointer); + return res; +} + +/* Handle ACPI event notifications */ +static void acpi_power_meter_notify(struct acpi_device *device, u32 event) +{ + struct acpi_power_meter_resource *resource; + int res; + + if (!device || !acpi_driver_data(device)) + return; + + resource = acpi_driver_data(device); + + mutex_lock(&resource->lock); + switch (event) { + case METER_NOTIFY_CONFIG: + free_capabilities(resource); + res = read_capabilities(resource); + if (res) + break; + + remove_attrs(resource); + setup_attrs(resource); + break; + case METER_NOTIFY_TRIP: + sysfs_notify(&device->dev.kobj, NULL, POWER_AVERAGE_NAME); + update_meter(resource); + break; + case METER_NOTIFY_CAP: + sysfs_notify(&device->dev.kobj, NULL, POWER_CAP_NAME); + update_cap(resource); + break; + case METER_NOTIFY_INTERVAL: + sysfs_notify(&device->dev.kobj, NULL, POWER_AVG_INTERVAL_NAME); + update_avg_interval(resource); + break; + case METER_NOTIFY_CAPPING: + sysfs_notify(&device->dev.kobj, NULL, POWER_ALARM_NAME); + dev_info(&device->dev, "Capping in progress.\n"); + break; + default: + BUG(); + } + mutex_unlock(&resource->lock); + + acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS, + dev_name(&device->dev), event, 0); +} + +static int acpi_power_meter_add(struct acpi_device *device) +{ + int res; + struct acpi_power_meter_resource *resource; + + if (!device) + return -EINVAL; + + resource = kzalloc(sizeof(struct acpi_power_meter_resource), + GFP_KERNEL); + if (!resource) + return -ENOMEM; + + resource->sensors_valid = 0; + resource->acpi_dev = device; + mutex_init(&resource->lock); + strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME); + strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); + device->driver_data = resource; + + free_capabilities(resource); + res = read_capabilities(resource); + if (res) + goto exit_free; + + resource->trip[0] = resource->trip[1] = -1; + + res = setup_attrs(resource); + if (res) + goto exit_free; + + resource->hwmon_dev = hwmon_device_register(&device->dev); + if (IS_ERR(resource->hwmon_dev)) { + res = PTR_ERR(resource->hwmon_dev); + goto exit_remove; + } + + res = 0; + goto exit; + +exit_remove: + remove_attrs(resource); +exit_free: + kfree(resource); +exit: + return res; +} + +static int acpi_power_meter_remove(struct acpi_device *device, int type) +{ + struct acpi_power_meter_resource *resource; + + if (!device || !acpi_driver_data(device)) + return -EINVAL; + + resource = acpi_driver_data(device); + hwmon_device_unregister(resource->hwmon_dev); + + free_capabilities(resource); + remove_attrs(resource); + + kfree(resource); + return 0; +} + +static int acpi_power_meter_resume(struct acpi_device *device) +{ + struct acpi_power_meter_resource *resource; + + if (!device || !acpi_driver_data(device)) + return -EINVAL; + + resource = acpi_driver_data(device); + free_capabilities(resource); + read_capabilities(resource); + + return 0; +} + +static struct acpi_driver acpi_power_meter_driver = { + .name = "power_meter", + .class = ACPI_POWER_METER_CLASS, + .ids = power_meter_ids, + .ops = { + .add = acpi_power_meter_add, + .remove = acpi_power_meter_remove, + .resume = acpi_power_meter_resume, + .notify = acpi_power_meter_notify, + }, +}; + +/* Module init/exit routines */ +static int __init enable_cap_knobs(const struct dmi_system_id *d) +{ + cap_in_hardware = 1; + return 0; +} + +static struct dmi_system_id __initdata pm_dmi_table[] = { + { + enable_cap_knobs, "IBM Active Energy Manager", + { + DMI_MATCH(DMI_SYS_VENDOR, "IBM") + }, + }, + {} +}; + +static int __init acpi_power_meter_init(void) +{ + int result; + + if (acpi_disabled) + return -ENODEV; + + dmi_check_system(pm_dmi_table); + + result = acpi_bus_register_driver(&acpi_power_meter_driver); + if (result < 0) + return -ENODEV; + + return 0; +} + +static void __exit acpi_power_meter_exit(void) +{ + acpi_bus_unregister_driver(&acpi_power_meter_driver); +} + +MODULE_AUTHOR("Darrick J. Wong "); +MODULE_DESCRIPTION("ACPI 4.0 power meter driver"); +MODULE_LICENSE("GPL"); + +module_param(force_cap_on, bool, 0644); +MODULE_PARM_DESC(force_cap_on, "Enable power cap even it is unsafe to do so."); + +module_init(acpi_power_meter_init); +module_exit(acpi_power_meter_exit); -- cgit v1.2.3-70-g09d2